图
图
基本概念
图的定义
-
图由顶点集V和边集E组成,记G=(V,E)
V(G)表示图G中顶点的有限非空集。
E(G)表示图G中顶点间关系(边)集合。
E={(u,v)|u∈V,v∈V},用|E|表示图中边的个数。
-
若V={v1,v2,…,vn},则用|V|表示图G中顶点的个数,也称为图G的阶。
-
线性表可以是空表,树可以是空树,但图不可以是空图。
图中不可能一个顶点也没有,图的顶点集一定非空,但边集E可以为空,此时图中只有顶点没有边。
图和树的区别是逻辑上的区别,而不是边数的区别,图的边数也可能小于树的边数。
有向图
-
若E是有向边的有限集合时,则图G为有向图。
-
顶点集,弧集
-
弧是顶点的有序对,记为<v,w>
其中v,w是顶点,v称为弧尾,w称为弧头
<v,w>称为从顶点v到顶点w的弧,也称v邻接到w,或w邻接自v。
无向图
-
若E是无向边的有限集合时,则图G为有向图。
-
顶点集,边集
-
边是顶点的无序对,记为(v,w)或(w,v)
其中v,w是顶点,可以说顶点w和顶点v互为邻接点。
边(v,w)依附于顶点w和v,或者说边(v,w)和顶点v,w相关联
简单图
-
不存在重复边。
-
不存在顶点到自身的边
-
G1和G2均为简单图
完全图(简单完全图)
- 无向完全图:任意两个顶点之间都存在边。
- 含有n个顶点的无向完全图有n(n-1)/2条边。
- 有向完全图:任意两个顶点之间都存在方向相反的两条弧。
- 含有n个顶点的有向完全图有n(n-1)条有向边
子图
- 设有两个图G=(V,E)和G'=(V',E'),若V'是V的子集,且E'是E的子集,则称G'是G的子图。
- 若有满足V(G')=V(G)的子图G',则称其为G的生成子图
- 并非V和E的任何子集都能构成G的子图,这样的子集可能不是图,即E的子集中的某些边关联的顶点可能不在这个V的子集中。
连通图
-
v和w连通:从顶点v到顶点w有路径存在。
-
连通图:图中任意两个顶点都是连通的。
若一个图有n个顶点,并且边数小于n-1,此图必为非连通图。
连通图可能是树也可能存在环。
图的遍历要求每个结点只能被访问一次,若图非联通,则从某一顶点出发无法访问到其他全部顶点,
-
连通分量:无向图中的极大连通子图。
对无向连通图做一次深度优先搜索,可以访问到该连通图的所有顶点。
-
极大连通子图:无向图的连通分量。
-
极小连通子图:既要保持图连通又要使得边数最少的子图。
强连通图
-
强连通:有向图中,从顶点v到顶点w和从顶点w到顶点v之间都有路径。
-
强连通分量:有向图中的极大强连通子图。
-
强连通图和强连通分量只针对与有向图。
无向图中讨论连通性,有向图中考虑强连通性。
-
无向连通图中边最少构成一棵树。
无向连通图对应的生成树也是无向连通图,但此时边数等于顶点数减1。
强连通有向图,边最少构成一个有向环。
生成树、 生成森林
- 生成树:包含图中全部顶点的一个极小连通子图。
- 图中顶点数为m,则它的生成树含有n-1条边。
- 砍去生成树的一条边会变成非连通图,加上一条边会形成一个回路。
- 非连通图中,连通分量的生成树构成了非连通图的生成森林。
顶点的度、入度和出度
-
度:以该顶点为一个端点的边的数目。
-
无向图中的度:依附于顶点的边的条数,\(TD(v)\),
具有n个顶点、e条边的无向图中,\(\displaystyle \sum^{n}_{i=1}{TD(v_i)=2e}\),
即无向图的全部顶点的度的和等于边数的2倍。
-
有向图顶点v的度分别为入度和出度,
入度是以顶点v为终点的有向边的数目,记为\({ID(v_i)}\),
出度是以顶点v为起点的有向边的数目,记为\(OD(v)\)。
顶点v的度等于其入度和出度之和,即\(TD(v)=ID(v)+OD(v)\)
-
在具有n个顶点、e条边的有向图中,\(\displaystyle \sum^{n}_{i=1}{ID(v_i)=\displaystyle \sum^{n}_{i=1}{OD(v_i)}=e}\)
即有向图的全部顶点的入度和出度之和相等,并且等于边数。
边的权和网
-
带权图(网):边上带权值的图。
有向网,无向网
稠密图、稀疏图
- 稠密图:边数很多的图。
- 稀疏图:当图G满足\(|E|<|V|log|V|\)即\(e<nlogn\)
路径、路径长度和回路
- 路径:顶点\(V_p\)到顶点\(V_q\)之间的顶点序列。
- 路径长度:路径上边的数目。
- 第一个顶点和最后一个顶点相同的路径称为回路或环。
- 若一个图有n个顶点,并且有大于n-1条边,则此图一定有环。
简单路径,简单回路
- 简单路径:在路径序列中顶点不重复的路径。
- 简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。
距离
-
距离:从顶点v到顶点u的最短路径。
若根本不存在路径记为∞无穷
有向树
- 有向树:顶点的入度为0、其余顶点的入度均为1的有向图。
存储结构
顺序存储
邻接矩阵
- 用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
- 邻接矩阵中的元素仅表示相应的边是否存在时,EdgeType可定义为值为0和1的枚举类型。
- 对规模特大的邻接矩阵可采用压缩存储。
- 空间复杂度为\(O(n^2)\),其中n是图的顶点数|V|。
- 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。实际存储邻接矩阵只需存储上(或者下)三角矩阵的元素。
- 无向图的邻接矩阵第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的度\(TD(v_i)\)。
- 有向图的邻接矩阵第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的出度\(OD(v_i)\)或入度\(ID(v_i)\)。
- 优点:便于确定图中任意两个顶点之间是否有边相连。
缺点:①确定边的总数费时间,须按行按列对每个元素进行检测。 - 稠密图适合用邻接矩阵的存储表示。
- 设图G的邻接矩阵为\(A\),\(A^n\)的元素\(A^n[i][j]\)等于由顶点i到顶点j的长度为n的路径的数目。
链式存储
邻接表法
-
顺序存储和链式存储。
-
对比树的孩子表示法
-
对图G中每个顶点\(v_i\)建立一个单链表,第i个单链表中的结点表示依附于顶点\(v_i\)的边(对于有向图则是以顶点\(v_i\)为尾的弧)的单链表称为边表。
-
边表的头指针和顶点的数据信息采用顺序存储(称为顶点表)
邻接表中存在两种结点:顶点表结点和边表结点。
data firstarc 顶点域 边表头指针 【顶点表】
adjvex nextarc 邻接点域 指针域 【边表 】
-
顶点表结点由顶点域和指向第一条邻接边的指针构成,
边表结点由邻接结点域和指向下一条邻接边的指针域构成。
| 邻接矩阵 | 邻接表 | |
|---|---|---|
| 存储方式 | 顺序存储 | 顺序存储+链式存储 |
| 应用 | 稠密图 | 稀疏图 |
| 表示方式 | 唯一 | 不唯一 |
| 广度优先遍历 | 唯一 | 不唯一 |
| 节点数量 | 无向图:$2 | |
| 空间复杂度 | $O( | V^2 |
| 时间复杂度 | $O( | V^2 |
| 计算度 | 必须遍历对应行或列 | 计算有向图的度和入度不方便 |
| 找相邻边 | 必须遍历对应行或列 | 找有向图的入边不方便 |
十字链表法
- 存储有向图
- O(|V|+|E|)
邻接多重表
- 存储无向图
- O(|V|+|E|)
基本操作
-
Adjacent(G,x,y):判断图G是否存在有向边<x,y>或无向边(x,y)。
-
Neighbors(G,x):列出图G中与结点x邻接的边。
-
InsertVertex(G,x):在图G中插入顶点x。
-
DeleteVertex(G,x) :从图G中删除顶点x。
-
AddEdge(G,x,y):若(x,y)或<x,y>不存在,则向图中添加该边。
-
RemoveEdge(G,x,y):若(x,y)或<x,y>存在,则从图中删除该边。
-
FisrtNeighbor(G,x,y):求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1.
-
NextNeighbor(G,x,y):假设图中顶点y是顶点x的一个邻接点,返回y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,返回-1.
-
Get_edge_value(G,x,y):获取图G中(x,y)或<x,y>的对应权值。
-
Set_edge_value(G,x,y,v):设置图G中(x,y)或<x,y>的对应权值。
| 邻接矩阵 | 邻接表 | |
|---|---|---|
| Adjacent(G,x,y) | O(1) | O(1)--O(|V|) |
| Neighbors(G,x) | O(|V|) | O(1)--O(|V|) |
| 有向图入边:O(|E|) | ||
| InsertVertex(G,x) | O(1) | O(1) |
| DeleteVertex(G,x) | O(|V|) | O(1)--O(|E|) |
| 出边:O(1)--O(|V|) 入边:O(|E|) | ||
| AddEdge(G,x,y) | O(1) | O(1) |
| RemoveEdge(G,x,y) | O(|V|) | O(|E|) |
| FirstNeighbor(G,x,y) | O(1)--O(|V|) | O(1) |
| 出边:O(1) 入边:O(1)--O(|E|) | ||
| NextNeighbor(G,x,y) | O(1)--O(|V|) | O(1) |
| Get_edge_value(G,x,y) | O(1) | O(1)--O(|V|) |
| Set_edge_value(G,x,y,v) | O(1) | O(1)--O(|V|) |
图的遍历
广度优先遍历(BFS)
与树的广度优先(层序)遍历之间的联系
| 树 | 图 | |
|---|---|---|
| 从根结点出发, 横向查找所有子结点 | 从某一结点出发,查找有关联的结点 | |
| 不存在回路 | 有回路 |
算法实现
①找到一个顶点相邻的所有顶点
②标记哪些顶点被访问过
③需要一个辅助队列
//无向图
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSTraverse(Graph G){ //对图G进行广度优先遍历
for(i=0;i<G.vexnum;i++)
visited[i]=FALSE; //访问标记数组初始化
INitQueue(Q); //初始化辅助队列Q
for(i=0;i<G.vexnum;++i) //从0号顶点开始遍历
if(!visited[i]) //对每个连通分量调用一次BFS
BFS(G,i); //vi未访问过,从vi开始BFS
}
//广度优先遍历
void BFS(Graph G,int v){ //从顶点v出发,广度优先遍历图G
visit(V); //访问初始顶点v
visited[v]=TRUE; //对v做已访问标记
Enqueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)){
DeQueue(Q,v); //顶点v出队列
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
//检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接顶点
visit(w); //访问顶点w
visited[w]=TRUE; //对w做已访问标记
EnQueue(Q,w); //顶点w入列
}//if
}//while
}
//对于无向图,调用BFS函数的次数=连通分量数
//有向图
广度优先生成树
广度优先生成树由广度优先遍历过程确定。由于邻接表的表示方式不唯一,因此基于邻接表的广度优先生成树也不唯一。
对非连通图的广度优先遍历,可得到广度优先生成森林。
深度优先遍历(DFS)
与树的深度优先遍历之间的联系
//树的先根遍历
void PreOrder(Tree *R){
if(R!=NULL){
visit(R);
while(R还有下一个子树T)
}
}
//
void DFSTraverse(Graph G){
for(v=0;v<G.vexnum;++v)
visited[v]=FALSE; //初始化
for(v=0;v<G.vexnum;++v)
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v){
visit(V); //访问顶点v
visited[v]=TRUE; //已访问顶点
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w]){ //w尚未访问临界顶点
DFS(G,w);
}
}
图的遍历和图的连通性
对与强连通图,从任意节点出发都只需调用一次BFS/DFS
最小生成树
所有地方都联通,且成本尽可能的低
最小生成树可能有多个,但边的权值之和总是唯一且最小的
最小生成树的边数=顶点数-1。砍掉一条则不连通,增加一条则出现回路。
如果连通图本身就是一棵树,最小生成树就是它本身。
| 算法 | bfs | dfs | Kruskal | Prime | Dijkstra | floyd |
|---|---|---|---|---|---|---|
| dijkstra;prime | 每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的不选)直到所有结点都联通。 | 从某个顶点开始构建生成树,每次将代价最小新顶点纳入生成树,直到所有顶点都纳入。 | 从某个顶点开始,寻找到其他顶点的最小路径,每找到一个最短路径就更新其他路径距离,直到所有顶点距离都最小。 | 求出每一对顶点之间的的最短路径,其间允许中转的顶点数逐次增加 | ||
| 时间复杂度 | \(O(|V^2|)\) | \(O(|E|log_2|E|)\) | \(O(|V^2|)\) | \(O(|V^2|)\) | \(O(|V^3|)\) | |
| 适用类型 | 无权图 | 稀疏图 | 稠密图· | 非负权值 | 非负权值 | |
| 最短路径 | 单源最短路径 | 总路径最短 | 总路径最短- | 单源最短路径 | 各顶点间最短路径 | |
| 最小生成树 | 最小生成树 |
最短路径
单源最短路径
BFS
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(Graph G,int u){
//d[i]表示从u到i结点的最短路径
for(i=0;i<G.vexnum;++i){
d[i]=∞; //初始化路径长度
path[i]=-1; //最短路径从哪个顶点过来
}
d[u]=0;
visited[u]=TRUE;
EnQueue(Q,u);
while(!isEmpty(Q)){
DeQueue(Q,u); //队头元素u出队
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
if(!visited[w]{ //w为u的尚未访问的邻接顶点
d[w]=d[u]+1; //路径长度+1
path[w]=u; //最短路径u到v,记录前驱顶点
visited[w]=TRUE;//设已访问标记
EnQueue(Q,w); //顶点w入队
}//if
}//while
}
AOV网
用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边表示活动进行顺序
拓扑排序
定义Ⅰ:找到做事的先后顺序
实现:1.从AOV网中选择一个没有前驱的顶点并输出
2.从网中删除该顶点和所有以它为起点的有向边。
3.重复1和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。
定义Ⅱ:由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑序列:①每个顶点出现且只出现一次。②若每个顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。
定义Ⅲ:对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV网都有一个或多个拓扑排序序列。
#define MaxVertexNum 100
typedef struct ArcNode{ //边表结点
int adjvex;
struct ArcNode *nextarc;
}ArcNode;
typedef struct VNode{ //顶点表
VertexType data;
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
}Graph; //以邻接表存储的图类型
bool TopologicalSort(Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for(int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
Push(S,i); //将入度为0的顶点进栈
int count=0; //计数,记录当前已经输出的顶点数
while(!IsEmpty(S)){ //栈不空,则入度为0的顶点
Pop(S,i); //栈顶元素出栈
print[count++]=i; //输出顶点i
for(p=G.vertices[i].firstarc;p;p=p->nextarc){
//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈
v=p->adjvex;
if(!(--indegree[v]))
Push(S,v); //入度为0
}
}
if(count<G.vexnum)
return false;
else
return true;
}

浙公网安备 33010602011771号