【筆記】樹、森林與二叉樹的轉換與應用

Time-space發表於2017-10-30

1.樹和森林轉換為二叉樹

  • 樹轉換為二叉樹

  樹的孩子兄弟表示和二叉樹的二叉連結串列在儲存方式上是相同的,即從它們的相同的物理結構可以得到一棵樹,也可以得到一顆二叉樹。


這裡寫圖片描述

  將一顆樹轉換為二叉樹的步驟如下:

  • 加線:在所有兄弟結點之間加一條連線。
  • 去線:對樹中的每個結點,只保留每個結點與它的第一個孩子節點間的連線,刪除它與其他孩子的連線。
  • 調整:以樹的根結點為軸心,將整棵樹順時針旋轉一定的角度, 使之結構層次分明。第一個孩子為二叉樹中結點的左孩子,兄弟轉換過來的孩子為右孩子。

  任何一棵和樹對應的二叉樹,其右子樹必為空。


這裡寫圖片描述

  • 森林轉換為二叉樹

  森林是若干顆樹組成的集合。森林也可以轉換為對應的二叉樹。方法為:

  • 把森林中的每棵樹都轉換為二叉樹。
  • 第一顆二叉樹不動,從第二顆二叉樹開始,一次把後一顆二叉樹的根結點作為前一顆二叉樹的根結點的右孩子,用線連線起來。當所有的二叉樹連線起來以後就得到了由森林轉換來的二叉樹,最後進行相應的調整,使其層次分明。


這裡寫圖片描述

  森林轉換為二叉樹的形式化描述:
  如果F={T1,T2,,Tm}

F=\{T_1,T_2,…,T_m\}
是森林,則可按如下規則轉換成一棵二叉樹B=(root,LB,RB)
B=(root,LB,RB)

  • 若F為空,即m=0
    m=0
    ,則B為空樹;
  • 若F非空,即m0
    m \neq 0
    ,則B的根root即為森林中第一棵樹的根ROOT(T1)
    ROOT(T_1)
    ;B的左子樹LB
    LB
    是從T1
    T_1
    中根結點的子樹森林F1={T11,T12Tm1}
    F_1=\{T_{11},T_{12},T_{m1}\}
    轉換而成的二叉樹;其右子樹RB
    RB
    是從森林F={T2,T3Tm}
    F'=\{T_2,T_3,T_m\}
    轉換而成的二叉樹。

2.二叉樹轉換為樹和森林

  把一顆二叉樹轉換為樹的方法如下:

  • 加線:若某結點的左孩子結點存在,則將該結點的左孩子的右孩子結點、右孩子的右孩子結點……都與該結點用線條連線。
  • 去線:刪除原二叉樹中所有結點與右孩子結點的連線。
  • 調整:使結構層次分明。


這裡寫圖片描述

  與二叉樹轉換樹的方法類似,二叉樹轉換為森林的過程如下圖所示。


這裡寫圖片描述

  二叉樹轉換為森林的形式化描述:
  如果B=(root,LB,RB)

B=(root,LB,RB)
是一棵二叉樹,則可按如下規則轉換成森林F={T1,T2,,Tm}
F=\{T_1,T_2,…,T_m\}

  • 若B為空,則F為空;
  • 若B非空,則F中第一棵樹T1
    T_1
    的根ROOT(T1)
    ROOT(T_1)
    即為二叉樹B的根root;T1
    T_1
    中的根結點的子樹森林F1
    F_1
    是由B的左子樹LB
    LB
    轉換而成的森林;F中除T1
    T_1
    之外其餘樹組成的森林F={T2,T3Tm}
    F'=\{T_2,T_3,T_m\}
    是由B的右子樹RB
    RB
    轉換而成的森林。

