在计院的保研选手推荐我算法笔记之后,我觉得我又找到了一本适合上机联系、学习知识的好书,所以一下内容是我学习《算法笔记》的读书笔记
目录
DFS
DFS 深度优先搜索,首先,DFS是一种算法,但是对于不同的问题,DFS的代码形式也不同,其次,DFS是一种思想,是有规律可循的代码架构思想。
以下走迷宫很好的说明了DFS是一种什么样的思想:(图片来自《算法笔记》)
假如你一个人来到了一个迷宫,如果你想要找出口,那么最简单,也一定可以找到出口的方法就是使用DFS。从上图可以看到我们是一直往右遍走,如果遇到墙就折返,然后继续向右。如果用DFS来描述,我们每次遇到岔道口就会进行选择,马上去探索我们选择的某个分支,如果遇到墙就折返,要是又遇到岔路口,那么就老母猪带胸罩——一套又一套,继续按照上面这一套动作,完成这个岔路口的所有分支,直至找到终点。
一套又一套,是不是就想到了递归嵌套了呢?
是的,DFS经常是通过递归来完成的,如果不用递归也可以,自己实现这样栈式的搜索,只不过比较麻烦,这里碍于篇幅(lan)便不与说明。
那么如何实现DFS呢,实现斐波那契数列的时候,我们也是F(n)=F(n-1)+F(n-2) 这就是上面我们加粗的岔道口,要是遇到n=1 0那么就返回1,这就是我们上面加粗的墙。
所以,实现DFS,需要明白的第一点,就是,什么是分支(递归式),什么是墙(递归边界)。
for example
这道题也是算法笔记的题目,对于这道题,岔路口便是对于每一个物体i,我应该选择放入还是不放入呢?那什么是墙呢,是背包中放入的物体总重量大于V了,那就是墙。以及我们需要一个全局变量,让每次递归到最后一个物体的时候进行对比,是否遇到了价值最大的方案。
所以,我们来写一个这个问题的DFS吧。
//先写一下函数的定义
void DFS(int index,int sumW,int sumC){...}
//为什么要这样写?每次递归我们就是处理完了上一个物品,准备看下一个物品要不要放嘛,所以index是必须的,来标记现在处理到第几个物品了
//sunW和sumC是题目中提到了唯二个会变化的变量,用来在最后进行比较是否超重是否价值最大。
其实细心的小伙伴会发现这里有一个怪怪的地方,不过我们先这样认为写一下代码吧。
#include <iostream>
#include <stdio.h>
using namespace std;
const int maxn=30;//最多30个物品可供选择
int n,V,maxValue = 0;
int w[maxn],c[maxn];
//DFS,index is the code of stuff
//sumW and sumC refer sum weight and value
void DFS(int index,int sumW,int sumC){
//墙->处理完n个数
if(index==n){
//最优处理
if(sumW<=V&&sumC>maxValue){
maxValue=sumC;
}
return;
}
//岔道口
DFS(index+1,sumW,sumC);//不选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;
}
测试集:
5 8
3 5 1 2 2
4 5 2 1 3
答案:
10
其实我们发现,这个就是把n个物品这个序列求出所有子序列(可以不连续),对于每一个子序列进行判断选取最优。
对!你已经掌握DFS的真谛了
(麦兜:啊 DFS也有真谛啊)
对的,深度优先之所以叫做深度优先,是因为这个探索的过程中,就可以抽象成对每一个子序列的结果进行判断,看一下这个序列满不满足条件。当然,我上面说怪怪的地方,就是这里其实可以不用列出所有的子序列,当你发现放入这一个物品就超重的时候,我们就可以直接结束操作了,不用再遍历那些没用的序列,这就叫做剪枝。
void DFS(int index,int sumW,int sumC){
if(index==n){
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]);
}
}
最后,我们再来一道题康康DFS如何下手
我们的传入参数 index ,为了便于了解已经选了几个数,nowK传入当前已经选择的数的个数,sum和sumSqu分别记录当前选择的整数的和,以及已经选择的整数的平方和。
此外,需要一个数组来保存给定的N个整数,需要一个数组来选择已选的整数,需要一个数组来保存最优方案,以及一个保存最大平方和。
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
const int maxn=30;
int n,k,X,maxSquareValue = -1;
int num[maxn];
vector<int> temp,ans;
void DFS(int index,int nowK,int sum,int squareSum){
//墙
if(nowK==k&&sum==X){
if(squareSum>maxSquareValue){//最优处理
maxSquareValue=squareSum;
ans=temp;
}
return;
}
//墙
if(index==n||nowK>k||sum>X)return;
//岔道口
temp.push_back(num[index]);
DFS(index+1,nowK+1,sum+num[index],squareSum+num[index]*num[index]);
temp.pop_back();
//岔道口
DFS(index+1,nowK,sum,squareSum);//不选index这个数
}
int main()
{
scanf("%d %d %d",&n,&k,&X);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
}
DFS(0,0,0,0);
printf("%d\n",maxSquareValue);
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++){
cout<<*it<<' ';
}
return 0;
}
BFS
又是这样一个迷宫,不过,这一次是一大群人一起去了,并且这一次,在一个路口的下面所有分支大声说话,大家都能听到,除非有个人到了这个分支的分支(比如分支是A,但是D这边说话A听不到,只有BC可以传话到A),那么就听不见了,所以大家遇到路口决定分头行动,但是为了保证安全性,每个路口只探索这个路口的不同分支有没有遇到终点,如果没有遇到重点,那么最好还是再回到分支起点,然后共同探索这个分支的第一个路口,如果遇到墙就回来上个分支路口,遍历没有遇见的第二个路口。依次类推,最后直到找到终点。这就是BFS的迷宫策略。
所以上图的过程就是
A
BC
CDE
DEFG
EFGHIJ
FGHIJKLM
GHIJKLM
G是出口算法结束
所以你可以看到,这个BFS的过程很像一个队列,不断的出一个,然后将出去分支的下面所有分支传入,直到出去的是出口。
BFS的基本写法
void BFS(int s){
queue<int> q;//定义队列,并且将起点s入队
q.push(s);
while(!q.empty()){//循环条件是q非空
取出队首元素top;
访问队首元素top;//访问可以是任何事情,例如将其输出
将队首元素出队;//访问完将其出队
将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
}
}
for example
#include <iostream>
#include <queue>
#include <stdio.h>
using namespace std;
const int maxn=100;
struct node{
int x,y;
}Node;
int n,m;//矩阵大小
int matrix[maxn][maxn];
bool inq[maxn][maxn]={false};
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
bool judge(int x,int y){//判断该位置是否入需要访问
//越界
if(x>=n||x<0||y>=m||y<0)return false;
//当前位置为0 或者已经入过队
if(matrix[x][y]==0||inq[x][y]==true)return false;
return true;
}
void BFS(int x,int y){
queue<node> Q;//定义队列,并且将起点s入队
Node.x=x,Node.y=y;
Q.push(Node);
inq[x][y]=true;
while(!Q.empty()){//循环条件是q非空
node top=Q.front();//取出队首元素top;
//访问可以是任何事情,例如将其输出
Q.pop();//访问完将其出队
//将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
for(int i=0;i<4;i++){
int newX=top.x+X[i];
int newY=top.y+Y[i];
if(judge(newX,newY)){
Node.x=newX,Node.y=newY;
Q.push(Node);
inq[newX][newY]=true;
}
}
}
}
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++){
if(matrix[x][y]==1&&inq[x][y]==false){
ans++;
BFS(x,y);
}
}
}
cout << ans << endl;
return 0;
}
测试代码
7 6
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
在这里,我们发现为了防止检测四周的时候回回头检测之间就已经检测过的,我们就设定一个inq数组来表示是否入队过,当然也可以在strude node的定义里定义。
for another example
给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T代表终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1) (x,y-1) (x-1,y) (x+1,y)四个位置的平地,求从起点S到达终点T的最少步数。
这是《算法笔记给出的代码》
#include <iostream>
#include <queue>
#include <stdio.h>
#include <cstring>
using namespace std;
const int maxn =100;
struct node{
int x,y;
int step;
}S,T,Node;
int n,m;
char maze[maxn][maxn];
bool inq[maxn][maxn]={false};
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
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<node> q;//定义队列,并且将起点s入队
q.push(S);
while(!q.empty()){//循环条件是q非空
node top =q.front();//取出队首元素top;
//访问可以是任何事情,例如将其输出
q.pop();//访问完将其出队
if(top.x==T.x&&top.y==T.y){
return top.step;
}
for(int i=0;i<4;i++){//将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
int newX=top.x+X[i];
int newY=top.y+Y[i];
if(test(newX,newY)){
Node.x=newX,Node.y=newY;
Node.step=top.step+1;
q.push(Node);
inq[newX][newY]=true;
}
}
}
return -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;
printf("%d\n",BFS());
return 0;
}
这是我自己写的代码
#include <iostream>
#include <queue>
#include <stdio.h>
using namespace std;
const int maxn=100;
char matrix[maxn][maxn];
int STEP[maxn][maxn]={0};//相比于上面的inq 我使用步数来又存放这一步骤的步数又防止回走
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
int n,m;
struct node{
int x;
int y;
}S,E,Node;
bool viable(int x,int y){//防止越接和撞墙,其实这里可以和防止回走在一起
if(x<0||x>=n||y<0||y>=m)return false;
if(matrix[x][y]=='*')return false;
return true;
}
void BFS(){
queue<node> q;
while(!q.empty())q.pop();
q.push(S);
while(!q.empty()){
node top=q.front();
int x=top.x;
int y=top.y;
int step=STEP[x][y];
cout<<x<<','<<y<<','<<step<<endl;
q.pop();
if(x==E.x&&y==E.y){
cout<<"get the END!"<<endl;
break;
}
for(int i=0;i<4;i++){
int newx=x+X[i];
int newy=y+Y[i];
cout<<newx<<newy<<endl;
if(viable(newx,newy)&&(STEP[newx][newy]==0)){//后面那个条件是防止回走
cout<<newx<<newy<<"viable"<<endl;
Node.x=newx;Node.y=newy;
STEP[newx][newy]=step+1;
q.push(Node);
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
getchar();
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
scanf("%c",&matrix[i][j]);
STEP[i][j]=0;
if(matrix[i][j]=='S'){
S.x=i;S.y=j;STEP[i][j]=1;
}
else if(matrix[i][j]=='E'){
E.x=i;E.y=j;
}
}
getchar();
}
BFS();
return 0;
}
稍作修改就可以了呢
测试数据:
5 5
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3
结果:
11
牢记这6步,BFSso easy
- 定义队列,并且将起点s入队
- 循环条件是q非空
- 取出队首元素top;
- 访问可以是任何事情,例如将其输出
- 访问完将其出队
- 将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
本文纯属本人瞎编,如果错误,欢迎指出。