一、图的基本要素
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。
以上为本人初学图论的内容,学习过程参考了多篇博文,以后再学深入后若有需要修改之处再修改。