zoukankan      html  css  js  c++  java
  • 二叉树四种遍历方式总结及解剖及求深度节点数及宽度

     花了些时间总结了二叉树四种经典遍历方式。特别是层次遍历,值得思考。
    #include<stdio.h>
    #include<malloc.h>
    
    typedef char ElemType;
    typedef struct BiNode
    {
        ElemType data;
        struct BiNode *lchild;
        struct BiNode *rchild;
    }BiNode,*BiTree;
    
    //先序拓展序列建立二叉树 
    void create(BiTree &T)
    {
           
            ElemType c;
             //printf("Enter the data \n");
            scanf("%c",&c);
            if(c=='#')
                T=NULL; //很重要 创建的时候要注意,如果输入为#,则T=NULL,否则新建节点。这个很容易错误。
    
            else{
                   T =(BiNode*) malloc (sizeof(BiNode));
                    T->data=c;
                    create(T->lchild);
                    create(T->rchild);
            }
    }
    
    void preorder(BiTree T)
    {
        if(T)
        {
            printf("%c\t",T->data);
            preorder(T->lchild);
            preorder(T->rchild);
        }
    }
    void inorder(BiTree T)
    {
        if(T)
        {
            inorder(T->lchild);
            printf("%c\t",T->data);
            inorder(T->rchild);
        }
    }
    
     void postorder(BiTree T)
     {
         if(T)
         {
             postorder(T->lchild);
             postorder(T->rchild);
             printf("%c\t",T->data);
         }
     }
    
    
    void levelorder(BiTree T)
    {
        BiTree Queue[20];
        BiTree p;
        int front=0,rear=0;
        if(T)
        {
            p=T;
            Queue[rear]=p;
            rear=(rear+1)%20;
            while(front!=rear)
            {
                p=Queue[front] ;
                printf("%c\t",p->data);
                front=(front+1)%20;
                if(p->lchild)
                {
                    Queue[rear]=p->lchild;
                    rear=(rear+1)%20;
                }
                if(p->rchild)
                {
                    Queue[rear]=p->rchild;
                    rear=(rear+1)%20;
                }
            }
        }
    }
    
    
    
    
     int main()
     {
         BiTree T;
         
         create(T);
         preorder(T);
         printf("\n");
         inorder(T);
          printf("\n");
         postorder(T);
         printf("\n");
         levelorder(T);
         printf("\n");
     }

           

    注意创建二叉树的时候一定要引用

    BiTree &T     改变指针T的指向(最开始是指向0)

    我最开始没有写引用,导致错误

     输入时要特别注意,是按照先序方式来构造二叉树的,还有,用#代表没有空节点。输入了n个字符,就应该有n+1个#空字符。

    层次遍历中用到队列,是用数组来实现 的。

    /*按层遍历*/
    void levelTraverse(BiTree T)
    {
    BiTree Queue[20];              
    BiTree p;
    int front=0,rear=0;           /*表示队头指针和队尾指针*/
    if(T)
    {
        p=T;                         /*根指针入队*/
        Queue[rear]=p;
        rear=(rear+1)%20;         /*队尾指针加一对20取余,可实现循环队列,合理利用空间*/
        while(front!=rear)          /*队不空*/
        {
          p=Queue[front];           /*出队,将值赋给p*/
          printf("%c",p->data);
          front=(front+1)%20;
          if(p->lchild)              /*如果p有左子树,将左子树入队*/
          {
            Queue[rear]=p->lchild;
            rear=(rear+1)%20;
          }
          if(p->rchild)                  /*如果p有右子树,将右子树入队*/
          {
            Queue[rear]=p->rchild;
            rear=(rear+1)%20;
          }
        }
    }
    }

     用队列实现层次遍历真正的思想如下:

    编写按层次(同一层从左至右)遍历二叉树的算
    法。
    void LayerOrder(Bitree T)//层序遍历二叉树
    {  InitQueue(Q);  
         EnQueue(Q,T);
        while(!QueueEmpty(Q)) {
          DeQueue(Q,p);
       visit(p);
          if(p->lchild) EnQueue(Q,p->lchild);
       if(p->rchild) EnQueue(Q,p->rchild);
       }
    }

     我们来解剖下中序遍历的代码。代码看起来简单,真正的威力来源于递归,实际上是双重递归,系统在运行时栈上完成这些工作。

    void inorder(BiTree T)
    {
        if(T)
        {
            inorder(T->lchild);
            printf("%c\t",T->data);
            inorder(T->rchild);
        }
    }


    按照robert sedgewick的书中的概念想法,对于中序,我们压入右子树,然后是节点,最后是左子树,按照这种想法,一开始就无从下手了:

    void inOrderNonRecur(BiTree T)
    {
    stack<BiTree> s;
    if(T->right!=0) s.push(T->right);
    else s.push(T);

    因为不知道右子树是否为空,我们要做判断,接下来的代码就越来越不好写了。必须放弃这种思路。我们还是根先入栈

     T是要遍历树的根指针,中序遍历要求在遍历完左子树后,访问根,再遍历右子树。
    问题:如何用栈来保存信息,使得在中序遍历过左子树后,能利用栈顶信息获取T指针?
    方法:先将T入栈,遍历左子树;遍历完左子树返回时,栈顶元素应为T,出栈,访问T->data,再中序遍历T的右子树。

    void inorder_nonrecursive(BiTree T)
    {
        stack<BiTree> s;
        s.push(T); //根指针入栈
        
        BiTree p;
        while(!s.empty())
        {
            while(p=s.top())//向左走到尽头
                s.push(p->lchild);
            
            s.pop();//空指针NULL退栈
    
            if(!s.empty())
            {
                p=s.top(); s.pop();
                 printf("%c\t",p->data);
                 s.push(p->rchild);
            }
        }
    }

    过程如下:

    15
    15 4 1 #向左走到尽头 #表示空
    15 4 1 //s.pop
    15 4 输出1
    15 4 #
    15 //输出4
    15 #
    输出15,
    20
    20 16 #
    20 输出16
    20 #
    输出20 s.push(25)
    25
    25 #
    输出25 .push(#)
    #
    s.pop() if(!s.empty())不成立,
    while 退出

     看过上面的,应该知道,根也是左孩子线上的。斜线看更清楚

    前序遍历:

    void preorder_nonrecursive(BiTree T)
    {
        stack<BiTree> s;
        s.push(T); //根指针进栈
        BiTree p;
        while(!s.empty())
        {
            while(p=s.top())
            {
                printf("%c\t",p->data);
                s.push(p->lchild);//向左走到尽头,每向前一步都访问当前节点
            }
            s.pop(); 
            if(!s.empty())
            {
                p=s.top(); s.pop();
                s.push(p->rchild);   /* 向右走一步 */
            }
        }
    }

    15 输出15
    15 4 1 输出 4 ,1
    15 4 1 #
    15 4 1

    15 4 s.pop() 1
    15 4 #
    15 4
    15 s.top 4
    15 #

    15 s.pop
    pop 15
    s.push(20);

     个人推荐的前序和中序遍历方法

    void preOrder3(BiTree root)
    {
        stack<Node*> s ;
        Node* p=root;
        while(p || !s.empty())
        {
            while(p)
            {
                cout<<p->data<<" ";
                s.push(p);
                p=p->left;
            }
            p=s.top();s.pop();
            p=p->right;
            
        }
    }
    
    void inOrder3(BiTree root)
    {
        stack<Node*> s;
        Node* p=root;
        while(p || !s.empty())
        {
            while(p)
            {
                s.push(p);
                p=p->left;
            }
    
            p=s.top();s.pop();
            cout<<p->data;
            p=p->right;
        }
    }

    上面的代码大致相同,只是改变cout<<p->data即可。

    个人推荐的后续遍历方法:

    void postOrder5(BiTree root)
    {
        stack<Node*> s;
        Node *p=root;
        Node* lastVisited=NULL;
        while(p || !s.empty())
        {
            while(p)
            {
                s.push(p);
                p=p->left;
            }
            p=s.top();//现在还不能pop()
    
            if(!p->right || p->right==lastVisited)
            {
                cout<<p->data<<ends;
                s.pop();
                lastVisited=p;
                p=NULL;//很重要,否则while(p)循环会继续下去
            }
            else
                p=p->right;
        }
    }

    另一种方法:

    后续遍历跟前面2个不同;

    typedef struct {
                            BTNode* ptr;
                            enum {0,1,2} mark;
                    } PMType;                                         /* 有mark域的结点指针类型 */
    
                    void postorder_nonrecursive(BiTree T)                /* 后续遍历二叉树的非递归算法 */
                    {
                            PMType a;
                            initstack(S);                                 /* S的元素为PMType类型 */
                            push (S,{T,0});                         /* 根结点入栈 */
                            while(!stackempty(S)) {
                                    pop(S,a);
                                    switch(a.mark)
                                    {
                                    case 0:
                                            push(S,{a.ptr,1});         /* 修改mark域 */
                                            if(a.ptr->;lchild) 
                                                    push(S,{a.ptr->;lchild,0}); /* 访问左子树 */
                                            break;
                                    case 1:
                                            push(S,{a.ptr,2});         /* 修改mark域 */
                                            if(a.ptr->;rchild) 
                                                    push(S,{a.ptr->;rchild,0}); /* 访问右子树 */
                                            break;
                                    case 2:
                                            visit(a.ptr);                 /* 访问结点 */
                                    }
                            }
                    }

    另一个版本的:

    T是要遍历树的根指针,后序遍历要求在遍历完左右子树后,再访问根。需要判断根结点的左右子树是否均遍历过

    前序、中序、后序的非递归遍历中,要数后序最为麻烦,如果只在栈中保留指向结点的指针,那是不够的,必须有一些额外的信息存放在栈中。
    方法有很多,这里只举一种,先定义栈结点的数据结构
    typedef struct{Node * p; int rvisited;}SNode //Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过。
    
    lastOrderTraverse(BiTree bt){
      //首先,从根节点开始,往左下方走,一直走到头,将路径上的每一个结点入栈。
      p = bt;
      while(bt){
        push(bt, 0); //push到栈中两个信息,一是结点指针,一是其右结点是否被访问过
        bt = bt.lchild;
      }
    
      //然后进入循环体
      while(!Stack.empty()){ //只要栈非空
        sn = Stack.getTop(); // sn是栈顶结点
    
        //注意,任意一个结点N,只要他有左孩子,则在N入栈之后,N的左孩子必然也跟着入栈了(这个体现在算法的后半部分),所以当我们拿到栈顶元素的时候,可以确信这个元素要么没有左孩子,要么其左孩子已经被访问过,所以此时我们就不关心它的左孩子了,我们只关心其右孩子。
    
        //若其右孩子已经被访问过,或是该元素没有右孩子,则由后序遍历的定义,此时可以visit这个结点了
        if(!sn.p.rchild || sn.rvisited){
          p = pop();
          visit(p);
        }
        else //若它的右孩子存在且rvisited为0,说明以前还没有动过它的右孩子,于是就去处理一下其右孩子。
        { 
          //此时我们要从其右孩子结点开始一直往左下方走,直至走到尽头,将这条路径上的所有结点都入栈。
    
          //当然,入栈之前要先将该结点的rvisited设成1,因为其右孩子的入栈意味着它的右孩子必将先于它被访问(这很好理解,因为我们总是从栈顶取出元素来进行visit)。由此可知,下一次该元素再处于栈顶时,其右孩子必然已被visit过了,所以此处可以将rvisited设置为1。
          sn.rvisited = 1;
    
          //往左下方走到尽头,将路径上所有元素入栈
          p = sn.p.rchild;
          while(p != 0){
            push(p, 0);
            p = p.lchild;
          }
        }//这一轮循环已结束,刚刚入栈的那些结点我们不必管它了,下一轮循环会将这些结点照顾的很好。
      }
    }

    第一个while:

    ABD.注意我们没有把NULL进栈

    进入第二个while,由于D没有右孩子,也没用访问过,所以出栈并访问它(输出D),变成了

     AB 取出B,由于b的右孩子没有被访问过,把B设为1.AB(1),然后把E入栈。E没有左孩子,变成了:

    AB(1)E,E没有右孩子,访问E,栈变成了

    AB(1)由于B已经设置为1,代表它的有孩子已经被访问了,输出B,栈变成了:

    A(1)。(可以看到,设置1是必要的)

    由于A有有孩子并且没有访问过,把右孩子C入栈,还有F。栈变成了:

    A(1)CF

    F没有右孩子,输出F,C也没有右孩子,输出C,

    A,A虽然有右孩子,但是已经访问过,输出A。

    此时栈空,结束。 

    总输出:

    D E B F C A

    按照上面的方法写的c++代码,用了STL的stack:

    typedef struct SNode{
        Node* pNode;
        int rvisited;
        SNode(Node* p,int x){ pNode=p; rvisited=x;}
    }SNode;
    ////Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过。
    
    void postOrderNonCur2(BiTree T)
    {
        BiTree p=T;
        stack<SNode*> s;
        SNode * sNode;
        while(p)
        {
            sNode=new SNode(p,0);//每次都要新建一个节点
            s.push(sNode);
            p=p->left;
        }
        while(!s.empty())
        {
            sNode=s.top();
    
            if(!sNode->pNode->right || sNode->rvisited)
            {
                sNode=s.top(); s.pop();
                visit(sNode->pNode->data);
            }
            else
            {
                sNode->rvisited=1;
    
                //往左下方走到尽头,将路径上的所有元素入栈
                p=sNode->pNode->right;
                while(p!=0)
                {
                    sNode=new SNode(p,0);
                    s.push(sNode);
    
                    p=p->left;
                }
            }
        }
    }

    必须每次新建SNode,

     sNode=new SNode(p,0);//每次都要新建一个节点
    我最开始都是SNode->pNode=p;
    sNode->rvisited=0;
    导致stack栈里面都是一样的值,同一个元素的地址

    更多参考:
    二叉树遍历非递归算法 http://www.360doc.com/content/07/0601/21/9889_533413.shtml 详细列出了多种解法
    二叉树 非递归遍历 栈实现(前、中后序)http://www.360doc.com/content/12/0410/11/4186481_202433750.shtml


    先序遍历:

    void traverse(link h,void visit(link))
    {
      if(h==0) return;
      visit(h);
      traverse(h->lchild,visit);
      traverse(h->rchild,visit);
    }

    前序遍历函数调用如下:

    非递归遍历算法

      我们从一个抽象的栈开始考察,这个栈能够保存项或树,以将被遍历的树做初始化,然后,我们进入一个循环,在这个循环中我们弹出并处理在栈顶端的项,如此继续,直到栈空了为止。如果已经弹出的实体是个个项,我们就访问它,如果弹出的实体是一棵树,我们就以希望的顺序进行一系列入栈操作:

    1对于前序,我们压入右子树,然后左子树,最后是节点

    2对于中序,我们压入右子树,然后节点,最后左子树

    3对于后序,我们压入节点,然后是右子树,最后左子树。

      我们前面的描述是概念性的,在实际中实现要简单一些,比如,对于前序,我们不需要把节点推到栈上(我们访问弹出来的每一棵树的根),这样的话,我们就能使用一个仅包含一种类型的项(树链接)的简单栈,类似于下面的实现,支持递归程序的系统栈包含返回地址与参数值,而不是项或节点,但是我们进行计算(访问节点)的实际顺序对于递归方法和基于栈的办法来说是一样的。

    void preTraverse(BiTree T)
    {
        stack<BiTree> s;
        s.push(T);
        
        while(!s.empty())
        {
            T=s.top(); s.pop();
            printf("%c\t",T->data);
            if(T->rchild!=0) s.push(T->rchild);
            if(T->lchild!=0) s.push(T->lchild);
        }
    }

    层次遍历:
        我们可以用一个队列来代替上面的stack实现层次便利了,


    void levelTraverse(BiTree T)
    {
        queue<BiTree> q;
        q.push(T);
        while(!q.empty())
        {
            T=q.front(); q.pop();
            printf("%c\t",T->data);
            if(T->lchild!=0) q.push(T->lchild);
            if(T->rchild!=0) q.push(T->rchild);
        }
    }
    或者:
    void inOrderTraverse(BiTree T)
    {
        queue<BiTree> q;
        q.push(T);
        while(!q.empty())
        {
            BiTree h=q.front();q.pop();
            visit(h->data);
            if(h->left!=0) q.push(h->left);
            if(h->right!=0) q.push(h->right);
        }
    }
    我们建了一个临时变量BiTree h 来代替T。
    其实层次遍历就是图的BFS。

    中序和后序要复杂些,参看:

    参考:algorithms in C++ parts 1-4


    求深度:
    int depth(BiTree t)
    {
        if(t==NULL) return 0;
        int left=depth(t->left);
        int right=depth(t->right);
    
        return 1+(left>right?left:right);
    }
     

    更多方法:

    /法1:后序遍历,结点最大栈长即为树的高度  
    //法2:层次遍历,层次即为高度  
    //法3:递归求树高  
    //输入:-+a##*b##-c##d##/e##f##  
    //输出:5  
       
    #include<iostream>  
    #include<stack>  
    #include<queue>  
    using namespace std;  
    typedef struct BiTNode{  
        char data;  
        struct BiTNode *lchild,*rchild;  
    }BiTNode,*BiTree;  
       
    void CreateTree(BiTree &T)  
    {  
        char ch;  
        cin>>ch;  
        if(ch=='#') T=NULL;  
        else 
        {  
            T=(BiTree)malloc(sizeof(BiTNode));  
            if(!T)  cout<<"生成结点错误!"<<endl;  
            T->data=ch;  
            CreateTree(T->lchild);  
            CreateTree(T->rchild);  
        }  
    }  
       
    //法1:后序遍历,结点最大栈长即为树的高度  
    int BT_high(BiTree T)  
    {  
        BiTree p=T,r=NULL;  
        int max=0;                                     //树高  
        stack<BiTree> s;  
        while(p||!s.empty())  
        {  
            if(p!=NULL)  
            {  
                s.push(p);  
                p=p->lchild;  
            }  
            else 
            {  
                p=s.top();  
                if(p->rchild!=NULL && p->rchild!=r)  
                    p=p->rchild;  
                else 
                {  
                    if(s.size()>max) max=s.size();//最大层次即为高度  
                    r=p;  
                    s.pop();  
                    p=NULL;  
                }  
            }  
        }  
        return max;  
    }  
       
    //法2:层次遍历,层次即为高度  
    int BT_level_depth(BiTree T)  
    {  
        if(!T)  return 0;  
        BiTree p=T,Q[100];  
        int front=-1,rear=-1,last=0,level=0;  
        Q[++rear]=p;  
        while(front<rear)  
        {  
            p=Q[++front];  
            if(p->lchild)  
                Q[++rear]=p->lchild;  
            if(p->rchild)  
                Q[++rear]=p->rchild;  
            if(front==last)  
            {  
                last=rear;  
                level++;               //层次+1  
            }  
        }  
        return level;  
    }  
       
    //法3:递归求树高1  
    int max1=0;//树高  
    int BT_depth1(BiTree T,int depth)  
    {  
        if(T)  
        {  
            if(T->lchild)  
                BT_depth1(T->lchild,depth+1);  
            if(T->rchild)  
                BT_depth1(T->rchild,depth+1);  
        }  
        if(depth>max1)     
            max1=depth;  
        return depth;  
    }  
       
    //法3:递归求树高2  
    int Height (BiTree T)  
    {    
        if(T==NULL) return 0;  
        else  
        {  
            int m = Height ( T->lchild );  
            int n = Height(T->rchild);  
            return (m > n) ? (m+1) : (n+1);   
        }  
    }  
       
    int main()  
    {  
        BiTree T=NULL;  
        CreateTree(T);  
        cout<<"后序遍历求树高:"<<endl;  
        cout<<BT_high(T)<<endl;  
        cout<<"层次遍历求树高:"<<endl;  
        cout<<BT_level_depth(T)<<endl;  
        cout<<"递归求树高1:"<<endl;  
        BT_depth1(T,1);  
        cout<<max1<<endl;  
        cout<<"递归求树高2:"<<endl;  
        cout<<Height(T)<<endl;  
        return 0;  
    }  
    View Code

    打印某一层所有节点:(编程之美:3.10)
    写一个函数,打印二叉树中某层次节点(从左到右)。其中根节点为第0层。函数原型
    int printNodeAtLevel(Node* root,int level)成功返回1,失败返回0.
    答案参考以前的:http://www.cnblogs.com/youxin/p/3288281.html

      二叉树的宽度:所有深度中含有的最多的子叶数。
    #include "stdafx.h"
    #include<iostream>
    #include<queue>
    using namespace std;
     
    typedef struct BTreeNode
    {
        int data;
        struct BTreeNode *lchild,*rchild;
    }BTree;
    int _tmain(int argc, _TCHAR* argv[])
    {
        return 0;
    }
    int width(BTree *bt)
    {
        BTree *p=bt;
        if(bt)return 0;
        BTree *q[100];
        int front=0,rear=0;//队头指针,队尾指针
        int last=0;//同一层最右结点在队列中位置
        int temp=0,maxw=0;//当前层宽度与最大宽度
        q[rear]=bt;
        while(front<=last)
        {
            p=q[front++];temp++;//同层元素加1;
            if(p->lchild)q[rear++]=p->lchild;
            if(p->rchild)q[rear++]=p->rchild;
            if(front>last)//一层结束
            {
                last=rear;
                if(temp>maxw)maxw=temp;//更新最大宽度
                temp=0;
            }
        }
        return maxw;
    }

    参考:http://blog.csdn.net/conanswp/article/details/19831129

     

    二叉树前序中序后序的DFS非递归实现

    https://www.cnblogs.com/anthony-dong/p/12250802.html

  • 相关阅读:
    利用SEH进行代码混淆
    HDU5294 Tricks Device(最大流+SPFA) 2015 Multi-University Training Contest 1
    输入字符串反序输出
    微信公众平台开发(104) 自定义菜单扫一扫、发图片、发地理位置
    不同编码页面之间表单的提交方法
    PHP登陆Session验证
    微信公众平台开发培训
    微信公众平台开发(98) UnionID
    微信电商再侵袭,腾讯要革淘宝的命
    微信企业号
  • 原文地址:https://www.cnblogs.com/youxin/p/2541039.html
Copyright © 2011-2022 走看看