简单搜索初步理解

版权声明:转载请注明 https://blog.csdn.net/li13168690086/article/details/81901415

  写在前面:搜索问题,最基础的就是深搜和广搜,也就是DFS和BFS,本文只讨论范围就仅限此两种方法。

 概念

 1.DFS

  深度优先搜索,字面意思是纵向,往下搜索。引用挑战程序设计竞赛里的描述:它从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终的解。

  请观察下图

图1
图1

  上图是用深度搜索,遍历所有点的演示。实心表示遇到的新点,圆圈内数字表示每个点被遍历的顺序,橙色表示回退前一步。根据题目的不同,在遍历所有点的过程中,有条件的进行匹配。

2.BFS

  广度优先搜索,(BFS, Breadth-First Search)是一种发散式的搜索,横向搜索。引用挑战程序设计竞赛里的描述:它与深度优先搜索类似,从某个状态出发探索所有可以到达的状态。与DFS不同在于搜索的顺序,BFS总是先搜索距离初始状态近的状态。也就是说,它是按照开始状态--->只需1次转移就可以到达的所有状态--->只需2次转移就可以到达的所有状态--->......这样的顺序进行搜索。对于同一个状态,宽度优先搜索只经过一次,因此复杂度为O(状态数*转移的方式)

  请观察下图

图2
图2

从图2可以知道,BFS进行遍历时,是从上至下一层一层进行搜索的。深色表示遇到遍历的新点,橙色表示对此点可以到达的其他状态进行遍历。明确看出,BFS对于同一个状态只经过了一次。

实现

  对于DFS,一般采用递归方式,隐式地利用栈进行计算。而BFS则采用队列的方式,先进先出,先进先遍历,将初始状态的添加到队列,此后不断地在队列最前端取出状态,把从改状态可以转移到的状态中尚未访问过的部分加入队列,如此反复,直到队列被取空或找到了问题的解[摘自挑战程序设计竞赛]。

  DFS耗费空间比BFS小,因为DFS是一种深度的搜索,复杂度与空间深度成正比;而BFS是通过遍历每个状态进行搜索,复杂度与状态数有关。可想而知状态数必定大过递归的深度,所以DFS比BFS更加节省空间。

  但对于寻找最短路的问题,BFS不会反复进过同一个状态,而DFS则会如此,那么在这类问题中,BFS比DFS更适合解决。

  BFS不一定总比DFS快,这是根据具体问题具体分析的。下面我们将引用两道例题,来更详细对比两者的不同。

例题1 棋盘问题POJ--1321 

描述

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

Input

输入含有多组测试数据。 
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 
当为-1 -1时表示输入结束。 
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。 

Output

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。

Sample Input

2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

Sample Output

2
1

题目分析

  题目很明确,就是寻找有多少种可行的摆放方式,即路径。那么这一条路有什么条件限制呢?1、一定要将所有棋子落下(棋子数保证不会比格子数大) ;2、同一行或者同一列只能有一个棋子。

解题思路

  寻找路径,但不是最短的,只要符合条件的路即可,所以必定需要不断遍历整个棋盘,以寻找可行的摆放方式。那么我们可以采用DFS或BFS,但在这我们用DFS,因为可以让思路更加明确。在进行DFS的过程中,加入题目条件即可。1、退出条件:当所有棋子落下或棋盘无法继续遍历;2、棋子只能落在可以放的格子,以及这个格子所在行或列没有其他棋子占有。

  其中对于条件2,需要再进行思考,如何判断这个格子所在行或列是否被其他棋子占有呢?首先解决对行的观察,我们可以逐行深搜,先对一行的所有点遍历,找到了可以落的格子,就再进行下一行的DFS,如果无法找到路径就返回上一行继续搜索,这样就保证每行只有一个点。然后对于列,我们则需要设置标记,给每列设置一个标记,如果此列被占就更改标记,以后就只要观察标记就好了。

AC代码

#include <iostream>
#include <string.h>
using namespace std;

int n,k;
int maxway;     //总方案数
int dir[9][9];      //用矩阵储存棋盘,dir[0]是用作标记存储
int maxroad;

