学习图论(一)——DFS与BFS

一、图的基本要素
1.顶点/节点(vertex);
2.边(edge),连接两个点,可以为无向边也可以为有向边,可以为有权边也可以为无权边;
3.连通图:图上的任意两个点之间都是连通的。 即是任意两个点都有一条或者多条边连接着。
4.连通分量:最大连通子图。即是①是该图的子图;②该子图是连通的;③是含节点数最多的子图。

二、两种基本遍历算法
学习、参考的代码:https://blog.csdn.net/u011437229/article/details/53188837
其中对DFS和BFS的思想举例很形象。

1、深度优先搜索(DFS)

参考博客:https://blog.csdn.net/liangzhaoyang1/article/details/51415719

思想:顾名思义,深度优先,就是从一个顶点开始,往下搜索与该顶点相邻的节点,一直搜到所搜的节点不存在相邻节点,然后开始回溯,往回走,一次回到上一个节点,搜索其他路径,知道遍历所有的节点或者找到解为止。

核心代码:
void DFS(int cur)// cur为当前的点
{
if(边界条件或者需要判断的条件) //条件可以有多个,即有多个 if 语句
{
执行所需的操作;
return ;
}
做需要执行的操作;
如在走一张图时要分别搜索上下左右四个方向,则有一个for循环,每次走一个方向
//执行完应有的操作后,选择符合条件的节点继续进行搜索。
DFS(符合条件的下一个节点);
}

简单例题
输入n个结点,m条边,之后输入 有向图的 m条边,
边的前两元素表示起始结点,第三个值表权值,
输出1号城市到n号城市的最短距离。
AC代码:

#include
#include
using namespace std;
#define inf 999999999
#define maxn 110
//minpath 最短路径,因为用DFS,每个点都要搜索,所搜索完一条路径,都记录下来,用于比较; 
//egde声明为二维数组,表示两个有向连接点之间的距离(能表示双向),但是数据大时不可以用;
//mark作为标记作用,标记以访问过的节点;
int n,m,minpath,egde[maxn][maxn],mark[maxn];

void dfs(int cur,int dist) //当前点, 当前距离 
{
	if(dist>minpath) //在当前点就有距离大于最小路程,下面不用再遍历 
		return ;
	if(cur==n) // 已经到终点 
	{	
		if(minpath>dist)  //求最短距离 
			minpath = dist;
		return ;
	}
	for(int i=1;i<=n;++i)
	{
		//条件分别为:两点间有边,下一个点还没访问过,两个点不是同一个点,因为同一点的话没必要进行判断
		if(egde[cur][i]!=inf&&!mark[i]&&egde[cur][i])
		{
			mark[i]=1;
			dfs(i,dist+egde[cur][i]);//刷新当前距离距离
			mark[i]=0;   //回溯,因为每条路径都要搜索一遍,可能有重复经过的点 
		}
	}
	return ;
}

