最基礎的“窮竭搜尋”
窮竭搜尋:將所有可能羅列出來
常用方法:深度優先搜尋,廣度優先搜尋
1.1遞迴函式
int fact ( int n ) { if ( n == 0) return 1; return n * fact ( n -1 ); }
int fib (int n) { if ( n <= 1) return n; return fib( n - 1 ) + fib (n - 2); }
1.2 棧
棧(Stack)後進先出支援push(棧頂放入資料),pop(棧頂取出資料),top(檢視棧頂元素)
#include<iostream> #include<stack> //入棧 出棧 取棧頂 using namespace std; int main() { stack<int> s; s.push(1); s.push(2); s.push(3); cout << s.top()<<endl; s.pop(); cout << s.top()<<endl; s.pop(); cout << s.top()<<endl; s.pop(); // cout << s.top()<<endl; 報錯空棧return 0; }
執行結果
1.3 佇列
佇列(Queue)與棧一樣支援push,pop操作,但是佇列採取先進先出,front 操作查詢隊首元素
#include<iostream> #include<queue> //佇列有隊頭 入隊 出隊 取隊頭 using namespace std; int main() { queue<int> que ; que.push(1); que.push(2); que.push(3); cout << que.front() <<endl; que.push(4); cout << que.front() <<endl; que.pop(); cout << que.front() <<endl; que.pop(); cout << que.front() <<endl; que.pop(); return 0; }
執行結果
1.4 深度優先搜尋
深度優先搜尋(DFS):一條路走到底,沒有結果回到上一條路繼續走。
例題1.4-1
部分和問題
給定整數a1 、a2、···、an,判斷是否可以從中選出若干數,使他們的和恰好為k。
輸入
n = 4
a = { 1, 2, 4, 7}
k = 13
輸出
Yes (13 = 2 + 4 + 7)
輸入
n = 4
a = { 1, 2, 4, 7}
k = 15
輸出
No
複雜度O(2n)
1 #include<iostream> 2 #include<stdio.h> 3 #define Max_N 50 4 5 using namespace std; 6 7 //輸入 8 //int a[Max_N] 9 //int n = 4, k = 10; 10 11 //輸入太麻煩了,還是直接點吧 哈哈 12 int a[Max_N] = { 1, 2, 4, 7}; 13 int n = 4, k = 10; 14 15 //已經從前i項得到了和sum,然後對於i項之後的進行分支 16 bool dfs(int i, int sum ) 17 { 18 //如果前n項都計算過了,則返回sum是否和k相等 19 if( i == n) return sum == k; 20 21 //不加上a[i]的情況 22 if(dfs( i + 1, sum)) return true; 23 24 //加上a[i]的情況 25 if(dfs( i + 1, sum + a[i])) return true; 26 27 //無論是否加上a[i]都不能湊成k就返回false 28 return false; 29 } 30 31 void solve() 32 { 33 //起初i = 0 即第一層,此處便於使用陣列a[i],起始和為0 34 if(dfs( 0, 0)) printf("Yes\n"); 35 else printf("No\n"); 36 } 37 38 int main() 39 { 40 solve(); 41 return 0; 42 }DFS深度優先搜尋
例題1.4-2
Lake Counting
有一個大小為N×M的園子,雨後積起了水。八連通的積水被認為是連線在一起的。請求出園子裡總共有多少水窪?
輸入
N = 10, M = 12
園子如下圖( ’ W ‘ 表示積水, ’ . ‘ 表示沒有積水)
W . . . . . . . . W W .
. W W W . . . . . W W W
. . . . W W . . . W W .
. . . . . . . . . W W .
. . . . . . . . . W . .
. . W . . . . . . W . .
. W . W . . . . . W W .
W . W . W . . . . . W .
. W . W . . . . . . W .
. . W . . . . . . . W .
輸出
3
圖解:
按照數字順序將有水W處改為陸地 .直到周圍無水W 返回開始處向下處尋找
W . . . . . . . . W W . 1. . . . . . . . W W .
. W W W . . . . . W W W . 2 3 4 . . . . . W W W
. . . . W W . . . W W . . . . . 5 6 . . . W W .
. . . . . . . . . W W . . . . . . . . . . W W .
. . . . . . . . . W . . . . . . . . . . . W . .
. . W . . . . . . W . . . . W . . . . . . W . .
. W . W . . . . . W W . . W . W . . . . . W W .
W . W . W . . . . . W . W . W . W . . . . . W .
. W . W . . . . . . W . . W . W . . . . . . W .
. . W . . . . . . . W . . . W . . . . . . . W .
複雜度O(8 × N× M) =O(N× M)
1 #include<iostream> 2 3 using namespace std; 4 5 ////輸入 6 //int N, M; 7 //char field[MAX_N][MAX_M + 1]; //園子 8 9 //0< x ≤N,0< y ≤M 10 int N = 10, M = 12; 11 char field[10][12]; 12 13 14 //現在位置(x,y) 15 void dfs(int x, int y) 16 { 17 //將現在所在位置替換為 . (無積水) 18 field[x][y] = '.'; 19 20 //迴圈遍歷移動的8個方向 21 for(int dx = -1; dx <= 1; dx++) 22 { 23 for(int dy = -1; dy <= 1; dy ++) 24 { 25 //向x方向移動dx,向y方向移動dy,移動的結果為(nx,ny) 26 int nx = x + dx, ny = y + dy; 27 //判斷(nx, ny)是不是在園子內,以及是否有積水 28 if(0 <= nx && nx < N && 0 <= ny && ny < M && field[nx][ny] == 'W') 29 { 30 dfs(nx,ny); 31 } 32 } 33 } 34 return; 35 } 36 37 void solve() 38 { 39 int res = 0; 40 for(int i = 0; i < N; i++) 41 { 42 for(int j = 0; j < M; j++) 43 { 44 if (field[i][j] == 'W') 45 { 46 //從有W的地方開始dfs 47 dfs(i, j); 48 res++; 49 } 50 } 51 52 } 53 cout << res; 54 } 55 56 int main() 57 { 58 for(int i=0; i< N; i++) 59 { 60 for(int j = 0; j < M; j++) 61 { 62 cin >> field[i][j]; 63 } 64 } 65 solve(); 66 }DFS深度優先搜尋2
1.5 寬度優先搜尋
寬度優先搜尋(DFS):找遍周圍,沒有結果去下一條路繼續找。複雜度O(狀態數×轉移的方式)
例題1.5-1
迷宮的最短路徑
給定一個大小為N×M的迷宮。迷宮由通道和牆壁組成,每一步可以向鄰接的上下左右四格的通道移動。請求出從起點到終點所需要的最小步數。請注意,本題假定從起點一定可以移動到終點。
輸入
N=10, M=10 (迷宮如下圖所示。 ’#‘ , '.' , 'S' , 'G' 分別表示牆壁、通道、起點、終點)
# S # # # # # # . #
. . . . . . # . . #
. # . # # . # # . #
. # . . . . . . . .
# # . # # . # # # #
. . . . # . . . . #
. # # # # # # # . #
. . . . # . . . . .
. # # # # . # # # .
. . . . # . . . G #
輸出
22
圖解佇列變化:
# S # # # # # # . #
3 1 2 4 7 . # . . #
6 # 5 # # . # # . #
9 # 8 . . . . . . .
# # . # # . # # # #
. . . . # . . . . #
. # # # # # # # . #
. . . . # . . . . .
. # # # # . # # # .
. . . . # . . . G #
以此類推
1 #include<iostream> 2 #include<queue> 3 #include<stdio.h> 4 using namespace std; 5 6 const int INF = 100000000; 7 8 //使用pair表示狀態時,使用typedef會更加方便一些 9 typedef pair<int,int> P; 10 11 //輸入 12 char maze[100][100]; //表示迷宮的字串的陣列 13 int N = 10,M = 10; 14 int sx = 0, sy = 1; //起點座標 15 int gx = 9, gy = 8; //終點座標 16 17 int d[100][100]; //到各個位置的最短距離的陣列 18 19 //4個方向移動的向量 20 int dx[4] = {1, 0 , -1, 0}, dy[4] = {0, 1, 0, -1}; 21 22 //求從(sx, sy) 到 (gx, gy)的最短距離 23 //如果無法到達,則是INF 24 int bfs(){ 25 queue<P> que; 26 //把所有的位置都初始化為INF 27 for(int i = 0; i < N; i++){ 28 for(int j = 0; j < M; j++){ 29 d[i][j] = INF; 30 } 31 } 32 //將起點加入佇列,並把這一地點的距離設定為0 33 que.push(P(sx,sy)); 34 d[sx][sy] = 0; 35 36 //不斷迴圈直到佇列的長度為0; 37 while(que.size()){ 38 //從佇列的最前端取出元素 39 P p = que.front(); 40 que.pop(); 41 //如果取出的狀態已經是終點,則結束搜尋 42 if(p.first == gx && p.second ==gy) { 43 break; 44 } 45 //四個方向的迴圈 46 for(int i=0; i<4;i++){ 47 //移動之後的位置記為(nx,ny) 48 int nx = p.first + dx[i] , ny = p.second + dy[i]; 49 50 //判斷是否可以移動以及是否已經訪問過(d[nx][ny]!=INF即為已經訪問過) 51 if(0 <= nx && nx < N && 0 <=ny && ny <M && maze[nx][ny] != '#' && d[nx][ny] ==INF){ 52 //可以移動的話,則加入到佇列,並且到該位置的距離確定為到p的距離+1 53 que.push(P(nx, ny)); 54 d[nx][ny] = d[p.first][p.second] +1; 55 } 56 } 57 } 58 return d[gx][gy]; 59 60 } 61 62 void solve(){ 63 int res = bfs(); 64 printf("%d\n",res); 65 } 66 67 68 69 int main() 70 { 71 for(int i=0; i< N; i++) 72 { 73 for(int j = 0; j < M; j++) 74 { 75 cin >> maze[i][j]; 76 } 77 } 78 solve(); 79 }BFS迷宮最短路徑
1.6特殊狀態的列舉
C++ 提供的next_permutation函式教程 好東西就是要分享嘛
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 5 bool used[MAX_N]; 6 int perm[MAX_N]; 7 8 //生成{0,1,2,3,4, ... ,n-1}的n!種排序 9 10 void permutation1(int pos, int n){ 11 if(pos == n){ 12 /* 13 * 這個編寫需要對perm進行的操作 14 */ 15 return; 16 } 17 18 //針對perm的第pos個位置,究竟使用0~n-1中的哪一個進行迴圈 19 for(int i=0; i < n ; i++){ 20 if(!used[i]){ 21 perm[pos] = i; 22 //i 已經被使用了,所以把這個標誌設定為true 23 used[i] = true; 24 permutation1(pos + 1, n); 25 //返回之後把標誌復位 26 used[i] = false; 27 } 28 } 29 return; 30 } 31 32 33 //即使有重複的元素也會生成所有的排列 34 //next_permutation是按照字典序生成下一個排序的 35 int perm2[MAX_N]; 36 37 void permutation2(int n){ 38 for (int i = 0 ;i < n ;i++ ){ 39 perm2[i] = i; 40 } 41 do{ 42 /* 43 *這裡編寫對perm2進行的操作 44 */ 45 }while(next_permutation(perm2,perm2 + n)); 46 //所有排序都生成後,next_permutation會返回false 47 return; 48 } 49 50 51 int main() 52 { 53 54 }特殊狀態的列舉
1.7剪枝
如果sum已經超過了預期的k,以後無論選擇那些數都不可能讓sum = k,所以以後沒有必要繼續搜尋。即為需要剪枝