算法之递归(2)- 链表遍历

在递归(1)中,简单的介绍了递归的思想,并且通过一个例子简单阐述了递归是如何工作的,并且递归的实现是以线性结构来表示的。之所以用线性的,是因为其易于理解;如果使用树结构,将加大对问题的难度,不利于初学者理解递归的思想。

为什么用递归

关 于为什么用递归,我个人的理解是递归不要违背算法的初衷,即期待传入xxx值,加工后返回xxx值。不要为了递归而递归,容易造成对函数语义的奇异。另 外,通过递归,可以让代码更加整洁,短小,精湛,优美。当然,还会存在一定程度的性能损耗;不过,合理的使用,对于那部分的损耗可以忽略不计。

 

让我们以单链表为例,理解一下递归是如何工作的。

1. 遍历单链表

循环

递归

    


private void TraveserL(LNode head)
 {
     if (head == null)
         return;

     while (head != null)
     {
         Console.WriteLine(p.data);
         p = p.next;
     }
 }


 


private void TraveserR(LNode head)
{
    if (head == null)
        return;

    Console.WriteLine(head.data);
    TraveserR(head.next);
}


 

对于循环遍历我想不用多说了吧。不过,值得注意的是递归的遍历,先对数据进行打印,然后遍历下一个节点。有趣的是,如果将打印语句放在递归调用的后面,将是一个逆序的打印。

分析

假设链表的结构是这样的

A->B->C->D->E->F

递归调用时将发生如下情形

1.在递归调用之前打印(意味着进入函数体的时候打印)

(1)进入函数 (从左到右读)

A->

B->

C->

D->

E->

F->

打印A

打印B

打印C

打印D

打印E

打印F

 

输出:A, B, C, D, E, F.

(2)当函数退出 (从右到左度)

<-A

<-B

<-C

<-D

<-E

<-F

 

2. 在递归调用之后 (意味着只有退出函数体的时候,才进行打印)

(1)    进入函数体(从左到右读)

A->

B->

C->

D->

E->

F->

 

(2)    退出出函数体(从右到左读)

<-A

<-B

<-C

<-D

<-E

<-F

打印A

打印B

打印C

打印D

打印E

打印F

 

输出:F, E, D, C, B, A

结论,当操作在递归调用之后进行,那么是从后向前对链表执行操作。这也是栈的特性之一。当然,具体何时决定,取决与程序员自己,是想从头开始顺序操作,还是后到前逆序操作,具体问题具体分析。