zoukankan      html  css  js  c++  java
  • 递归示例(一):遍历二叉树

    最近做项目经常用到递归,刚开始很久没用,不太熟悉,现在研究了下,并写下了学习笔记及开发经验总结。

    递归热身

     

    一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(Recursive)。根据调用方式的不同,它分为直接递归(Direct Recursion)和间接递归(Indirect Recursion)。 比如,在收看电视节目时,如果演播室中也有一台电视机播放的是与当前相同的节目,观众就会发现屏幕里的电视套有一层层的电视画面。这种现象类似于直接递归。 

    如果把两面镜子面对面摆放,便可从任意一面镜子里看到两面镜子无数个影像,这类似于间接递归。 

    一个递归算法必须有两个部分:初始部分(Base Case)和递归部分(Recursion Case)。初始部分只处理可以直接解决而不需要再次递归调用的简单输入。递归部分包含对算法的一次或多次递归调用,每一次的调用参数都在某种程度上比原始调用参数更接近初始情况。 

    函数的递归调用可以理解为:通过一系列的自身调用,达到某一终止条件后,再按照调用路线逐步返回。递归是程序设计中强有力的工具,有很多数学函数是以递归来定义的。 

    如大家熟悉的阶乘函数,我们可以对n!作如下定义:f(n)= 

    1 (n=1)

    n*f(n-1)  (n>=2)

     

          一个算法具有的特性之一就是有穷性(Finity):一个算法总是在执行有穷步之后结束,即算法的执行时间是有限的。递归算法当然也是算法,也满足算法的特性,因此递归不可能无限递归下去,总有一个终止条件。对该示例,递归的终止条件是n=1. n=1是,返回1,不在调用自己本身,递归结束。

     

     

     

     
    class Program
    {
    static void Main(string[] args)
    {
    long result = function(20);
    Console.WriteLine(result);
    Console.ReadLine();
    }

    static long function(long n)
    {
    if (n == 1) //算法终止条件
    {
    return 1;
    }

    return n * function(n - 1);
    }
    }

    

    递归算法通常不是解决问题最有效的计算机程序,因为递归包含函数调用,函数调用需要时空开销。所以,递归比其他替代选择诸如while循环等,所花费的代价更大。但是,递归通常提供了一种能合理有效地解决某些问题的算法。 

    递归示例():遍历二叉树

    二叉树是一种典型的树形结构,常用到递归算法来遍历。遍历按照根节点的相对顺序可分为前序遍历(DLR)、中序遍历(LDR)、后序遍历(RDL)

    

    

    

    对二叉树节点,有数据域存放数据,左孩子和右孩子为引用域存放孩子的引用:

    左孩子  LChhild

    数据域  data

    右孩子 RChild

    

     

    
    /// <summary>
    /// 二叉树节点
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Node<T>
    {
    private T data;//数据域
    private Node<T> lChild;//左孩子
    private Node<T> rChild;//右孩子

    public Node()
    {
    data
    = default(T);
    lChild
    = null;
    rChild
    = null;
    }

    public Node(T data, Node<T> lChild, Node<T> rChild)
    {
    this.data = data;
    this.lChild = lChild;
    this.rChild = rChild;
    }

    public Node(Node<T> lChild, Node<T> rChild)
    {
    data
    = default(T);
    this.lChild = lChild;
    this.rChild = rChild;
    }

    public Node(T data)
    :
    this(data, null, null)
    {
    this.data = data;
    }

    /// <summary>
    /// 数据域
    /// </summary>
    public T Data
    {
    get { return data; }
    set { this.data = value; }
    }

    /// <summary>
    /// 左孩子
    /// </summary>
    public Node<T> LChild
    {
    get { return lChild; }
    set { lChild = value; }
    }

    /// <summary>
    /// 右孩子
    /// </summary>
    public Node<T> RChild
    {
    get { return rChild; }
    set { rChild = value; }
    }

    }

     

    先假设有以下结构的二叉树:

    

     

     

     

     

     

     

     

     

    

    

    先在构造函数中简单构造下对应的数据:

    

     

    
    public Node<string> A;
    public 遍历二叉树()
    {
    A
    = new Node<string>("A");
    Node
    <string> B = new Node<string>("B");
    Node
    <string> C = new Node<string>("C");
    Node
    <string> D = new Node<string>("D");
    Node
    <string> E = new Node<string>("E");
    Node
    <string> F = new Node<string>("F");
    Node
    <string> G = new Node<string>("G");
    Node
    <string> H = new Node<string>("H");
    Node
    <string> I = new Node<string>("I");
    Node
    <string> J = new Node<string>("J");

    D.LChild
    = H;
    D.RChild
    = I;

    E.LChild
    = J;

    B.LChild
    = D;
    B.RChild
    = E;

    C.LChild
    = F;
    C.RChild
    = G;

    A.LChild
    = B;
    A.RChild
    = C;

    }

    

    前序遍历:先访问根结点A,然后分别访问左子树和右子树,把BB的子孙看作一个结点处理,CC的子孙看作一个结点处理,访问B时,把B当作根结点处理,B的左子树及左子树的子孙看作一个结点处理……可见,顺序依次是顶点-左孩子-右孩子(DLR),直到结点为叶子(即不包含子结点的结点),即为递归的终止条件。对任意结点,只要结点确定,其左孩子和右孩子就确定,因此递归算法方法参数将结点传入即可。

    

     

     

     

    /// <summary>
    /// 前序遍历--DLR
    /// </summary>
    /// <param name="root"></param>
    public void PreOrder(Node<T> root)
    {
    if (root == null)
    {
    return;
    }

    Console.Write(
    "{0} ",root.Data);
    //当节点无左孩子时,传入参数为null,下次调用即返回,终止
    PreOrder(root.LChild);
    //当节点无右孩子时,传入参数为null,下次调用即返回,终止
    PreOrder(root.RChild);

    }

     

     

    同理,中序遍历和后序遍历如下:

     

     
    /// <summary>
    /// 中序遍历 LDR
    /// </summary>
    /// <param name="node"></param>
    public void InOrder(Node<T> node)
    {
    //if (node == null)
    //{
    // return;
    //}
    //InOrder(node.LChild);
    //Console.Write("{0} ",node.Data);
    //InOrder(node.RChild);

    //另外一种写法
    if (node.LChild!=null)
    {
    InOrder(node.LChild);
    }
    Console.Write(
    "{0} ", node.Data);
    if (node.RChild != null)
    {
    InOrder(node.RChild);
    }
    }

    /// <summary>
    /// 后序遍历--LRD
    /// </summary>
    /// <param name="node"></param>
    public void PostOrder(Node<T> node)
    {
    if (node == null)
    {
    return;
    }

    PostOrder(node.LChild);
    PostOrder(node.RChild);
    Console.Write(
    "{0} ",node.Data);
    }

    /// <summary>
    /// 层序遍历
    /// </summary>
    /// <param name="node"></param>
    public void LevelOrder(Node<T> node)
    {
    if (node == null)
    {
    return;
    }
    Queue
    <Node<T>> sq = new Queue<Node<T>>();
    //根结点入队
    sq.Enqueue(node);

    while (sq.Count != 0)
    {
    Node
    <T> tmp = sq.Dequeue(); //出队
    Console.Write("{0} ",tmp.Data);

    if (tmp.LChild != null)
    {
    sq.Enqueue(tmp.LChild);
    }
    if (tmp.RChild != null)
    {
    sq.Enqueue(tmp.RChild);
    }
    }
    }

     

     

    其中,另外一种写法就是在递归前判断下,满足递归条件才调用自己,这也是处理递归终止的一种方法。

    static void Main(string[] args)
    {
    遍历二叉树
    <string> t = new 遍历二叉树<string>();
    Console.Write(
    "前序遍历:");
    t.PreOrder(t.A);
    Console.WriteLine();

    Console.Write(
    "中序遍历:");
    t.InOrder(t.A);
    Console.WriteLine();

    Console.Write(
    "后序遍历:");
    t.PostOrder(t.A);
    Console.WriteLine();

    Console.Write(
    "层序遍历:");
    t.LevelOrder(t.A);

    Console.ReadLine();
    }

     

    运行结果为

     

     

     

     

     

     

    

     代码下载:https://files.cnblogs.com/sndnnlfhvk/TViewSource.rar
    递归示例(一):遍历二叉树 http://www.cnblogs.com/sndnnlfhvk/archive/2011/03/31/2001015.html
    递归示例(二):WinForm之TreeView的应用—绑定区域树  http://www.cnblogs.com/sndnnlfhvk/archive/2011/03/31/2001064.html
    递归示例(三):WinForm之TreeView的应用—绑定磁盘目录(一)  http://www.cnblogs.com/sndnnlfhvk/archive/2011/03/31/2001065.html
    递归示例(四):WinForm之TreeView的应用—绑定磁盘目录(二)  http://www.cnblogs.com/sndnnlfhvk/archive/2011/03/31/2001072.html
  • 相关阅读:
    AVFoundation 文本语音播报
    单元测试 + UI测试
    scrollView
    设备旋转---横竖屏切换
    SDK 开发 .a .framework .bundle (xcode引用) 依赖sdk工程
    多线程 NSThread 的使用
    多线程 NSOpeartion 的使用
    多线程 GCD 的使用
    swift pod 第三方库异常的处理
    按钮重复点击问题 UIbutton
  • 原文地址:https://www.cnblogs.com/sndnnlfhvk/p/2001015.html
Copyright © 2011-2022 走看看