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

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

递归热身

 

一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(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
posted @ 2011-03-31 15:27  霜天雪舞  阅读(2742)  评论(0编辑  收藏  举报