【转】更简单的非递归遍历二叉树的方法

解决二叉树的很多问题的方案都是基于对二叉树的遍历。遍历二叉树的前序,中序,后序三大方法算是计算机科班学生必写代码了。其递归遍历是人人都能信手拈来,可是在手生时写出非递归遍历恐非易事。正因为并非易事,所以网上出现无数的介绍二叉树非递归遍历方法的文章。可是大家需要的真是那些非递归遍历代码和讲述吗?代码早在学数据结构时就看懂了,理解了,可为什么我们一而再再而三地忘记非递归遍历方法,却始终记住了递归遍历方法?

三种递归遍历对遍历的描述,思路非常简洁,最重要的是三种方法完全统一,大大减轻了我们理解的负担。而我们常接触到那三种非递归遍历方法,除了都使用栈,具体实现各有差异,导致了理解的模糊。本文给出了一种统一的三大非递归遍历的实现思想。

三种递归遍历

//前序遍历
void preorder(TreeNode *root, vector<int> &path)
{
    if(root != NULL)
    {
        path.push_back(root->val);
        preorder(root->left, path);
        preorder(root->right, path);
    }
}
//中序遍历
void inorder(TreeNode *root, vector<int> &path)
{
    if(root != NULL)
    {
        inorder(root->left, path);
        path.push_back(root->val);
        inorder(root->right, path);
    }
}
//后续遍历
void postorder(TreeNode *root, vector<int> &path)
{
    if(root != NULL)
    {
        postorder(root->left, path);
        postorder(root->right, path);
        path.push_back(root->val);
    }
}

由上可见,递归的算法实现思路和代码风格非常统一,关于“递归”的理解可见我的《人脑理解递归》

教科书上的非递归遍历

