(深度优先遍历和宽度优先遍历)DFS和BFS(2)

(深度优先遍历和宽度优先遍历)DFS和BFS(2),第1张

目录

前言

一、图的种类

二、样题引入

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!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/914592.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)