数据结构笔记:二叉树的遍历

概述

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

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

假设 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);
   }
}
原文地址:https://www.cnblogs.com/wbyixx/p/12190594.html