二叉树的遍历(包括递归遍历和非递归遍历)

二叉树基础知识

概念

  • 二叉树定义:含有n(n>=0)个节点的有限集合
  • 非空二叉树中:
    • 有且仅有一个根节点
    • 其他节点划分为两个互不相交的子集L和R,L称为左子树,R称为右子树
  • 任一结点的左、右子树的根称为该结点的左、右孩子,反过来,该结点称为孩子的双亲
  • 度:结点的孩子个数
  • 叶子结点:度为0 的结点
  • 非叶子结点:度大于0,也称为内部结点或分支结点
  • 二叉树的深度(或高度):结点的最大层次称为二叉树的深度(或高度)。(所谓层次,根节点即为第一层,以此类推)

二叉树的性质

  1. 在非空二叉树的第 i 层上最多右 2^(i-1) 个结点(i>=1)
  2. 深度为 k 的二叉树最多有 2^k - 1 个结点(k>=1)
  3. 对于任意一颗二叉树,若是度为 0 的结点个数为 n0 ,度为 2 的结点个数为 n2,则 n0 = n2+1
  4. 具备 n 个结点的彻底二叉树的深度 k = [log2n] + 1
  5. 对于含 n 个结点的彻底二叉树中编号为 i (1<=i<=n) 的结点
    1. 若是 i = 1,则 i 结点是这颗彻底二叉树的根,没有双亲;不然其双亲的编号为 [i/2]
    2. 若是 2i>n,则 i 结点没有左孩子;不然其左孩子的编号为 2i
    3. 若是 2i+1>n,则 i 结点没有右孩子;不然其右孩子的编号为 2i+1

满二叉树与彻底二叉树

  • 满二叉树:深度为 k 且有 2^k - 1 个结点的二叉树web

  • 彻底二叉树:彻底二叉树是效率很高的数据结构,彻底二叉树是由满二叉树而引出来的。深度为K且含 n 个结点的二叉树,若是其每个结点都与深度为K的满二叉树中编号从1至n的结点一一对应,则称之为彻底二叉树。换句话讲,在一棵二叉树中,除最后一层外,若其他层都是满的,而且最后一层或者是满的,或者是在右边缺乏连续若干节点,则此二叉树为彻底二叉树。算法

二叉树的存储结构

顺序存储

// 一维数组实现
    typedef char TElemType;	  // 假设结点元素类型为字符
    typedef struct {
    TElemType * elem;	      // 0号单元闲置
    int lastIndex;		      // 二叉树最后一个结点的编号
    } SqBiTree;

链式存储

  • 二叉链表
typedef struct BiTNode{
    TElemType data;	  				// 数据域
    struct BiTNode *lchild,*rchild;	// 左、右孩子指针域
} BiTNode,*BiTNode;
  • 三叉链表
typedef struct TriTNode{
    TElemType data;	  						// 数据域
    struct TriTNode *parent,*lchild,*rchild;// 双亲左、右孩子指针域
} TriTNode,*TriTNode;

二叉树的遍历

首先来认识一下遍历

遍历:树的遍历(也称为树的搜索)是图的遍历的一种,指的是按照某种规则,不重复地访问某种树的全部结点,使得每一个结点被且仅被访问的过程。具体的访问操做多是检查节点的值、更新节点的值等。二叉树的遍历也适用于其它树的遍历。数组

遍历是二叉树的一类重要操做,也是二叉树的其余一些操做和各类应用算法的基本框架。数据结构

遍历有两种类别,一种是深度优先遍历,另外一种是广度优先遍历框架

  • 深度优先遍历:先访问子节点,再访问父节点,最后是第二个子节点
    • 先序遍历:VLR,即根结点->左结点->右节点
    • 中序遍历:LVR,即左结点->根结点->右节点
    • 后序遍历:LRV,即左结点->右结点->根节点
  • 广度优先遍历:先访问第一个子节点,再访问第二个子节点,最后访问父节点,二叉树的广度优先遍历就是层次遍历

遍历.jpg

因为从给定的某个节点出发,有多个能够前往的下一个节点(树不是线性数据结构),因此在顺序计算(即非并行计算)的状况下,只能推迟对某些节点的访问——即以某种方式保存起来以便稍后再访问。常见的作法是采用栈(LIFO)或队列(FIFO)。因为树自己是一种自我引用(即递归定义)的数据结构,所以很天然也能够用递归方式。svg

因此,下面我重点总结了这两种不一样的遍历实现方式。函数

注:本文所讲的数据结构均为C语言版学习

先序遍历

递归遍历

Status PreOrderTraverse(BiTree T,Status(*visit)(TElemType e)){
   if(NULL == T) return OK;
   if(ERROR == visit(T->data))
       return ERROR; //访问结点的数据域
   if(ERROR == PreOrderTraverse(T->lchild,visit))
       return ERROR; //递归遍历T的左子树
   return PreOrderTraverse(T->rchild,visit);//递归遍历T的右子树
    
}

非递归遍历

1.使用栈的非递归先序遍历算法spa

/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; 栈类型Stack的相关定义: typedef BiTree SElemType; // 栈的元素类型 Status InitStack(Stack &S); // 初始化栈 Status StackEmpty(Stack S); // 判栈空 Status Push(Stack &S, SElemType e); // 进栈 Status Pop(Stack &S, SElemType &e); // 出栈 Status GetTop(Stack S, SElemType &e); // 取栈顶元素 **********/
void PreOrder(BiTree T, void (*visit)(TElemType))
/*对每一个结点的元素域data调用函数visit进行访问 */
{
   Stack S;   InitStack(S);
   BiTree p = T;
   // 先序访问根节点,遍历左节点 ,左节点入栈
   // StackEmpty(Stack S);S为空返回true反之false;
   // 当栈不空或 p非空时
   while(!StackEmpty(S) || p!=NULL){
     while(p!=NULL){
        visit(p->data);
        Push(S,p);
        p = p->lchild;
     }
     if(!StackEmpty(S)){
        Pop(S,p); // 执行完 p指向S出栈的元素
        p = p->rchild;
     }
   }
}

先序遍历.jpg

2.不使用栈,使用三叉链表的非递归先序遍历算法指针

/********** 三叉链表类型定义: typedef struct TriTNode { TElemType data; struct TriTNode *parent, *lchild, *rchild; } TriTNode, *TriTree; **********/  
void PreOrder(BiTree T, void (*visit)(TElemType)){
    TriTree p, pr;
    if(T!=NULL){
        p = T;
        while(p!=NULL){
            visit(p->data);//输出当前的结点
            if(p->lchild!=NULL){
                p = p->lchild;//如有左孩子,继续访问
            }
            else if(p->rchild!=NULL){
                p = p->rchild;//如有右孩子,继续访问
            }
            else{
              //沿双亲指针链查找,找到第一个由右孩子的p结点
              //找不到则结束
                do{
                    pr = p;
                    p = p->parent;
                }while(p!=NULL&&(p->rchild==pr||NULL==p->rchild)) //p有右孩子但不是pr,结束循环
            	if(p) p = p->rchild;//找到后,p指向右孩子结点
            }
        }
    }
}

中序遍历

递归遍历

Status InOrderTraverse(BiTree T,Status(*visit)(TElemType e)){
   if(NULL == T) return OK;
   if(ERROR == InOrderTraverse(T->lchild,visit))
       return ERROR; //递归遍历T的左子树
   if(ERROR == visit(T->data))
       return ERROR; //访问结点的数据域
   return InOrderTraverse(T->rchild,visit);//递归遍历T的右子树
    
}

非递归遍历

1.使用栈的非递归中序遍历算法

/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; 栈类型Stack的相关定义: typedef BiTree SElemType; // 栈的元素类型 Status InitStack(Stack &S); // 初始化栈 Status StackEmpty(Stack S); // 判栈空 Status Push(Stack &S, SElemType e); // 进栈 Status Pop(Stack &S, SElemType &e); // 出栈 Status GetTop(Stack S, SElemType &e); // 取栈顶元素 **********/
BiTNode *GoFarLeft(BiTree T,Stack &S){
    //从T结点出发,沿左分支走到底,沿途结点的指针入栈S,返回左上结点的指针
    if(!T) return NULL;
    while(T->lchild!=NULL){
        Push(S,T);
        T = T->lchild;
    }
    return T;
} 
void InOrderTraverse(BiTree T,void(*visit)(TElemType e)){
    Stack S;   InitStack(S);
    BiTree p;
    p = GoFarLeft(T, S);//找到最左下的结点,并将沿途结点的指针入栈S
    while(p!=NULL){
        visit(p->data);//访问结点
        if(p->rchild!=NULL){
            //令p指向其右孩子为根的子树的最左下结点
            p = GoFarLeft(p->rchild, S);
        }
        else if(!StackEmpty(S)){
            //栈不空时退栈
            Pop(S,p);
        }
        else p = NULL;//栈空代表遍历结束
    }
}

中序遍历.jpg

2.不使用栈,使用三叉链表的非递归中序遍历算法

