基本上,尾遞歸是你有一個函數自己調用時,但不需要做任何事情之後,除了可能返回遞歸調用的結果。這是遞歸的一種特殊情況......不是它的工作原理,而是事實上很容易變成循環。對於不優化尾遞歸的編譯器/解釋器,這可能意味着正確工作和溢出調用堆棧之間的區別。
例如:
void countdown(int t) {
if (t <= 1) return;
printf("%d\n", t);
countdown(t - 1);
}
這是尾遞歸,因爲沒有什麼需要後countdown
調用自身的情況發生。
對比的東西,如
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
這是不尾遞歸。區別在於函數在調用自身之後需要重新獲得控制權,所以它可以將結果乘以n
。
(附加點雖然:你可以用一個叫做continuation-passing style的技術把它變成一個尾遞歸函數,基本上,在這種情況下,你可以將結果和其他參數一起傳遞。
int factorial(int n, int product = 1) {
if (n <= 1) return product;
return factorial(n - 1, product * n);
}
但我離題了。
無論如何,刪除尾遞歸的編譯器基本上只是調整當前的調用框架,以便變量是它們對於調用的內容,然後跳回到函數的開頭。你的老師可能不會那麼喜歡,原因很多。但我們從那裏開始。
selection_sort(int a, int b){
tail_call:
if(a == b+1){
//do nothing
}else{
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
雖然我們正在重新編排東西,但空白條件塊相當可怕。這也可以改寫爲
selection_sort(int a, int b){
tail_call:
if(a != b+1){
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
現在,你可能已經被告知,goto
是魔鬼。 :)(這被誇大了,但它不應該用在有更好的選擇的地方。)所以重構來擺脫它。
我們如何擺脫它?我們找到一個控制結構來完成goto
試圖做的事情。在這種情況下,while
循環很適合這個法案。在condition
缺席範圍的差異,此代碼:
loop:
if (condition) {
...magic...
goto loop;
}
不完全一樣的東西就象這樣:
while (condition) {
...magic...
}
的地步,許多編譯器甚至會產生兩個完全相同的代碼。
所以讓我們用while
循環清理代碼。
selection_sort(int a, int b){
while (a != b+1) {
int i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
a += 1;
}
}
非常感謝你一步一步地走過我。儘管如此,你確實在失去了我。我實際上從來沒有使用goto,並認爲這只是一個僞裝的東西。它是實際的代碼嗎? – YazanLpizra
@ yazan:在很多語言中,它是。 Java沒有'goto',所以你可以認爲它是一個僞代碼的東西,如果你堅持它。但C'ish家族中的大多數其他語言(以及其他許多其他語言)都這樣做。 – cHao
謝謝:)。我再次感謝它 – YazanLpizra