二叉樹順序存儲和鏈式存儲的C語言代碼實現


二叉樹是在樹的基礎上對本身的結構做了更高的限制:

  1. 二叉樹本身是有序樹。
  2. 二叉樹中各結點的度最多是 2,可以是 0,1,2。

 
圖1 二叉樹

滿二叉樹和完全二叉樹

如果二叉樹中除了葉子結點,每個結點的度都為 2,那么此二叉樹為滿二叉樹。例如圖 1 就是一個滿二叉樹。

如果二叉樹除了最后一層外為滿二叉樹,最后一層的結點依次從左到右分布,此二叉樹為完全二叉樹。

  (A)                                                                  (B)
圖2 完全二叉樹
 

圖 2(A)和(B)都是二叉樹,但圖 2(A)是完全二叉樹,(B)由於最后一層不符合從左往右依次分布的要求,所以不是完全二叉樹,只是一個普通的二叉樹。

二叉樹的性質

二叉樹有以下幾個性質:

  1. 二叉樹中,第 i 層最多有2^(i-1)個結點。
  2. 如果二叉樹的深度為 K,那么此二叉樹最多有2^K - 1個結點。
  3. 二叉樹中,終端結點數(葉子結點數)為 n0,度為 2 的結點數為 n2,則n0 = n2 + 1

性質 3 的計算方法為:

對於一個二叉樹來說,除了度為 0 的葉子結點和度為 2 的結點,剩下的就是度為 1 的結點(設為 n1),那么總結點n = n0 + n1 + n2
同時,對於每一個結點來說都是由其父結點分支表示的,假設樹中分枝數為 B,那么總結點數n = B + 1。而分枝數是可以通過 n1 和 n2 表示的:B = n1 + 2 * n2
所以,n 用另外一種方式表示為:n=n1+2*n2+1
兩種方式得到的 n 值組成一個方程組,就可以得出n0 = n2 + 1

完全二叉樹特有的性質

n 個結點的完全二叉樹的深度為[log2n]+1

  證明過程:

      對於滿二叉樹來說,k層總的節點個數n = 2^k - 1;則k = log(n + 1); 同理,對於倒數第二層以上的滿二叉樹的節點個數為2^(k - 1) - 1,對於完全二叉樹來說,節點個數 2^(k - 1) - 1 < n <=2^k - 1,  由於n是整數,因此上式可以看成2^(k - 1) <= n < 2^k;故 k - 1 <= log2 n < k;因此,k = [log2 n](取下限) + 1;

[log2n]表示取小於log2n的最大整數。例如,[log24] = 2,而 [log25] 結果也是 2。

對於任意一個完全二叉樹來說,將含有的結點按照層次從左到右依次標號(如圖 2(A)),對於任意一個結點 i ,有以下幾個結論:

  • 當 i > 1時,父親結點為結點 [i / 2] 。( i = 1時,表示的是根結點,無父親結點)
  • 如果 2*i > n ,則結點 i 肯定沒有左孩子(為葉子結點);否則其左孩子是結點 2*i 。
  • 如果 2*i +1 > n ,則結點 i 肯定沒有右孩子;否則右孩子是結點 2*i +1 。
二叉樹和完全二叉樹的各自所特有的性質,需要熟記,在對其進行存儲以及利用二叉樹解決問題時,會經常用到。

二叉樹的存儲結構

二叉樹有兩種存儲結構:順序存儲結構和鏈式存儲結構。

順序存儲


借用數組將二叉樹中的數據元素存儲起來。此方式只適用於完全二叉樹,如果想存儲普通二叉樹,需要將普通二叉樹轉化為完全二叉樹。

使用數組存儲完全二叉樹時,從數組的起始地址開始,按層次順序從左往右依次存儲完全二叉樹中的結點。當提取時,根據完全二叉樹的第 2 條性質,可以將二叉樹進行還原。

例如,存儲圖 2(A)時,數組中存儲為:



根據完全二叉樹的第 2 條性質就可以根據數組中的數據重新搭建二叉樹的結構。

如果普通二叉樹也采取順序存儲的方式,就需要將其轉化成完全二叉樹,然后再存儲,例如:



 轉化前                              轉化后
圖 3 普通二叉樹轉完全二叉樹


圖 3 中,轉化后的二叉樹中,數據元素 0 表示此位置沒有數據。將轉化后的完全二叉樹按照層次並從左到右的次序存儲到數組中:



由此可見。深度為 K 且只有 K 個結點的單支樹(樹中不存在度為 2 的結點),需要 2K-1 的數組空間,浪費存儲空間。所以,順序存儲方式更適用於完全二叉樹。

鏈式存儲


采用鏈式存儲結構存儲二叉樹,就非常容易理解了。根據每個結點的結構,至少需要 3 部分組成:


圖5 二叉鏈表結點構成


圖 5 中,Lchild 代表指向左孩子的指針域;data 為數據域;Rchild 代表指向右孩子的指針域。使用此種結點構建的二叉樹稱為“二叉鏈表”。

結點結構代碼表示:

typedef struct BiTNode{
    TElemType data;//數據域
    struct BiTNode *lchild,*rchild;//左右孩子指針
}BiTNode,*BiTree;
View Code

 

 

如果程序中需要頻繁地訪問結點的父結點,就可以使用下面這種結點結構:


圖 6 三叉鏈表結點構成


圖 6 中,Lchild 指向左孩子;Rchild 指向右孩子;data 為數據域;parent 指向父結點。使用這種結構的結點創建的樹稱為“三叉鏈表”。

結點結構代碼表示:

typedef struct BiTNode{
    TElemType data;//數據域
    struct BiTNode *lchild,*rchild;//左右孩子指針
    struct BiTNode *parent;
}BiTNode,*BiTree;
View Code

 

例如,分別用兩種結點創建圖 3 中的單支樹:
 


圖7 單支樹示意圖


實現代碼(以二叉鏈表為例)

#include <stdio.h>
#include <stdlib.h>
#define TElemType int

typedef struct BiTNode{
    TElemType data;//數據域
    struct BiTNode *lchild,*rchild;//左右孩子指針
}BiTNode,*BiTree;

void CreateBiTree(BiTree *T){
    *T=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->data=1;
    (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild=NULL;
    (*T)->lchild->data=2;            
    (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild=NULL;
    (*T)->lchild->lchild->data=3;
    (*T)->lchild->lchild->lchild=NULL;
    (*T)->lchild->lchild->rchild=NULL;
}
int main() {
    BiTree Tree;
    CreateBiTree(&Tree);
    printf("%d",Tree->lchild->lchild->data);
    return 0;
}
View Code

 

運行結果:

3

總結

對於二叉樹和完全二叉樹的性質,需要學員在理解的情況下進行記憶。有關二叉樹存儲結構的選擇,以及結點結構的選擇,要視情況而定,基本上遵循以下兩個原則:

    1. 如果是普通二叉樹,用鏈式存儲結構;如果是完全二叉樹,用順序存儲結構。
    2. 如果問題中涉及到要訪問某結點的父結點,就建立三叉鏈表;反之,使用二叉鏈表即可解決問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM