二叉树的遍历

说明:文章内容来自课程的课件内容,本人只做了微小的修改!!!

顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次

二叉树是非线性结构,每个结点有两个后继,因此,存在如何遍历即按什么样的搜索路径遍历的问题。

对“二叉树”而言,可以有三条搜索路径:

        §1先上后下的按层次遍历;

§2先左(子树)后右(子树)的遍历;

§3先右(子树)后左(子树)  的遍历。

 

(根)序的遍历算法:

 若二叉树为空树,则空操作;否则,1访问根结点;2)先序遍历左子树;3)先序遍历右子树。

void Preorder ( BiTree T, void( *visit)(TElemType& e) )
{ // 先序遍历二叉树 
   if (T) {
       visit(T->data);                 // 访问结点
       Preorder(T->lchild, visit); // 遍历左子树
       Preorder(T->rchild, visit);// 遍历右子树
   }
}

(根)序的遍历算法

 若二叉树为空树,则空操作;否则,1)中序遍历左子树;2访问根结点;3)中序遍历右子树。

(根)序的遍历算法

若二叉树为空树,则空操作;否则,1)后序遍历左子树;2)后序遍历右子树;3访问根结点。

 

递归函数中的尾递归可以消除!

void PreOrderTraverse( BiTree T) {
       While (T) {
           Visit(T->data);
           PreOrderTraverse(T->lchild);
           T = T->rchild;
        }
} // PreOrderTraverse

补充:[以下尾递归内容摘抄自尾递归百度百科链接]

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

以尾递归的形式计算阶乘的一个函数实现:

int facttail(int n, int a)
{
 
    /*Compute a factorialina tail - recursive manner.*/
     
    if (n < 0)
        return 0;    
    else if (n == 0)
        return 1;    
    else if (n == 1)
        return a;
    else
        return facttail(n - 1, n * a);
 
}

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。

尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去.

先序遍历非递归算法算法关键:当前指针和栈

  1.得到新的当前指针,则访问结点,然后,先进栈,再访问左孩子;

  2.使用栈存放待访问结点的指针,以备在访问该结点的左子树之后,弹出结点指针,再访问该结点的右子树.

  3.初始条件:指针指向树根结点, 栈为空;

  4.结束条件:当前指针空,且栈空;

  5.循环:指针不空则访问结点,指针空则退栈;

bool PreOrderTraverse ( BiTree T,  Visit ( ) )
 { 
    InitStack (S);                // 使用栈 S 存放已访问的根结点
    p = T;                                              // 令 p 指向二叉树根结点
    while ( p || ! StackEmpty (S) ) { // 当 p 不为空,或栈 S 不为空时
       if ( p ) {                     // 如果 p 非空
       //Visit (p);              //访问 p 指针指向的结点
       Push ( S, p );          //将 p 指针入栈
       p = p->lchild;          //指向左子树根结点,遍历左子树
      } // if 结束
    else {                      //如果 p 为空
      Pop ( S, p );    Visit (p);           //退栈,将上一层根结点指针弹出
      p = p->rchild;          //指针指向右子树根结点,遍历右子树
      } // else 结束
  } // while 结束
  return OK;
} // PreOrderTraverse

遍历算法的应用举例

1、统计二叉树中叶子结点的个数

算法1(后序遍历):二叉树叶子结点个数的递归定义: 如果是空树,则结点个数为0;

 如果非空,但只有唯一的根结点,则叶子结点个数为1;否则: 叶子结点个数 = 左子树叶子结点个数  + 右子树叶子结点个数                           

int CountLeaf (BiTree T){
   if (!T ) return 0;
   if (!T->lchild && !T->rchild) return 1;
   else{
       m = CountLeaf( T->lchild);  
       n = CountLeaf( T->rchild); 
        return (m+n);   
   } //else
} // CountLeaf
          递归调用结束返回叶结点的计数值 !

算法2先序遍历):先序遍历二叉树,在遍历过程中查找叶子结点,并计数。由此,需在遍历算法中增添一个“计数”的参数,并将算法中“访问结点” 的操作改为:若是叶子,则计数器增1

void CountLeaf (BiTree T,  int& count){
   if ( T ) {
      if ((!T->lchild)&& (!T->rchild))
               count++;     // 对叶子结点计数
      CountLeaf( T->lchild, count);  
      CountLeaf( T->rchild, count); 
   } // if
} // CountLeaf
    递归调用过程对同一个引用参数count进行操作!

2、查询二叉树中某个结点

Status Preorder (BiTree T, ElemType x, BiTree &p) 
{// 若二叉树中存在和x相同的元素,则p指向该结点并返回true  
   if (T) {
      if (T->data==x) { p=T; return OK,} 
      else {
          if (Preorder(T->lchild, x, p) ) return OK;
          else (Preorder(T->rchild, x, p))  return OK;
       }//else
   }//if 
   return FALSE;
}

3、求二叉树的深度

算法归纳:如果是空树,则树的深度为0;

如果非空:树的深度=1+ Max{左子树深度, 右子树深度}

int Depth (BiTree T ){ // 返回二叉树的深度
   if ( !T )    depthval = 0;
   else   {
     depthLeft = Depth( T->lchild );
     depthRight= Depth( T->rchild );
     depthval = 1 + (depthLeft > depthRight  ?
                               depthLeft : depthRight);
   } 
   return depthval;
}

4、复制二叉树 其基本操作为:复制一个结点。

(后序遍历)

BiTNode *PostCopyTree(BiTNode *T) {  
   if (!T )    return NULL;				
    newLptr = PostCopyTree(T->lchild);  //复制左子树    
    newRptr = PostCopyTree(T->rchild);  //复制右子树
    if (!(newtT = new BiTNode))  
       exit(1); 					//生成新结点
    newtT->data  = T->data; 
    newtT->lchild = newLptr;
    newtT->rchild = newRptr;
    return newT;
} // CopyTree

(先序遍历)

BiTNode *PreCopyTree(BiTNode *T) {  
    if (!T )    return NULL;
    if (!(newtT = new BiTNode))  exit(1);
     newtT->data  = T->data; 
						//复制左子树
     newtT-> lchild = PreCopyTree(T->lchild);
						//复制右子树
     newtT-> rchild = PreCopyTree(T->rchild);
     return newT;
} // CopyTree

5、建立二叉树的存储结构

以字符串的形式定义二叉树 

算法执行过程举例如下:

Status CreateBiTree(BiTree &T) {
    scanf(&ch);
    if (ch==' ') T = NULL;
    else {
      if (!(T = new BiTNode))   exit(OVERFLOW);
      T->data = ch;              // 生成根结点
      CreateBiTree(T->lchild);   // 构造左子树
      CreateBiTree(T->rchild);   // 构造右子树
    }
    return OK; 
 } // CreateBiTree

void CrtNode(BiTree& T,char ch)
{ //ch为操作数
   if (!(T= new BiTNode))  
      exit(OVERFLOW);
   T->data = ch;
   T->lchild = T->rchild = NULL;
   Push( PTR, T );   //新创建的结点子树进栈
}

按给定的表达式建相应二叉树 

基本操作:

scanf(&ch);

if (In(ch, 字母集 ))  { 建叶子结点; 指针PTR; }

else  if  (In(ch, 运算符集))

      { while(chS顶运算符优先数“低”)

        运算符出栈,建运算符子树;指针PTR;

         cpS顶优先数“高”, 则运算符S;

      }

具体代码如下:

void CrtExptree(BiTree &T, char exp[] ) {
  InitStack(S);  Push(S, '#');  InitStack(PTR); 
  p = exp;  ch = *p;
  while ( GetTop(S) !='#' || ch !='#' ) {
     if (!IN(ch, OP))   
        CrtNode( t, ch );// 建叶子结点并入栈  
     else {  

         switch (ch) {  //运算符处理
         case '(' : Push(S, ch); break;
         case ')' : Pop(S, ch);                //运算符出栈
                   while (ch!= '(' ) {
                      CrtSubtree( t, ch);  // 建二叉树并入栈
                      Pop(S, ch)      
                   }
                   break; 
         defult :      //当前运算符栈顶比较, 高则进栈,低则出栈并建子树
            while(!Gettop(S与, c) && ( precede(c,ch))) { 
                CrtSubtree( t, ch);
                Pop(S, ch);
            }
             if ( ch!= '#' ) Push( S, ch); 
                break;
          } // switch
 
      }    // 运算符处理
     if ( ch!= '#' ) 
     { p++;  ch = *p;
     }
  } // while
  Pop(PTR, T);
} // CrtExptree

void CrtNode(BiTree& T,char ch)
{ //ch为操作数
   if (!(T= new BiTNode))  
      exit(OVERFLOW);
   T->data = ch;
   T->lchild = T->rchild = NULL;
   Push( PTR, T );   //新创建的结点子树进栈
}

void CrtSubtree (Bitree& T, char ch)
{
   if (!(T= new BiTNode))  
        exit(OVERFLOW);
   T->data = ch;          //ch为运算符
   Pop(PTR, rc);  T->rchild = rc;
   Pop(PTR, lc);  T->lchild = lc;
   Push(PTR, T); //新创建的运算符子树进栈
}


不得不说,根据表达式来建二叉树比较复杂,既要考虑到建树的相关操作,比如左子树、右子树指针的赋值,还有考虑到操作符的优先级和相应的入栈出栈操作,同时也要考虑到#号等特殊情况!!!(至少以下代码我就没看懂)

defult :      //当前运算符栈顶比较, 高则进栈,低则出栈并建子树
            while(!Gettop(S与, c) && ( precede(c,ch))) { 
                CrtSubtree( t, ch);
                Pop(S, ch);
            }
             if ( ch!= '#' ) Push( S, ch); 
                break;
          } // switch

由二叉树的先序和中序序列建树

仅知二叉树的先序序列“abcdefg 不能唯一确定一棵二叉树,

因为只有先序序列我甚至不能判断根节点之后的节点哪些属于左子树和右子树!!!更不知道有没有左右子树!!!

同理,中序和后序遍历也是不行的!!!

算法步骤:

1. 先序序列的第一个字符为二叉树T的根;

2. 中序序列找先序序列的第一个字符;

3. 该字符将中序序列分成两个子串,这两个子串分别为T左子树中序序列右子树中序序列

4. 根据左子树中序序列和右子树中序序列的长度可以将先序序列分解成两个部分,它们分别是对应的两个左右子树中序序列的子先序序列

代码:

void CrtBT(BiTree& T, char pre[], char ino[], int ps, int is, int n ) {
  // 已知pre[ps...ps+n-1]为二叉树的先序序列, 
  // ino[is...is+n-1]为二叉树的中序序列,
  //   n为先序和中序序列的长度       
  if (n==0) T=NULL;
   else {
       k=Search(ino, pre[ps]); // 在中序序列中查询
       if (k== -1)  return error;
       else {             
        if (!(T= new BiTNode))  exit(OVERFLOW);
            T->data = pre[ps];
            if (k==is) 
                 T->Lchild = NULL;       //没有左子树
            else  CrtBT(T->Lchild, pre[], ino[], ps+1, is, k-is );   //左子树递归
            if (k=is+n-1) T->Rchild = NULL;  //没有右子树
            else  CrtBT(T->Rchild, pre[], ino[], 
                    ps+1+(k-is), k+1, n-(k-is)-1 );      //右子树递归

       }
   } //

    
}// CrtBT       

 

QUESTION:

根据任意两种遍历方法都可以构造唯一的树吗???

答案是这样的:中序+先序和中序+后序都可以还原二叉树,而先序+后序是不可以的,大家自己举个只有左子树或者右子树的二叉树来写出先序序列和后序序列就知道了!!!

这个问题我也会之后进行相关学习和百度搜索,尽量把结果写出来。

欢迎各位评论!!!

 

 

  • 7
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值