二叉樹是我們在學習數據結構過程中的重難點,這里對其內容稍作總結,鞏固自己知識的同時,也希望可以幫助到正在學習此部分內容的同學。廢話不多講,先來做好准備工作,創建好一個二叉樹,實現它的一些基本操作。
由於后面實現層次遍歷,非遞歸遍歷二叉樹時需要用到隊列、棧,為實現方便,這里直接把二叉樹的定義放到了上次實現的隊列、棧的頭文件里面( 若有需要:http://www.cnblogs.com/tp-16b/p/8252253.html )。在對應地方作稍作改動(改動地方后面有注釋)
StackQueue.h
#ifndef _STACKQUEUE_H_ #define _STACKQUEUE_H_
typedef int BTDataType; typedef struct BinaryTreeNode { struct BinaryTreeNode* _lchild; struct BinaryTreeNode* _rchild; BTDataType _data; }BTNode; typedef BTNode* SDataType; //棧里存放二叉樹結點地址
typedef struct Stack { SDataType* _array; size_t _top; //棧頂
size_t _end;
}Stack;
typedef BTNode* QDataType; //隊列里面存放二叉樹結點地址 typedef struct QueueNode { QDataType _qdata; struct QueueNode* _next; }QueueNode; typedef struct Queue { QueueNode* _head; QueueNode* _tail; }Queue; void StackInit(Stack* s); void StackPush(Stack* s, SDataType x); void StackPop(Stack* s); SDataType StackTop(Stack* s); size_t StackSize(Stack* s); int StackEmpty(Stack* s); void QueueInit(Queue* q); void QueuePush(Queue* q, QDataType x); void QueuePop(Queue* q); QDataType QueueFront(Queue* q); QDataType QueueBack(Queue* q); size_t QueueSize(Queue* q); int QueueEmpty(Queue* q); #endif
二叉樹的創建以及普通操作
BinaryTree.c 4 #include<stdio.h> 5 #include<malloc.h> 6 #include<assert.h> 7 #include "StackQueue.h" 8 9 BTNode* BuyBTNode(BTDataType x) { 10 BTNode* newNode = (BTNode*)malloc(sizeof(BTNode)); 11 assert(newNode); 12 13 newNode->_data = x; 14 newNode->_lchild = NULL; 15 newNode->_rchild = NULL; 16 return newNode; 17 } 18 // 創建二叉樹 19 BTNode* CreateBTree(BTDataType* a, size_t* pIndex, BTDataType invalid){ 20 assert(a); 21 22 if(a[*pIndex] == invalid){ 23 return NULL; 24 } 25 26 BTNode* root = BuyBTNode( a[*pIndex]); 27 ++*(pIndex); //哇,調好幾遍才注意到。不僅是要傳Index的指針pIndex,還得注意不是對pIndex++ 28 root->_lchild = CreateBTree(a, pIndex , invalid); 29 ++*(pIndex); 30 root->_rchild = CreateBTree(a, pIndex , invalid); 31 return root; 32 } 33 void BTreePrevOrder(BTNode* root){ //前序遍歷 34 if(root == NULL){ 35 return ; 36 } 37 38 printf("%d ",root->_data); 39 BTreePrevOrder(root->_lchild); 40 BTreePrevOrder(root->_rchild); 41 } 42 void BTreeInOrder(BTNode* root){ //中序遍歷 43 if(root == NULL){ 44 return ; 45 } 46 47 BTreeInOrder(root->_lchild); 48 printf("%d ",root->_data); 49 BTreeInOrder(root->_rchild); 50 } 51 void BTreePostOrder(BTNode* root) { //后序遍歷 52 if(root == NULL){ 53 return; 54 } 55 56 BTreePostOrder(root->_lchild); 57 BTreePostOrder(root->_rchild); 58 printf("%d ",root->_data); 59 }
考察二叉樹屬性的相關操作
<1>求結點總數 size_t BTreeSize(BTNode* root){ if(root == NULL){ return 0; } //划分子問題 return 1 + BTreeSize(root->_lchild) + BTreeSize(root->_rchild); }
<2>求葉子結點數 size_t BTreeLeafSize(BTNode* root) { if(root == NULL){ return 0; } //該結點的左右子樹為空,返回1 if(root->_lchild == NULL && root->_rchild == NULL){ return 1; } return BTreeLeafSize(root->_lchild) + BTreeLeafSize(root->_rchild); } <3>求二叉樹的深度
//選左右子樹深度較大的遞歸
size_t BTreeDepth(BTNode* root) { if(root == NULL){ return 0; } size_t leftDepth = BTreeDepth(root->_lchild); size_t rightDepth = BTreeDepth(root->_rchild); if(leftDepth > rightDepth){ //選深度 return leftDepth + 1; } return rightDepth + 1; }
<4>求第K層二叉樹的結點數 //轉化為k -1層結點數子問題 size_t BTreeKLevelSize(BTNode* root, size_t k){ if(root == NULL || k <= 0){ return 0; } if(root && k == 1){ //到根結點 return 1; } return BTreeKLevelSize(root->_lchild, k-1 ) + BTreeKLevelSize(root->_rchild, k-1 ); }
<5>二叉樹查找 BTNode* BTreeFind(BTNode* root, BTDataType x) { if(root == NULL){ return NULL; } if(root->_data == x) return root; //定義變量來保存結果,這樣如果在左子樹找到了,就不用在去右子樹查找,提高了效率 BTNode* lChildRet = BTreeFind(root->_lchild , x); if(lChildRet){ return lChildRet; } BTNode* rChildRet = BTreeFind(root->_rchild , x); if(rChildRet){ return rChildRet; } }
先簡單測一下:
void TestBinaryTree0(){ int a[] = {1, 2, 3, '#','#',4,'#', '#', 5, 6,7,'#','#' ,'#' ,'#',}; size_t index = 0; BTNode* tree = CreateBTree(a, &index, '#'); BTreePrevOrder(tree); printf("\n"); BTreeInOrder(tree); printf("\n"); BTreePostOrder(tree); printf("\n"); printf("BTreeSize?%d\n", BTreeSize(tree)); printf("BTreeLeafSize?%d\n", BTreeLeafSize(tree)); printf("BTreeKLevelSize?%d\n", BTreeKLevelSize(tree, 6)); printf("BTreeDepth?%d\n", BTreeDepth(tree)); printf("BTreeFind?%#p\n", BTreeFind(tree , 6)); }

二叉樹的層次遍歷
思路:借助隊列來實現,先把二叉樹根結點地址入隊列,以后每從隊列里面出一個樹結點地址,訪問該結點的同時將其左右子結點的地址入隊列,直到隊列為空。
//層次遍歷 void BTreeLevelOrder(BTNode* root) { //用隊列來實現 if(root == NULL){ return ; } Queue q; QueueInit(&q); QueuePush(&q, root); while(QueueEmpty(&q) != 1){ //QueueEmpty(&q)等於1表示 隊列里面為空 //每從隊列出一個BTNode* 就將其不為NULL的左右孩子入隊列 BTNode* cur = QueueFront(&q); QueuePop(&q); printf("%d ",cur->_data); if(cur->_lchild){ QueuePush(&q , cur->_lchild); } if(cur->_rchild){ QueuePush(&q , cur->_rchild); } } }
借助層次遍歷判斷完全二叉樹的兩種方法
先說一下什么是完全二叉樹:完全二叉樹其實就是其前n層都是滿的,且第n層從左往右必須是連續的
<1>第一種方法:按層次遍歷的思路,不過要注意的是要把二叉樹結點地址為NULL的結點的地址也入隊列,最后當隊列Pop到QueueFront為NULL時,再去判斷隊列里面存的是否全為NULL,若不是,就不是完全二叉樹。
如下:
int IsCompleteBTree(BTNode* root){ if(root == NULL){ //空樹也算完全二叉樹
return 1; } Queue q; QueueInit(&q); QueuePush(&q, root); while(QueueFront(&q)){ //隊列頭元素值為NULL時停止 //不管左右子樹結點是否為空,都將其入隊列
BTNode* cur = QueueFront(&q); QueuePop(&q);
QueuePush(&q , cur->_lchild); QueuePush(&q , cur->_rchild); } while(QueueEmpty(&q) != 1){ if(QueueFront(&q)){ //隊列里面不全是NULL的則判定為其不是完全二叉樹
return 0; } QueuePop(&q); } return 1; }

因為完全二叉樹‘左半’部分是滿的,是沒有空缺的,這一點在隊列很容易體現出來。
<2>再一個方法是添加一個flag標記位來標識同層結點前面是否出現‘‘空缺’(為0表示有‘空缺’,不是完全二叉樹)。
具體實現也是借助層次遍歷的實現方式,一開始flag置為1;在往后執行的過程中,每從隊列出一個結點,就判斷往隊列里帶入該結點的左右子結點是否為空?是空就將flag置0,不是空就再依據當前flag的值來決定是否將左右子結點入隊列;如果flag為1,左右子結點入隊列,否則,其不是完全二叉樹。
int IsCompleteBTree1(BTNode* root){ // flag的方式判斷 if(root == NULL){ return 1; } int flag = 1; Queue q; QueueInit(&q); QueuePush(&q , root); while(QueueEmpty(&q) != 1) { //QueueEmpty(&q)等於1 表示隊列為空 BTNode* cur = QueueFront(&q); QueuePop(&q); if(cur->_lchild){ if(flag == 0){ // 說明前面已經出現null,不是完全二叉樹 return 0; } QueuePush(&q , cur->_lchild); } else{ flag = 0; } if(cur->_rchild){ if(flag == 0){ //前面已經出現null ,不是完全二叉樹 return 0; } QueuePush(&q , cur->_rchild); } else{ flag = 0; } } return 1; }
非遞歸實現二叉樹前、中、后序遍歷
<1>非遞歸前序遍歷:
仿效遞歸的思路,利用棧來實現的非遞歸,具體看下圖
//非遞歸前序遍歷 void BTreePrevOrderNonR(BTNode* root) { BTNode* cur = root; Stack s; StackInit(&s); BTNode* topCur = NULL; while(cur || !StackEmpty(&s)){ //只有在cur指向了空,同時棧也空的情況下,停止循環。 while(cur){ printf("%d ",cur->_data); StackPush(&s , cur); cur = cur->_lchild; } topCur = StackTop(&s); cur = topCur->_rchild; StackPop(&s); } printf("\n"); }
<2>非遞歸中序遍歷:
和前面的前序遍歷十分相似,開始還是將根節點入棧,非空的左子結點不停入棧,到左子結點為空時,訪問當前根結點,然后再訪問它的右子樹,重復以上步驟。
//非遞歸中序 void BTreeInOrderNonR(BTNode* root){ BTNode* cur = root; Stack s; StackInit(&s); BTNode* topCur = NULL; while(cur || !StackEmpty(&s)){ while(cur){ StackPush(&s , cur); cur = cur->_lchild; } topCur = StackTop(&s); printf("%d ",topCur->_data); //訪問當前根結點 cur = topCur->_rchild; StackPop(&s); } printf("\n"); }
<3>非遞歸后序遍歷:
和中序遍歷一樣的思路,只是后序遍歷的步驟會稍微繁瑣一些。①開始還是將根結點入棧;②非空左子結點不斷入棧,③左子結點為空時,就考察當前結點(即棧頂結點)的右子結點是否被訪問過, 若已經訪問過,則訪問當前結點自身,並將其出棧,否則,將該右子結點入棧;如果棧非空就重復②、③直到棧空為止,結束算法。
后序遍歷繁瑣之處就在於要去判斷當前結點的右子樹是否被訪問過。解決這個問題,有一個辦法就是添加一個prev指針來標識上一個訪問的結點。
注意:在結點3都入棧后(topCur指向3),由於是后序遍歷,此時不能立即就訪問結點3,得先去訪問它的右子樹,等到結點7入棧后,且它的左右子結點為null,那么便可訪問結點7,同時讓prev指向它,隨后結點7出棧;繼續循環,然后topCur又指向結點3,但此時prev == topCur->_rchild 這樣就說明結點3的右樹已經訪問過了;然后訪問結點3 同時讓prev又指向3,3再出棧 .....依次循環,最后便實現了非遞歸的后序遍歷 。
void BTreePostOrderNonR(BTNode* root){ BTNode* cur = root; Stack s; StackInit(&s); BTNode* topCur = NULL; BTNode* prev = NULL; while(cur || !StackEmpty(&s)){ while(cur){ StackPush(&s , cur); cur = cur->_lchild; } topCur = StackTop(&s); //判斷右樹是否被訪問? if(topCur->_rchild == NULL || topCur->_rchild == prev){ //右子結點為空也記作訪問過了 printf("%d ",topCur->_data); prev = topCur; StackPop(&s); } //右樹沒有被訪問, else{ cur = topCur->_rchild; } } printf("\n"); }
② 實現后序遍歷還有一個比較好理解的方法,那就是利用雙棧來實現。先以 根->右->左的先序順序遍歷,其遍歷的結果其實就是后序遍歷結果的逆序順序,將遍歷結果存放一個棧中后,再利用一個棧將順序反轉過來即可,如此便實現了后序遍歷。不足之處就是這樣有額外的空間開銷。
void BTreePostOrderNonR1(BTNode* root){ if(root == NULL){ return ; } BTNode* cur = root; Stack s; StackInit(&s); BTNode* topCur = NULL; //定義的SaveStack棧來保存后序遍歷的逆序結果 Stack SaveStack; StackInit(&SaveStack); while(cur || !StackEmpty(&s)){ while(cur){ StackPush(&SaveStack , cur); StackPush(&s , cur); cur = cur->_rchild; } topCur = StackTop(&s); StackPop(&s); cur = topCur->_lchild; } //從SaveStack棧里輸出 while(!StackEmpty(&SaveStack)) { printf("%d ",StackTop(&SaveStack)->_data); StackPop(&SaveStack); } printf("\n"); }
菜鳥小結,難免出錯 ~ 如若有錯,懇求指教。