算法洗脑系列(8篇)——第二篇 递归思想

   今天说说递归思想,在我们编码时,有的时候递归能够让我们的算法更加通俗易懂,并且代码量也是大大的减少。比如我先前的系列中说到了

关于树的“先序,中序和后序”遍历,那么看看用递归来描叙这个问题是多少的简洁,多么的轻松。

#region 二叉树的先序遍历
         /// <summary>
 /// 二叉树的先序遍历
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="tree"></param>
         public void BinTree_DLR<T>(ChainTree<T> tree)
         {
             if (tree == null)
                 return;

             //先输出根元素
             Console.Write(tree.data + "\t");

             //然后遍历左子树
             BinTree_DLR(tree.left);

             //最后遍历右子树
             BinTree_DLR(tree.right);
         }
         #endregion

         #region 二叉树的中序遍历
         /// <summary>
 /// 二叉树的中序遍历
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="tree"></param>
         public void BinTree_LDR<T>(ChainTree<T> tree)
         {
             if (tree == null)
                 return;

             //优先遍历左子树
             BinTree_LDR(tree.left);

             //然后输出节点
             Console.Write(tree.data + "\t");

             //最后遍历右子树
             BinTree_LDR(tree.right);
         }
         #endregion

         #region 二叉树的后序遍历
         /// <summary>
 /// 二叉树的后序遍历
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="tree"></param>
         public void BinTree_LRD<T>(ChainTree<T> tree)
         {
             if (tree == null)
                 return;

             //优先遍历左子树
             BinTree_LRD(tree.left);

             //然后遍历右子树
             BinTree_LRD(tree.right);

             //最后输出节点元素
             Console.Write(tree.data + "\t");
         }
         #endregion

看看,多么简洁明了。当然递归都是可以改成非递归的,但是就不见得简洁和通俗易懂了。

 

一: 概念

       递归,说白了就是直接或者间接的调用自己的一种算法。它是把求解问题转化为规模较小的子问题,然后通过多次递归一直到可以得出结果

的最小解,然后通过最小解逐层向上返回调用,最终得到整个问题的解。总之递归可以概括为一句话就是:“能进则进,不进则退”。

 

二:三要素

      <1>  递归中每次循环都必须使问题规模有所缩小。

      <2>  递归操作的每两步都是有紧密的联系,如在“递归”的“归操作时”,前一次的输出就是后一次的输入。

      <3>  当子问题的规模足够小时,必须能够直接求出该规模问题的解,其实也就是必须要有结束递归的条件。

 

三: 注意

       <1>  前面也说了,递归必须要有一个递归出口。

       <2>  深层次的递归会涉及到频繁进栈出栈和分配内存空间,所以运行效率比较低,当问题规模较大时,不推荐使用。

       <3>  在递归过程中,每次调用中的参数,方法返回点,局部变量都是存放在堆栈中的,如果当问题规模非常大时,容易造成堆栈溢出。

 

四:  举二个例子

       <1> 相信大家在初中的时候都学过阶乘吧,比如:5!=5*4*3*2*1

        思路:根据上面的阶乘特征很容易我们就可以推导出n!=n*(n-1)*(n-2)....*2*1,

                  那么进一步其实就是: n!=n*(n-1)! ,

                                              (n-1)!=(n-1)*(n-2)!。

                 显然他是满足递归的三要素,当n的规模不大时,我们可以用递归拿下。

       

static void Main(string[] args)
        {
            while (true)
            {
                //阶乘问题
                Console.WriteLine("\n请输入一个求阶乘的一个数:");

                int num = int.Parse(Console.ReadLine());

                Console.WriteLine("\n阶乘的结果为:" + fact(num));
            }
        }

        static int fact(int n)
        {
            if (n == 1)
                return 1;

            return n * fact(n - 1);
        }

 

第一次: 输入5的时候能够正确求出。

第二次: 输入10的时候求出来竟然362万之多,可见多恐怖,如果俺们的时间复杂度是n!,那程序也就Game Over了,

第三次:输入100,已经超过了int.MaxValue了,

第四次: 输入10w,蹦出著名了“堆栈溢出”,好家伙,我们知道“递归”在程序中使用“栈”的形式存放的,每一次“递归”中,方法的返回值

          包括函数中的参数都会存放在栈中,C#中每个线程分配的栈空间为1M,所以当N的规模非常大时,就把栈玩爆了。

 

   <2> 在大一时上计算机文化基础的时候我们就接触过”进制转换问题“,比如将”十进制“转化为”二进制“。

       思路:采用除2取余法,取余数为相应二进制数的最低位,然后再用商除以2得到次低位.......直到最后一次相除商为0时得到二进制的最高位,

                比如(100)10=(1100100)2,   仔细分析这个问题,会发现它是满足”递归“的三要素的,

               ① 进制转换中,数据规模会有所缩小。

               ② 当商为0时,就是我们递归的出口。

            所以这个问题我们就可以用递归拿下。

