zoukankan      html  css  js  c++  java
  • 二叉树遍历基础 -- 递归与非递归的实现方法

            之前也写过不少关于二叉树的东西了,但是总体来说,二叉树还是一个很绕的东西,所以单独择出来写一篇笔记,之前也没计划什么的,就想到什么写什么吧。不过该篇文章的主要内容是关于二叉树的三种遍历(前序、中序、后序)不同的实现方式(递归与非递归)

             首先,我觉得很有必要去彻底理解一下递归。

             (1)递归的主体大概分两部分:递归停止的条件、递归内容。

             (2)递归应用的实例:这个超级多,就比如最典型的斐波那契数列。个人认为,可以用循环实现的,递归基本上都可以实现,但有时递归的效率不如循环。

             (3)递归又分为单递归与多递归(二叉树的三种遍历递归方法均用到了双递归!)

              根据上面的三点,举个例子先。

              假设当x=0时,F(x)=1;否则F(x)=F(n-1)*n。这个时候就可以用递归了,代码实现如下。

    class Solution{
         public int F(int n)
        {
            //递归停止条件
            if (n == 0)
            {
                return 1;
            }
            //递归内容
            else
           {
                 return F(n - 1) * n;  
            } 
        }
    }

            代码分析一下如下:

             二叉树的三种遍历:前序(根左右)、中序(左根右)、后序(左右根)

            首先看三种遍历的递归实现方法。(特点:代码清晰,量少,但不易理解)

            // (1)前序遍历
            public TreeNode PreOrder(TreeNode pRoot)
            {
                //递归终止条件
                if (pRoot != null)
                {
                    // 根->左->右
                    Console.Write(pRoot.data + " ");
                    PreOrder(pRoot.left);
                    PreOrder(pRoot.right);
                }
            }
    
            // (2)中序遍历
            public TreeNode MidOrder(TreeNode pRoot)
            {
                //递归终止条件
                if (node != null)
                {
                    // 左->根->右
                    MidOrder(pRoot.left);
                    Console.Write(pRoot.data + " ");
                    MidOrder(pRoot.right);
                }
            }
    
            // (3)后序遍历
            public TreeNode PostOrder(TreeNode pRoot)
            {
                //递归终止条件
                if (pRoot != null)
                {
                    // 左->右->根
                    PostOrder(pRoot.left);
                    PostOrder(pRoot.right);
                    Console.Write(pRoot.data + " ");
                }
            } 

     分析:

            表达能力是多么重要的事情啊,会是一回事,表述清楚又是一回事。难受啊,马飞。。

            当然,我写的东西可能有点乱,但是想表现几个想法:

            递归和栈是密不可分的。

            上述三个方法均存在一个打印,两个递归,但是唯一的区别就是顺序的不同,所以,如何理解呢!!!关键点:如果打印在递归后面,则递归是不受打印影响的,也就是,我递归要先执行完,才开始执行你打印,但是如果打印在递归前面,相当于打印已经属于这个递归体了,没次递归的时候都要执行一次打印!!!

            非递归下如何实现三种遍历。

            // 前序遍历
            public void PreOrderNoRecurise(Node<T> node)
            {
                if (node == null)
                {
                    return;
                }
                // 定义一个栈存放数据
                Stack<Node<T>> stack = new Stack<Node<T>>();
                //把根节点放进去
                stack.Push(node);
                //定义一个空节点名称
                Node<T> tempNode = null;
    
                while (stack.Count > 0)
                {
                    // 根节点出栈打印
                    tempNode = stack.Pop();
                    Console.Write(tempNode.data);
                    // 右子节点进栈
                    if (tempNode.right != null)
                    {
                        stack.Push(tempNode.right);
                    }
                    // 左子节点进栈
                    if (tempNode.left != null)
                    {
                        stack.Push(tempNode.left);
                    }
                }
            }
    
            //中序遍历
            public void MidOrderNoRecurise(Node<T> node)
            {
                if (node == null)
                {
                    return;
                }
                // 定义一个栈存放数据
                Stack<Node<T>> stack = new Stack<Node<T>>();
                //定义一个空节点
                Node<T> tempNode = node;
    
                while (tempNode != null || stack.Count > 0)
                {
                    // 左子节点进栈
                    while(tempNode != null)
                    {
                        stack.Push(tempNode);
                        tempNode = tempNode.left;
                    }
                    // 2.出栈遍历节点并打印
                    tempNode = stack.Pop();
                    Console.Write(tempNode.data);
                    // 3.左子节点遍历结束则跳转到右子节点
                    tempNode = tempNode.right;
                }
            }
    
            //后序遍历
            public void PostOrderNoRecurise(Node<T> node)
            {
                if (root == null)
                {
                    return;
                }
    
                // 两个栈:一个存储,一个输出
                Stack<Node<T>> stackIn = new Stack<Node<T>>();
                Stack<Node<T>> stackOut = new Stack<Node<T>>();
                Node<T> currentNode = null;
                // 根节点进栈
                stackIn.Push(node);
                // 左->右->根
                while (stackIn.Count > 0)
                {
                    currentNode = stackIn.Pop();
                    stackOut.Push(currentNode);
                    // 左子节点进栈
                    if (currentNode.left != null)
                    {
                        stackIn.Push(currentNode.left);
                    }
                    // 右子节点进栈
                    if (currentNode.right != null)
                    {
                        stackIn.Push(currentNode.right);
                    }
                }
    
                while (stackOut.Count > 0)
                {
                    // 依次遍历各节点
                    Node<T> outNode = stackOut.Pop();
                    Console.Write(outNode.data);
                }
            }

            非递归方法变化多样,上述代码借鉴Edison Zhou的文章,自己不想写了,哈哈,但是道理并不难懂,不会向递归那样难理解,有时间的时候再慢慢修改补充吧。。

  • 相关阅读:
    CAS配置记录
    线程同步机制
    线程
    异常
    List集合
    数据结构
    泛型+通配符高级使用--受限泛型
    Collection集合+迭代器+foreach循环
    easyui获取日期datebox中的值
    EL表达式与三目运算符
  • 原文地址:https://www.cnblogs.com/WeiMLing/p/10976602.html
Copyright © 2011-2022 走看看