二叉树重难点总结(判断完全二叉树,非递归前、中、后序遍历的实现等...)

 

二叉树是我们在学习数据结构过程中的重难点,这里对其内容稍作总结,巩固自己知识的同时,也希望可以帮助到正在学习此部分内容的同学。废话不多讲,先来做好准备工作,创建好一个二叉树,实现它的一些基本操作。

由于后面实现层次遍历,非递归遍历二叉树时需要用到队列、栈,为实现方便,这里直接把二叉树的定义放到了上次实现的队列、栈的头文件里面( 若有需要: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"); 
} 

 

菜鸟小结,难免出错 ~   如若有错,恳求指教。

posted @ 2018-01-14 19:12  tp_16b  阅读(4126)  评论(0编辑  收藏  举报