關于樹的定義和存儲結構可以查看上一篇文章樹的定義和樹的三種存儲結構
一、二叉樹的定義
二叉樹的定義
二叉樹(Binary Tree)是n(n>=0)個結點的有限集合,該集合或者為空集(稱為空二叉樹),或者是由一個根結點和兩顆互不相交的、分別稱為根結點的左子樹和右子樹的二叉樹組成。(官方概念,不是很直觀,直接上圖)
二叉樹
二叉樹的特點
- 每個結點最多有兩顆子樹,所以二叉樹中不存在度大于2的結點。(注意不是只有兩顆子樹,是最多有)
- 左子樹和右子樹是有順序的,次序不能任意顛倒。
- 即使樹中某個結點只有一棵樹,也要區分它是左子樹還是右子樹。
二叉樹的五中基本形態
- 空二叉樹。
- 只有一個根節點。
- 根結點只有左子樹。
- 根結點只有右子樹。
- 根結點既有左子樹又有右子樹。
特殊二叉樹
斜樹
- 左斜樹:所有的結點都只有左子樹的二叉樹。
- 右斜樹:所有的結點都只有右子樹的二叉樹。
滿二叉樹
所有分支結點都存在左子樹和右子樹,并且所有葉子都在同一層上的二叉樹。
滿二叉樹的特點
- 葉子結點只能出現在最下一層。出現在其他層就不可能達到平衡。
- 非葉子結點的度一定是2。
- 在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最少。
完全二叉樹
對于一顆具有n個結點的二叉樹按層序編號,如果編號為i(1<=i&&i<=n)
的結點與同樣深度的滿二叉樹中編號為i的結點在二叉樹中位置完全相同的二叉樹。(概念難理解,直接上圖)
完全二叉樹
完全二叉樹的特點
- 葉子結點只能出現在最下兩層。
- 最下層的葉子一定集中在左部連續位置。
- 倒數第二層,若有葉子結點,一定都在右部連續位置。
- 如果結點度為1,則該節點只有左孩子,即不存在只有右子樹的情況。
- 同樣結點的二叉樹,完全二叉樹的深度最小。
區分滿二叉樹和完全二叉樹
- 滿二叉樹一定是完全二叉樹。
- 完全二叉樹不一定是滿二叉樹。
二、二叉樹的性質
- 性質1:在二叉樹的第i層上至多有
2^(i-1)
個結點(i>=1)。 - 性質2:深度為k的二叉樹至多有
2^k - 1
個結點(k>=1)。(注意是2^k后再減去1) - 性質3:對任何一顆二叉樹T,如果其終端結點數為
n0
,度為2的結點樹為n2
,則n0 = n2+1
. - 性質4:具有n個結點的完全二叉樹的深度為:向下取整
log2n+1
;(注意,對x的向下取整就是取不大于x的最大整數) - 性質5:如果對一顆有n個結點的完全二叉樹的結點按層序編號(從第1層到第(向下取整log2n+1),每層從左到右)層,對任一結點i(
1<=i&&i<=n
)有:- 如果
i=1
,則結點i是二叉樹的根,無雙親;如果i>1
,則其雙親是結點:向下取整i/2; - 如果
2i>n
,則結點i無左孩子(結點i為葉子結點);否則其左孩子是結點2i; - 如果
2i+1>n
,則結點i無右孩子;否則其右孩子是結點2i+1
;
- 如果
三、二叉樹的存儲結構
樹的存儲結構可以查看前一篇文章:
由于二叉樹是特殊的樹樹的定義和樹的三種存儲結構
由于二叉樹是一種特殊的樹,所以一般采用二叉鏈表來存儲二叉樹。將二叉樹的結點的最多有兩個孩子,所以為它設計一個數據域和兩個指針域。
lchild | data | rchild |
---|---|---|
指向左孩子的指針 | 數據域 | 指向右孩子的指針 |
代碼實現:
/* 二叉樹的二叉鏈表結點結構定義 */
typedef int ElemeType;
typedef struct BiTNode{ // 結點結構
ElemeType data; // 結點數據
struct BiTNode * lchild; // 左孩子指針
struct BiTNode * rchild; // 右孩子指針
}BiTNode, *BiTree;
二叉鏈表
四、二叉樹的遍歷
二叉樹的遍歷定義
二叉樹的遍歷是指從根節點出發,按照某種次序訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
二叉樹的遍歷方式有很多,如果我們限制了從左到右的習慣方式,那么主要分為四種:
- 前序遍歷
- 中序遍歷
- 后序遍歷
- 層序遍歷
前序遍歷
- 若二叉樹為空,則空操作返回;
- 若不為空,則先訪問根結點,然后前序遍歷左子樹,在前序遍歷右子樹。
-
下圖的前序遍歷結果為:ABDGHCEIF
前序遍歷
中序遍歷
- 若二叉樹為空,則空操作返回;
- 若不為空,則從根節點開始(注意并不是先訪問根結點),中序遍歷根節點的左子樹,然后訪問根節點,最后中序遍歷右子樹。
-
下圖的中序遍歷結果是:GDHBAEICF
中序遍歷
后序遍歷
- 若二叉樹為空,則空操作返回;
- 若不為空,則從左到右先葉子后結點的方式遍歷訪問左右子樹,最后訪問根節點。
-
下圖的后序遍歷結果為:
層序遍歷
- 若二叉樹為空,則空操作返回;
- 若不為空,則從樹的第一層(也就是根結點)開始訪問,從上到下逐層遍歷,在同一層中,從左到右的順序對結點逐個訪問。
-
下圖的層序遍歷結果為:ABCDEFGHI
層序遍歷
代碼實現
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef char ElemType;
typedef struct BiTNode{
char data; // 數據域
struct BiTNode * lchild; // 指向左孩子的指針
struct BiTNode * rchild; // 指向右孩子的指針
}BiTNode, *BiTree;
/**
* 按照前序輸入的方式構造二叉樹
*/
void CreatBitree(BiTree *T){
char c = '\0';
scanf("%c", &c);
if (c == '#') { // 空結點
*T = NULL;
}else{
*T = (BiTNode *)malloc(sizeof(BiTNode));
if (!T)
exit(0);
(*T)->data = c; // 生成根節點
CreatBitree(&(*T)->lchild); // 構造左子樹
CreatBitree(&(*T)->rchild); // 構造右子樹
}
}
/**
* 二叉樹的前序遞歸遍歷
*/
void PreOrderTraverse(BiTree T){
if (T == NULL) // 空樹
return;
printf("%c", T->data); // 顯示結點數據
PreOrderTraverse(T->lchild); // 先序遍歷左子樹
PreOrderTraverse(T->rchild); // 再先序遍歷右子樹
}
/**
* 二叉樹的中序遞歸遍歷
*/
void InOrderTraverse(BiTree T){
if (T == NULL) // 空樹
return;
InOrderTraverse(T->lchild); // 中序遍歷左子樹
printf("%c", T->data); // 顯示結點數據
InOrderTraverse(T->rchild); // 最后中序遍歷右子樹
}
/**
* 二叉樹的后序遞歸遍歷
*/
void PostOrderTraverse(BiTree T){
if (T == NULL) // 空樹
return;
PostOrderTraverse(T->lchild); // 先后序遍歷左子樹
PostOrderTraverse(T->rchild); // 再后序遍歷右子樹
printf("%c", T->data); // 顯示結點數據
}
測試代碼
int main(int argc, const char * argv[]) {
printf("請按照先序輸入二叉樹的結點,空結點用#表示, 回車結束\n");
BiTree T = NULL;
CreatBitree(&T);
printf("該二叉樹的前序遍歷結果為:\n");
PreOrderTraverse(T);
printf("\n");
printf("該二叉樹的中序遍歷結果為:\n");
InOrderTraverse(T);
printf("\n");
printf("該二叉樹的后序遍歷結果為:\n");
PostOrderTraverse(T);
printf("\n");
return 0;
}
擴展:遍歷二叉樹的同時輸出該節點所在的層數
在二叉樹的幾種遍歷方式的代碼中,為了輸出該節點的數據,會有一個printf,要輸出該節點的層數 ,就可以改變這里的代碼來實現,比如在前序遞歸遍歷的同時輸出節點所在層數,可以將這個打印代碼寫在一個封裝在一個方法里面然后調用
代碼實現
/**
* 打印該節點所在層數
*/
void visit(char c, int level){
printf("%c 位于第 %d 層\n", c, level);
}
/**
* 二叉樹的前序遞歸遍歷
*/
void PreOrderTraverse(BiTree T, int level){
if (T == NULL) // 空樹
return;
// printf("%c", T->data); // 顯示結點數據
visit(T->data, level); // 調用寫好的打印方法
PreOrderTraverse(T->lchild, level + 1); // 先序遍歷左子樹
PreOrderTraverse(T->rchild, level + 1); // 再先序遍歷右子樹
}
int main(int argc, const char * argv[]) {
printf("請按照先序輸入二叉樹的結點,空結點用#表示, 回車結束\n");
int level = 1;
BiTree T = NULL;
CreatBitree(&T);
printf("該二叉樹的前序遍歷結果為:\n");
PreOrderTraverse(T, level);
printf("\n");
return 0;
}
結果輸出