c++學習筆記(16) 遞迴
遞迴:能夠解決那些難以用簡單迴圈解決的問題
例如:八皇后問題,階乘
遞迴函式: 呼叫自身的函式
1.階乘:
0 !=1
1!=1
n!=n*(n-1)!
(n-1)!=(n-1)(n-2)!
假設函式factorial(n)為求n!;
如果呼叫到n=0的情況,函式直接返回結果,函式知道如何求解最簡單的情形,稱之為基本情況或者“停止條件”。如果引數大於0,函式將問題規約為求解(n-1)!的子問題。在遞迴中,子問題應該更簡單或者更小,但是子問題和原問題具有相同的性質,可以用一個不同的引數呼叫本函式來求解子問題。這稱之為遞迴呼叫。
#include <iostream> using namespace std; int factorial(int); int main(int argc, char *argv[]) { cout << "Enter a int number: "; int num; cin >> num; cout << num << "! = " << factorial(num) << endl; return 0; } int factorial(int n) { if(n==0) return 1; // 停止條件 else return n*factorial(n-1); // 遞迴呼叫 }
函式遞迴呼叫的執行過程:
例如: 斐波那契額數列:
定義函式fib(n),求數列前n項
fib(0)=0;
fib(1) = 1;
fib(2) = fib(1)+fib(0)
fib(3) = fib(2)+fib(1)
#include <iostream> using namespace std; int fib(int); int main(int argc, char *argv[]) { cout << "Enter a int number: n" << endl; int num; cin >> num; cout << "The fib is " << fib(num) << endl; return 0; } int fib(int n) // 求解斐波那契額數列 { if(n==0) return 0; // 停止條件 else if(n==1) return 1; else return fib(n-1)+fib(n-2); // 遞迴呼叫 }
例如:fib(4)的呼叫過程。 呼叫棧的變化情況
利用遞迴判斷迴文字串:
#include <iostream> #include <string> using namespace std; bool isPalindrome(const string& s) { // 基本情況分為兩種 1.首尾字元不相同,return false 2. 字串長度為1或者0 return true if(s[0]!=s[s.size()-1]) return false; else if(s.size()<=1) return true; else return isPalindrome(s.substr(1,s.size()-2)); // 獲得子串,遞迴判斷 } int main(int argc, char *argv[]) { cout << "Enter a string: " << endl; string ss; getline(cin, ss); if(isPalindrome(ss)) cout << "The string is Palindrome" << endl; else cout << "The string is not Palindrome" << endl; return 0; }
遞迴輔助函式
給類似原問題的問題定義一個遞迴函式.(這句話有點繞),得到原問題的求解方案,這種新的函式叫做遞迴輔助函式
對isPalindrome()函式的改進。
#include <iostream>
#include <string>
using namespace std;
// 為了避免每次遞迴都要建立新的字串,這裡傳入了引數low和high,指出上下界的範圍
// 過載isPalindrome()函式作為輔助函式
// 定義一個輔助函式接受額外的引數
// 遞迴方案中有字串和陣列,遞迴輔助函式是非常有用的
bool isPalindrome(const string& s, int low, int high)
{
// 基本情況分為兩種 1.首尾字元不相同,return false 2. 字串長度為1或者0 return true
if(s[low]!=s[high])
return false;
else if(low>=high)
return true;
else
return isPalindrome(s, low+1, high-1); // 獲得子串,遞迴判斷
}
// 函式過載
bool isPalindrome(const string& s)
{
return isPalindrome(s, 0, s.size()-1);
}
int main(int argc, char *argv[])
{
cout << "Enter a string: " << endl;
string ss;
getline(cin, ss);
if(isPalindrome(ss))
cout << "The string is Palindrome" << endl;
else
cout << "The string is not Palindrome" << endl;
return 0;
}
遞迴的方法實現選擇排序,對字串進行排序:
#include <iostream>
#include <string>
using namespace std;
// 定義一個輔助函式接受額外的引數
// 遞迴方案中有字串和陣列,遞迴輔助函式是非常有用的
void sort(string& s, int high) //遞迴輔助函式
{
if(high>0)
{
int max_index = 0;
char max_char = s[0];
for(int i=0; i<=high; i++)
{
if(s[i]>max_char)
{
max_char = s[i];
max_index = i;
}
}
s[max_index] = s[high];
s[high] = max_char;
sort(s, high-1); // 遞迴呼叫
}
}
void sort(string& s)
{
sort(s, s.size()-1);
}
int main(int argc, char *argv[])
{
cout << "Enter a string: " << endl;
string ss;
getline(cin, ss);
sort(ss);
cout << "The String After sorted: " << ss << endl;
return 0;
}
遞迴實現二分搜尋:
#include <iostream>
#include <string>
using namespace std;
// 定義一個輔助函式接受額外的引數
// 遞迴方案中有字串和陣列,遞迴輔助函式是非常有用的
int binary_search(const int list[], int key, int low, int high)
{
if(low>high) // 未找到
return -low-1;
int mid = (low+high)/2;
if(key<list[mid])
return binary_search(list, key, low, mid-1);
else if(key==list[mid])
return mid;
else
return binary_search(list, key, low+1, high);
}
int binary_search(const int list[], int key, int size)
{
return binary_search(list, key, 0, size-1);
}
int main(int argc, char *argv[])
{
int list[] = {2,3,4,5,6,7,8,9,12,14,16,18,19,20,34};
int i = binary_search(list, 4, 15);
int j = binary_search(list, 9, 15);
int k = binary_search(list, 20, 15);
int m = binary_search(list, 11, 15);
cout << "binary_search(list, 4, 15) return" << i << endl;
cout << "binary_search(list, 9, 15) return" << j << endl;
cout << "binary_search(list, 20, 15) return" << k << endl;
cout << "binary_search(list, 11, 15) return" << m << endl;
return 0;
}
執行結果:
漢諾塔問題:
漢諾塔問題的遞迴特點:
此問題的基本情況(停止條件)是:n==1時,將唯一一個盤子從A移動到B.
原問題分解為三個子問題:
1.將n-1個盤子從A->C, B作為輔助
2.將最下面的第n個盤子從A->B.
3.將n-1個盤子從C->B,A作為輔助
#include <iostream>
#include <string>
using namespace std;
void moveDisk(int n, char fromTower, char toTower, char auxTower)
{
if(n==1)
cout << "Move disk " << n << " from " << fromTower << " to " << toTower << endl;
else
{
moveDisk(n-1, fromTower, auxTower, toTower);
cout << "Move disk " << n << " from " << fromTower << " to " << toTower << endl;
moveDisk(n-1, auxTower, toTower, fromTower);
}
}
int main(int argc, char *argv[])
{
cout << "Enter number of disks: ";
int diskNumber;
cin >> diskNumber;
moveDisk(diskNumber, 'A', 'B', 'C');
return 0; //
}
執行結果:
八皇后問題:
#include <iostream>
#include <string>
using namespace std;
// 定義陣列queen[8]
// queen[i]=j 代表i行j列的皇后
const int NUMBER_OF_QUEENS = 8;
int queen[NUMBER_OF_QUEENS];
// 判斷解的合法性
bool isValid(int row, int column)
{
for(int i=1; i<=row; i++)
{
if(queen[row-i]==column || queen[row-i]==column-i || queen[row-i]==column+i)
return false;
}
return true;
}
//
bool search(int row)
{
if(row==NUMBER_OF_QUEENS) //停止條件
return true;
for(int column=0; column<NUMBER_OF_QUEENS; column++)
{
queen[row] = column;
if(isValid(row, column) && search(row+1))
return true;
}
return false;
}
// display the chessboard
void display()
{
cout << "\n-----------------\n";
for(int row=0; row<NUMBER_OF_QUEENS; row++)
{
for(int column=0; column<NUMBER_OF_QUEENS; column++)
{
cout << ((column==queen[row])?"|Q":"| ");
}
cout << "|\n-----------------\n";
}
}
int main(int argc, char *argv[])
{
search(0);
display();
return 0; //
}
遞迴有著嚴重的額外開銷,每當程式進行函式呼叫時,系統必須為所有的區域性變數和引數分配記憶體空間,需要消耗相當大的記憶體,可能造成棧溢位(stack overflow)的錯誤。
尾遞迴
尾遞迴可以有效地減少堆疊空間。
注:(書本)
尾遞迴是可取的,因為當最後一次遞迴呼叫結束時,函式就結束了,所以沒有必要儲存在堆疊中的中間呼叫,有些編譯器可以優化尾遞迴來減少堆疊空間。
一個非為尾遞迴的函式可以通過使用輔助引數轉換為一個尾遞迴函式。