算法之遞歸(2)- 鏈表遍歷
在遞歸(1)中,簡單的介紹了遞歸的思想,並且通過一個例子簡單闡述了遞歸是如何工作的,並且遞歸的實現是以線性結構來表示的。之所以用線性的,是因為其易於理解;如果使用樹結構,將加大對問題的難度,不利於初學者理解遞歸的思想。
為什么用遞歸
關於為什么用遞歸,我個人的理解是遞歸不要違背算法的初衷,即期待傳入xxx值,加工后返回xxx值。不要為了遞歸而遞歸,容易造成對函數語義的奇異。另外,通過遞歸,可以讓代碼更加整潔,短小,精湛,優美。當然,還會存在一定程度的性能損耗;不過,合理的使用,對於那部分的損耗可以忽略不計。
讓我們以單鏈表為例,理解一下遞歸是如何工作的。
1. 遍歷單鏈表
| 循環 |
遞歸 |
|
private void TraveserL(LNode head) { if (head == null) return; while (head != null) { Console.WriteLine(p.data); p = p.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
結論,當操作在遞歸調用之后進行,那么是從后向前對鏈表執行操作。這也是棧的特性之一。當然,具體何時決定,取決與程序員自己,是想從頭開始順序操作,還是后到前逆序操作,具體問題具體分析。
