图的定义
-
图G是一个有序二元组(V,E),其中V称为顶点集(Vertices Set),E称为边集(Edges set),E与V不相交。它们也可写成V(G)和E(G)。E的元素都是二元组,代表一条边的两个端点,用(x,y)表示,其中x,y∈V
-
图G是一个三元组(V,E,I),其中V称为顶集,E称为边集,I称为关联函数。I将E中的每一个元素映射到 V×V。如果e被映射到(u,v),那么称边e连接顶点u,v,而u,v则称作e的端点,u,v此时关于e相邻。同时,若两条边i,j有一个公共顶点u,则称i,j关于u相邻
图的概念
子图(Sub-Graph):这个概念类似于集合的子集。当图G’=(V’,E’)其中V’包含于V,E’包含于E,则G’称作图G=(V,E)的子图,每个图都是本身的子图
度(Degree):一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。通常对于不包含自环的图来说,所有点度的和等于边数的二倍,即∑v∈V d(v) = 2*E。对于有向图来说,指向某点的边数之和称为该点的入度,以该点为起点的边数之和称为该点的出度
路径(Path):从顶点 s 到顶点 t 的一条路径是指一个序列 v0, e1, v1, e2, v2, …ek , vk,ei 的起点终点为 vi−1及 vi;k 称作路径的长度;v0 = s,称为路径的起点;vk = t,称为路径的终点。如果 s = t,称该路径是闭的,反之则称为开的
回路(Circuit):闭的路径
图的常见分类
无向图(Undirected graphs):图中任意两个顶点之间的边都是无向边,即E(u,v) = E(v,u)
有向图(Directed graphs):图中任意两个顶点之间的边都是有向边,即E<u,v> ≠ E<v,u>
有权图:图的边上都带有权值
无权图:图的边上没有权值
简单图:图中不存在顶点到其自身的边,且同一条边不重复出现
无向完全图:无向图中,任意两个顶点之间都存在边。若顶点为n个,则边数为n(n-1)/2
有向完全图:有向图中,任意两个顶点之间都存在方向互为相反的两条弧。若顶点为n个,则边数为n(n-1)
稀疏图:有很少条边
稠密图:有很多条边
图的存储方式
1.邻接矩阵
存储:int G[N][N](声明 N × N 的数组)
内容:G[i][j] 表示节点 i 到 j 的边权,通常我们依据题目背景选定一个值来作为无边的标识,如采用 0 或 ±∞
性质:有向图中,G[i][j] ≠ G[j][i],无向图中,G[i][j] = G[j][i]
优点:适合稠密图,易于实现、维护
缺点:空间复杂度 O(V2) 太高,且对重边的处理有较大局限性
2.邻接表
存储:链表或 vector 数组,一般使用vector数组vector<edge> G[N];
内容:存下每个点的出边
优点:复杂度为 O(V+E),能存重边,易于操作邻边集
缺点:实现略微复杂,访问和修改会慢一点,因为找每个点的某个邻接点时需要线性遍历
下图以有向图的邻接表为例:
struct edge{
int from,to,w; //from始点,to终点,w权值
edge(){}
edge(int a,int b,int c){ from=a; to=b; w=c; }
};
vector<edge> G[N];
int main(){
//初始化
for(int i=1;i<=n;i++) G[i].clear();
//存边
G[?].push_back(edge(a,b,c));
return 0;
}
3.链式前向星
图的连通性
连通图
无向图 G 中,若对任意两点i,j,从顶点 Vi 到顶点 Vj有路径,则称 Vi和 Vj 是连通的,则图 G 是连通图
强连通图
有向图 G 中,若对任意两点,从顶点 Vi 到顶点 Vj,都存在从 Vi 到 Vj 以及从 Vj 到 Vi 的路径,则称 G 是强连通图
判断连通性
无向图:由于无向图的边是双向的,如果存在路径 Vi 到 Vj,那么必然有路径 Vj 到 Vi。因此,对任意一个点判断其连通性,若其能与该图中所有其它点连通,则该图为连通图。跑一遍 DFS 或 BFS 都可以,此外还可以用并查集判断
有向图:使用点的连通性的判断方法,对每个点都判断其连通集合是否为该点外所有的点的集合即可
图的遍历
DFS
vector<int> G[N];
bool vis[N];
void DFS(int v){
//省略了边界特判
vis[v]=true;
for(int i = 0;i<G[v].size();i++){
if(!vis[G[v][i]])
dfs(G[v][i]);
}
//省略了处理
}
BFS
vector<int> G[N];
queue<int> q;
bool vis[N];
void BFS(int s){
vis[s]=true;
q.push(s);
int t;
while(!q.empty) {
t=q.front();
q.pop();
for (int i=0;i<G[t].size();i++) {
if (!vis[G[t][i]]){
vis[G[t][i]]=true;
q.push(G[t][i]);
//省略了处理
}
}
}
}