二叉樹重難點總結(判斷完全二叉樹,非遞歸前、中、后序遍歷的實現等...)


 

二叉樹是我們在學習數據結構過程中的重難點,這里對其內容稍作總結,鞏固自己知識的同時,也希望可以幫助到正在學習此部分內容的同學。廢話不多講,先來做好准備工作,創建好一個二叉樹,實現它的一些基本操作。

由於后面實現層次遍歷,非遞歸遍歷二叉樹時需要用到隊列、棧,為實現方便,這里直接把二叉樹的定義放到了上次實現的隊列、棧的頭文件里面( 若有需要: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"); } 

 

菜鳥小結,難免出錯 ~   如若有錯,懇求指教。


免責聲明!

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



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