1. 程式人生 > 實用技巧 >最基礎的“窮竭搜尋”

最基礎的“窮竭搜尋”

窮竭搜尋:將所有可能羅列出來

  常用方法:深度優先搜尋,廣度優先搜尋

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,所以以後沒有必要繼續搜尋。即為需要剪枝