zoukankan      html  css  js  c++  java
  • 【算法总结】“穷竭”搜索

    一、深度优先搜索

    POJ No.2386 Lake Counting

    Description
    
    Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors. 
    
    Given a diagram of Farmer John's field, determine how many ponds he has.
    Input
    
    * Line 1: Two space-separated integers: N and M 
    
    * Lines 2..N+1: M characters per line representing one row of Farmer John's field. Each character is either 'W' or '.'. The characters do not have spaces between them.
    Output
    
    * Line 1: The number of ponds in Farmer John's field.
    Sample Input
    
    10 12
    W........WW.
    .WWW.....WWW
    ....WW...WW.
    .........WW.
    .........W..
    ..W......W..
    .W.W.....WW.
    W.W.W.....W.
    .W.W......W.
    ..W.......W.
    Sample Output
    
    3
    Hint
    
    OUTPUT DETAILS: 
    
    There are three ponds: one in the upper left, one in the lower left,and one along the right side.
    View Code

      从任意'W'开始,不断将其八连通区域内的'W'换成'.'。则一次DFS后与起始点'W'相连接的所有'W'都被替换为'.',说明此次DFS找到了一个水洼。当图中不再有‘W'时,说明所有水洼都被找到。DFS的次数就等于水洼的个数。

      需要注意的是,scanf("%c", &ch)会将空格、换行符等空字符也作为合法输入,所以需要用 getchar()跳过空字符。

     1 #include <iostream>
     2 #include <vector>
     3 #include <limits.h>
     4 #include <algorithm>
     5 #include <math.h>
     6 using namespace std;
     7 
     8 const int MAX = 100 + 5;
     9 int N, M;
    10 char field[MAX][MAX];
    11 
    12 void dfs(int x, int y) {
    13     field[x][y] = '.';
    14     // 遍历八连通区域
    15     for (int dx = -1; dx <= 1; ++dx) { 
    16         for (int dy = -1; dy <= 1; ++dy) {
    17             int nx = dx + x, ny = dy + y;
    18             if (0 <= nx && nx < N && 0 <= ny && ny < M && field[nx][ny] == 'W')
    19                 dfs(nx, ny);
    20         }
    21     }
    22 }
    23 
    24 int main() {
    25     while (scanf("%d%d", &N, &M) != EOF) {
    26         for (int i = 0; i < N; ++i) {
    27             getchar(); // 为了跳过换行符
    28             for (int j = 0; j < M; ++j) {
    29                 // scanf("%c", &ch) 会将空格和换行符等空字符作为合法输入
    30                 scanf("%c", &field[i][j]);
    31             }
    32         }
    33         int num = 0;
    34         for (int i = 0; i < N; ++i) {
    35             for (int j = 0; j < M; ++j) {
    36                 if (field[i][j] == 'W') {
    37                     dfs(i, j);
    38                     ++num;
    39                 }
    40             }
    41         }
    42 
    43         printf("%d
    ", num);
    44     }
    45     system("pause");
    46     return 0;
    47 }
    View Code

    二、宽度优先搜索

      宽度优先搜索总是先搜索距离初始状态近的状态,开始状态->只需一次转移就可以到达的所有状态->只需2次转移就可以到达的所有状态->。。。对于同一个状态,BFS只经过一次。复杂度为O(状态数$ imes$转移的方式)。

    迷宫的最短路径

      给定一个大小为N*M的迷宫,迷宫由通道和墙壁组成,每一步移动可以向邻接的上下左右四格的通道移动。。试求出起点到终点所需的最小步数。(本题假定从起点一定可以移动到终点。'#', ’.’,'S', 'G' 分别表示墙壁、通道、起点和终点)

      宽度优先搜索按照据开始状态由近及远的顺序进行搜索,因此可以很容易地来求最短路径、最少操作之类问题的答案。此题中状态仅仅是目前位置的坐标,因此可以构造为pair或者编码为int来表达状态。当状态更加复杂时,需要封装成一个类来表示状态。转移的方向为4个,状态数与迷宫的大小相等。那么复杂度为$O(4 imes N imes M)=O(N imes M)$。

      宽度优先搜索与深度优先搜索一样,都会生成所有能够遍历到的状态,因此需要对所有状态进行处理时使用宽度优先搜索也是可以的。

      宽度优先搜索中,只要将已经访问过的状态用标记管理起来,就可以很好地做到由近及远的搜索。这个问题中由于要求最短距离,不妨用d[N][M]数组把最短距离保存起来。初始时用充分大的常数INF来初始化它,这样尚未到达的位置就是INF,也就同时起到了标记的作用。虽然到达终点时就会停止搜索,可如果继续下去直到队列为空的话,就可以计算出到各个位置的最短距离。此外,如果搜索到最后,d依然为INF的话,便可得知这个位置就是无法从起点到达的位置。

     1 #include <iostream>
     2 #include <vector>
     3 #include <limits.h>
     4 #include <algorithm>
     5 #include <math.h>
     6 #include <queue>  
     7 using namespace std;
     8 
     9 const int MAX_N = 100 + 5;
    10 const int MAX_M = 100 + 5;
    11 const int INF = 0x3f3f3f3f; // 常用用法,防止溢出
    12 typedef pair<int, int> P;
    13 
    14 char maze[MAX_N][MAX_M]; // 迷宫数组
    15 int N, M;
    16 int sx, sy; //起点的位置  
    17 int gx, gy; //终点的位置  
    18 
    19 int d[MAX_N][MAX_M];//到各个位置的最短距离的数组  
    20 int dx[4] = { 1, 0, -1, 0 }, dy[4] = { 0, 1, 0, -1 }; // 4个方向
    21 
    22 void bfs() { // 状态转移次数即距离
    23     queue<P> que;
    24     for (int i = 0; i < N; i++)
    25         for (int j = 0; j < M; j++)
    26             d[i][j] = INF;  //初始化起始点到所有点的距离为INF  
    27     // 将起始点假如队列,并将这一地点的距离设置为0
    28     que.push(P(sx, sy));
    29     d[sx][sy] = 0;
    30 
    31     //不断循环直到队列的长度为0
    32     while (que.size()) {
    33         P p = que.front(); que.pop();
    34         // 已经到达终点则结束搜索
    35         if (p.first == gx && p.second == gy)
    36             break;
    37 
    38         for (int i = 0; i < 4; i++) {
    39             // 移动后的位置坐标
    40             int nx = p.first + dx[i];
    41             int ny = p.second + dy[i];
    42             //判断是否可移动到该位置以及该位置是否已经被访问过  
    43             if (0 <= nx && nx < N && 0 <= ny && ny < M  // 坐标合法,未溢出
    44                 && maze[nx][ny] != '#' // 该位置不是障碍
    45                 && d[nx][ny] == INF) {//已经被访问过的话不用考虑,因为距离在队列中是递增的 
    46 
    47                 que.push(P(nx, ny));    //可以移动则加入队列,并且该位置的距离为到p的距离+1  
    48                 d[nx][ny] = d[p.first][p.second] + 1;
    49             }
    50         }
    51     }
    52 }
    53 
    54 int main()
    55 {
    56     scanf("%d%d", &N, &M);
    57     for (int i = 0; i < N; ++i) {
    58         getchar();
    59         for (int j = 0; j < M; ++j) {
    60             scanf("%c", &maze[i][j]);
    61             if (maze[i][j] == 'S')
    62             {
    63                 sx = i; sy = j;
    64             }
    65             if (maze[i][j] == 'G')
    66             {
    67                 gx = i; gy = j;
    68             }
    69         }
    70     }
    71     bfs();
    72     printf("%d
    ", d[gx][gy]);
    73 
    74     return 0;
    75 }
    View Code

    三、特殊状态枚举   

      虽然生成可行解空间多数采用深度优先搜索,但在状态空间比较特殊时其实可以很简短地实现。比如,C++的标准库中提供了next_permutation这一函数,可以把n个元素共n!种不同的排列生成出来。又或者,通过使用位运算,可以枚举从n个元素中取出k个的共$ extrm{C}_{n}^{k}$种状态或是某个集合中的全部子集等。

    四、剪枝

      深度优先搜索时,有时早已很明确地知道从当前状态无论如何转移都不会存在解。这种情况下,不再继续搜索而是直接跳过,这一方法被称作剪枝。

  • 相关阅读:
    与(&,&&)和或(|,||)的区别
    vue笔记(更新中)
    echarts实现心脏图的滚动三种实现方法
    生成四则运算
    软件工程第四次作业
    软件工程第三次作业
    软件工程第二次作业
    软件工程第一次作业
    前端优化
    关于事件监听
  • 原文地址:https://www.cnblogs.com/Atanisi/p/8711322.html
Copyright © 2011-2022 走看看