遞迴漫談(一)

2021-07-30 10:42:35 字數 2884 閱讀 7926

遞迴,是我們在日常開發過程中經常會使用的技術。它廣泛的被現代開發語言所支援。然而,在日常的管理和招聘工作中,我發現由於開發者個人經驗不足及團隊開發規範不明確等原因,很多開發者不知道應該何時編寫遞迴函式,怎麼編寫遞迴函式。一些開發者編寫的遞迴函式存在可讀性差、除錯困難、邏輯混亂等問題,甚至還會引入死迴圈等後果嚴重的bug。我將在本文中帶領讀者回顧遞迴的基礎知識,剖析遞迴函式的設計要點並分享一些相關的最佳實踐。

遞迴在維基百科的解釋如下:遞迴(英語:recursion),又譯為遞迴,在數學與電腦科學中,是指在函式的定義中使用函式自身的方法。

簡單的說,對於開發者而言,遞迴函式就是乙個在函式中呼叫自己的函式。比如,下面是乙個計算階乘的遞迴函式。

/**

* 計算階乘

*@param num 欲計算階乘的數值

*@return 階乘的值

*/static

public

intfactorial(int num)else

}

該函式的邏輯非常簡單,如果當前值為0則返回1,否則返回當前值與「當前值減一」的乘積。

本節我將介紹遞迴函式的幾種常見使用場景。需要指出的是,在這些常見場景中,遞迴並不是唯一的解決方案,在實際工作中,需要讀者具體情況具體分析,因地制宜的做出更貼合實際的設計。

3.1 線性逼近

線性逼近是遞迴函式最原始也是最常見的應用場景。比如前邊提到的計算階乘的例子,就是典型的線性逼近——當前值的計算依賴於的對逼近直接結果的另乙個值的計算。這樣,只有逐漸沿著變化趨勢線性的找到可以計算出直接結果的值才能逆向返回,最終得到目標結果。線性逼近這類場景,每次處理的物件均不同,但從業務含義上,它們是存在一定變化趨勢的「同級」物件,所以叫做線性逼近。

3.2 重試

對於這類場景,函式不停的執行同一業務操作,直到滿足一定條件(通常是達到業務目的)。這種遞迴函式的結構看起來更像是乙個while迴圈。比如,我們在進行資料庫連線時,可以編寫如下的遞迴函式:

/**

* 獲取資料庫連線

*@param url 資料庫連線串

*@param maxretrynum 最大重試數

*@return 資料庫連線

*/public connection getconnection(string url,int maxretrynum)

try catch (sqlexception e)

}

上邊**中的遞迴函式有乙個表示最大重試次數的引數maxretrynum。當獲取資料庫連線失敗時,它將通過遞迴不停重試,除非超過最大重試次數。

可以看到,對於重試型別的遞迴函式,每次呼叫處理的都是相同物件的相同業務。

3.3 樹狀結構遍歷

在日常開發中,經常會遇到樹狀結構,比如組織機構樹、功能樹等。我們常常需要按照某個順序遍歷樹狀結構的某個分支的各個節點。這裡說的「某個順序」一般為自頂向下(指定節點及其子孫節點)和自底向上(指定節點及其祖先節點)。對這些符合條件的節點,我們會做一些操作,比如匹配查詢、設定狀態等。此時,我們可以使用遞迴函式來實現遍歷。比如下面是乙個對組織結構樹進行遍歷處理的示例:

// 組織機構

class org

// 組織名

public string name;

// 子組織

public listchildrenorg = new arraylist();

/*** 選中組織

*/public

void

select()

}/**

* 選擇組織及其子孫組織

**@param org 要選擇的組織

*/public

void

selectorganddescendants (org org)

}

結合上面的**可以看到,對於樹狀結構遍歷型別的遞迴,每次呼叫遞迴函式都會首先處理當前節點,之後遍歷當前節點的子節點,為每個子節點呼叫遞迴函式,做相同的業務處理。

3.4 監聽

模組、系統間的協同會用到一種監聽策略。監聽主體根據既定規則(如時間間隔)向被監聽物件詢問(或直接查詢)狀態,判斷被監聽物件狀態,進而做出某個動作。下面介紹監聽的幾種典型的應用場景。

守護程序。當程序b發現自己守護的程序a被關閉後立刻啟動程序a使其得以恢復執行。

心跳監測及雙機保障。當副伺服器b發現主伺服器a故障或失聯後,伺服器b馬上將自己的工作模式切換為主伺服器,接管伺服器a的工作,並向管理員發出通知,處理伺服器a的故障。

資源處理。這裡提到的資源可能是一組存放在ftp伺服器中指定目錄下的檔案,也可能是記憶體中的乙個鍊錶。當監聽執行緒發現資源池不為空(即有待處理的資源)時,就將資源按照一定的順序一一摘取,執行預定操作。比如對ftp伺服器中指定目錄下的使用者上傳檔案做防毒、壓縮、歸檔處理。

上面這些典型的監聽場景都可以通過遞迴函式來實現,我們來看示例**。

/**

* 監聽

*/public

void

monitor()

// 被監聽物件狀態

state objectstate = getmonitoredobjectstate();

// 被監聽物件狀態符合某條件

if (objectstate.haschanged()) else

monitor();

}

在上面的示例**中,遞迴函式monitor對被監聽物件的狀態進行監控。如果被監聽物件的狀態符合某個條件則對被監聽物件執行某操作,否則休眠500毫秒。之後再次對自身呼叫。我們可以看到,這種型別的遞迴函式形態與while迴圈十分相似。

(未完待續)

漫談遞迴思想

程式設計裡面估計最讓人摸不著頭腦的基本演算法就是遞迴了。很多時候我們看明白乙個複雜的遞迴都有點費時間,尤其對模型所描述的問題概念不清的時候,想要自己設計乙個遞迴那麼就更是有難度了。今天我也花費了半個小時來搞明白二叉樹的平衡性的遞迴模型,首先我不明白什麼叫做平衡性,所以花費的時候大部分實在試探理解平衡...

漫談遞迴 遞迴的思想

為什麼要用遞迴 程式設計裡面估計最讓人摸不著頭腦的基本演算法就是遞迴了。很多時候我們看明白乙個複雜的遞迴都有點費時間,尤其對模型所描述的問題概念不清的時候,想要自己設計乙個遞迴那麼就更是有難度了。用歸納法來理解遞迴 數學都不差的我們,第一反應就是遞迴在數學上的模型是什麼。畢竟我們對於問題進行數學建模...

漫談遞迴 遞迴的效率問題

遞迴在解決某些問題的時候使得我們思考的方式得以簡化,也更加精煉,容易閱讀。那麼既然遞迴有這麼多的優點,我們是不是什麼問題都要用遞迴來解決呢?難道遞迴就沒有缺點嗎?今天我們就來討論一下遞迴的不足之處。談到遞迴就不得不面對它的效率問題。為什麼遞迴是低效的 還是拿斐波那契 fibonacci 數列來做例子...