void dfs(int k,int x){
    if(x > n+1)     //如果超过棋盘边线,则退出
        return;
    if(k <= 0){     //如果将所有棋子落下,则退出,答案+1
        maxway++;
        return;
    }
    for(int j = x; j <= n; j++){    //遍历第X行的所有格子(因为行是逐层遍历,所以不需要给格子所占行进行标记)
        for(int i = 1; i <= n; i++){
            if(dir[0][i] == 0 && dir[j][i] == 1){   //如果这个格子所占的列没被占,并且有空位,则放棋子
                dir[0][i] = 1;      //给所占列进行标记
                dfs(k-1, j+1);      //继续遍历下一行
                dir[0][i] = 0;      //如果没法放下所有棋子,则回退,所占列的标记清零
            }
        }
    }
    return;
}

int main(){
    while(1){
        cin >> n >> k;
        if(n == -1 && k == -1)
            break;
        memset(dir, 0, sizeof(dir));
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                char k;
                cin >> k;
                if(k == '#')
                    dir[i][j] = 1;
                else
                    dir[i][j] = 0;
            }
        }
        maxway = 0;
        dfs(k,1);
        cout << maxway << endl;
    }
    return 0;
}

例题2(POJ--3278)

描述

Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.

* Walking: FJ can move from any point X to the points - 1 or + 1 in a single minute
* Teleporting: FJ can move from any point X to the point 2 × X in a single minute.

If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?

Input

Line 1: Two space-separated integers: N and K

Output

Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.

Sample Input

5 17

Sample Output

4

Hint

The fastest way for Farmer John to reach the fugitive cow is to move along the following path: 5-10-9-18-17, which takes 4 minutes.

题目分析

  问题描述是讲有一个农夫,要去抓住他的牛,规定两者都处于一条直线上。农夫在位置N,牛在位置K。农夫只能左右移动,共有两种移动方式:1、向左走一步或向右走一步;2、向右移动2倍的距离(比如:农夫在5,那他可以移动到10,5*2=10)

  然后规定了,这条直线有两个端点0和100000,所以农夫和牛的位置范围也在这条直线内。最后需要我们求出农夫抓到牛,最短需要多少时间,移动一次算1min,也就是最少的步数。

解题思路

  虽然农夫的移动是在一条直线上,但因为移动的方式不同,所在的位置也不同,所以其实抽象来看是一个三叉树。在每个位置都有三种移动方式:向左一步、向右一步、向右跨2倍距离。最后退出条件,就是农夫的位置和牛的位置一样 。最后题目要求最短的时间,即最短的步数。那么就非常符合BFS的特点。

AC代码

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 100000;
int N,K;
int visited[MAXN+10];   //标记数组

struct Step{
    int x;
    int steps;
    Step(int xx, int s): x(xx),steps(s){}
};

void BFS(queue<Step> q){
    while(!q.empty()){      //若队列被取空,则退出
        Step s = q.front();     //不断取最前端的状态
        if(s.x == K){       //如果此时的位置是牛的位置,则退出
            cout << s.steps << endl;    //输出步数
            break;
        }
        else{
            if(s.x+1 >= 0 && !visited[s.x+1]){      //如果往右走一步没越界,并且这个点没有走过
                q.push(Step(s.x+1,s.steps+1));      //将此状态装入队列
                visited[s.x+1] = 1;     //更改点的标记
            }
            if(s.x-1 >= 0 && !visited[s.x-1]){      //如果往左走一步没越界,并且这个点没走过
                q.push(Step(s.x-1,s.steps+1));
                visited[s.x-1] = 1;
            }
            if(s.x*2 <= MAXN && !visited[s.x*2]){   //如果往右跨2倍的距离没越界,并且这个点没有走过
                q.push(Step(s.x*2,s.steps+1));
                visited[s.x*2] = 1;
            }
            q.pop();    //将此状态弹出队列
        }
    }
}

int main(){
    cin >> N >> K;
    queue<Step> q;
    memset(visited, 0, sizeof(visited));
    q.push(Step(N,0));  //从点N出发,步数为0
    visited[N] = 1;     //出发点标记一下
    BFS(q);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/li13168690086/article/details/81901415