static void Main(string[] args)
        {
            Console.WriteLine("请输入一个十进制数:");

            int num = int.Parse(Console.ReadLine());

            string result = string.Empty;

            Console.WriteLine("转化的二进制为:" + ConvertToBinary(ref result, num));

            Console.ReadLine();

        }

        static string ConvertToBinary(ref string str, int num)
        {
            //递的过程
            if (num == 0)
                return string.Empty;

            ConvertToBinary(ref str, num / 2);

            //归的过程
            return str += (num % 2);
        }

时间: 2024-12-03 09:25:40

算法洗脑系列(8篇)——第二篇 递归思想的相关文章

算法洗脑系列(8篇)——第八篇 概率思想

    今天写最后一篇来结束这个系列,我们知道很多算法解决问题的步骤都是固定的,而概率算法每一步的选择都是随机的, 当在某些领域问题中通常比最优选择省时,所以就大大提高了算法的效率,降低了复杂度.   一:思想       这里主要讲一下"数值概率算法",该算法常用于解决数值计算问题,并且往往只能求得问题的近似解,同一个问题同样的概率算法 求解两次可能得到的结果大不一样,不过没关系,这种"近似解"会随时间的增加而越接近问题的解.   二:特征      现实生活中,

算法洗脑系列(8篇)——第六篇 回溯思想

 记得广告中经常听到过,抱着试试看的态度买了3个疗程,效果不错........  也经常听人说过什么车到山前必有路,船到桥头自然直. 哈哈,这种思想就是回溯思想,也可称为试探思想.   一: 思想        有时我们要得到问题的解,先从其中某一种情况进行试探,在试探过程中,一旦发现原来的选择是错误的,那么就退回一步重新选择,    然后继续向前试探,反复这样的过程直到求出问题的解.   二:场景       回溯思想是一个非常重要的思想,应用场景也是非常广泛.       ①   "下棋&q

算法洗脑系列(8篇)——第五篇 分治思想

  由于最近工作比较忙,好长时间都没有更新博客了,今天就分享下分治思想.   一: 思想      有时候我们处理一个复杂的问题,可能此问题求解步骤非常杂,也可能是数据非常多,导致我们当时很难求出或者无法求出,古语有云: 步步为营,各个击破,这个思想在算法中称为分治思想,就是我们可以将该问题分解成若干个子问题,然后我们逐一解决子问题,最后将子问题 的答案组合成整个问题的答案.   二: 条件      当然各个思想都有它的使用领域,所以玩这场分治游戏就要遵守它的游戏规则.        ①  

算法洗脑系列(8篇)——第七篇 动态规划

     今天跟大家分享下算法思想中比较难的一种"动态规划",动态规划给人像是作战时常用的"迂回战术",或者说是 游击战,在运动中寻找突破口.   一: 思想    首先要了解"动态规划",必须先知道什么叫做"多阶段决策",百科里面对这个问题解释的很全,我就load一段出来, 大家得要好好品味,好好分析.   上面图中最后一句话就定义了动态规划是要干什么的问题.   二:使用规则     现在我们知道动态规划要解决啥问题了,那

数据结构与算法(C#实现)系列---演示篇(一)

数据|数据结构|算法 数据结构与算法(C#实现)系列---演示篇(一) Heavenkiller(原创) 这一篇主要是针对以后各篇的数据类型进行一个实质性的演示.因此希望大家具体看了各种数据结构的分析之后再看这篇. 主要包括如下几个方面的演示: 1. 堆栈. 演示了一个利用堆栈作的RPN计算器 2. 排序表.演示了一个利用排序表做的多项式表达式的加法运算 3. 广义树.演示了深度遍历和广度遍历 4. N叉树.演示了N叉树的生成插入删除等基本操作 5. 表达式树.演示了一个用二叉树和堆栈做的可以将

数据结构与算法(C#实现)系列---演示篇(二)

数据|数据结构|算法 数据结构与算法(C#实现)系列---演示篇(二) Heavenkiller(原创) public static void ShowGeneralTree_travel() { IEnumerator tmpIEnum; Tree.TraversalType travelType=0; //---------------------提示---------------------------- Console.WriteLine("please choose a the No.

CoreOS Fest 系列之第二篇: Systemd、Go、Calico、Sysdig

本文讲的是CoreOS Fest 系列之第二篇: Systemd.Go.Calico.Sysdig,[编者的话]在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS . Go 语言和容器. Calico 项目. Sysdig 等. 在 CoreOS Fest 的第一天会议中,陆续介绍了 CoreOS 的架构.规划和规范.第二天的会议,演讲者展示了多个开源项目和工具,包括 systemd-nspawn . Calico 项目和 Sysd

BOM系列第二篇之定时器requestAnimationFrame_javascript技巧

引入 计时器一直是javascript动画的核心技术.而编写动画循环的关键是要知道延迟时间多长合适.一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅:另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化 大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次.大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升.因此,最平滑动画的最佳循环间隔是lOOOms/60,约等于16.6ms 而setTimeout和

数据结构与算法(C#实现)系列---演示篇(三)

数据|数据结构|算法 数据结构与算法(C#实现)系列---树(二) Heavenkiller(原创) public class InOrder:IPrePostVisitor { private IVisitor visitor; public InOrder(IVisitor _vis){visitor=_vis;} #region IPrePostVisitor 成员 public void PreVisit(object _obj) { // TODO: 添加 InOrder.PreVis