深度优先搜索概述
深度优先搜索和广度优先搜索一样,都是对图进行搜索的算法,目的也都是从起点开始搜索直到到达指定顶点(终点)。深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为止,然后再折返,开始搜索下一条候补路径。
DFS思想
A为起点,G为终点。一开始我们在起点A上。
将可以从A直达的三个顶点B、C、D设为下一步的候补顶点。
从候补顶点中选出一个顶点。优先选择最新成为候补的点,如果几个顶点同时成为候补,那么可以从中随意选择一个。
注:此处,候补顶点是用“后入先出”(LIFO)的方式来管理的,因此可以使用“栈”这个数据结构。
到达终点G,搜索结束。
深度优先搜索的特征为沿着一条路径不断往下,进行深度搜索。虽然广度优先搜索和深度优先搜索在搜索顺序上有很大的差异,但是在操作步骤上却只有一点不同,那就是选择哪一个候补顶点作为下一个顶点的基准不同。
广度优先搜索选择的是最早成为候补的顶点,因为顶点离起点越近就越早成为候补,所以会从离起点近的地方开始按顺序搜索;而深度优先搜索选择的则是最新成为候补的顶点,所以会一路往下,沿着新发现的路径不断深入搜索。
背包问题
题目描述
现有n件物品,重量分别为w[0],w[1],…w[n-1],价值为c[i],现需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品总重量不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值(1 <= n <= 20)
思路:
这就相当于迷宫问题,当物品总重量超过V,则到达死胡同,而岔道口就是选或不选这个物品
示例代码如下:
#include<cstdio>
const int maxn = 30;
int n, V, maxValue = 0; //物品件数n,背包容量V
int w[maxn], c[maxn]; //w[i]每件物品的重量,c[i]为每件物品的价值
void DFS(int index, int sumW, int sumC)
{
if(index == n)
{
if(sumW <= V && sumC > maxValue)
{
maxValue = sumC; //不超过背包容量时更新最大价值maxValue
}
return;
}
DFS(index + 1, sumW, sumC);
DFS(index + 1, sumW + w[index], sumC + c[index]);
}
int main()
{
scanf("%d%d", &n, &V);
for(int i = 0; i < n; i++)
{
scanf("%d", &w[i]);
}
for(int i = 0; i < n; i++)
{
scanf("%d", &c[i]);
}
DFS(0, 0, 0);
printf("%d\n", maxValue);
return 0;
}
样例运行结果:
优化版代码:
#include<cstdio>
const int maxn = 30;
int n, V, maxValue = 0; //物品件数n,背包容量V
int ans = 0;
int w[maxn], c[maxn]; //w[i]每件物品的重量,c[i]为每件物品的价值
void DFS(int index, int sumW, int sumC)
{
if(index == n)
{
if(sumW <= V && sumC > maxValue)
{
maxValue = sumC; //不超过背包容量时更新最大价值maxValue
ans = maxValue;
}
return;
}
DFS(index + 1, sumW, sumC);
if(sumW + w[index] <= V)
{
if(sumC + c[index] > ans)
{
ans = sumC + c[index];
}
DFS(index + 1, sumW + w[index], sumC + c[index]);
}
}
int main()
{
scanf("%d%d", &n, &V);
for(int i = 0; i < n; i++)
{
scanf("%d", &w[i]);
}
for(int i = 0; i < n; i++)
{
scanf("%d", &c[i]);
}
DFS(0, 0, 0);
printf("%d\n", maxValue);
return 0;
}
上面优化的代码在于,只有当加入的第index件物品后背包未超重,才能进入岔路,否则就不走该岔路。这样做可以节省计算量
DFS子序列问题
题目描述
给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X;如果有多种方案,选择它们中元素平方和最大的一个。数据保证这样的方案唯一。例如,从4个整数(2,3,3,4}中选择2个数,使它们的和为6,显然有两种方案{2,4}与{3,3},其中平方和最大的方案为{2,4}。
思路:
使用深度搜索遍历来实现,一般每次只有两种选择:
1、将当前数字放入已选序列
2、不将当前数字放入已选序列
注:若将当前数字放入已选序列,则当要执行不放入序列的时候,需要将当前数字去掉
详细C++代码如下:
#include<iostream>
#include<vector>
using namespace std;
const int maxn = 50;
int n, k, x, maxSumSqu = -1, A[maxn];
vector<int> temp, ans; //存放最优方案的数组ans
//sum是整数之和,sumSqu是整数平方和
//nowK是已选整数的个数
//index是当前的序列号
void DFS(int index, int nowK, int sum, int sumSqu)
{
if(nowK == k && sum == x)
{
if(sumSqu > maxSumSqu)
{
maxSumSqu = sumSqu;
ans = temp;
}
return;
}
if(index == n || nowK > k || sum > x)
return;
temp.push_back(A[index]);
DFS(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
temp.pop_back();
DFS(index + 1, nowK, sum, sumSqu);
}
int main()
{
int num;
cin >> num;
for(int i = 0; i < num; i++)
{
cin >> A[i];
}
cout << "please input 限定选择的K个数中的K, 要求数字的和x:";
cin >> k >> x;
n = num;
DFS(0, 0, 0, 0);
int sum = 0;
cout << "ans:";
for(int i = 0; i < ans.size(); i++)
{
cout << ans[i] << " ";
sum += ans[i];
}
cout << "\nsum:" << sum << endl;
cout << "maxSumSqu:" << maxSumSqu << endl;
return 0;
}
样例运行结果如下:
变式题
题目描述
假设N个整数中的每一个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X。
思路:
这个问题只需要对上面的代码进行少量的修改即可。由于每个整数可以被选择多次,因此当选择了index号数时,不应当直接进入index + 1号进行处理,应当继续选择index号数,知道某个时刻决定不再选择index号数,就会通过"不选index号数"这条分支进入处理,即
DFS(index + 1, nowK, sum, sumSqu);
需要修改的代码为
DFS(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
改成
DFS(index, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
完整C++代码如下:
#include<iostream>
#include<vector>
using namespace std;
const int maxn = 50;
int n, k, x, maxSumSqu = -1, A[maxn];
vector<int> temp, ans; //存放最优方案的数组ans
//sum是整数之和,sumSqu是整数平方和
//nowK是已选整数的个数
//index是当前的序列号
void DFS(int index, int nowK, int sum, int sumSqu)
{
if(nowK == k && sum == x)
{
if(sumSqu > maxSumSqu)
{
maxSumSqu = sumSqu;
ans = temp;
}
return;
}
if(index == n || nowK > k || sum > x)
return;
temp.push_back(A[index]);
DFS(index, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
temp.pop_back();
DFS(index + 1, nowK, sum, sumSqu);
}
int main()
{
int num;
cin >> num;
for(int i = 0; i < num; i++)
{
cin >> A[i];
}
cout << "please input 限定选择的K个数中的K, 要求数字的和x:";
cin >> k >> x;
n = num;
DFS(0, 0, 0, 0);
int sum = 0;
cout << "ans:";
for(int i = 0; i < ans.size(); i++)
{
cout << ans[i] << " ";
sum += ans[i];
}
cout << "\nsum:" << sum << endl;
cout << "maxSumSqu:" << maxSumSqu << endl;
return 0;
}
样例运行结果如下:
迷宫问题
题目描述
解题思路
dfs函数中有三个关键参数:横坐标、纵坐标、到目前为止所走的步数
方向数组
C代码:
#include<stdio.h>
int n, m, p, q, min = 99999999;
int a[51][51], book[51][51];
void dfs(int x, int y, int step)
{
/*右 下 左 上
*/
int next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int tx, ty, k;
if(x == p && y == q)
{
if(step < min)
{
min = step;
}
}
for(k = 0; k <= 3; k++)
{
tx = x + next[k][1];
ty = y + next[k][0];
if(tx < 1 || tx > n || ty < 1 || ty > m)
{
continue;
}
if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = 1;
dfs(tx, ty, step + 1);
book[tx][ty] = 0;
}
}
}
int main()
{
int i, j, startx, starty;
scanf("%d %d", &n, &m);
for(i = 1; i <= n; i++)
{
for(j = 1; j <= m; j++)
{
scanf("%d", &a[i][j]);
}
}
scanf("%d %d %d %d", &startx, &starty, &p, &q);
book[startx][startx] = 1;
dfs(startx, starty, 0);
printf("%d", min);
getchar();
return 0;
}
完整C++代码如下:
#include<bits/stdc++.h>
using namespace std;
//n, m是迷宫的行列数,p,q是目标位置
int n, m, p, q, minn = 999999;
int a[51][51], book[51][51];
void dfs(int x, int y, int step)
{
//方向数组
int next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int tx, ty, k;
//判断是否达到目标位置
if(x == p && y == q)
{
if(step < minn)
{
minn = step;
}
return;
}
//枚举4种走法
for(k = 0; k <= 3; k++)
{
tx = x + next[k][0];
ty = y + next[k][1];
//判断是否越界
if(tx < 1 || tx > n || ty < 1 || ty > n)
{
continue;
}
if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = 1;
dfs(tx, ty, step + 1);
book[tx][ty] = 0;
}
}
}
int main()
{
int i, j, startx, starty;
cin >> n >> m;
for(i = 1; i <= n; i++)
{
for(j = 1; j <= m; j++)
{
cin >> a[i][j];
}
}
//输入起点和终点
cin >> startx >> starty >> p >> q;
book[startx][starty] = 1;
dfs(startx, starty, 0);
cout << minn << endl;
return 0;
}
样例运行结果:
完善版C++代码
#include<bits/stdc++.h>
using namespace std;
int m, n, p, q, minn = 99999999;
//a数组记录原始地图情况,book数组记录路径是否已被走过
int a[100][100], book[100][100];
//二维数组不要用memset(book, 0, sizeof(book));
void dfs(int x, int y, int step)
{
//方向数组(x, y) 右 下 左 上
int next[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int tx, ty, k;
if(x == p && y == q)
{
//更新最小步数
if(step < minn)
{
minn = step;
}
}
for(k = 0; k <= 3; k++)
{
tx = x + next[k][0];
ty = y + next[k][1];
//m表示行,n表示列
if(tx < 1 || tx > n || ty < 1 || ty > m)
{
continue;
}
//判断该点是否为障碍物或者已经走过了
if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = 1;
dfs(tx, ty, step + 1);
book[tx][ty] = 0;
}
}
}
int main()
{
cin >> m >> n;
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n; j++)
{
cin >>a[i][j];
}
}
int startx, starty;
book[startx][starty] = 1;
cin >> startx >> starty >> p >> q;
dfs(startx, starty, 0);
cout << minn << endl;
return 0;
}
样例运行结果:
全排列问题
题目描述
解题思路:
dfs的演化过程:
首先建立第step个盒子的处理函数
(注释里很详细了,这里就不做过多说明了)
完整C++代码如下:
#include<iostream>
using namespace std;
int n;
int a[10];
int book[10];
void dfs(int step)
{
int i;
if(step == n + 1)
{
for(i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
cout << "\n";
return;
}
for(i = 1; i <= n; i++)
{
if(book[i] == 0)
{
a[step] = i;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
C代码如下:
#include<stdio.h>
int a[10], book[10], n;
void dfs(int step)
{
int i;
if(step == n + 1)
{
for(i = 1; i <= n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return;
}
for(i = 1; i <= n; i++)
{
if(book[i] == 0) //表示扑克牌在手上
{
a[step] = i;
book[i] = 1; //标记扑克牌不在手上
dfs(step + 1);
book[i] = 0;
}
}
}
int main()
{
scanf("%d", &n);
dfs(1);
return 0;
}
运行结果: