資料結構 分治策略與遞迴(一)

2021-10-10 13:40:54 字數 2127 閱讀 8050

分治策略是將規模比較大的問題可分割成規模較小的相同問題。問題不變,規模變小。這就自然導致遞迴過程的產生。

遞迴是指乙個函式能夠直接或者間接的調動自己,就稱之為遞迴函式(自己呼叫自己)。

分治法所能解決的問題一般具有以下特徵:

分治法步驟:

比如說求n!這個問題就適合用分治的策略來求解,我們將乙個問題的規模縮小,而問題不變,如下圖所示:

我們可以用迴圈來解決階乘這個問題,其時間和空間複雜度分別為o(n)和s(1):

int

fun(

int m)

return sum;

}

除了迴圈以外,我們還可以用遞迴來解決階乘問題,遞迴的時間和空間複雜度分別為o(n)和s(n):

int

fac(

int n)

可以看到,遞迴的**要比迴圈**簡潔好多。但是考慮到每遞迴一次就要為其分配一塊棧空間(windows下的棧大小預設1m),這就導致棧空間的消耗比較大,因此在平常我們解決的問題如果能用迴圈解決就盡量去使用迴圈解決。

遞迴函式的執行分為「遞推」和「回歸」兩個過程,這兩個過程由遞迴終止條件控制,即逐層遞迴,直至遞迴終止條件滿足,終止遞迴,然後逐層回歸。

遞迴呼叫同普通的呼叫函式一樣,每當呼叫發生時就要分配新的棧幀(形引數據、現場保護、區域性變數),而與普通的函式呼叫不同的是,由於遞迴的過程是乙個逐層呼叫的過程,因此存在乙個逐層連續的分配棧幀的過程,直到遇到遞迴終止條件時,才開始回歸,這時才逐層釋放棧幀空間,返回到上一層,直至最後返回到主調函式。

首先就上面階乘這個例子我們來分析**層面的遞迴呼叫過程,如下圖所示:

接著我們來分析一下棧幀的動態呼叫過程,如下圖所示:

小結:函式被呼叫,不管是自己呼叫自己,還是被其它函式呼叫,都將會給被呼叫函式分配棧幀。不存在無窮遞迴(棧空間大小有限)。即遞迴函式必須要有乙個是遞迴結束的出口(要有遞迴終止的條件語句)。問題的規模不要太大,遞迴過深,引起棧溢位。

下面我們使用遞迴方法逆序列印陣列中的元素(注意本段**在vs2019的編譯器下可以通過):

#include

#include

using

namespace std;

void

print

(const vector<

int>

& vec,

int n)

}int

main()

;print

(vec, vec.

size()

);return0;

}

結果如下圖所示:

根據上面的**分析,我們很容易可以知道這個逆序列印是在遞迴的過程中列印出來的。接下來我們將列印函式裡面的兩行**做個調換:

void

print

(const vector<

int>

& vec,

int n)

}

我們就會發現它會順序輸出陣列中的元素,根據上**我們也可以分析出它在回歸的過程中進行了列印,自然而然也就成了順序輸出。執行結果如下圖所示:

需要特別注意的是,當在遞迴中使用n+/-=1時,盡量不要寫成n–/n++/++n/–n這樣的前置或後置++,–,很有可能會引發無窮遞迴導致棧溢位。

遞迴與分治策略

1 全排列問題 設r n 是要進行排列的n個元素。集合x中元素的全排列記為perm x 求r n 的全排列perm r n 用遞迴演算法求解 1 找出遞迴子結構性質 即原問題的解包含了子問題的解,且子問題的描述與原問題相同。這就可以用子問題的解來構造原問題的解。設r i r n 這是乙個子問題。設 ...

遞迴與分治策略

1.遞迴 直接或間接地呼叫自身的演算法稱為遞迴演算法。用函式自身給出定義的函式稱為遞迴函式。1 階乘函式 include using namespace std int main int factorial int n 2 fibonacci數列 include using namespace st...

分治策略與遞迴

先看 資料結構與演算法分析 中對分治策略的解釋 把問題分成兩個大致相等的子問題,然後再遞迴地對他們進行求解,這是 分 治 階段將兩個子問題的解合併到一起並可能再做些少量的附加工作,最後得到整個問題的解。由定義可以看出,分治需要進行兩步操作 分 將問題恰當的劃分為需要迭代處理的兩個子問題,治 將兩個子...