数据结构第六章-图 小结


1.图的术语与定义
1.1图的定义
顶点ii的出度:第ii行1的个数。顶点ii的入度,第ii列1的个数。

图由顶点集V(G)和边集E(G)组成,记为G=(V,E)。其中E(G)是边的有限集合,边是顶点的无序对(无向图)或有序对(有向图)。

对有向图来说,E(G)是有向边(也称弧(Arc))的有限集合,弧是顶点的有序对,记为<v,w><v,w>,v、w是顶点,v为弧尾(箭头根部),w为弧头(箭头处)。

对无向图来说,E(G)是边的有限集合,边是顶点的无序对,记为(v, w)或者(w, v),并且(v, w)=(w,v)。

1.2顶点的度、入度、出度
顶点v的度:与v相关联的边的数目;

顶点v的出度:以v为起点有向边数;

顶点v的入度:以v为终点有向边数。

1.3路径与回路
简单路径:序列中顶点不重复出现的路径

简单回路:序列中第一个顶点和最后一个顶点相同的路径

1.4连通图(强连通图)
在无(有)向图中,若对任何两个顶点v、u都存在从v到u的路径,则称图G为连通图(强联通图)。

极大连通子图:该子图是G连通子图,将G的任何不在该子图的顶点加入,子图将不再连通。

极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图都将不再连通。

无向图G的极大连通子图称为G的连通分量。

有向图D的极大强连通子图称为D的强连通分量。

包含无向图G的所有顶点的极小连通子图称为G生成树

若T是G的生成树当且仅当T满足:T是G的连通子图、T包含G的所有顶点、T中无回路。

2.图的存储
2.1邻接矩阵
顶点ii的出度:第ii行1的个数。顶点ii的入度,第ii列1的个数。

typedef enum {DG, DN, UNG, UDN} GraphKind;//有向图,有向网,无向图,无向网
typedef struct ArcCell {
VRType adj;//无权图,用0,1表示;带权图,用权值类型表示
InfoType *info;//弧相关信息的指针
}ArcCell, AdjMatrix[maxn][maxn];
typedef struct {
VertexType vexs[maxn];//顶点信息
AdjMatrix arcs;//建立邻接矩阵
int vexnum, arcnum;//图的当前顶点数和弧数
GraphKind kind;
}MGraph;

2.2邻接表
无向图的邻接表
typedef struct ArcNode {//一般结点
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *nextarc;//链域,指向下一条边或者弧
}ArcNode;
typedef struct tnode{//表头结点
int vexdata;//存放顶点信息
ArcNode *firstarc;//指向第一个邻接点
}VNode, ADjList[maxn];
typedef struct{//最终建立邻接表
ADjList vertices;
int vexnum, arcnum;
int kind;
}ALGraph;

无向图采用邻接表存储的特点:

在G连接表中,同一条边对应两个结点。顶点v的度,等于 v对应线性链表的长度。在G中增减边,需要在两个单链表中插入、删除结点。

设存储顶点的一维数组大小为mm(顶点数m>顶点数n顶点数m>顶点数n),图的边数为ee,则图G占用存储空间大小为m+2∗em+2∗e。适用于边稀疏的图。

有向图的邻接表
有向图的邻接表:以同一顶点作为起点的弧。出边表。ee个边结点。

有向图的逆邻接表:以同一顶点作为终点的弧。入边表。ee个边结点。

2.3有向图的十字链表表示法
typedef struct ArcBox{//建立弧结点
int tailvex, headvex;//弧头、弧尾在表头数组中位置
struct arcnode *hlink;//指向弧头相同的下一条弧
struct arcnode *tlink;//指向弧尾相同的下一条弧
}ArcBox;
typedef struct VexNode{//顶点结点
VertexType data;
ArcBox *firstin;//指向以该顶点为弧头的第1个弧结点
ArcBox *firstout;//指向以该顶点为弧尾的第1个弧结点
}VexNode;
VexNode OLGraph[M];

之前的邻接表都是弧尾相同,弧尾即箭头根部。

2.4无向图的邻接多重链表表示法
注意一下与上图的差别,在于顶点结点的指针数量。

typedef struct node {//弧结点
VisitIf mark;//标志域,记录是否已经搜索过
int ivex, ijex;//该边依附的两个顶点在表头数组中位置
struct EBox *ilink, *jlink;//分别指向依附于ivex和ijex的下一条边
}EBox;
typedef struct VexBox{//顶点结点
VertexType data;//存与顶点有关的信息
EBox *firstedge;//指向第一条依附于该顶点的边
} VexBox;
VexBox AMLGraph[M];

3.图的遍历
3.1深度优先遍历(Depth First Search)
从图的某顶点v出发,进行深度优先遍历:

访问顶点v
对于v的所有邻接点w1、w2、w3…w1、w2、w3… ,若wiwi没有被访问,则从wiwi出发进行深度优先遍历。
由于没有规定访问邻接点的顺序,所以深度优先序列不唯一。