3.森林的遍歷

  森林的遍歷有兩種:先根遍歷和後根遍歷。

  • 先根遍歷
  • 訪問第一顆樹的根結點。
  • 按先根順序遍歷第一棵樹的全部子樹。
  • 按先根順序遍歷其餘的樹。
  • 後跟遍歷
  • 按後根順序遍歷第一棵樹的全部子樹。
  • 訪問第一顆樹的根結點。
  • 按後根順序遍歷其餘的樹。

  從森林與二叉樹的自然對應關係可以看到,對森林遍歷的先根順序和後跟順序的定義正好與這種自然的對應關係相符合。這是因為第一棵樹的所有子樹對應於二叉樹的左子樹,而其餘的樹則對應於二叉樹的右子樹。將這些定義和關於二叉樹結點訪問順序的相應定義相比較可知,將先根順序對一個森林進行遍歷操作,與按先根順序對二叉樹進行遍歷操作完全一致。而按後根順序對一個森林進行遍歷操作,與按中根順序對相應的二叉樹進行遍歷操作完全一致。

  此外可以把樹看成是森林的特殊情況(即森林中僅有一棵樹)時,可以發現對樹的先根遍歷順序是對森林的先根遍歷順序的特例;而對樹的後根遍歷的順序是對森林的後根遍歷順序的特例。


這裡寫圖片描述
這裡寫圖片描述

4.樹與二叉樹的應用

由兩種遍歷序列確定二叉樹

  已知先序和中序序列、中序和後序序列、先序和後序序列能唯一確定一棵二叉樹嗎?

  • 由先序序列和中序序列唯一確定一棵二叉樹

  在二叉樹的先序遍歷過程中,根結點一定是第一個訪問的結點。在中序遍歷二叉樹時,先中序遍歷左子樹,然後是根結點,最後遍歷右子樹。由此在二叉樹的中序序列中,根結點位於左右子樹序列的中間,把序列分為兩部分,左邊序列為左子樹結點,右邊是右子樹結點。
  根據先序序列的左子樹和中序序列左子樹部分,左子樹的根結點可繼續將中序序列分為左子樹和右子樹兩個部分,依此類推,就可以構造出二叉樹。
  設結點的先序序列為(A,B,C,D,E,F,G),中序序列為(B,D,C,A,F,E,G),下圖為確定二叉樹的過程。


這裡寫圖片描述

  由先序序列和中序序列構造二叉樹的演算法實現如下:

void CreateBiTree1(BiTree *T,char *pre,char *in,int len) 
/*由先序序列和中序序列構造二叉樹*/
{   
    int k;
    char *temp;
    if(len<=0)
    {
        *T=NULL;
        return;
    }
    *T=(BitNode*)malloc(sizeof(BitNode));/*生成根結點*/
    (*T)->data=*pre;                    
    for(temp=in;temp<in+len;temp++)     /*在中序序列in中找到根結點所在的位置*/
        if(*pre==*temp)
            break;
    k=temp-in;                          /*左子樹的長度*/
    CreateBiTree1(&((*T)->lchild),pre+1,in,k); /*建立左子樹*/
    CreateBiTree1(&((*T)->rchild),pre+1+k,temp+1,len-1-k); /*建立右子樹*/        
}
  • 由中序序列和後序序列唯一確定一棵二叉樹

  由中序序列和後序序列也可以唯一確定一棵二叉樹。在二叉樹的後序序列中,最後一個結點元素一定是根結點。在中序遍歷二叉樹的過程中,先中序遍歷左子樹,然後是根結點,最後遍歷右子樹。因此在二叉樹的中序序列中,根結點將中序序列分為左子樹序列和右子樹序列兩部分。由中序序列的左子樹結點個數,通過掃描後序序列, 可以將後序序列分為左子樹序列和右子樹序列。依此類推就可以構造出二叉樹。
  設結點的中序序列為(D,B,G,E,A,C,F),後序序列為(D,G,E,B,F,C,A)。下圖為確定二叉樹的過程。


這裡寫圖片描述

  由中序序列和後序序列構造二叉樹的演算法實現如下:

void CreateBiTree2(BiTree *T,char *in,char *post,int len) 
/*由中序序列和後序序列構造二叉樹*/
{

    int k;
    char *temp;
    if(len<=0)
    {
        *T=NULL;
        return;
    }
    for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根結點所在的位置*/
        if(*(post+len-1)==*temp)
        {
            k=temp-in;              /*左子樹的長度*/
            (*T)=(BitNode*)malloc(sizeof(BitNode));
            (*T)->data =*temp;
            break;
        }
    CreateBiTree2(&((*T)->lchild),in,post,k);               /*建立左子樹*/
    CreateBiTree2(&((*T)->rchild),in+k+1,post+k,len-k-1); /*建立右子樹*/     
}
  • 由先序序列和後序序列不能唯一確定二叉樹

  假設有一個先序序列為(A,B,C),一個後序序列為(C,B,A),可以構造出兩棵樹。


