算法之递归
什么是递归?递归是一种算法思想。从字面上看,递归包含两层含义,传递和回归。现实中有很多问题,只是传递而不用回归。比如说,军训时,每天的一项训练:”报数”。报数从头到尾,依次传递,到最后一个人停止,这时候军官就知道了总体人数是多少,有没有少人。假如,队伍中有人想知道自己是几号,怎么办呢?问下旁边的人,如果旁边的人说自己是“N”号,那么自己是“N+1”号。如果旁边的人不知道自己的号,那么他会继续问他旁边的人,以此类推,知道某个人知道自己是多少号,然后把这个号往回传。回传其实就是报数。用一段程序表示:
public static int GetPeopleCount(int n) {
if (n<1) return 0; if (n == 1) return 1; return GetPeopleCount(n - 1) + 1; }
有人问了,你这还用得着递归吗?直接循环搞定。我说此时用循环意义变了,那军官从第一个人数到最后一个人,不就是很累吗?递归(报数)效率多高啊。军官自己数的话,相当于每次指针移动。在现实当中不可取,原因不光是累,还有一个致命的原因,就是大家都是迷彩服,数数肯定花眼了,难免出错。
好了,递归可以用来数数,我们的循环当然可以数数,而且循环效率肯定高。放到计算机中,这只是不同的算法而已。
要算一个数的阶乘,怎么算呢?
public static int GetNumber(int n) { if (n < 1) return 0; if (n == 1) return 1; return GetNumber(n - 1) * n; }
和上面的数数多么相似啊,只是递归的时候函数变了而已。为了程序的正确性,小测一下,求5的阶乘:
有人说,循环也可以啊,效率也高,那我们改为循环:
public static int GetNumber(int n) { int result = 1; for (int i = 1; i <= n; i++) { result = result * i; } return result; }
的确根据阶乘的定义,可以改为循环。
有一个很经典的问题,那就是 斐(fei)波那契数列(兔子总数) 1,1,2,3,5,8,13..这个数列的特点就是从第三个数开始,每一项都是前两项之和。那这个数列跟兔子又有什么联系呢,原来还有这么个问题:
有一对兔子,从出生后第三个月起每个月都生一对兔子,小兔子长到第三个月后又生一对兔子,假如兔子都不死,每个月兔子对数为多少?
第一个月:1对;
第二个月:1对;
第三个月:2对;
第四个月:3对:
第五个月:5对:
第六个月:8对;
请问第五个月,为什么是5对?那是因为自己生了一对,在第三个月生下的一对兔子,在第五个月开始生了一对兔子,所以加起来就增加了2对兔子,因此是5,请问第20月的时候,一共有多少兔子?要让人算,恐怕得费点事情了,好在我们发现这个兔子数的规律就是斐(fei)波那契数列。
程序编写为:
public static int GetNumber(int n) { if (n < 1) return 0; if (n == 1 || n == 2) return 1; return GetNumber(n - 2) + GetNumber(n - 1); }
第20个月兔子总数:
这么多啊,果然非人力所为,那怎么知道这个方法正确呢?测试下,第6个月:
这段程序依然可以改为循环,就不用写了,为什么我一直把递归和循环比较呢?那是因为我曾经面试的时候,有个面试官问我循环和递归有什么不同,递归在什么时候用,我当时一脸懵逼,心里嘀咕,该用递归时自然用递归。他给的结论是:能用递归的,就能用循环。
果真是这样的吗?
1、给定一个文件夹,找出下面所有的文件
2、遍历二叉树
你给我来个循环试试。这个我就确实用循环写不出来,为什么呢?因为之前的例子,都是数学问题,有明显的规律可寻,但是遍历文件和二叉树没有这样的规律,而且我无法确定循环多少次,什么时候才能循环结束。也许有某些高人能写出来。
附:递归遍历文件:
1 /// <summary> 2 /// 获取目录path下所有子文件名 3 /// </summary> 4 public static List<string> getAllFiles(String path) 5 { 6 List<string> fileNames = new List<string>(); 7 if (System.IO.Directory.Exists(path)) 8 { 9 //所有子文件名 10 string[] files = System.IO.Directory.GetFiles(path); 11 foreach (string file in files) 12 { 13 fileNames.Add(file); 14 } 15 //所有子目录名 16 string[] Dirs = System.IO.Directory.GetDirectories(path); 17 foreach (string dir in Dirs) 18 { 19 var tmp = getAllFiles(dir); //子目录下所有子文件名 20 if (tmp.Count>0) 21 { 22 fileNames.AddRange(tmp); 23 } 24 } 25 } 26 return fileNames; 27 }