參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure
1.二叉樹的定義
二叉樹(Binary Tree)是結點的有限集合,這個集合或者是空,或者是由一個根結點和兩棵互不相交的稱爲左子樹和右子樹的二叉樹構成。
以上對二叉樹的定義也是一種遞歸的方式,二叉樹也是樹,它是一種特殊的樹,每一個結點最多只能有兩棵子樹,而且它的子樹也滿足二叉樹的定義,也是或者爲空,或者是一個根結點和兩個稱爲左子樹和右子樹的二叉樹構成。
二叉樹的特點是每個結點最多有兩個孩子,或者說,在二叉樹中,不存在度大於2的結點,並且二叉樹是有序樹(樹爲無序樹),其子樹的順序不能顛倒,因此,二叉樹有五種不同的形態,參見下圖:
二叉樹的性質:
1.二叉樹第i層上最多有2i-1個結點(i≥1)。
2.深度爲k的二叉樹最多有2k-1個結點(k≥1)。
3.在任意二叉樹中,葉子結點的數目(即度爲0的結點數)等於度爲2的結點數加1。即n0=n2+1。
4.具有n個結點的完全二叉樹高度爲log2(n)向下取整+1 或 log2(n+1)向上取整 。
5.如果將一棵有n個結點的完全二叉樹從上到下,從左到右對結點編號1,2,…,n,然後按此編號將該二叉樹中各結點順序地存放於一個一維數組中,並簡稱編號爲j的結點爲 j(1≤j≤n),則有如下結論成立:
(1)若 j=1,則結點j爲根結點,無雙親,否則j的雙親爲 (j/2)向下取整;
(2)若2j≤n,則結點j的左子女爲2j ,否則無左子女。即滿足 2j>n的結點爲葉子結點;
(3)若2j+1≤n,則結點j的右子女爲2j+1,否則無右子女;
(4)若結點j序號爲奇數且不等於1,則它的左兄弟爲j-1;
(5)若結點j序號爲偶數且不等於n,它的右兄弟爲j+1;
(6) 結點j所在層數(層次)爲(log2j)向下取整+1;
補充:
6.有n個節點的完全二叉樹有(n/2)向上取整個葉子節點。
7.給定一個前序序列和一箇中序序列,或者給定一箇中序序列和後序序列,可以唯一確定一個二叉樹。
2.二叉樹的存儲
2.1順序存儲結構
#define MAX_TREE_SIZE 50
typedef char TElemType;
typedef TElemType SqBiTree[MAX_TREE_SIZE];
SqBiTree tree;
對於節點tree[i],雙親結點:tree[i/2]
左孩子結點:tree[2*i]
右孩子結點:tree[2*i+1]
對於一般的二叉樹,在二叉樹中補上若干虛擬結點使其成爲完全二叉樹,然後,按上述方法存儲。
對於一棵二叉樹,若採用順序存貯時,當它爲完全二叉樹時,比較方便,若爲非完全二叉樹,將會浪費大量存貯存貯單元。最壞的非完全二叉樹是全部只有右分支,設高度爲K,則需佔用2k-1個存儲單元,而實際只有k個元素,實際只需k個存儲單元。因此,對於非完全二叉樹,宜採用下面的鏈式存儲結構。
2.2鏈式存儲結構
2.2.1動態二叉鏈表
typedef char TElemType;
//動態二叉鏈表
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
對於一棵二叉樹,若採用二叉鏈表存儲時,當二叉樹爲非完全二叉樹時,比較方便,若爲完全二叉樹時,將會佔用較多存儲單元(存放地址的指針)。
若一棵二叉樹有n個結點,採用二叉鏈表作存儲結構時,共有2n個指針域,其中只有n-1個指針指向左右孩子,其餘n+1個指針爲空,沒有發揮作用,被白白浪費掉了,但我們可以利用這些空鏈域存儲其他有用信息,從而得到另一種鏈式存儲結構----線索鏈表,將在以後的博文中逐漸介紹。
2.2.2靜態二叉鏈表
#define MAXSIZE 20
typedef char TElemType;
typedef struct{
int llink;
TElemType data;
int rlink;
}BiNode;
typedef BiNode BiTree[MAXSIZE+1];//下標爲0的單元空出來
3.二叉樹的靜態鏈表存儲以及遍歷的實現
遍歷:系統地訪問數據結構中的結點, 每個結點都正好被訪問一次。
遍歷二叉樹的過程實際上就是把二叉樹中的結點形成一個線性序列的過程,或者說把二叉樹線性化。
層次遍歷、前序遍歷、中序遍歷、後序遍歷
測試實例:
測試按先序序列輸入:abc..de.g..f...#,點號代表空樹,#爲輸入結束符
3.1定義靜態二叉鏈表
#include<stdio.h>
#define MAXSIZE 20
typedef char TElemType;
typedef struct{
int llink;
TElemType data;
int rlink;
}BiNode;
typedef BiNode BiTree[MAXSIZE+1];//下標爲0的單元空出來
3.2按先序序列輸入創建二叉樹的遞歸算法
//按先序次序輸入二叉樹中結點的值(一個字符),點號字符表示空樹,構造二叉鏈表表示的二叉樹T
void createBiTree(BiTree &T, int &root, int &i){
TElemType e;
scanf("%c", &e);
if(e!='#'){
if(e!='.'){//輸入的當前節點不是“空樹”,是實結點
T[i].data = e;
//T[i].llink = 0;
//T[i].rlink = 0;
root = i;
createBiTree(T, T[root].llink, ++i);
createBiTree(T, T[root].rlink, ++i);
}else{
root = 0;//輸入的當前節點是“空樹”,是虛結點,則對應指向它的鏈爲0
i--;//靜態鏈表下標回退,因爲空樹沒有進表,只有實節點進表了
}
}
}
3.3先序遍歷二叉樹的遞歸算法
//先序遍歷二叉樹(根、左、右)(遞歸算法)
void preOrderPrint(BiTree T, int root){
if(root){//根節點不爲空
printf("%c ", T[root].data);
preOrderPrint(T, T[root].llink);
preOrderPrint(T, T[root].rlink);
}
}
3.4先序遍歷二叉樹的非遞歸算法
//先序遍歷二叉樹(根、左、右)(非遞歸算法)
void preOrderPrint2(BiTree T, int root){
//BiNode s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點
int s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點
int top = 0;//指向棧頂元素的下一個位置,初始時top = 0
int pointer = root;//pointer指向當前層的根節點
while(pointer || top){
if(pointer){//根節點不爲空
printf("%c ", T[pointer].data);
//s[top++] = T[pointer];//根節點入棧
s[top++] = pointer;//根節點入棧
pointer = T[pointer].llink;//找根節點的左孩子
}else{//根節點爲空
//BiNode topElem = s[--top];//棧頂元素出棧,即上一層的根節點
//pointer = topElem.rlink;
int topElemPt = s[--top];//棧頂元素出棧,即上一層的根節點
pointer = T[topElemPt].rlink;
}
}
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
printf("先序遍歷打印二叉樹(遞歸算法):\n");
preOrderPrint(tree, root);
printf("\n");
printf("先序遍歷打印二叉樹(非遞歸算法):\n");
preOrderPrint2(tree, root);
printf("\n");
}
3.5中序遍歷二叉樹的遞歸算法
//中序遍歷二叉樹(左、根、右)(遞歸算法)
void inOrderPrint(BiTree T, int root){
if(root){//根節點不爲空
inOrderPrint(T, T[root].llink);
printf("%c ", T[root].data);
inOrderPrint(T, T[root].rlink);
}
}
3.6中序遍歷二叉樹的非遞歸算法
//中序遍歷二叉樹(左、根、右)(非遞歸算法)
void inOrderPrint2(BiTree T, int root){
//BiNode s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點
int s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點
int top = 0;//指向棧頂元素的下一個位置,初始時top = 0
int pointer = root;//pointer指向當前層的根節點
while(pointer || top){
if(pointer){//根節點不爲空
//s[top++] = T[pointer];//根節點入棧
s[top++] = pointer;//根節點入棧
pointer = T[pointer].llink;//找根節點的左孩子
}else{//根節點爲空
//BiNode topElem = s[--top];//棧頂元素出棧,即上一層的根節點
//printf("%c ", topElem.data);
//pointer = topElem.rlink;
int topElemPt = s[--top];//棧頂元素出棧,即上一層的根節點
printf("%c ", T[topElemPt].data);
pointer = T[topElemPt].rlink;
}
}
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
printf("中序遍歷打印二叉樹(遞歸算法):\n");
inOrderPrint(tree, root);
printf("\n");
printf("中序遍歷打印二叉樹(非遞歸算法):\n");
inOrderPrint2(tree, root);
printf("\n");
}
3.7後序遍歷二叉樹的遞歸算法
//後序遍歷二叉樹(左、右、根)(遞歸算法)
void postOrderPrint(BiTree T, int root){
if(root){//根節點不爲空
postOrderPrint(T, T[root].llink);
postOrderPrint(T, T[root].rlink);
printf("%c ", T[root].data);
}
}
3.8後序遍歷二叉樹的非遞歸算法
//後序遍歷二叉樹(左、右、根)(非遞歸算法)
void postOrderPrint2(BiTree T, int root){
//BiNode s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點
int s[MAXSIZE];//維護一個棧,用來記錄遍歷的節點指針
int top = 0;//指向棧頂元素的下一個位置,初始時top = 0
int pointer = root;//pointer指向當前層的根節點
while(pointer || top){
if(pointer){//根節點不爲空
//s[top++] = T[pointer];//根節點入棧
s[top++] = pointer;//根節點指針入棧
pointer = T[pointer].llink;//找根節點的左孩子
}else{//根節點爲空
//BiNode topElem = s[top-1];//取得棧頂元素,即上一層的根節點
int topElemPt = s[top-1];//取得棧頂元素指針,即上一層的根節點指針
if(topElemPt > 0){//當前層根節點的右子樹還沒有被訪問過,則訪問右子樹,並賦“右子樹已遍歷”標誌
pointer = T[topElemPt].rlink;
s[top-1] = -s[top-1];
}else{
//BiNode topElem = s[--top];//棧頂元素出棧,即上一層的根節點
topElemPt = -topElemPt;//還原
printf("%c ", T[topElemPt].data);
top--;
}
}
}
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
printf("後序遍歷打印二叉樹(遞歸算法):\n");
postOrderPrint(tree, root);
printf("\n");
printf("後序遍歷打印二叉樹(非遞歸算法):\n");
postOrderPrint2(tree, root);
printf("\n");
}
3.9按層次遍歷二叉樹的非遞歸算法
typedef BiNode QElemType;
typedef struct{
//QElemType data[20];
QElemType data[20];
int f;//指向隊頭元素
int r;//指向對尾元素的下一個位置
}SqQueue;
//初始化一個空隊列
void initQueue(SqQueue &Q){
Q.f = Q.r = 0;
}
//按層次遍歷二叉樹(從上到下、從左到右)
void hierarchicalTraversePrint(BiTree T, int root){
SqQueue Q;//維護一個隊列,按層次從上到下、從左到右存放二叉樹的各個節點(實際上是按廣度優先搜索算法實現層次遍歷)
initQueue(Q);
//BiNode queue[20];
//根節點入隊
Q.data[Q.r] = T[root];
Q.r++;
while(Q.f != Q.r){
//先降隊頭元素的左孩子(實節點)入隊
if(Q.data[Q.f].llink){
Q.data[Q.r] = T[Q.data[Q.f].llink];
Q.r++;
}
//將隊頭元素的右孩子(實節點)入隊
if(Q.data[Q.f].rlink){
Q.data[Q.r] = T[Q.data[Q.f].rlink];
Q.r++;
}
//打印(訪問)隊頭元素,並將其出隊
printf("%c ", Q.data[Q.f].data);
Q.f++;
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
printf("按層次遍歷打印二叉樹(非遞歸算法):\n");
hierarchicalTraversePrint(tree, root);
printf("\n");
}
4.遍歷二叉樹的應用
4.1求二叉樹的深度
//求二叉樹的深度
int getBiTreeDepth(BiTree T, int root){
if(!root)//根節點爲空樹
return 0;
int leftTreeDepth = getBiTreeDepth(T, T[root].llink);
int rightTreeDepth = getBiTreeDepth(T, T[root].rlink);
return leftTreeDepth > rightTreeDepth ? (leftTreeDepth + 1) : (rightTreeDepth + 1);
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
int depth = getBiTreeDepth(tree, root);
printf("該二叉樹的深度爲:%d\n", depth);
}
4.2求二叉樹的結點數
//求二叉樹的結點數
int getBiTreeSize(BiTree T, int root){
if(!root)
return 0;
int leftTreeSize = getBiTreeSize(T, T[root].llink);
int rightTreeSize = getBiTreeSize(T, T[root].rlink);
return leftTreeSize + rightTreeSize + 1;
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
int size = getBiTreeSize(tree, root);
printf("該二叉樹的結點數爲:%d\n", size);
}
4.3求二叉樹的葉子節點數
//先序遍歷的方法求二叉樹的葉子節點數
int getBiTreeLeafNodesNum(BiTree T, int root){
if(!root)
return 0;
else{//根節點不爲空樹
if(!T[root].llink && !T[root].rlink){//如果當前根節點的左右孩子均不爲空
return 1;
}
int leftTreeLeafNodesNum = getBiTreeLeafNodesNum(T, T[root].llink);
int rightTreeLeafNodesNum = getBiTreeLeafNodesNum(T, T[root].rlink);
return leftTreeLeafNodesNum + rightTreeLeafNodesNum;
}
}
演示:
void main(){
BiTree tree;
int root = 1;//根節點的位置
printf("請按先序次序輸入二叉樹各節點以#號結束,空樹用點號代替:\n");
int pos = 1;//控制加入靜態數組的位置
createBiTree(tree, root, pos);
int leafNodesNum = getBiTreeLeafNodesNum(tree, root);
printf("該二叉樹的葉子結點數爲:%d\n", leafNodesNum);
}