這裡寫圖片描述

  由此可知,給定先序序列和後序序列不能唯一確定二叉樹。

  • 程式示例

【例1】已知先序序列(E,B,A,C,F,H,G,I,K,J)和中序序列(A,B,C,D,E,F,G,H,I,J,K)、中序序列(A,B,C,D,E,F,G,H,I,J,K)和後序序列(A,C,D,B,G,J,K,I,H,F,E),構造一棵二叉樹。

#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include<conio.h>
#define MaxSize 100
/*二叉樹型別定義*/
typedef struct Node
{ 
    char data;
    struct Node * lchild,*rchild;
}BitNode,*BiTree;
/*函式宣告*/
void CreateBiTree1(BiTree *T,char *pre,char *in,int len);
void CreateBiTree2(BiTree *T,char *in,char *post,int len);
void Visit(BiTree T,BiTree pre,char e,int i);
void PrintLevel(BiTree T);
void PrintTLR(BiTree T);
void PrintLRT(BiTree T);
void PrintLevel(BiTree T);
void main()
{ 
    BiTree T,ptr=NULL;
    char ch;
    int len;
    char pre[MaxSize],in[MaxSize],post[MaxSize];
    T=NULL;
    /*由中序序列和後序序列構造二叉樹*/
    printf("由先序序列和中序序列構造二叉樹:\n");
    printf("請你輸入先序的字串序列:");
    gets(pre);
    printf("請你輸入中序的字串序列:");
    gets(in);
    len=strlen(pre);
    CreateBiTree1(&T,pre,in,len);
    /*後序和層次輸出二叉樹的結點*/
    printf("你建立的二叉樹後序遍歷結果是:\n");
    PrintLRT(T);
    printf("\n你建立的二叉樹層次遍歷結果是:\n");
    PrintLevel(T);
    printf("\n");
    printf("請你輸入你要訪問的結點:");
    ch=getchar();getchar();
    Visit(T,ptr,ch,1); 
    /*由中序序列和後序序列構造二叉樹*/
    printf("由先序序列和中序序列構造二叉樹:\n");
    printf("請你輸入中序的字串序列:");
    gets(in);
    printf("請你輸入後序的字串序列:");
    gets(post);
    len=strlen(post);
    CreateBiTree2(&T,in,post,len);
    /*先序和層次輸出二叉樹的結點*/
    printf("\n你建立的二叉樹先序遍歷結果是:\n");
    PrintTLR(T);
    printf("\n你建立的二叉樹層次遍歷結果是:\n");
    PrintLevel(T);
    printf("\n");
    printf("請你輸入你要訪問的結點:");
    ch=getchar();getchar();
    Visit(T,ptr,ch,1); 
}
void PrintLevel(BiTree T)
/*按層次輸出二叉樹的結點*/
{ 
    BiTree Queue[MaxSize];
    int front,rear;
    if(T==NULL) 
        return;
    front=-1;                           /*初始化化佇列*/
    rear=0;
    Queue[rear]=T;
    while(front!=rear)                  /*如果佇列不空*/
    { 
        front++;                        /*將隊頭元素出隊*/
        printf("%4c",Queue[front]->data);/*輸出隊頭元素*/
        if(Queue[front]->lchild!=NULL)  /*如果隊頭元素的左孩子結點不為空,則將左孩子入隊*/
        {
            rear++;
            Queue[rear]=Queue[front]->lchild;
        }
        if(Queue[front]->rchild!=NULL)  /*如果隊頭元素的右孩子結點不為空,則將右孩子入隊*/
        {
            rear++;
            Queue[rear]=Queue[front]->rchild;
        }
    }
}
void PrintTLR(BiTree T)
/*先序輸出二叉樹的結點*/
{ 
    if(T!=NULL)
    { 
        printf("%4c ",T->data); /*輸出根結點*/
        PrintTLR(T->lchild);    /*先序遍歷左子樹*/
        PrintTLR(T->rchild);    /*先序遍歷右子樹*/
    }
}
void PrintLRT(BiTree T)
/*後序輸出二叉樹的結點*/
{ 
    if (T!=NULL)
    { 
        PrintLRT(T->lchild);    /*先序遍歷左子樹*/
        PrintLRT(T->rchild);    /*先序遍歷右子樹*/
        printf("%4c",T->data);  /*輸出根結點*/   
    }
}
void Visit(BiTree T,BiTree pre,char e,int i)
/*訪問結點e*/
{  
    if(T==NULL&&pre==NULL) 
    { 
        printf("\n對不起!你還沒有建立二叉樹,先建立再進行訪問!\n");
        return;
    }
    if(T==NULL) 
        return;
    else if(T->data==e)             /*如果找到結點e,則輸出結點的雙親結點*/
    { 
        if(pre!=NULL)
        {
            printf("%2c的雙親結點是是:%2c\n",e,pre->data);
            printf("%2c結點在%2d層上\n",e,i);
        }
        else
            printf("%2c位於第1層,無雙親結點!\n",e);  
    }
    else
    {
        Visit(T->lchild,T,e,i+1);/*遍歷左子樹*/
        Visit(T->rchild,T,e,i+1);/*遍歷右子樹*/
    } 
}
void CreateBiTree1(BiTree *T,char *pre,char *in,int len) 
/*由先序序列和中序序列構造二叉樹*/
{   
    int k;
    char *temp;
    if(len<=0)
    {
        *T=NULL;
        return;
    }
    *T=(BitNode*)malloc(sizeof(BitNode));/*生成根結點*/
    (*T)->data=*pre;                    
    for(temp=in;temp<in+len;temp++)     /*在中序序列in中找到根結點所在的位置*/
        if(*pre==*temp)
            break;
    k=temp-in;                          /*左子樹的長度*/
    CreateBiTree1(&((*T)->lchild),pre+1,in,k); /*建立左子樹*/
    CreateBiTree1(&((*T)->rchild),pre+1+k,temp+1,len-1-k); /*建立右子樹*/        
}
void CreateBiTree2(BiTree *T,char *in,char *post,int len) 
/*由中序序列和後序序列構造二叉樹*/
{

    int k;
    char *temp;
    if(len<=0)
    {
        *T=NULL;
        return;
    }
    for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根結點所在的位置*/
        if(*(post+len-1)==*temp)
        {
            k=temp-in;              /*左子樹的長度*/
            (*T)=(BitNode*)malloc(sizeof(BitNode));
            (*T)->data =*temp;
            break;
        }
    CreateBiTree2(&((*T)->lchild),in,post,k);               /*建立左子樹*/
    CreateBiTree2(&((*T)->rchild),in+k+1,post+k,len-k-1); /*建立右子樹*/     
}
  • 測試結果


