最近做项目经常用到递归,刚开始很久没用,不太熟悉,现在研究了下,并写下了学习笔记及开发经验总结。
递归热身
一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(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,然后分别访问左子树和右子树,把B及B的子孙看作一个结点处理,C及C的子孙看作一个结点处理,访问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(); }
运行结果为:
递归示例(二):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
来源:https://www.cnblogs.com/sndnnlfhvk/archive/2011/03/31/2001015.html