/********** 三叉链表类型定义: typedef struct TriTNode { TElemType data; struct TriTNode *parent, *lchild, *rchild; } TriTNode, *TriTree; **********/  
void InOrder(TriTree PT, void (*visit)(TElemType)){
	TriTree p=PT, pr;  
	while(NULL != p) {
        if (p->lchild != NULL) {  
            p = p->lchild;   //寻找最左下结点
        }  
        else {  
            visit(p->data);
            //当前节点左子树为空,但右子树不必定为空,
            //因此该节点多是这个子树的根节点,要先访问,
            //若是有右节点,才能在右节点以前访问
            if (p->rchild != NULL) {  
                //如有右子树,转到该子树,继续寻找最左下结点
                p =p->rchild;
            }
            else { 
                //执行到这里
                //p 要不是最左下节点,要不是没有孩子的右节点
                //其实也就是叶子节点
                pr = p;    //不然返回其父亲
                p = p->parent;  
                while (p != NULL && (p->lchild != pr || NULL == p->rchild))//若其不是从左子树回溯来的,或左结点的父亲并无右孩子 
                {
                    if (p->lchild == pr) {//若最左结点的父亲并无右孩子 
                    	visit(p->data);//直接访问父亲
                    }
                    pr = p;  //父亲已被访问,故返回上一级
                    p = p->parent;  
                //该while循环沿双亲链一直查找,若无右孩子则访问
                //直至找到第一个有右孩子的结点为止
                //(但不访问该结点,留给下步if语句访问
                }
                if (NULL != p) {
                 //访问父亲,并转到右孩子
                 //(经上步while处理,能够肯定此时p有右孩子)
                    visit(p->data);
                    p = p->rchild;
                }
            }
        }
    }
}

后序遍历

递归遍历

Status PostOrderTraverse(BiTree T,Status(*visit)(TElemType e)){
   if(NULL == T) return OK;
   if(ERROR == PostOrderTraverse(T->lchild,visit))
       return ERROR; //递归遍历T的左子树
   if(ERROR == PostOrderTraverse(T->rchild,visit))
       return ERROR; //递归遍历T的右子树
   return visit(T->data);//访问结点的数据域
    
}

非递归遍历

1.使用栈的非递归后序遍历算法

