zoukankan      html  css  js  c++  java
  • 数据结构笔记:二叉树的遍历

    概述

    二叉树的遍历,就是按照一定的规则访问二叉树,

    将二叉树的非线性结构转换为二叉树结点的一个线性序列

    假设 L, R, V 分别代表遍历一个结点的左子树,右子树,以及访问该结点的操作

    则遍历总共有六种规则:

    VLR VRL ------ 前序

    LVR RVL ------ 中序

    LRV RLV ------ 后序

    为了方便,以下算法都采用先左后右的三种,先右后左同理

    三种遍历的递归算法

    当树非空时:按照顺序访问,若是L或者R则递归访问,遇到V就直接访问.

    中序遍历的递归算法:

    template <class T>
    void BinaryTree<T>::inOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p))
    {
        if (subTree != NULL)
        {
            InOrder(subTree->leftChild, visit);
            visit(subTree);
            InOrder(subTree->rightChild, visit);
        }
    }
    

    前序遍历的递归算法:

    template <class T>
    void BinaryTree<T>::PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p))
    {
        if (subTree != NULL)
        {
            visit(subTree);
            PreOrder(subTree->leftChild, visit);
            PreOrder(subTree->rightChild, visit);
        }
    }
    
    

    后序遍历的递归算法:

    template <class T>
    void BinaryTree<T>::PostOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p))
    {
        if (subTree != NULL)
        {
            PostOrder(subTree->leftChild, visit);
            PostOrder(subTree->rightChild, visit);
            visit(subTree);
        }
    }
    

    为了把一个递归过程改写为一个非递归过程需要利用一个工作栈,记录遍历时的回退路径

    前序遍历的非递归算法

    前序遍历时,当前结点的访问时机就是当前,

    所以只需要在访问左或者右的时候用栈暂存另一边即可

    第一种

    初始化:p取根结点,栈变为空
    循环:
    	访问结点
    	预留右子树指针在栈中
    	左子树非空则进入左子树,左子树为空则弹出右子树,下一次循环访问的就是右子树
    
    template <class T>
    void BinaryTree<T>::preOrder(void(visit)(BinTreeNode<T> *p))
    {
        stack<BinTreeNode<T>*> S;
        BinTreeNode<T> *p = root;
        S.Push(NULL);
    
        while (p != NULL)
        {
            visit(p);   //访问当前结点
            if (p->rightChild != NULL)   
                S.Push(p->rightChild);    //右子树不为空就将右子树进栈
            if (p->leftChild != NULL)   //左子树不为空下一次循环就访问左子树根结点
                p = p->leftChild;
            else                    
                S.Pop(p);    //左子树为空就释放刚刚保存或者前面保存的右子树,下一次访问
        }
    }
    

    另外一种方法:

    初始化:根结点入栈
    循环:
     		p取栈顶元素,访问p
    	   如果右子树存在,右子树进栈
    	   如果左子树存在,左子树进栈
    
    template <class T>
    void BinaryTree<T>::PreOrder(void(visit)(BinTreeNode<T> *p))
    {
        stack<BinTreeNode<T>*> S;
        BinTreeNode<T> *p;
        S.Push(root);
    
        while (p != NULL)
        {
            S.Pop(p);
            visit(p);
            if (p->rightChild != NULL)
                S.Push(p->rightChild);
            if (p->leftChild != NULL)
                S.Push(p->leftChild);
        }
    }
    

    中序遍历的非递归算法

    中序遍历时,当前结点的访问时机是左孩子为空或者左孩子访问完了

    所以栈用来存放当前结点,当左孩子为空时就是访问的时机,

    算法描述:

    初始化:栈为空,p取根结点
    
    重复以下操作
    
      当p不为空时,p进栈,p指向其左孩子,直到p左为空
    
      若栈非空,出栈一个,访问,p指向其右孩子
    
    直到p为空,且栈为空(do-while结构)
    
    template <class T>
    void BinaryTree<T>::InOrder(void(*visit)(BinTreeNode<T> *p))
    {
        stack<BinTreeNode<T>*> S;
        BinTreeNode<T> *p = root;
        do
        {
            //左孩子不停入栈,直到左孩子为空
            while (p != NULL)
            {
                S.Push(p);
                p = p->leftChild;
            }
            //执行到此处,说明前一个p的左孩子为空
            //所以此时应该访问前一个p的父结点,即栈中最后进入的元素
            if (!S.IsEmpty())
            {
                S.Pop(p);
                visit(p);  //执行到此处,左孩子和父结点都访问完了,所以应该考察右孩子
                p = p->rightChild;
            }
        } while (p != NULL || !S.IsEmpty());
    }
    

    结束条件为栈为空且遍历指针为空。

    栈不为空,遍历指针为空表示左子树访问完了,该访问栈里面的结点了

    栈为空,但指针不为空时表示右子树还没访问。

    后序遍历的非递归算法

    后序遍历的一个重要性质:当前访问节点的时候,工作栈内的元素顺序刚好是其祖先结点到它的路径

    后序遍历的非递归比前序和中序复杂。

    在遍历左子树时不能访问根结点,还要遍历右子树。

    右子树遍历完了才能访问根结点。

    所以我们要搞清楚的是上次访问时在左子树中还是右子树中。

    方法一(结点中增加标志域):

    即每次入栈时,要同时给进栈结点一个标记,在访问完左子树时,还要把栈顶结点的标记改为右。

    经过分析,访问结点的时机就是其右孩子为空或者右孩子访问完了的时候

    总结算法步骤:

    初始化:栈为空,p取根结点
    重复以下操作:
      当p不为空时,进栈,标记为左,直到左孩子为空
      定义一个标记,用于判断是否处于左子树,赋值真
      当处于左子树且栈非空时,
        取出栈顶元素,
        判断标记:
          如果是左标记,将其改为右,再放进去,左子树标记改为假,并指向其右孩子
          如果是右标记,访问
    
    直到栈为空
    
    template <class T>
    void BinaryTree<T>::PostOrder(void(*visit)(BinTreeNode<T> *p))
    {
        Stack<stkNode<T>> S;
        stkNode<T> w;
        BinTreeNode<T> *p = root;
        do
        {
            while (p != NULL)
            {
                w.ptr = p;
                w.tag = L;
                S.Push(w);
                p = p->leftChild;
            }
            int continue1 = 1;
            while (continue1 %% !S.IsEmpty())
            {
                S.Pop(w);
                p = w.ptr;
                switch (w.tag)
                {
                case L:
                    w.tag = R;
                    S.Push(w);
                    continue1 = 0;
                    p = p->rightChild;
                    break;
                case R:
                    visit(p); 
                    break;
                }
            }
        } while (!S.IsEmpty());
        cout << endl;
    }
     
    

    方法二(增加辅助指针,记录最后一次访问的结点):

    当 p非空或栈非空时:
      如果p非空,则p的左孩子进栈
      否则:
        p取栈顶元素
        如果p右孩子存在且上次没被访问,p指向右孩子
        否则:
          出栈
          访问p元素
          记录访问的结点
          p置空
    

    另一种写法:

    p取根结点,last取root,根结点进栈
    当栈非空时:
      p取栈顶元素
      如果,左右孩子都存在或者右孩子为空last为左孩子或者last为右孩子
        访问p指向结点,last设为p,s出栈
      否则:
        如果右孩子存在,右孩子进栈
        如果左孩子存在,左孩子进栈
    

    方法三:

    p取根结点,栈s为空
    p进栈两次
    当栈非空时:
      p取栈顶元素,s出栈
       如果栈非空且p等于栈顶元素
         若p右孩子存在,右孩子进栈两次
         若p左孩子存在,左孩子进栈两次
       否则:
         访问p指向的结点
    

    二叉树的层次序遍历

    入队顺序即出队顺序,也就是访问顺序

    入队时一定要,先上后下,先左后右

    初始化:p取根结点,根结点入队列
    当队列非空时:
      出队列,访问出来的元素
      如果出来的元素有左孩子,则其左孩子入队列
      如果出来的元素有右孩子,则其右孩子入队列
    
    template <class T>
    void BinaryTree<T>::levelOrder(void(*visit)(BinTreeNode<T> *p))
    {
       Queue<BInTreeNode<T>*> Q;
       BinTreeNode<T> *p = root;
       Q.EnQueue(p);
       while (!Q.IsEmpty())
       {
           if (p->leftChild != NULL)
               Q.EnQueue(p->leftChild);
           if (p->rightChild != NULL)
               Q.EnQueue(p->rightChild);
       }
    }
    
  • 相关阅读:
    数据仓库建设随笔(2)
    实战剖析三层架构2:实例代码
    数据仓库建设随笔(1)
    如何正确地死磕一个问题
    finally块中的代码一定会执行吗
    eclipse中任务进度的使用
    如何在单元测试编码实现类的访问器?这里给出一个答案
    SplitContainer.SplitterDistance属性值设置应注意的与FixedPanel有关
    再谈ReportingService报表中数据源类型为存储过程的数据集如何使用多值参数
    工作流加载及本地通信服务常见的异常
  • 原文地址:https://www.cnblogs.com/wbyixx/p/12190594.html
Copyright © 2011-2022 走看看