void DFSTraverse(Graph G, void(*visit)(VertexType e))//对G做深度优先遍历
{
for(v = 0; v < G.vexnum; v++)
visited[v] = flase;
for(v = 0; v < G.vexnum; v++){
//注意:若图为非连通图,只有这样才能完全深度优先遍历;若为连通图,则可以用一个DFS函数
if(!visited[v])
DFS(G, v, Visit);
}
}
void DFS(Graph G, int v, void(*Visit)(VertexType e))//对每个顶点做深度优先遍历
{
visited[v] = true;
Visit(v);
for(w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)){
if(!visited[w])
DFS(G, w, Visit);
}
}

时间复杂度:

若图为邻接表表示为O(n+e)O(n+e),若为邻接矩阵表示为O(n+n2)=O(n2)O(n+n2)=O(n2) 。

3.2广度优先遍历(Breadth First Search)
从图中某顶点v出发:

访问顶点v
访问顶点v所有未被访问的邻接点、w1、w2…wn、w1、w2…wn,并用栈或队列存储
依次取出邻接点进行广度优先遍历
深度优先遍历是回溯算法,广度优先遍历时一种分层的顺序搜索过程,不是递归。

void BFSTraverse(Graph G, void(*visit)(VertexType))//对G做广度优先遍历
{
for(v = 0; v < G.vexnum; v++)
visited[v] = false;
for(v = 0; v < G.vexnum; v++){
if(!visited[v])
BFS(G, v, Visit);
}
}
void BFS(Graph G, int v, void(*Visit)(VertexType e))
{
initqueue(Q);//在把每一个结点放到队列中前,先把它标记和处理
visited[v] = true;
Visit(v);
push(v);
while(!empty(Q)){
v = pop(Q);
for(w = FirstAdjvex(G, v); w >= 0; w = NextAdjvex(G, v, w)){
if(!visited[w]){
visited[w] = true;
Visit(w);
push(w);
}
}
}
}

3.3遍历的应用
求一条从顶点v到顶点s的简单路径
方法:从v开始深度优先遍历,直到找到s。在对v深度优先遍历的过程中,首先将v添加到路径中,判断v的值是否为s,如果为s,返回真。如果不为s,则对v的邻接点进行递归,直到找到s为止。如果没找到,则将v从路径中删除,返回失败。

Status _DFSearch(Graph G, int v, VertexType s, SqList &Path)//递归函数
{
visited[v] = true;
ListAppend(v, Path);//将v添加到路径中
for(w = FirstAdjvex(G, v); w >= 0; w = NextAdjvex(G, v, w)){//判断是否有路
if(!visited[w]){
if(_DFSearch(G, w, s, Path)) return true;
}
}
ListDelete(v, Path);//将v从路径中删除
return False;
}

求两个顶点之间的一条路径长度最短的路径
由于广度优先搜索有路径渐增的性质,所以使用广度优先搜索,搜索到终点的路径即为最短路径。应该会需要用一个数组记录每个节点的父节点,便于还原路径。

4.图的最小生成树
4.1最小生成树的概念
包含无向连通图G所有n个顶点的极小连通子图称为G的生成树。

生成树的特点:T是G的连通子图;T包含G的所有顶点;T中无回路;T中有n-1条边。

权之和最小的生成树为最小生成树。

MST(Minimum Spanning Tree)性质: 若U集是V的一个非空子集,若(U0U0, V0V0)是一条权值最小的边,其中U0∈UU0∈U,V0∈V−UV0∈V−U,则:(U0U0, V0V0)必在最小生成树上。

4.2 Prim算法:将顶点归并,与边数无关,适合稠密网 O(n2)O(n2)
设G=(V,GE)为一个具有n个顶点的连通网络,T=(U,TE)为构造的生成树。

初始时,U={u0u0},TE为空集
在所有的u∈Uu∈U且v∈V−Uv∈V−U的边(u,v)中选择一条权值最小的边(u,v)
将(u,v)加入TE,同时将v加入U
重复23步,直到U=V为止
//辅助数组closege[],对当前V-U集中的每个顶点,记录与顶点集U中顶点相连接的代价最小的边。
struct{
VertexType Adjvex;//顶点v到子集U中权最小边关联的顶点u
VRType lowcost;//顶点v到子集U中权最小边的权值
}closedge[maxn];
void MiniSpanTree_P(MGraph G, VertexType u)//从u出发构造G的最小生成树
{
k = LocateVex(G, u);
for(j = 0; j < G.vexnum; ++j){//辅助数组初始化
if(j != k)
closedge[j] = {u, G.arcs[k][j]};//各点到u的距离
}
for(i = 0; i < G.vexnum; i++){
k = minimum(closedge);//选择距离最小且不为0的作为k
printf(closedge[k].Adjvex, G.vexs[k]);//输出k对应的顶点和新加入的边权值
closedge[k].lowcost = 0;//加入新的点
for(j = 0; j < G.vexnum; j++){
if(G.arcs[k][j] < closedge[j].lowcost)//更新V-U中点到U中点的距离
closedge[j] = {G.vexs[k], G.arcs[k][j]};
}
}
}

