遞歸和分治思想
如果可以使用迭代,盡量別使用遞歸。由編譯原理可以知道,每次自調用的時候,計算機都需要保存在調用,浪費時間空間。當然,迭代是當我們知道循環次數的時候。而當我們不知道循環次數,比如說對於文件夾和文件進行遍歷,不知道深度的情況下,我們就需要遞歸來實現。
顯然,遞歸是先解決小的問題,這種思想是分治思想。根據具體需求,來決定是否使用遞歸。
遞歸要注意:
- 結構是選擇結構,而迭代是循環結構
- 必須有基線條件和遞歸條件,防止出現死循環
- 如果知道循環次數的話,盡量使用遞歸
- 對於某些編程式函數,有對於尾遞歸的迭代優化
- 遞歸邏輯更容易理解
一些實例
逆序輸出字符串
#include<iostream>
using namespace std;
void print(){
char a;
cin>>a;
if(a!='#') print(); // 不是停止符,先自調用
if(a!='#') cout<<a; //在回來的時候,打印自己的字符
}
int main(){
print();
return 0;
}
查找數組元祖是否存在
很多時候我們需要查找一個數組中是否有一個元素。如果使用迭代,肯定十分簡單,時間復雜度為O(n)。
此時,如果使用分而治之的思想,我們可以使用二分法來進行查找。不論多大的數據,時間復雜度顯著降低為O(log_2 n)。也就是說一個大小為123456789的數組,使用迭代,我們需要123456789個時間單位。但是二分法只需要27次。
實現思路:
- 首先轉化的思想對數組進行排序。如果不排序,那么low和high就沒有意義了。
- 再用迭代進行二分
#include<iostream>
#include<algorithm>
using namespace std;
const int SIZE = 5;
const int NONE = -1;
//二分查找並且返回element的位置,沒查找到則返回NONE
template<class T>
int BinFind(T *arr,int low,int high,T elem){
int mid;
if(low>high)
return NONE;
else{
mid = (low+high)/2;
if(arr[mid] == elem)
return mid;
else if(elem>arr[mid])
return BinFind(arr,mid+1,high,elem);
else
return BinFind(arr,low,mid-1,elem);
}
}
int main(){
int *arr = new int [SIZE];
cout<<"請輸入"<<SIZE<<"個數據:";
for(int i=0;i<SIZE;++i)
cin>>arr[i];
sort(arr,arr+SIZE);
int elem;
cout<<"輸入您要查找的數據:"<<endl;
cin>>elem;
int index = BinFind(arr,0,SIZE-1,elem);
if (index+1)
cout<<"含有這個數據\n";
else
cout<<"不含有這個數據";
return 0;
}
漢諾塔問題
首先我們假設需要移動64層,那么思路如下(附截圖):
此時,需要解決兩個問題(附截圖):
一直這樣類推,知道最后從begin(開始柱子)->end(目標柱子)。
按照第一張截圖,我們可以寫出來函數里面else的遞歸部分。並且,每次輸出的時候,就對應着思路里面的移動(而不是分治),此時step步數需要加+。
#include<iostream>
#include<algorithm>
using namespace std;
void Hanoi(int num,char begin,char by,char end,int &step){
if(num==1){
cout<<begin<<"-->"<<end<<endl;
++step;
}
else{
Hanoi(num-1,begin,end,by,step);
cout<<begin<<"-->"<<end<<endl;
++step;
Hanoi(num-1,by,begin,end,step);
}
}
int main(){
int step = 0;
int num;
cout<<"漢諾塔層數是: ";
cin>>num;
Hanoi(num,'X','Y','Z',step);
cout<<"一共有:"<<step<<"步數"<<endl;
return 0;
}
八皇后問題
在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。正規的方法是遞歸,如果不考慮效率,這里采用遞歸實現。假設從第一行開始,每一行都找到符合條件的一個位置,而條件就是新的一行的新位置符合要求,以此類推,就可以寫出來遞歸函數。
#include<iostream>
using namespace std;
const int Q_NUM = 8;
int queens[Q_NUM][Q_NUM] = {0};
int RESULT = 0;
void print(){
for(int i=0;i<Q_NUM;++i){
for (int j=0;j<Q_NUM;++j)
cout<<queens[i][j]<<" ";
cout<<endl;
}
cout<<endl<<endl;
}
bool IfQueen(int row,int col){
if(row==0){
//當第一行時候,隨便擺放
queens[row][col] = 1;
return true;
}
/**************其他時候,需要考慮上面的同一列、左上角斜線、右上角斜線,以下分別實現*****/
for(int i=0;i<row;++i)
if(queens[i][col]==1)
return false;
for (int i=row-1,j = col-1;i>=0 && j>=0;--i,--j)
if(queens[i][j]==1)
return false;
for(int i=row-1,j=col+1;i>=0 && j<Q_NUM;--i,++j)
if(queens[i][j]==1)
return false;
/******當所有情況都滿足********/
queens[row][col] = 1;
return true;
}
void Queen(int row){
if(row==Q_NUM){ //注意row是從0開始到Q_NUM-1結束。這樣當row==Q_NUM時,已經排完所有情況
++RESULT; //這樣當row==Q_NUM時,已經排完所有情況,進行輸出就可以了。
print();
return ;
}
for(int i=0;i<Q_NUM;++i){ //i代表列數
if(IfQueen(row,i)) //如果row行i列可以放得話,判斷下一行
Queen(row+1);
queens[row][i] = 0; //重置為0,防止下次結果干擾
}
}
int main(){
Queen(0);
cout<<"一共"<<RESULT<<"種擺法\n";
return 0;
}
更多:
毫無疑問,遞歸以及分治思想還有很多用法:斐波那契數列、快速排序、文件查找、字典樹的建立等等。理論上遞歸可以解決任何問題,而作為我們只需要提供思路,其他的交給計算機解決。所以聽人說過計算機最適合解決遞歸問題。但是,有利有弊,遞歸同樣會消耗更多的內存。在初步實現階段,將大問題分而治之,分裝成遞歸函數,還是邏輯代碼化的最佳表達。
歡迎進一步交流本博文相關內容:
博客園地址 : http://www.cnblogs.com/AsuraDong/
CSDN地址 : http://blog.csdn.net/asuradong
也可以致信進行交流 : xiaochiyijiu@163.com
歡迎轉載 , 但請指明出處 : )