常見算法減治、分治、遞歸、迭代、回溯、動態規劃、貪心的基本思想
本文由博主經過查閱網上資料整理總結後編寫,如存在錯誤或不恰當之處請留言以便更正,內容僅供大家參考學習。
一、減而治之
減而治之與分而治之都是遞歸中常用的算法策略。其中減而治之是將一個大規模的問題,將其劃分爲兩個子問題,其一是平凡問題,另一個規模縮減。遞歸每深入一層,待求解問題的規模都縮減一個常數,直至最終蛻化爲平凡問題。
應用舉例1:求解n個數組的和
int sum(int A[], int n){
return (n<1)?0:sum(A,n-1)+A[n-1];
}
應用舉例2:數組中元素倒置
//爲得到整個數組的倒置,可以先對換其首、末元素,然後遞歸地倒置除這兩個元素以外的部分。
void reverse(int *A, int lo, int hi)
{
if(lo < hi) {
swap(A[lo],A[hi]);
reverse(A, lo + 1, hi - 1);
}
}
二、分而治之
可以將其劃分爲多個(通常情況下爲兩個)子問題,兩個問題的規模大體相同。由子問題的解,通過遞歸得到原問題的解。
應用舉例1:求解n個數組的和
int sum(int A[], int low, int high){
return (low == high) ? A[low] : sum(A, low, (low + high) >> 1) + sum(A, ((low + high) >> 1) + 1, high);
}
應用舉例2:歸併排序 https://blog.csdn.net/cqfdcw/article/details/97891302
template<typename T>
void merge(vector<T> &arr, int L, int mid, int R) { //有序向量的逐層歸併
vector<int> temp; //臨時變量用來存放本次合併後的數組
int p1 = L;
int p2 = mid + 1;
// 比較左右兩部分的元素,哪個小,把那個元素填入temp中
while (p1 <= mid && p2 <= R) {
temp.push_back(arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]);
}
// 上面的循環退出後,把剩餘的元素依次填入到temp中,只有一個while會執行
while (p1 <= mid) {
temp.push_back(arr[p1++]);
}
while (p2 <= R) {
temp.push_back(arr[p2++]);
}
// 把最終的排序的結果複製給原數組
for (int i = 0; i < temp.size(); i++) {
arr[L + i] = temp[i];
}
}
template<typename T>
void mergeSort(vector<T> &arr , int L, int R) { //無序向量的逐層分解
if (L == R) { //只有一個元素的時候返回
return;
}
int mid = L + ((R - L) >> 1);
mergeSort(arr, L, mid);
mergeSort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
三、遞歸與迭代
遞歸:重複調用函數自身實現循環,A函數調用A函數。簡而言之,通過不斷地深層調用函數,直到函數有返回纔會逐層的返回,把一個大型複雜的問題層層轉化爲一個與原問題相似的規模較小的問題來求解(子問題須與原始問題爲同樣的事,且更爲簡單);遞歸是用棧機制實現的,每深入一層,都要佔去一塊棧數據區域。如,二叉樹的先序遍歷
//遞歸 階乘
public static int recursion(int num){
if (num <= 1){
return 1;
} else {
return num * recursion(num - 1);
}
}
迭代:利用變量的原值推出新值稱爲迭代,或着說迭代是函數內某段代碼實現循環,A函數調用B函數;每一次對過程的重複稱爲一次“迭代”,而每一次迭代得到的結果會作爲下一次迭代的初始值。重複執行一系列運算步驟,從前面的量依次求出後面的量的過程。
//迭代 階乘
public static int iteration(int num){
if (num <= 0){
return 1;
}
int result = 1;
for (int i = num; i >= 1; i--){
result *= i;
}
return result;
}
區別:遞歸滿足條件後,逐層返回,每層都計算完後才返回結果;迭代滿足條件後,通過計數器結束循環,直接返回計算結果。 遞歸與迭代相比較,效率低。
定義 | 優點 | 缺點 | |
遞歸 | 重複調用函數自身實現循環 |
a.用有限的循環語句實現無限集合; b.代碼易讀; c.大問題轉化成小問題,減少了代碼量。 |
a.遞歸不斷調用函數,浪費空間 b.容易造成堆棧溢出 |
迭代 |
利用變量的原值推出新值; 函數內某段代碼實現循環。 |
a.效率高,運行時間只隨循環的增加而增加; b.無額外開銷。 |
a.代碼難理解; b.代碼不如遞歸代碼簡潔; c.編寫複雜問題時,代碼邏輯不易想出 |
關係 |
a.遞歸中一定有迭代,但是迭代中不一定有遞歸;大部分可以相互轉換。 b.相對來說,能用迭代不用遞歸(因爲遞歸不斷調用函數,浪費空間,容易造成堆棧溢出) |
四、回溯
又稱爲試探法,可以理解爲嘗試不同岔路口,遇到錯誤原路返回到岔路口走另外一條路,當解決問題的每一步都有多種選擇時候,在某一步選擇了其中一個選項時,則進入此選項,然後繼續新的選擇。若選擇符合題目要求則此選擇是正確的;若此選擇不符合題目要求則此選擇是不正確的,此時就需要(遞歸)返回上一步,重新進行選擇。具體的例子參見如下博客,
https://blog.csdn.net/sinat_27908213/article/details/80599460
https://www.jianshu.com/p/dd3c3f3e84c0
https://www.cnblogs.com/vincently/p/4769937.html
五、貪心算法
在對問題進行求解時,總是做出當前看來是最好的選擇的一種方法,從而希望能夠導致結果是最好或者最優的算法(可能是局部最優解也可能是全局最優解)。是動態規劃的一種特例,能用貪心解決的問題,也可以用動態規劃解決。
參考https://www.jianshu.com/p/b613ae9d77ff
https://blog.csdn.net/T_I_A_N_/article/details/81837665
六、動態規劃
動態規劃的實質是分治思想和解決冗餘,是一種將問題實例分解爲更小的、相似的子問題,求解每個子問題僅一次,並將其結果保存在一個表中,以後用到時直接存取,避免計算重複的子問題,以解決最優化問題的算法策略。(可分爲多個相關子問題,子問題的解被重複使用)
https://blog.csdn.net/q547550831/article/details/51909156
https://blog.csdn.net/weixin_33762130/article/details/91388572