int main()
{
	while(cin>>n>>m&&n)
	{
		int i,j;
		for(int i=1; i<=n; ++i)
		{
			for(int j=1; j<=n; ++j)
				egde[i][j] = inf; //把每条边赋做无穷,再输入边长,则无穷表示的是两个节点无连接
			egde[i][i]=0; //同一点距离为 0  
		}
		int a,b;//表前后两个节点 
		while(m--) //输入边长
		{
			cin>>a>>b;
			cin>>egde[a][b];
		} 
		memset(mark,0,sizeof(mark));//将标记数组全置为 0 ,头文件为  
		mark[1]=1;
		minpath=inf;
		dfs(1,0);
		cout<

总而言之,用DFS,就是每个点你都要变遍历(因为我们不知道需要遍历几个点才能找到答案,所以一开始当然是准备要遍历所有的点),所以为了防止在每一条路上遍历时会访问已经访问过的点,我们需要把访问过的点做标记,当一条路走完后,再把每个标记过的点重设为未标记,因为遍历另一条路可能会经过上一条路经过的点。
使用DFS发生TLE(超时)时一般是边界条件出错了,或者没有标记导致循环访问。(想象三个点互相连接,要访问这三个点,如果没有标记已经访问过的点,则会无尽循环下去)发生WA(答案错误),可能是没有重置标记。

2.广度优先搜索(BFS)
思想:广度,同宽度,也就是先搜索所有和当前的节点相邻的点,然后在往下一层搜索,发现目标或者达到目的时停止搜索。
和DFS不同,由于BFS需要先变遍历所有相邻的点,所以无法用递归去实现,而是使用队列这一数据结构——先入先出。

核心代码:
void BFS(data_type u)
{
queue< 数据类型> q; // 创建一个队列 队列
定义当前状态;
q.push(u); // 开始的节点入队
visit[u]=1; // 访问标记
while(!q.empty())//队列空时退出,此时已经有答案或者无解
{
now=q.front(); // 取队首元素进行扩展
//if 和 for 的顺序可以颠倒,视情况而定
if(到达目标条件)
{
进行相应操作;
return;
}
执行需要的操作,依次访问与队首元素相邻的点;
找到后进行标记,将符合条件的点放入队列中;
队首元素出出队,因为已经访问过并且执行过需要的操作了。
}
return;
}

简单例题:
创建一个简单的5×5的迷宫,0表示可以走的路,1表示障碍物,不可走。
只能往上下左右四个方向行走,请找出从左上角到右下角的最短路线。
(输出依次经过的坐标)

#include
#include
using namespace std;

//BFS实例 

struct path{
	int curx; //当前坐标
	int cury;
	int prex; //所走路线上前一个点的坐标
	int prey;
}move[5][5];
int maze[5][5];//迷宫
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//四个不同的方向
int vit[5][5]={0};//标记数组,记录节点是否已经被访问
bool check(int x,int y) //检查节点是否满足可以继续搜索的条件
{
	if(0<=x&&x<5&&0<=y&&y<5&&!vit[x][y]&&!maze[x][y])
		return true;
	return false;
}
void print(int x,int y)//输出答案的函数,用了DFS的思想
{
	if((x==0)&&(y==0))//从起点开始输出
		cout<<'('< q;
	struct path now;//当前状态
	int tx,ty;
	move[stax][stay].curx=stax;
	move[stax][stay].cury=stay;
	q.push(move[0][0]);//起始点入队
	vit[0][0]=1;//已经入队,将被访问,标记为1
	while(!q.empty())
	{
		now = q.front();	
		for(int i=0;i<4;++i)//依次走与当前点相邻的四个方向		
		{
			//更新节点状态
			tx=now.curx+dir[i][0];
			ty=now.cury+dir[i][1];
			if(check(tx,ty))
			{
				//相邻节点满足情况,则记录该节点的前一个节点为队首节点
				move[tx][ty].prex=now.curx;
				move[tx][ty].prey=now.cury;
				vit[tx][ty]=1;
				q.push(move[tx][ty]);//满足情况的节点入队
			}
			if(tx==4&&ty==4)//到达目的地
			{
			print(tx,ty);
			return ;
			}
		}
		q.pop();//队首元素出队,因为已经访问过了。
	}
	return ;
}
int main()
{
	for(int i=0;i<5;++i)
		for(int j=0;j<5;++j)
		{
			cin>>maze[i][j];
			move[i][j].curx=i;
			move[i][j].cury=j;
		}
	BFS(0,0);
}

BFS常用于求最短路问题,它是按照一层一层的访问,一层上的节点再对应下一层的相连接的点,所以BFS需要的内存比较大。
而DFS常用于求连通性问题,判断一个图是否连通,先一条路走到死,再回到起点换一条路走。DFS其实类似于栈(stack)。因为它需用多层递归,所以容易TLE。

以上为本人初学图论的内容,学习过程参考了多篇博文,以后再学深入后若有需要修改之处再修改。

你可能感兴趣的:(图论)