最近做项目经常用到递归,刚开始很久没用,不太熟悉,现在研究了下,并写下了学习笔记及开发经验总结。
递归热身
一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(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