//非递归前序遍历
void preorderTraversal(TreeNode *root, vector<int> &path)
{
    stack<TreeNode *> s;
    TreeNode *p = root;
    while(p != NULL || !s.empty())
    {
        while(p != NULL)
        {
            path.push_back(p->val);
            s.push(p);
            p = p->left;
        }
        if(!s.empty())
        {
            p = s.top();
            s.pop();
            p = p->right;
        }
    }
}
//非递归中序遍历
void inorderTraversal(TreeNode *root, vector<int> &path)
{
    stack<TreeNode *> s;
    TreeNode *p = root;
    while(p != NULL || !s.empty())
    {
        while(p != NULL)
        {
            s.push(p);
            p = p->left;
        }
        if(!s.empty())
        {
            p = s.top();
            path.push_back(p->val);
            s.pop();
            p = p->right;
        }
    }
}
//非递归后序遍历-迭代
void postorderTraversal(TreeNode *root, vector<int> &path)
{
    stack<TempNode *> s;
    TreeNode *p = root;
    TempNode *temp;
    while(p != NULL || !s.empty())
    {
        while(p != NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点
        {
            TreeNode *tempNode = new TreeNode;
            tempNode->btnode = p;
            tempNode->isFirst = true;
            s.push(tempNode);
            p = p->left;
        }
        if(!s.empty())
        {
            temp = s.top();
            s.pop();
            if(temp->isFirst == true)   //表示是第一次出现在栈顶
            {
                temp->isFirst = false;
                s.push(temp);
                p = temp->btnode->right;
            }
            else  //第二次出现在栈顶
            {
                path.push_back(temp->btnode->val);
                p = NULL;
            }
        }
    }
}

看了上面教科书的三种非递归遍历方法,不难发现,后序遍历的实现的复杂程度明显高于前序遍历和中序遍历,前序遍历和中序遍历看似实现风格一样,但是实际上前者是在指针迭代时访问结点值,后者是在栈顶访问结点值,实现思路也是有本质区别的。而这三种方法最大的缺点就是都使用嵌套循环,大大增加了理解的复杂度。

更简单的非递归遍历二叉树的方法

这里我给出统一的实现思路和代码风格的方法,完成对二叉树的三种非递归遍历。

//更简单的非递归前序遍历
void preorderTraversalNew(TreeNode *root, vector<int> &path)
{
    stack< pair<TreeNode *, bool> > s;
    s.push(make_pair(root, false));
    bool visited;
    while(!s.empty())
    {
        root = s.top().first;
        visited = s.top().second;
        s.pop();
        if(root == NULL)
            continue;
        if(visited)
        {
            path.push_back(root->val);
        }
        else
        {
            s.push(make_pair(root->right, false));
            s.push(make_pair(root->left, false));
            s.push(make_pair(root, true));
        }
    }
}
//更简单的非递归中序遍历
void inorderTraversalNew(TreeNode *root, vector<int> &path)
{
    stack< pair<TreeNode *, bool> > s;
    s.push(make_pair(root, false));
    bool visited;
    while(!s.empty())
    {
        root = s.top().first;
        visited = s.top().second;
        s.pop();
        if(root == NULL)
            continue;
        if(visited)
        {
            path.push_back(root->val);
        }
        else
        {
            s.push(make_pair(root->right, false));
            s.push(make_pair(root, true));
            s.push(make_pair(root->left, false));
        }
    }
}
//更简单的非递归后序遍历
void postorderTraversalNew(TreeNode *root, vector<int> &path)
{
    stack< pair<TreeNode *, bool> > s;
    s.push(make_pair(root, false));
    bool visited;
    while(!s.empty())
    {
        root = s.top().first;
        visited = s.top().second;
        s.pop();
        if(root == NULL)
            continue;
        if(visited)
        {
            path.push_back(root->val);
        }
        else
        {
            s.push(make_pair(root, true));
            s.push(make_pair(root->right, false));
            s.push(make_pair(root->left, false));
        }
    }
}

以上三种遍历实现代码行数一模一样,如同递归遍历一样,只有三行核心代码的先后顺序有区别。为什么能产生这样的效果?下面我将会介绍。

有重合元素的局部有序一定能导致整体有序

这就是我得以统一三种更简单的非递归遍历方法的基本思想:有重合元素的局部有序一定能导致整体有序
如下这段序列,局部2 3 4和局部1 2 3都是有序的,但是不能由此保证整体有序。

而下面这段序列,局部2 3 4,4 5 6,6 8 10都是有序的,而且相邻局部都有一个重合元素,所以保证了序列整体也是有序的。

应用于二叉树

基于这种思想,我就构思三种非递归遍历的统一思想:不管是前序,中序,后序,只要我能保证对每个结点而言,该结点,其左子结点,其右子结点都满足以前序/中序/后序的访问顺序,整个二叉树的这种三结点局部有序一定能保证整体以前序/中序/后序访问,因为相邻的局部必有重合的结点,即一个局部的“根”结点是另外一个局部的“子”结点。

如下图,对二叉树而言,将每个框内结点集都看做一个局部,那么局部有A,A B C,B D E,D,E,C F,F,并且可以发现每个结点元素都是相邻的两个局部的重合结点。发觉这个是非常关键的,因为知道了重合结点,就可以对一个局部排好序后,通过取出一个重合结点过渡到与之相邻的局部进行新的局部排序。我们可以用栈来保证局部的顺序(排在顺序前面的后入栈,排在后面的先入栈,保证这个局部元素出栈的顺序一定正确),然后通过栈顶元素(重合元素)过渡到对新局部的排序,对新局部的排序会导致该重合结点再次入栈,所以当栈顶出现已完成过渡使命的结点时,就可以彻底出栈输出了(而这个输出可以保证该结点在它过渡的那个局部一定就是排在最前面的),而新栈顶元素将会继续完成新局部的过渡。当所有结点都完成了过渡使命时,就全部出栈了,这时我敢保证所有局部元素都是有序出栈,而相邻局部必有重合元素则保证了整体的输出一定是有序的。这种思想的好处是将算法与顺序分离,定义何种顺序并不影响算法,算法只做这么一件事:将栈顶元素取出,使以此元素为“根”结点的局部有序入栈,但若此前已通过该结点将其局部入栈,则直接出栈输出即可

从实现的程序中可以看到:三种非递归遍历唯一不同的就是局部入栈的三行代码的先后顺序。所以不管是根->左->右,左->根->右,左->右->根,甚至是根->右->左,右->根->左,右->左->根定义的新顺序,算法实现都无变化,除了改变局部入栈顺序。

值得一提的是,对于前序遍历,大家可能发现取出一个栈顶元素,使其局部前序入栈后,栈顶元素依然是此元素,接着就要出栈输出了,所以使其随局部入栈是没有必要的,其代码就可以简化为下面的形式。

void preorderTraversalNew(TreeNode *root, vector<int> &path)
{
    stack<TreeNode *> s;
    s.push(root);
    while(!s.empty())
    {
        root = s.top();
        s.pop();
        if(root == NULL)
        {
            continue;
        }
        else
        {
            path.push_back(root->val);
            s.push(root->right);
            s.push(root->left);
        }
    }
}

这就是我要介绍的一种更简单的非递归遍历二叉树的方法。

转自http://www.jianshu.com/p/4db970d8ddc1

时间: 2024-10-11 01:50:35

【转】更简单的非递归遍历二叉树的方法的相关文章

重拾算法(1)——优雅地非递归遍历二叉树及其它

重拾算法(1)——优雅地非递归遍历二叉树及其它 本文中非递归遍历二叉树的思想和代码都来自这里(http://jianshu.io/p/49c8cfd07410#).我认为其思想和代码都足够优雅动人了,于是稍作整理,得到如下的程序. 前中后序遍历二叉树 1 public class BinaryTreeNode<T> 2 { 3 public T Value { get;set; } 4 public BinaryTreeNode<T> Parent { get;set; } 5 p

JAVA递归、非递归遍历二叉树(转)

原文链接: JAVA递归.非递归遍历二叉树 import java.util.Stack; import java.util.HashMap; public class BinTree { private char date; private BinTree lchild; private BinTree rchild; public BinTree(char c) { date = c; } // 先序遍历递归 public static void preOrder(BinTree t) {

史上最简明易懂非递归遍历二叉树算法

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 遍历二叉树的递归函数是体现了算法之美的高妙算法,思路清晰,代码简洁,读之赏心悦目.代码例如以下: 程序代码: void PreOrderTraverse_R(BiTree BT)//採用递归方式先序遍历二叉树BT { if(BT != NULL) { printf("%c", BT->data);//输出该结点(根结点) PreOrderTraverse_R(BT->lchi

非递归遍历二叉树Java实现

2018-10-03 20:16:53 非递归遍历二叉树是使用堆栈来进行保存,个人推荐使用双while结构,完全按照遍历顺序来进行堆栈的操作,当然在前序和后序的遍历过程中还有其他的压栈流程. 一.Binary Tree Preorder Traversal 问题描述: 问题求解: 先序遍历就是在第一次访问到节点的时候将其值进行打印,然后递归打印其左子树,最后递归打印其右子树. 解法一.双while public List<Integer> preorderTraversal(TreeNode

非递归遍历二叉树的前序中序后序

/** * 二叉树先序遍历,非递归算法 * 1.申请一个新的栈,记为stack.然后将头节点head压入stack中. * 2.从stack弹出栈顶节点,记为cur,然后打印cur节点的值,再将cur右孩子(不为空) * 压入stack中,最后将cur的左孩子(不为空)压入stack中 * 3.不断重复步骤2,直到stack为空,全部过程结束. * @param head */ public void preOrderNoRecur(Node head){ System.out.print("非

非递归遍历二叉树之中序遍历

//中序遍历int inorder_tree_walk(BinTreeNode * root){ if(root == NULL){ return -1; } stack<BinTreeNode *> s; BinTreeNode * p = root; while(!s.empty() || p != NULL) { while(p != NULL){ s.push(p); p = p->lchild; } p = s.top(); s.pop(); cout << p-&

非递归遍历二叉树之前序遍历

前序遍历二叉树 int preorder_tree_walk(BinTreeNode * root){ if(root == NULL){ return -1; } stack<BinTreeNode *> s; BinTreeNode * p = root; while(!s.empty() || p != NULL) { while(p != NULL){ cout << p->key<< endl; s.push(p); p = p->lchild;

非递归遍历二叉树

#include <stack> #include <stdio.h> #include <malloc.h> #include <iostream> using namespace std; typedef struct node { int flag; char value; struct node *lchild; struct node *rchild; }Node, *PNode; void CreateBinaryTree(PNode &

非递归遍历二叉树【层次遍历,先序、中序、后序遍历】

一.层次遍历:借助队列实现 1 void LevelOrderTraversal(BiTree root) 2 { 3 BiTree e = root;//从根节点开始 4 Queue *q; 5 InitQueue(q); 6 7 if(e)//若根结点非空,则入队列 8 { 9 EnQueue(q,e); 10 } 11 12 while(!QueueEmpty(q)) 13 { 14 DelQueue(q,e); 15 Visit(e); 16 if(e->leftChild)//左孩子不