資料結構——樹的基本運算
阿新 • • 發佈:2018-12-02
1.二叉樹的建立和基本操作
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef char ElementType; //二叉樹的鏈式儲存結構定義 typedef struct BTreeNode{ char data; struct BTreeNode *Lchild,*Rchild; }tree; //用 先序遍歷序列(遍歷到的空子樹用#表示) 構造 tree *CreatTree1(tree *T) { ElementType ch; scanf("%c",&ch); if(ch=='#') return NULL; else{ T=(tree*)malloc(sizeof(tree)); T->data=ch; T->Lchild=CreatTree1(T->Lchild); T->Rchild=CreatTree1(T->Rchild); //如果用中序、後序構造,只需把賦值和建立左右子樹順序換一下 return T; } } //用 括號表示法(廣義表) 構造 void CreatTree2(tree *&T) { char ch[20]; tree *sta[100],*p; int top=-1,i=0,flag=0; T=NULL; gets(ch); while(ch[i]!='\0') { switch(ch[i]) { case '(': flag=1; top++; sta[top]=p; break; case ',': flag=2; break; case ')': top--; break; default: p=(tree*)malloc(sizeof(tree)); p->data=ch[i]; p->Lchild=NULL; p->Rchild=NULL; if(T==NULL) T=p; else{ //此處也在default條件下,輸入資訊不是(),時,才進行操作 if(flag==1) sta[top]->Lchild=p; if(flag==2) sta[top]->Rchild=p; } } i++; } } //用順序樹(空位用#表示)建樹 tree *CreatTree3(char* str, int pose, int size) { char ch; tree * T; char * p=str; ch = p[pose]; if(ch=='#'|| pose>=size) return NULL; // 表示空結點 else { T=(tree *)malloc(sizeof(tree)); //非空則構造新結點 T->data=ch; //新結點資料域即為讀入字元 T->Lchild=CreatTree3(p, 2*pose+1,size); //建立左子樹 T->Rchild=CreatTree3(p, 2*pose+2,size); //建立右子樹 } return T; } //銷燬二叉樹 void DestroyTree(tree *&T) //此處用*& 表示取此指標地址,在函式內部對指標做的改變(T=NULL)可以應用於函式外,避免出現野指標 { if(T==NULL) return; else { DestroyTree(T->Lchild); DestroyTree(T->Rchild); free(T); T=NULL; } //寫法二 便於理解 // if(T->Lchild!=NULL) // DestroyTree(T->Lchild); // if(T->Rchild!=NULL) // DestroyTree(T->Rchild); // if(T->Lchild==NULL && T->Rchild==NULL) // { // free(T); // T=NULL; // } } //用括號表示法輸出二叉樹 (即遞迴先序輸出,只是需要加上括號和逗號表示位置) void PrintTree(tree * T) { if(T!=NULL) { printf("%c",T->data); if(T->Lchild!=NULL || T->Rchild!=NULL) { printf("("); PrintTree(T->Lchild); if (T->Rchild!=NULL) printf(","); PrintTree(T->Rchild); printf(")"); } } } //先序遍歷 void PreOrderTraverse(tree *T) { if(T==NULL)return; printf("%c ",T->data); PreOrderTraverse(T->Lchild); PreOrderTraverse(T->Rchild); } //中序遍歷 void InOrderTraverse(tree *T) { if(T==NULL)return; InOrderTraverse(T->Lchild); printf("%c ",T->data); InOrderTraverse(T->Rchild); } //後序遍歷 void PostOrderTraverse(tree *T) { if(T==NULL)return; PostOrderTraverse(T->Lchild); PostOrderTraverse(T->Rchild); printf("%c ",T->data); } //層次遍歷 void LevelOrderTraverse(tree * T) { tree *p,* queue[100]; int front,rear; front=rear=0; rear++; queue[rear]=T; //層次遍歷要每讀取一個數,就把其左右子樹放到最後方,繼續之前的後續操作 while(rear!=front) //不影響順序讀取兄弟節點,同時儲存後面的資訊,顯然佇列的先進先出模式最適合這種結構,依次讀取節點 { front=(front+1)%100; p=queue[front]; if(p->Lchild!=NULL) //當子樹不為空時,將子樹“排隊” { rear=(rear+1)%100; queue[rear]=p->Lchild; } if(p->Rchild!=NULL) { rear=(rear+1)%100; queue[rear]=p->Rchild; } printf("%c ",p->data); } } //計算節點數 int node(tree *T) { if(T==NULL)return 0; return node(T->Lchild)+node(T->Rchild)+1; } //計算葉子節點數 int LeafNode(tree *T) { if(T==NULL) return 0;//易忘,缺少這句話會使結果出錯 else if(T->Lchild==NULL && T->Rchild==NULL) return 1; else return LeafNode(T->Lchild)+LeafNode(T->Rchild); } int main(){ tree * T; int flag=1; while(flag!=0) { printf("====================================\n"); printf("請按以下提示操作:\n" "0.退出程式\n" "1.先序建樹(空子樹用#)\n" "2.括號表示法建樹\n" "3.順序樹建樹(空子樹用#)\n" "4.以括號表示法輸出\n" "5.刪除二叉樹\n" "6.先序遍歷\n" "7.中序遍歷\n" "8.後序遍歷\n" "9.層次遍歷\n" "10.輸出葉子節點個數\n"); scanf("%d",&flag); getchar(); /* 注意:呼叫建樹函式中有scanf("%c")時,會讀取到輸入flag時的換行資訊,導致出錯,此處加上getchar防止此類錯誤出現 */ printf("====================================\n"); switch(flag) { case 0: return 0; break; case 1: printf("====================================\n"); T=CreatTree1(T); break; case 2: CreatTree2(T); break; case 3: char a[20]; gets(a); T=CreatTree3(a,0,strlen(a)); break; case 4: PrintTree(T); printf("\n") ; break; case 5: DestroyTree(T); break; case 6: PreOrderTraverse(T); printf("\n") ; break; case 7: InOrderTraverse(T); printf("\n") ; break; case 8: PostOrderTraverse(T); printf("\n") ; break; case 9: LevelOrderTraverse(T); printf("\n"); break; case 10: printf("%d",LeafNode(T)); printf("\n"); break; default: printf("您輸入了錯誤值"); } } return 0; }
2.其他一些簡單操作
a.轉換左右子樹,並生成新樹:
void Turn(tree *T,tree *&B) //此處對B樹要用*&呼叫的同時改變本身,用這種思想在遞迴的過程中建立子樹 { if(T==NULL)B=NULL; else{ B=(tree *)malloc(sizeof(tree)); B->data=T->data; Turn(T->Lchild,B->Rchild); //遞迴交換左右子樹 Turn(T->Rchild,B->Lchild); } }
b.已知先序或者後序遍歷中的一種與中序遍歷,構造一顆確定的二叉樹
首先明確一個定義:已知中序序列和先序或者後序序列中的任意一個,便可以構造唯一確定的二叉樹;但是如果只確定先序序列與後序序列,則不行。
演算法首要的目的是在中序遍歷中找到先序遍歷的第一個值,即二叉樹的根,在中序遍歷的位置,利用字串相減得到位置K,程式碼實現如下:
int main() { char s1[20] ,s2[20] ,*s ; //S1是中序遍歷的串,S2是先序遍歷的串 int k; printf("請輸入中序遍歷:"); gets(s1); printf("請輸入先序遍歷:"); gets(s2); s=s1; for(;s<s1+20;s++) //S指標依次檢測中序串,在其中查詢與根節點相同節點的位置 if(*s==*s2) break; k=s-s1; //此處兩串相減代表指標的地址差 ,k值就是中序遍歷中根節點的位置 //迴圈部分也可以直接用數值i帶入迴圈,那麼i就是這裡的k,下面函式中用的這種方法 printf("右子樹中序遍歷為:"); puts(s1+k+1); printf("%d",k); return 0; }
大問題:整個串的先序遍歷,整個串的中序遍歷 f(s1,s2,n)
小問題:左子樹的先序遍歷、左子樹的中序遍歷,右子樹的先序遍歷,右子樹的中序遍歷 f(Ls1,Ls2,k) f(Rs1,Rs2,n-k-1)
程式碼實現:
tree *CreatTree0(char * pre,char * in,int n)
{
tree *T;
int i;
if(n==0) return NULL; //遞迴出口
for(i=0;i<n;i++)
if(*(in+i)==*pre)
break;
T=(tree *)malloc(sizeof(tree));
T->data=*pre;
T->Lchild=CreatTree0(pre+1,in,i); // 這裡的i帶入,遞迴時只判斷前i項,也就是左子樹部分長度
T->Rchild=CreatTree0(pre+i+1,in+i+1,n-i-1); //pre+i+1就是右子樹 部分,n-i-1對應右子樹的長度
return T;
}
利用上述思路,同樣可以用後序和中序遍歷構造樹。
C.查詢二叉樹某一層的節點數
對於某一層的節點數查詢,可以借鑑遞迴遍歷的思想,但是在查詢過程中,需要知道當前所在的節點層數。
如果所在節點層數和需要查詢的在同一層,則n++;依次遞迴。
void TreeFloor(tree *T,int h,int k,int &n)//注意最後一項的n前面的&符號,保證在遞迴的過程中統計n的值。此處也可以設定全域性變數
{
if(T==NULL) return;
if(h==k)n++;
if(h<k)
{
TreeFloor(T->Lchild,h+1,k,n);
TreeFloor(T->Rchild,h+1,k,n);
}
}
D.排序二叉樹的建立和插入內容
二輪複習補充。