/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; 为分辨后序遍历时两次进栈的不一样返回点,需在指针进栈时同时将一个标志进栈 typedef struct { struct BiTNode *ptr; // 二叉树结点的指针类型 int tag; // 标志 值为 0 或 1 } SElemType; // 栈的元素类型 栈类型Stack的相关定义: typedef BiTree SElemType; // 栈的元素类型 Status InitStack(Stack &S); // 初始化栈 Status StackEmpty(Stack S); // 判栈空 Status Push(Stack &S, SElemType e); // 进栈 Status Pop(Stack &S, SElemType &e); // 出栈 Status GetTop(Stack S, SElemType &e); // 取栈顶元素 **********/
void PostOrder(BiTree T, void (*visit)(TElemType))
/* 使用栈,非递归后序遍历二叉树T, */
/* 对每一个结点的元素域data调用函数visit */
{
    Stack S; InitStack(S); 
    SElemType e; BiTree p=T;
    e.tag=0; 
    while((!StackEmpty(S)||p==T)&&T!=NULL){
        while(p){// 这个小 while循环的目的是先遍历每一个子树的左节点到底,并将沿途的左节点入栈
            e.ptr=p;
            e.tag=0;
            Push(S,e); // 入栈
            p=p->lchild;                        
        }// 小while结束,此时的栈顶元素也就是要访问的起始节点 
        if(!StackEmpty(S)){  // tag==1,e为第二次出如今栈顶
            Pop(S,e); // 取出栈顶元素,并返回给e,e指向出栈元素,方便下面的tag值判断
            if(e.tag == 1)  {
                p=e.ptr;
                visit(p->data);// 访问该结点 
                p = NULL;
            }
            else  {                
                p=e.ptr;
                if(p->rchild){  // 若是存在右子树,设tag == 1
                    e.tag=1;
                    Push(S,e); // 从新入栈,它是根节点,留着等下再访问
                    p=p->rchild;  // 使 p指向右子树
                }
                else{//没有右子树 ,直接访问该节点
                    p=e.ptr;
                    visit(p->data);
                    p = NULL;
                }
            }
        }
        else{//栈空则 p为NULL
            p=NULL;
        }
    }//大while结束 
}

后序遍历.jpg

2.不使用栈,使用三叉链表的非递归后序遍历算法

/********** 在三叉链表的结点中增设一个标志域 (mark取值0,1或2)以区分遍历过程当中到达该结点时应继续向左或向右或访问该结点 带标志域的三叉链表类型定义: typedef struct TriTNode { TElemType data; struct TriTNode *lchild, *rchild, *parent; int mark; // 标志域 } TriTNode, *TriTree; **********/  
void PostOrder(TriTree T, void (*visit)(TElemType))  
/* 不使用栈,非递归后序遍历二叉树T, */  
/* 对每一个结点的元素域data调用函数visit */  
{  //mark == 0 向左访问,1 访问自身,2 向右访问 
    TriTree p= T, pr;  
    if(p==NULL) return ; 
    while(true){  
        while(p!=NULL&&p->mark!=1){  
            if(p->rchild!=NULL) //如有右孩子 
                p->mark=2;  
            else p->mark=1;  
            pr=p; 
            p=p->lchild;  
        }  
        p=pr; // p为最左下节点,可能有右孩子
        pr=pr->parent;  
        if(p->mark==2)  { // p有右孩子
            p->mark=1; // p访问完右边后下次要访问本身 
            p=p->rchild;
        }  
        else{  
            visit(p->data); // 访问
            p->mark=1;  
        }  
        if(p->data==T->data)  break;  
    }  
}

层次遍历

层次遍历是按二叉树的层次从小到大且每层从左到右的顺序依次访问结点

对于顺序存储结构的二叉树,其结点在数组中的顺序下标序列与层次遍历的访问顺序一致,所以可直接根据数组获得层次遍历的结果。

对于链式存储结构的二叉树,难以得到结点的同一层的下一层结点,从层末结点也难以获得下一层的层首结点。因此可以使用一个辅助队列存储当前层被访问过的结点。

使用队列的非递归遍历

算法以下:

/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; **********/
void LevelOrderTraverse(BiTree T,Status(*visit)(TEleType e)){
    if(T!=NULL){
        Queue Q; 	InitQueue(Q);
        BiTree p = T; //初始化
        visit(p->data); //访问根节点
        EnQueue(Q,p); //并将根节点入队
    }
    while(OK==DeQueue(Q,p))//当队非空时重复执行出队操做
    {
        if(p->lchild!=NULL)//访问左孩子并入队
        {
        	visit(p->lchild->data);
            EnQueue(Q,p->lchild);
        }
        if(p->rchild!=NULL)//访问右孩子并入队
        {
            visit(p->rchild->data);
            EnQueue(Q,p->rchild);
        }
    }
}

遍历的应用

求二叉树深度

利用后序遍历框架递归求左、右子树深度,而后取二者的较大值加 1(根节点)做为深度值返回。代码以下:

int BiTreeDepth(BiTree T){
    int depthLeft,depthRight;
    if(NULL==T) return 0; //二叉树深度为0
    else{
        depthLeft = BiTreeDepth(T->lchild);
        depthRight = BiTreeDepth(T->rchild);
        return 1+((depthLeft>depthRight)?depthLeft:depthRight);
    }
}

叶子节点计数

利用先序遍历框架递归实现,算法以下:

void CountLeaf(BiTree T, int &count){
    if(T!=NULL){
        if(NULL == T->lchild && NULL == T->rchild){
            count++;  // 对叶子结点计数
        }
        CountLeaf(T->lchild, count); // 对左子树递归计数
        CountLeaf(T->rchild, count); // 对右子树递归计数
    }
}

销毁二叉树

代码以下:

void DestroyBiTree(BiTree &T){
    if(T!=NULL){
        DestroyBiTree(T->lchild);// 递归销毁左子树
        DestroyBiTree(T->rchild);// 递归销毁右子树
        free(T);				 // 释放根节点
    }
}

删除指定元素结点的子树

代码以下:

void ReleaseX(BiTree &T, char x)
/* 对于二叉树T中每个元素值为x的结点, */
/* 删去以它为根的子树,并释放相应的空间 */
{
    if(!T) return ;
    ReleaseX(T->lchild,x);
    ReleaseX(T->rchild,x);
    if(T->data == x) free(T);
}

复制二叉树

代码以下:

void CopyBiTree(BiTree T, BiTree &TT)
/* 递归复制二叉树T获得TT */
{
   TT = (BiTree)malloc(sizeof(BiTNode));
   if(T){
        TT->data = T->data;
        TT->lchild = T->lchild;
        TT->rchild = T->rchild;
        if(T->lchild) CopyBiTree(T->lchild,TT->lchild);
        if(T->rchild)CopyBiTree(T->rchild,TT->rchild);
   }
   else  free(TT);
}