4.3 Kruskal算法:将边归并,适用于求稀疏网的最小生成树 O(eloge)O(eloge)
初始时最小生成树值包含图的n个顶点,每个顶点为一棵子树
选取权值较小且所关联的两个顶点不再同一连通分量的边,将此边加入最小生成树中
重复第二步n-1次,即得到包含n个顶点和n-1个条边的最小生成树
int find(int n)//找到点的树根
{
while(n != find[n])
n = find[n];
return n;
}
void join(int n, int m)//将两棵树并起来
{
int fn = find(n);//一定要找到两棵树的树根再并到一起
int fm = find(m);
find[fn] = fm;
}
int kruskal(int n, int m)
{
int num = 0;
for(i = 1; i <= n; i++)//初始化寻根函数
find[i] = i;
for(i = 0; i < m; i++){//此时A中边已经全部从小到大排好顺序
if(find(A[i].u) != find(A[i].v)){//如果边的顶点不属于一棵树
join(u, v);//并到一棵树
cost += A[i].w;
num++;//通过最后num是否达到n-1来判断是否有最小生成树
}
}
}

5.有向无环图及其应用
对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系,由此所得顶点的线型序列被称之为拓扑有序序列。

AOV网(Activity On Vertex network)是以顶点表示活动,弧表示活动之间的先后关系的有向图。AOV网中不允许有回路,不允许某项活动以自己为先决条件。

检测AOV网是否存在环:对有向图构造顶点的拓扑有序序列,若图中所有顶点都在该序列中,则AOV网必定不存在环。

AOE网(Activity On Edge)用边表示活动的网。它是一个带权的有向无环图。

AOE网中重要概念:

顶点表示时间,弧表示活动
权值:活动持续的时间
路径长度:路径上各活动的持续时间之和
关键路径:路径长度最长的路径叫作关键路径
关键活动:该弧上的权值增加将使有向图上的最长路径的长度增加
AOE网和AOV网的区别:

一个用顶点表示活动,一个用边表示活动
AOV网侧重表示活动的前后次序,AOE网除了表示活动的前后次序,还表示了活动的持续时间等。
AOV网或AOE网构造拓扑序列的方法:

在有向图中选一个没有前驱(入度为0)的顶点并输出它。
从图中删除该顶点和所有以它为尾的弧。(弧头顶点的入度减一)
重复上述两步,直至全部顶点均已输出。
数据结构:

邻接表存储图
数组记录顶点当前的入度
栈记录入度为0的点
6.最短路径
6.1 Dijkstra算法求单源最短路径
方法:S=v0S=v0,其余顶点T=其余顶点u其余顶点T=其余顶点u
从T中选择一个未标记的权值最小的顶点w,将w加入S,并对w进行标记。若所有点都被标记,则结束。
考察T中的所有顶点,对路径进行松弛
void SPath_Dij(MGraph G, int u0, int dis[], int Path[])
{
for(i = 0; i < G.vexnum; i++){
visited[i] = flase;
dis[i] = G.arcs[u0][i];
if(dis[i] < INFINITY) Path[i] = u0;//path用来记住前一个结点
else Path[i] = -1;
}
dis[u0] = 0; visited[u0] = true;
for(i = 0; i < G.vexnum; i++){
k = selectmin(dis);
visited[k] = true;//注意k此处应该被标记
for(j = 0; j < G.vexnum; j++){
if(dis[j] > dis[k] + G.arcs[k][j] && !visited[j]){
//选出的j不应该被访问过
dis[j] = dis[k] + G.arcs[k][j];
Path[j] = k;//注意对Path进行更新
}
}
}
}

无向图和有向图都适用,弧的权值必须非负。

6.2 Floyd算法求每对顶点间最短路径
当然也可以重复执行Dijkstra算法n次。

弗洛伊德算法:从vivi到vjvj所有可能存在的路径中,选出一条长度最短的路径。

方法:

初始设置一个n阶方阵,令其对角线元素为0,若存在弧<vi,vj><vi,vj>,则对应元素为权值,否则为无穷。
逐步试着在原直接路径中增加中间顶点,若加入中间点后路径变短,则修改。否则,维持原值。
所有顶点试探完毕,算法结束。
//核心算法类似于动态规划
for(i = 0; i < G.vertex; i++){
for(j = 0; j < G.vertex; j++){
for(k = 0; k < G.vertex; k++){
if(A[i][j] > A[i][k] + A[k][j]){
A[i][j] = A[i][k] + A[k][j];
path[i][j] = k;//记录分段点
}
}
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值