一。深度优先搜索
深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。
类似树的先根遍历。简单来说,就是一直往深处走,直到找到解或走不下去了为止。
可以使用栈来实现,用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出站进行新的检测。
也可以使用递归实现,相对代码简洁,但使用递归的时候系统会调用系统栈来存放递归中每一层的状态,亦是用到了栈。
举个栗子:
有n件物品,每件物品的重量为w[i],价值为c[i]。现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包中的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值。(1 <= n <= 20)
分析:
岔路口——每件物品选或不选两种选择
死胡同——选择的物品重量总和不能超过V
代码实现:
#include <cstdio>
const int maxn = 30;
int n ,V, maxValue = 0; //物品件数n,背包容量V, 最大价值maxValue
int w[maxn], c[maxn]; //w[i]为每件物品重量,c[i]为每件物品价值
void DFS(int index, int sumW, int sumC){
if(index == n){ //已经完成对n件物品的选择(死胡同)
if(sumW <= V && sumC > maxValue){
maxValue = sumC;
}
return;
}
DFS(index+1, sumW, sumC); //不选第index件物品
DFS(index+1, sumW+w[index], sumC+c[index]); //选第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); //初始时为第0件物品,当前总重量和总价值均为0
printf("%d\n", maxValue);
return 0;
}
上面代码总是把n件物品的选择全部确定后才去更新最大价值。对DFS进行优化,先判断进入第index件物品后能否满足容量不超过V的要求,只有当条件满足时才更新最大价值以及进入这条岔路。
如此通过题目条件的限制来节省DFS计算量的方法称为剪纸。
void DFS(int index, int sumW, int sumC){
if(index == n){ //已经完成对n件物品的选择(死胡同)
return;
}
DFS(index+1, sumW, sumC); //不选第index件物品
if(sumW + w[index] <= V){ //加入第index件物品后未超过容量V,才继续
if(sumC + c[index] > ans){
ans = sumC + c[index]; //更新最大价值maxValue
}
DFS(index+1, sumW+w[index], sumC+c[index]); //选第index件物品
}
}
再举一个栗子,理解一下DFS函数该怎么写:
给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X:如果有多种方案的话,选择他们中元素平方和最大的一个。数据保证这样的方案唯一。
int n, k, x, maxSumSqu = -1, A[maxn]; //序列A中n个数选k个数使其和为x,最大平方和为maxSumSqu
vector<int> temp, ans; //temp存放临时方案,ans存放平方和最大的方案
// 当前处理index号整数,当前已选整数个数为nowK
// 当前已选整数之和为sum,当前已选整数平方和为sumSqu
void DFS(int index, int nowK, int sum, int sumSqu){
if(nowK == k && sum == x){
if(sumSqu > maxSumSqu){ //找到k个数的和为x
maxSumSqu = sumSqu; //更新最大平方和
ans = temp; //更新最优方案
}
return;
}
// 已经处理完n个数,或者超过k个数,或者和超过x,返回
if(index == n || nowK > k || sum > x) return;
// 选index号数
temp.push_back(A[index]);
DFS(index+1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
temp.pop_back();
// 不选index号数
DFS(index+1, nowK, sum, sumSqu);
}
二。广度优先搜索
BFS一般用队列保存未被检测的结点,且总是按照层次的顺序进行遍历。
类似树的按层次遍历的过程。
以上模板注释:
(1)定义队列q,并将起点s入队
(2)写一个while循环,循环条件是队列q非空
(3)在while循环中,先取出队首元素top,然后访问它(访问可以是任何事情,例如将其输出)。访问完后将其出队。
(4)将top的下一层结点中所有未曾入队的结点入队,并标记他们的层号为now的层号+1,同时设置这些入队的结点已入过队。
(5)返回(2)继续循环
举个栗子:
给出一个m*m的矩阵,矩阵中的元素为0或1。我们称位置(x, y)与其上下左右四个位置(x, y+1)、(x, y-1)、(x+1, y)、(x-1, y)是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么我们称这些1构成了一个“块”。求给定的矩阵中“块”的个数。
例如上面的6*7的矩阵中,“块”的个数为4。
思路:
枚举每一个位置的元素,如果为0则跳过;如果为1,则使用BFS查询与该位置相邻的四个位置(前提是不出界),判断他们是否为1(如果某个相邻的位置为1,则同样去查询与该位置相邻的四个位置,直到整个“1”块访问完毕)。
而防止走回头路,一般可以设置一个bool型数组inq(即in queue的简写)来记录每个位置是否在BFS中已入过队。
一个小技巧:对当前位置(x, y)来说,由于与其相邻的四个位置分别为(x, y+1)、(x, y-1)、(x+1, y)、(x-1, y),那么可以设置两个增量数组,来表示四个方向(0, 1)、(0, -1)、(1, 0)、(-1, 0)。
这样我们可以使用for循环来枚举四个方向,以确定与当前坐标(nowX, nowY)相邻的四个位置,如下:
代码实现:
#include <cstdio>
#include <queue>
using namespace std;
const int maxn = 100;
struct node{
int x, y; //位置(x, y)
}Node;
int 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;
//当前位置为0,或(x,y)已入过队,返回false
if(matrix[x][y] == 0 || inq[x][y] == true) return false;
//以上都不满足,返回true
return true;
}
//BFS函数访问位置(x,y)所在的块,将该块中所有"1"的inq都设置为true
void BFS(int x, int y){
queue<node> Q; //定义队列
Node.x = x; //当前结点坐标(x,y)
Node.y = y;
Q.push(Node); //将结点Node入队
inq[x][y] = true;
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.x = newX, Node.y = newY; //设置Node的坐标为(newX,mewY)
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]); //读入01矩阵
}
}
int ans = 0; //存放块数
for(int x = 0; x<n; x++){ //枚举每一个位置
for(int y = 0; y<m; y++){
if(matrix[x][y] == 1 && inq[x][y] == false){
ans ++;
BFS(x, y); //访问整个块,将该块所有"1"的inq都标记为true
}
}
}
printf("%d\n", ans);
return 0;
}
进一寸有一寸的欢喜。借此学习下算法。
算了算还有8天考ccf,考多少分无所谓啦。