图的概念
图是由顶点与边构成的集合(V,E),V是所有顶点的集合,E是所有边的集和。
有向图
图中的边是具有方向性的,只能按箭头方向从一点到另一点。我们把以这个顶点为起点的有向边的数目称作该顶点的出度,把以这个顶点为终点的有向边的数目称作该顶点的入度。
无向图
图中的边无方向性,可以由任意方向从一点到另一点。我们把与该顶点相连的边的数目称作该顶点的度。
带权图
图中的边带有权值,可以理解为从一个顶点到与它相连顶点的距离。
图的存储
邻接矩阵
存储图的最简单的方式就是利用二维数组g[i][j],i表示边的起点,j表示边的终点,值表示该边的权值,当两条边不直接相连时权值设置为正无穷。
#include<iostream>
#include<cstring>
using namespace std;
const int N = 10000;
int g[N][N];
int main() {
//以0x7f7f7f7f作为正无穷,memset是修改每个字节上的数据,int为4字节,每个字节修改为0x7f,合起来就是0x7f7f7f7f
memset(g, 0x7f, sizeof(g));
int n, m;//顶点数,边数;
cin >> n >> m;
while (m--) {
int u, v, w;//起始顶点,到达顶点,边权
cin >> u >> v >> w;
//无向图,两个方向都要赋值
g[u][v] = w;
g[v][u] = w;
//如果是有向图,只用赋值连通的方向
}
//打印图
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(g[i][j]!=0x7f7f7f7f)
cout<<i<<"到"<<j<<"的距离为"<<g[i][j]<<endl;
}
}
return 0;
}
邻接表
领接表保存所有的顶点,同时对每个顶点,保存其所有的边,每个顶点的所有边结点以链表形式组织起来,如果是带权图,边结点还要记录权值信息。
以上文图的概念中给出的无向图为例,其用邻接表表示如下:
实际代码编写中可以使用vector来简化编码过程:
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N = 150;
//邻接表
struct Edge{
int to,dis;
};
vector<Edge> head[N];
//添边
void addEdge(int from,int to,int dis){
Edge edge;
edge.to=to;
edge.dis=dis;
head[from].push_back(edge);
}
int main(){
int n,m;
scanf("%d%d", &n, &m);
int u,v,w;
//邻接表建图
for(int i=1;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
//无向图,两个方向都要添边
addEdge(u,v,w);
addEdge(v,u,w);
//如果是有向图,只用添连通方向的边
}
//打印图
for(int i=1;i<=n;++i){
for(int j=0;j<head[i].size();++j){
Edge edge=head[i][j];
printf("顶点%d到顶点%d的距离为%d",i,edge.to,edge.dis);
}
}
return 0;
}
十字链表
十字链表是为了解决有向图用邻接表存储时不便于计算顶点的入度,因此需要在邻接表的基础上额外保存入边。以上文图的概念中给出的有向图为例,其用十字链表表示如下:
邻接多重表
在邻接多重表表示下,无向图中每条边不需要两个方向都添加一次,因为每个边结点都保存了两个方向的信息。假设现要保存一条从顶点i到顶点j的边,则边结点要保存顶点i并指向下一条与i相连的边结点,同时要保存顶点j并指向下一条与j相连的边结点。以上文图的概念中给出的无向图为例,其用邻接多重表表示如下:
图的遍历
深度优先
深搜会优先沿某一分支一直搜到底,然后回溯到上一次出现分支的地方沿另一分支搜索,对于上面的图进行深度优先遍历,可能的一种结果为:ABCDEF GH I。
假设起点为A,从A出发,选择结点B所在分支,继续选择结点C所在分支,继续选择结点D所在分支,继续选择结点E所在分支,继续选择结点F所在分支,此时选择结点A所在分支发现重复,回退到结点F,选择结点G所在分支,此时选择结点B所在分支发现重复,回退到结点G,选择结点H所在分支,此时选择结点D所在分支发现重复,回退到结点H,继续选择结点E所在分支,仍然重复,回退到结点H,此时结点H已无分支可选,回退到结点G,同理结点G也无分支可选,回退到结点F,回退到结点E,回退到结点D,此时选择结点I所在分支,此后结点I无分支可选,一直回退到起点A都无新分支可选,深搜结束。
搜索结果为:ABCDEF GH I
广度优先
广搜会优先搜索该结点所有可能的分支(不会重复搜索已经遍历过的结点),对于上面的图进行广度优先遍历,可能的一种结果为:A BF CIGE DH。
若起点为A:
将起点A放入队列; q:A
取出A,A能扩展出B、F,BF入队; q:BF
取出B,B能扩展出C、I、G,CIG入队; q:FCIG
取出F,F能扩展出E,E入队; q:CIGE
取出C,C能扩展出D,D入队; q:IGED
取出I,I能扩展出的结点此前已经被扩展过,无需重复入队; q:GED
取出G,G能扩展出H,H入队; q:EDH
取出E,E能扩展出的结点此前已经被扩展过,无需重复入队; q:DH
取出D,D能扩展出的结点此前已经被扩展过,无需重复入队; q:H
取出H,q为空,遍历完成;
遍历结果为:A BF CIGE DH。