這裡寫圖片描述

樹的廣義表形式表示

【例2】一棵樹可以用廣義表形式表示,如下圖所示的一棵樹可以表示為A(B(D,E,F),C(G,H)),編寫演算法對以孩子連結串列表示的樹輸出其廣義表的表示形式。


這裡寫圖片描述

  在樹的廣義表形式中,如果不考慮括號和逗號,那麼結點是按照先根遍歷序列排列的,因此可以對以孩子兄弟連結串列表示的樹進行先根遍歷,並在輸出各子樹的結點時加上括號。
  當樹不為空時,輸出根結點;若根結點有孩子,則先輸出左括號“(”,再依次輸出各子樹的廣義表形式,並用逗號隔開,最後輸出右括號“)”。顯然這是一個遞迴過程,演算法描述如下:

void ListTree(CSTree T)
{
    CSTree p;
    if(T==NULL)
        return;
    printf("%c",T->data);/*輸出根結點*/
    p=T->firstchild;
    if(p!=NULL)/*若根結點有孩子*/
    {
        printf("(");/*輸出左括號*/
        ListTree(p);
        p=p->nextsibling;
        while(p!=NULL)
        {
            printf(",");/*輸出逗號,以分隔各子樹*/
            ListTree(p);/*輸出下一個子表的廣義表形式*/
            p=p->nextsibling;
        }
        printf(")");/*輸出右括號*/
    }
}

樹的實現與插入子樹

原始碼參見樹的實現

相關文章