深度优先搜索(DFS)
深度优先搜索,重点就在于“深度”一词,不碰到死胡同就不回头。
深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。
整个过程和出栈入栈的过程极为相似,可以使用栈来实现。
首先先对问题进行分析,得到岔路口和死胡同;再定义一个栈,以深度为关键词访问这些岔道口和死胡同,并将它们入栈;而当离开这些岔道口和死胡同时,将它们出栈。
使用递归可以很好地实现深度优先搜索。只能说递归是深度优先搜索的一种实现方式。在使用递归时,系统会调用一个叫系统栈的东西来存放递归中每一层的状态,因此使用递归来实现DFS的本质其实还是栈。
注意,当DFS复杂度过高,或数据极端的情况,必要的时候需用到剪枝。
关于dfs参数问题,什么在变化,就把什么设置成参数。
DFS模板
void dfs() { //参数用来表示状态
if(到达终点状态) {
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(扩展方式) {
if(扩展方式所达到状态合法) {
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
DFS 几道题目 推荐做一下
实例1(全排列加素数):
已知 n 个整数b1,b2,…,bn
以及一个整数 k(k<n)。
从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。
例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=22 3+7+19=29 7+12+19=38 3+12+19=34。
现在,要求你计算出和为素数共有多少种,并给出每种组合的和。
例如上例,只有一种的和为素数:3+7+19=29。
输入
第一行两个整数:n , k (1<=n<=20,k<n)
第二行n个整数:x1,x2,…,xn (1<=xi<=5000000)
输出
一个整数(满足条件的方案数)。
样例输入
4 3
3 7 12 19
样例输出
1
29
#include
using namespace std;
int sum = 0;
int n, k;
int a[105];
int p[105];
bool vis[105];
vector ans;
bool isprime(int sum) {
if(sum <= 1) return false;
for(int i = 2; i * i <= sum ; i++) {
if(sum % i == 0) {
return false;
}
}
return true;
}
void dfs(int index) {
if(index == k + 1 ) {
if(isprime(sum)) {
ans.push_back(sum);
}
return;
}
for(int i = 1; i <= n; i++) {
if(vis[i] == false && i > p[index - 1]) { //按序排列 剪枝
p[index] = i;
vis[i] = true;
sum += a[i];
dfs(index + 1);
vis[i] = false;
sum -= a[i];
}
}
}
int main() {
memset(vis, 0, sizeof(vis));
cin>> n >> k;
for(int i = 1; i <= n; i++) {
cin >> a[i];
p[i] = i;
}
dfs(1);
cout << ans.size() << endl;
for(vector::iterator it = ans.begin();it != ans.end(); it++) {
printf("%d\n", *it);
}
return 0;
}
广度优先搜索(BFS)
Breadth First Search , BFS, 以广度为第一关键词。
当碰到岔道口时,总是先依次访问从该岔道口能直接到达的所有结点,然后再按这些结点被访问的顺序去依次访问它们能直接到达的所有结点,依次类推,直到所有结点都被访问为止。
BFS模板
void BFS(int s) {
queue q;
q.push(s);
while(!q.empty()) {
node top = q.front();
//取出队首元素top;
...
//访问队首元素top操作;
q.pop();BFD
//将队首元素出队;
将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
}
}
实现步骤:
- 定义队列q,并将起点s入队
- 写一个while循环,循环条件是队列是q非空
- 在while循环中,先取出队首元素top,然后访问它(访问可以是任何事情,例如将其输出)。访问完将其出队。
- 将top的下一层结点中所有未曾入队的结点入队,并标记它们的层号为now的层号加1,同时设置这些入队的结点已入过队。
- 返回步骤2继续循环
实例2:
给出一个m×n的矩阵,矩阵中的元素为0或1。称位置(x, y)与其上下左右四个位置(x, y+1)、(x, y-1)、(x+1,y)、(x-1,y)是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么称这些1构成了一个“块"。求给定的矩阵中”块“的个数
样例输入
6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0
样例输出
4
代码
#include
#include
using namespace std;
const int maxn = 100;
struct node {
int x, y; //位置(x, y)
} Node;
int n, m; //矩阵大小为n*m
int matrix[maxn][maxn]; //01矩阵
bool inq[maxn][maxn] = {false}; //记录位置(x, y)是否已入过队
int X[4] = {0, 0, 1, -1}; //增量数组
int Y[4] = {1, -1, 0, 0};
bool judge(int x, int y) { //判断坐标(x, y)是否需要访问
//越界返回false
if(x >= n || x < 0 || y >= m || y < 0) return false;
//不满足条件返回false
if(matrix[x][y] == 0 || inq[x][y] == true) return false;
return true;
}
//BFS函数访问位置(x, y)所在的块,将该块中所有"1"的inq都设置为true
void BFS(int x, int y) {
queue Q; //定义队列
Node.x = x, Node.y = y; //当前结点坐标为(x, y)
Q.push(Node); //将结点Node入队
inq[x][y] = true; //设置(x, y)已入过队
while(!Q.empty()) {
node top = Q.front(); //取出队首元素
Q.pop(); //队首元素出队
for(int i = 0; i < 4; i++) { //循环四次,得到四个相邻位置
int newX = top.x + X[i];
int newY = top.y + Y[i];
if(judge(newX, newY)) { //如果新位置(newX, newY)需要访问
//设置Node的坐标为(newX, newY)
Node.x = newX, Node.y = newY;
Q.push(Node); //将结点Node加入队列
inq[newX][newY] = true; //设置位置(newX, newY)已入过队
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int x = 0; x < n; x++) {
for(int y = 0; y < m; y++) {
scanf("%d", &matrix[x][y]);
}
}
int ans = 0; //存放块数
for(int x = 0; x < n; x++) { //枚举每一个位置
for(int y = 0; y < m; y++) {
//如果元素为1, 且未入过队
if(matrix[x][y] == 1 && inq[x][y] == false) {
ans++; //块数+1
BFS(x, y); //访问整个块,将该块所有"1"的inq都标记未true
}
}
}
printf("%d\n", ans);
return 0;
}
实例3:
给定一个nm大小的迷宫,其中代表不可通过的墙壁,而”.”代表平地,S表示起点,T代表终点。移动过程中,如果当前位置是(x, y)(下标从0开始),且每次只能前往上下左右(x, y+1)、(x, y-1)、(x+1, y)、(x-1, y)四个位置的平地,求从起点S到达终点T的最少步数。
样例输入
5 5
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3
样例输出
11
代码
#include
#include
#include
using namespace std;
const int maxn = 100;
struct node {
int x, y; //位置(x, y)
int step; //step为从起点S到达该位置的最少步数(即层数)
}S, T, Node; //S为起点,T为终点,Node为临时终点
int n, m; //n为行,m为列
char maze[maxn][maxn]; //迷宫信息
bool inq[maxn][maxn] = {false}; //记录位置(x, y)是否已入过队
int X[4] = {0, 0, 1, -1}; //增量数组
int Y[4] = {1, -1, 0, 0};
//检测位置(x, y)是否有效
bool test(int x, int y) {
if(x >= n || x < 0 || y >= m || y < 0) return false; //超过边界
if(maze[x][y] == '*') return false; //墙壁*
if(inq[x][y] == true) return false; //已入过队
return true; //有效位置
}
int BFS() {
queue q; //定义队列
q.push(S); //将起点S入队
while(!q.empty()) {
node top = q.front(); //取出队首元素
q.pop(); //队首元素出队
if(top.x == T.x && top.y ==T.y) {
return top.step;
}
for(int i = 0; i < 4; i++) { //循环4次,得到4个相邻位置
int newX = top.x + X[i];
int newY = top.y + Y[i];
if(test(newX, newY)) {
//设置Node的坐标为(newX, newY)
Node.x = newX;
Node.y = newY;
Node.step = top.step + 1; //Node层数为top的层数加1
q.push(Node); //将结点Node加入队列
inq[newX][newY] = true; //设置位置(newX, newY)已入过队
}
}
}
return -1; //无法到达终点T时返回-1
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
getchar(); //过滤掉每行后面的换行符
for(int j = 0; j < m; j++) {
maze[i][j] = getchar();
}
maze[i][m + 1] = '\0';
}
scanf("%d%d%d%d", &S.x, &S.y, &T.x, &T.y); //起点和终点的坐标
S.step = 0; //初始化起点的层数为0, 即S到S的最少步数为0
printf("%d\n", BFS());
return 0;
}