目录
前言
一、图的种类
二、样题引入
1.(DFS和BFS在图中比较)图中点的层次
2.深度优先遍历解图的问题(树的重心)。
3.一道有意思的算法题(回味童年)
4.DFS的题目(深度还原DFS的过程)
总结
前言
在DFS和BFS(1)中,已经讲过了深度优先遍历和宽度优先遍历的过程,接下来我们通过实列,来进一步的了解深度优先遍历和宽度优先遍历。
一、图的种类1.有向图:图中边的方向是一定的,不能逆序走。
2.无向图:图中的边没有方向,可以逆序走。没有正负方向
3.完全图:完全图:对于顶中的每一个顶点,都与其他的点有边直接相连 无向完全图:编辑任意一个具有n个结点的无向简单图,其边数小于等于n*(n-1)/2;我们把边数恰好等于n*(n-1)/2的n个结点的无向图称为完全图。 有向...
4.稀疏图和稠密图:一般的对于一个图来说,边的数目多的就是稠密图,边的数目少的就是稀疏图。
邻接矩阵要储存图的所有节点,而邻接表只储存非零节点,所以对于稀疏图一般采用邻接表,而稠密图则用邻接矩阵比较好。 二、样题引入测试数据:
5 10
1 5
1 1
3 2
3 5
5 4
4 5
4 3
1 2
3 1
1 5
答案为:1
1.图中点的层次用这个题来感受一下邻接矩阵和邻接表
首先邻接矩阵就是用一个N*N的二维数组来储存边的信息,比如a[x][y],他的意思就是有一条x->y的边。但是这里有一个问题,二维数组它的最大容量是2万多一点,所以当我们的数据量比较大的时候(如这一题的5万),编译器就会出现以下这种提醒:
这就是二维数组开的太大,超出了限制导致的。所以用邻接矩阵虽然能写的的出来,但交到平台上也无法通过。但是如果数据量小于2万还是可以用邻接矩阵的。
邻接矩阵+STL(队列)解题
思路:因为点与点之间的距离都为1,所以这一题用宽搜比较合适。过程:到达一个点,检查这个点都能到哪些点,让能到达的点都入队,然后再将这个出队,重复这个过程。切记:走到每一个点,都将这个点做上标记,以后都不在走这一个点了。
这就是宽搜的过程。
题解为:#include
#include
#include
using namespace std;//用 邻接矩阵表示图
const int N=10000;
int map[N][N],hh=99999;//表示两点不存在路线
int book[N],m,n,e[N];//全局变量,初始值为0
queue qq;
void bfs()//因为每两个点的距离都为1,所以找最短路径用BFS比较合适
{
book[1]=1;//标记一下,1这一个点已经遍历过
e[1]=0;
qq.push(1);
while(qq.size())//当队列为空的时候停止
{
int k=qq.front();
qq.pop();
for(int i=1;i<=n;i++)//从1号点开始尝试,到n号点尝试结束
{
if(map[k][i]==1&&book[i]==0)//有k->i的路线,并且之前没走过
{
qq.push(i);
e[i]=e[k]+1;
book[i]=1;
}
}
}
}
int main()
{
//首先用邻接表输入地形
memset(e,-1,sizeof(e));
int a,b;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
map[i][j]=hh;//初始化,让所有的点都不链接,所以点之间的距离为无穷大
while(m--)
{
cin>>a>>b;//表示从a走向b
map[a][b]=1; //距离为一
} //这样一个邻接矩阵就构造完成
bfs();
cout<<"答案为:"<
这一题的正确写法应该是用邻接表,因为用邻接表就不必考虑数组越界的问题了。
嗯。。。。。这一题的话就不用STL了,用数组模拟队列的形式来写。感觉数组真的是个神奇的东西,学完数据结构一定要学这用数组模拟链表,栈,队列这些东西,真的很实用!
邻接表就是将一个点能一步到达的所有点用一个链表连接起来,让所有点都重复这个过程,拿上面测试数据来举例。
简单的说一下如何用数组来模拟链表吧。h数组相当于是头指针,它指向的是下一个元素的位置,而e数组是储存相应位置上的值,而ne数组则储存的是下标指向的下一个位置。没听懂的也没关系,可以百度搜一搜。
#include
#include
using namespace std;
const int N=1e5+10;
int h[N],ne[N],e[N],indx=0;//邻接表储存图 (数组模拟链表)
int qq[N],head,tail;//用数组来模拟队列
int m,n,ht[N],book[N];//ht[]数组用来储存从k点到其他点的最短路径
void add(int a,int b)//将 a到b的 这条路线插到邻接表当中
{
e[indx]=b;ne[indx]=h[a];h[a]=indx++;
}
void bfs(int k)
{
head=0,tail=0;//对队列初始化
book[k]=1;ht[k]=0;
qq[tail++]=k;
while(tail>head)
{
for(int i=h[qq[head]];i!=-1;i=ne[i])//因为是宽度优先遍历,让邻接表的h[qq[tail]]全部入队列
{
if(book[e[i]]==0)
{
ht[e[i]]=ht[qq[head]]+1;
qq[tail++]=e[i];
book[e[i]]=1;
}
}
head++;
}
}
int main()
{
int p,q;
memset(h,-1,sizeof(h));//先让邻接表中的每一个头节点都指向-1;
memset(ne,-1,sizeof(ne));
memset(ht,-1,sizeof(ht));
cin>>n>>m;
while(m--)
{
cin>>p>>q;//输入一个从p到q的路线
add(p,q);//将该路线插入到邻接表当中
}
bfs(1);
cout<
测试数据都通过了。
2.深度优先遍历解图的问题(树的重心)。测试数据:
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
树是一个无向不连通图,而无向图又是特殊的有向图。所以这一题也能用邻接矩阵或者邻接表来储存。用DFS深度优先解题比较合适,因为深度优先有一个性质,他会以一个节点为起点一直往下搜索,当无法搜索的时候就回溯,再在距离无路可走最近的地方寻找出路,我们只需要在深度搜索的时候记录其搜索了几个点,就能知道去除某一个节点后子树各有多少节点,然后再求出其它子树节点最多有多少个,再逐个节点进行比较即可。如图为测试数据。
题解为:#include
#include
#include
using namespace std;
const int N=1e5+10;
const int M=2*N;
int h[N],e[M],ne[M],inx=0,n;//用数组模拟点链表
int que[N],head,tail;//数组模拟队列
int sum=N;
bool book[N];
void add(int a,int b)
{
e[inx]=b;ne[inx]=h[a];h[a]=inx++;//相当于前插法
}
int dfs(int x)//删除x节点,然后算出子节点的个数
{
//利用深度搜索的一个特点,就是“不撞南墙不回头”,比如从1结点开始深度搜索,会把1节点
//以后的分支都会一条路遍历一遍,这样就能够得到删除节点1 后子节点的大小,然后再进行比较即可
book[x]=true;//标记一下x节点,以后不再遍历这个节点
int ans=0,ma=1;//用来找出x这个子节点的最大值
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(!book[j])
{
int s=dfs(j);
ans=max(ans,s);
ma+=s;//求出以j为根节点 子节点的个数,包含j;
}
}
ans=max(ans,n-ma);
sum=min(ans,sum);
return ma;
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i>x>>y;
add(x,y);
add(y,x);//树相当于一个无向图图(有向图的特殊形式) ,所以可以x->y,也可y->x;
}
dfs(1);
cout<
测试通过。
2.一道有意思的算法题(回味童年)测试数据:(上图的地形小编已按字符拼接完成,当然也可以自己设置地形啦!)
13 13 3 3
*************
*@@.@@@*@@@.*
***.*@*@*@*@*
*.......*..@*
*@*.***.*@*@*
*@@.@@@.*.@@*
*@*.*@*.*.*.*
**@...@.....*
*@*.*@***.*@*
*...@*@@@.@@*
*@*.*@*@*.*@*
*@@.@@@*@.@@*
*************
其主要的思想就是DFS和BFS。(过程都在题解上写着)
DFS代码如下(示例):
#include
#include
using namespace std;
//用DFS完成炸d人这一题
//用“@”表示小人,用“*”表示墙,用“.”表示空地
char a[20][20];
int book[20][20];
int mi,mx,my,m,n;
int Max1(int x,int y)//从(x,y)这个点往四周扩,数一数往四周能炸死几个小人
{
int i=x,j=y,k=0;
while(a[i][y]!='*')
{
if(a[i][y]=='@')
k++;
i++;
}
i=x,j=y;
while(a[x][j]!='*')
{
if(a[x][j]=='@')
k++;
j++;
}
i=x,j=y;
while(a[i][y]!='*')
{
if(a[i][y]=='@')
k++;
i--;
}
i=x,j=y;
while(a[x][j]!='*')
{
if(a[x][j]=='@')
k++;
j--;
}
return k;
}
void dfs(int x,int y)
{
int ne[4][2]={{0,1},{1,0},{-1,0},{0,-1}};//可以往上下左右四个方向走
int tx,ty,sum;
sum=Max1(x,y);
if(sum>mi)
{
mi=sum;
mx=x;
my=y;
}
for(int i=0;i<=3;i++)
{
tx=x+ne[i][0];
ty=y+ne[i][1];
if(book[tx][ty]==0&&a[tx][ty]=='.')//这个点没有走过,并且这个点是空地,可以安放炸弹
{
book[tx][ty]=1;//走过的就将其标记,以后不再走这个位置
dfs(tx,ty);//继续寻找下一个位置
//这里不需要像全排列一样,让book指针回溯,因为我们只需要保证每一个空地试放过一个炸弹就行
}
}
}
int main()
{
//输入地图,可以自己设计地图,也可以用图上的那个地图
/*
13 13 3 3(13行13列的地图,从(3,3)点开始走)
*************
*@@.@@@*@@@.*
***.*@*@*@*@*
*.......*..@*
*@*.***.*@*@*
*@@.@@@.*.@@*
*@*.*@*.*.*.*
**@...@.....*
*@*.*@***.*@*
*...@*@@@.@@*
*@*.*@*@*.*@*
*@@.@@@*@.@@*
*************
*/
int kx,ky;
cout<<"请输入图是几行几列" <>m>>n;
cout<<"请输入起始坐标"<>kx>>ky;
for(int i=0;i>a[i];
book[kx][ky]=1;
mx=kx;
my=ky;
mi=Max1(kx,ky);
dfs(kx,ky);
cout<<"炸弹放置在点("<
BFS代码如下:
#include
#include
using namespace std;
char a[20][20];//用于储存地图
int book[20][20];//用来判断空地是否尝试过放置炸d
int Max(int x,int y)//从(x,y)这个点往四周扩,数一数往四周能炸死几个小人
{
int i=x,j=y,k=0;
while(a[i][y]!='*')
{
if(a[i][y]=='@')
k++;
i++;
}
i=x,j=y;
while(a[x][j]!='*')
{
if(a[x][j]=='@')
k++;
j++;
}
i=x,j=y;
while(a[i][y]!='*')
{
if(a[i][y]=='@')
k++;
i--;
}
i=x,j=y;
while(a[x][j]!='*')
{
if(a[x][j]=='@')
k++;
j--;
}
return k;
}
struct kk{
int x;
int y;
int s;
}que[20*20];
int main()
{
int kx,ky,m,n;
cout<<"请输入图是几行几列" <>m>>n;
cout<<"请输入起始坐标"<>kx>>ky;
cout<<"请按照规定的符号,输入地形"<>a[i];
int next[4][2]={{0,1},{1,0},{-1,0},{0,-1}};//可以往上下左右四个方向走
int mx=kx,my=ky,sum=Max(kx,ky);
//对队列初始化
int head=0,tail=0;
que[tail].x=kx;
que[tail].y=ky;
que[tail++].s=0;//现在在(x,y)处,并且步数为0;
while(headsum)
{
sum=k;
mx=kx;
my=ky;
}
}
}
head++;//当某个点的上下左右四个点都判断完后,让头节点出队列,再 判断这个点的四周
}
cout<<"炸弹放置在点("<
为什么要列举这一题呢?因为我感觉这一题挺经典的,能够反映出DFS与BFS的共同点,又能够反映出其不同点。而且这两种方法都有一些固定的模板,只要理解其过程就可以在很多题中套用。
接下来写一道只能用DFS的题目这也是一道比较简单的题。其思想就是DFS,我们可以模拟一下,输入的字符串多长,就相当于有几个盒子。我们将这些字母往盒子里放,因为盒子和字母的数一样多,放到最后字母刚好放完,然后再退回到倒数第二个位置,将最后两个盒子里的字母拿出来,颠倒一下位置再放置,然后再退回到倒数第三个位置,拿回后三个字母再放置。其实这就是模拟DFS的过程。
题解:#include
#include
using namespace std;
//字符的全排列
char a1[10],b1[10];
int len,book[10];
void dfs(int step)
{
if(step==len)
{
for(int i=0;i>a1;//按照字母的升序从小到大输入
len=strlen(a1);
dfs(0);
return 0;
}
还有八皇后问题,迷宫问题等,都用到了搜索。
总结宽搜和深搜搜索的用处很广泛,在处理一些图的问题能起到意想不到的效果。而且这两种搜索方式在算法中的位置很高,所以一定要掌握这两种方法。当然看看基础知识还是远远不够的,还需要通过大量的刷题联系。
最后,希望我的文章能够给你带来帮助。
近一星期学习算法的一个小总结!一起努力变强吧! 文章的部分题来自acwing!欢迎分享,转载请注明来源:内存溢出
评论列表(0条)