1. 程式人生 > >淺談資料結構-二叉樹

淺談資料結構-二叉樹

二叉樹是樹的特殊一種,具有如下特點:1、每個結點最多有兩顆子樹,結點的度最大為2。2、左子樹和右子樹是有順序的,次序不能顛倒。3、即使某結點只有一個子樹,也要區分左右子樹。

一、特殊的二叉樹及特點

 

1、斜樹

所有的結點都只有左子樹(左斜樹),或者只有右子樹(右斜樹)。這就是斜樹,應用較少

QQ截圖20150818161122QQ截圖20150818161132

2、滿二叉樹

所有的分支結點都存在左子樹和右子樹,並且所有的葉子結點都在同一層上,這樣就是滿二叉樹。就是完美圓滿的意思,關鍵在於樹的平衡。

QQ截圖20150818161354

根據滿二叉樹的定義,得到其特點為:

  1. 葉子只能出現在最下一層。
  2. 非葉子結點度一定是2.
  3. 在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子樹最多。

3、完全二叉樹

對一棵具有n個結點的二叉樹按層序排號,如果編號為i的結點與同樣深度的滿二叉樹編號為i結點在二叉樹中位置完全相同,就是完全二叉樹。滿二叉樹必須是完全二叉樹,反過來不一定成立。

其中關鍵點是按層序編號,然後對應查詢。

QQ截圖20150818162144

 

在上圖中,樹1,按層次編號5結點沒有左子樹,有右子樹,10結點缺失。樹2由於3結點沒有字數,是的6,7位置空擋了。樹3中結點5沒有子樹。

QQ截圖20150818162402

上圖就是一個完全二叉樹。

結合完全二叉樹定義得到其特點:

  1. 葉子結點只能出現在最下一層(滿二叉樹繼承而來)
  2. 最下層葉子結點一定集中在左 部連續位置。
  3. 倒數第二層,如有葉子節點,一定出現在右部連續位置。
  4. 同樣結點樹的二叉樹,完全二叉樹的深度最小(滿二叉樹也是對的)。

根據下圖加深理解,什麼時候是完全二叉樹。

三、二叉樹性質

1、一般二叉樹性質

1、在非空二叉樹的i層上,至多有2i-1個節點(i>=1)。通過歸納法論證。

2、在深度為K的二叉樹上最多有2k-1個結點(k>=1)。通過歸納法論證。

3、對於任何一棵非空的二叉樹,如果葉節點個數為n0,度數為2的節點個數為n2,則有: n0 = n2 + 1

在一棵二叉樹中,除了葉子結點(度為0)之外,就剩下度為2(n2)和1(n1)的結點了。則樹的結點總數為T = n0+n1+n2;在二叉樹中結點總數為T,而連線數為T-1.所以有:n0+n1+n2-1 = 2*n2 +n1;最後得到n0 = n2+1;

QQ截圖20150818171417

上圖中結點總數是10,n2為4,n1為1,n0為5。

2、完全二叉樹性質

a、具有n的結點的完全二叉樹的深度為log2n+1.

滿二叉樹是完全二叉樹,對於深度為k的滿二叉樹中結點數量是2k-1 = n,完全二叉樹結點數量肯定最多2k-1,同時完全二叉樹倒數第二層肯定是滿的(倒數第一層有結點,那麼倒是第二層序號和滿二叉樹相同),所以完全二叉樹的結點數最少大於少一層的滿二叉樹,為2k-1-1。

根據上面推斷得出: 2k-1-1< n=<2k-1,因為結點數Nn為整數那麼n<=2k-1可以推出n<=2,n>2k-1-1可以推出 n>=2k-1,所以2k-1<n<=2k  。即可得k-1<=log2n<k 而k作為整數因此k=[log2n]+1。

b、如果有一顆有n個節點的完全二叉樹的節點按層次序編號,對任一層的節點i(1<=i<=n)有

    1.如果i=1,則節點是二叉樹的根,無雙親,如果i>1,則其雙親節點為[i/2],向下取整

    2.如果2i>n那麼節點i沒有左孩子,否則其左孩子為2i

    3.如果2i+1>n那麼節點沒有右孩子,否則右孩子為2i+1

QQ截圖20150818173731

在上圖中驗證

第一條:

當i=1時,為根節點。當i>1時,比如結點為7,他的雙親就是7/2= 3;結點9雙親為4.

第二條:

結點6,6*2 = 12>10,所以結點6無左孩子,是葉子結點。結點5,5*2 = 10,左孩子是10,結點4,為8.

第三條:

結點5,2*5+1>10,沒有右孩子,結點4,則有右孩子。

四、二叉樹遍歷

二叉樹遍歷:從樹的根節點出發,按照某種次序依次訪問二叉樹中所有的結點,使得每個結點被訪問僅且一次。

這裡有兩個關鍵詞:訪問次序。

1、前序遍歷

基本思想:先訪問根結點,再先序遍歷左子樹,最後再先序遍歷右子樹即根—左—右

圖中前序遍歷結果是:1,2,4,5,7,8,3,6。

a/前序遞迴遍歷的程式碼實現,如下所示

複製程式碼
//前序遞迴遍歷
void PreOrderTraverse(BiTree t)
{
  //注意跳出條件
    if(t != NULL)
    {
       //注意訪問語句順序
        printf("%c ", t->data);
        PreOrderTraverse(t->lchild);
        PreOrderTraverse(t->rchild);
    }
}
複製程式碼

前序非遞迴遍歷:

對於任一結點p:

        a. 訪問結點p,並將結點p入棧;

        b. 判斷結點p的左孩子是否為空,若為空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置為當前的結點p,迴圈置a;若不為空,則將p的左孩子置為當前結點p;

        c. 直到p為空,並且棧為空,則遍歷結束。

複製程式碼
//前序非遞迴遍歷
int NoPreOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
 
    BiTree tmp = t;
    if(tmp == NULL)
    {
        fprintf(stdout, "the tree is null.\n");
        return ERROR;
    }
   //現將左子樹壓入棧,當到葉子結點後,出棧,獲取右子樹,然後在壓入右子樹的左子樹。
  //順序不能變
    while((tmp != NULL) || (IsEmpty(&s) != 1)) 
    {
        while(tmp != NULL)
        {
            Push(&s, tmp);
            printf("%c ", tmp->data);
            tmp = tmp->lchild;
        }
        if(IsEmpty(&s) != 1)
        {
            Pop(&s, &tmp);
            tmp = tmp->rchild;
        }
    }
     
    return OK;
}
複製程式碼

2、中序遍歷

基本思想:先中序遍歷左子樹,然後再訪問根結點,最後再中序遍歷右子樹即左—根—右。

圖中中序遍歷結果是:4,2,7,8,5,1,3,6。

中序遍歷迭代程式碼

複製程式碼
//中序遞迴遍歷
void InOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        InOrderTraverse(t->lchild);
        printf("%c ", t->data);
        InOrderTraverse(t->rchild);
    }
}
複製程式碼

2)中序非遞迴遍歷

    根據中序遍歷的順序,對於任一結點,優先訪問其左孩子,而左孩子結點又可以看做一個根結點,然後繼續訪問其左孩子結點,直到遇到左孩子結點為空的結點才停止訪問,然後按相同的規則訪問其右子樹。其處理過程如下:

       對於任一結點:

       a. 若其左孩子不為空,則將p入棧,並將p的左孩子設定為當前的p,然後對當前結點再進行相同的操作;

       b. 若其左孩子為空,則取棧頂元素並進行出棧操作,訪問該棧頂結點,然後將當前的p置為棧頂結點的右孩子;

       c. 直到p為空並且棧為空,則遍歷結束。

複製程式碼
//中序非遞迴遍歷二叉樹
int NoInOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
     
    BiTree tmp = t;
    if(tmp == NULL)
    {
        fprintf(stderr, "the tree is null.\n");
        return ERROR;
    }
 
    while(tmp != NULL || (IsEmpty(&s) != 1))
    {
        while(tmp != NULL)
        {
            Push(&s, tmp);
            tmp = tmp->lchild;
        }
 
        if(IsEmpty(&s) != 1)
        {
            Pop(&s, &tmp);
            printf("%c ", tmp->data);
            tmp = tmp->rchild;
        }
    }
    return OK;
}
複製程式碼

3、後序遍歷

 

基本思想:先後序遍歷左子樹,然後再後序遍歷右子樹,最後再訪問根結點即左—右—根。

圖中後序遍歷結果是:4,8,7,5,2,6,3,1。

後序遞迴遍歷程式碼實現,如下所示。

複製程式碼
//後序遞迴遍歷
void PostOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        PostOrderTraverse(t->lchild);
        PostOrderTraverse(t->rchild);
        printf("%c ", t->data);
    }
}
複製程式碼

  後序遍歷的非遞迴實現是三種遍歷方式中最難的一種。因為在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子之前訪問才能訪問根結點,這就為流程控制帶來了難題。下面介紹一種思路。

     要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點p,先將其入棧。若p不存在左孩子和右孩子,則可以直接訪問它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已經被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將p的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子之前別訪問,左孩子和右孩子都在根結點前面被訪問。

複製程式碼
//後序非遞迴遍歷二叉樹
int NoPostOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
 
    BiTree cur;     //當前結點  
    BiTree pre = NULL;      //前一次訪問的結點
    BiTree tmp;
 
    if(t == NULL)
    {
        fprintf(stderr, "the tree is null.\n");
        return ERROR;
    }
 
    Push(&s, t);
    while(IsEmpty(&s) != 1)
    {
        GetTop(&s, &cur);//
        if((cur->lchild == NULL && cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
        {
            printf("%c ", cur->data);    //如果當前結點沒有孩子結點或者孩子結點都已被訪問過
            Pop(&s, &tmp);
            pre = cur;
        }
        else
        {
            if(cur->rchild != NULL)
            {
                Push(&s, cur->rchild);
            }
            if(cur->lchild != NULL)
            {
                Push(&s, cur->lchild);
            }
        }
    }
    return OK;
}
複製程式碼

五、二叉樹的建立

其實而二叉樹的建立就是二叉樹的遍歷,只不過將輸入內容改為建立結點而已,比如,利用前序遍歷建立二叉樹

複製程式碼
//建立樹
//按先後次序輸入二叉樹中結點的值(一個字元),#表示空樹
//構造二叉連結串列表示的二叉樹
BiTree CreateTree(BiTree t)
{
    char ch;
    scanf("%c", &ch);
 
    if(ch == '#')
    {
        t = NULL;
    }
    else
    {
        t = (BitNode *)malloc(sizeof(BitNode));
        if(t == NULL)
        {
            fprintf(stderr, "malloc() error in CreateTree.\n");
            return;
        }
 
        t->data = ch;                        //生成根結點
        t->lchild = CreateTree(t->lchild);    //構造左子樹
        t->rchild = CreateTree(t->rchild);    //構造右子樹
    }
    return t;
}
複製程式碼