1. 程式人生 > >資料結構——樹的基本運算

資料結構——樹的基本運算

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.排序二叉樹的建立和插入內容



二輪複習補充。