数据结构 学习总结7 图

概念

特点:非线性结构,是研究数据元素之间的多对多的关系。在这种结构中,任意两个元素之间可能存在关系。即结点之间的关系可以是任意的,图中任意元素之间都可能相关。
图的应用极为广泛,已渗入到诸如语言学、逻辑学、物理、化学、电讯、计算机科学以及数学的其它分支。
图:记为 G=( V, E )
其中:V 是G的顶点集合,是有穷非空集;E 是G的边集合,是有穷集。
问:当E(G)为空时,图G存在否?
答:还存在!但此时图G只有顶点而没有边。
图G中的每条边都是有方向的;有相图
图G中的每条边都是无方向的;无向图
图G任意两个顶点都有一条边相连接;完全图

若 n 个顶点的无向图有 n(n-1)/2 条边, 称为无向完全图
若 n 个顶点的有向图有n(n-1) 条边, 称为有向完全图

①完全无向图有n(n-1)/2 条边。
证明:若是完全无向图,则顶点1必与所有其他顶点各有1条连线,即有n-1条边,顶点2有n-2条边,…,顶点n-1有1条边,顶点n有0条边.
总边数= n-1+ n-2+…+1+0=(n-1+0)n/2= n(n-1)/2

② 完全有向图有n(n-1)条边。
证明:若是完全有向图,则顶点1必与所有其他顶点各有2条连线,即有2(n-1)条边, 顶点2有2(n-2)条边,…,顶点n-1有2条边,顶点n有0条边.
总边数=2( n-1+ n-2+…+1+0)=2(n-1+0)n/2= n(n-1)

稀疏图:边较少的图。通常边数<<n2

稠密图: 边很多的图。无向图中,边数接近n(n-1)/2 ;
有向图中,边数接近n(n-1)

子图:设有两个图 G=(V, E) 和 G’=(V’, E’)。若 V’  V 且 E’ E, 则称 图G’ 是 图G 的子图。
带权图(数据结构里也称之为网络):即边上带权的图。其中权是指每条边可以标上具有某种含义的数值(即与边相关的数)。

连通图
强连通图
以下两种图不讨论
在这里插入图片描述

问:当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?
答:是树!而且是一棵有向树!

生成树:是一个极小连通子图,它含有图中全部顶点,但只
有n-1条边。
■ 如果在生成树上添加1条边,必定构成一个环。
■ 若图中有n个顶点,却少于n-1条边,必为非连通图。

生成森林:由若干棵生成树组成,含全部顶点,但构成这些
树的边是最少的。

图的类型定义

ADT Graph {
数据对象V:v是具有相同特性的数据元素的集合,称为顶点集。
数据关系R:R={VR};VR={<v,w>|v,w∈V 且 P(v,w),
<v,w>表示从v到w的弧,
谓词P(v,w)定义了弧<v,w>的意义或信息}
基本操作P:
CreatGraph ( &G, V,VR);
初始条件:V是图的顶点集,VR是图中弧的集合。
操作结果:按V和VR的定义构造图G。
InsertVex ( &G, v);
初始条件:图G存在,v和图中顶点有相同特征。
操作结果:在图G中添加新顶点。
………………()
}

图的存储结构

顺序存储结构:无!!
(多个顶点,无序可言)
但可用数组描述元素间关系。

链式存储结构:可用多重链表

邻接矩阵
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)。
设图 A = (V, E) 有 n 个顶点,则图的邻接矩阵是一个二维数组 A.Edge[n][n],定义为:
在这里插入图片描述

分析1:无向图的邻接矩阵是对称的;
分析2:顶点i 的度=第 i 行 (列) 中1 的个数;
特别:完全图的邻接矩阵中,对角元素为0,其余全1。

分析1:有向图的邻接矩阵可能是不对称的。
分析2:顶点的出度=第i行元素之和,OD( Vi )= A.Edge[ i ][j ]
顶点的入度=第i列元素之和。ID( Vi )= A.Edge[ j ][i ]
顶点的度=第i行元素之和+第i列元素之和,
即:TD(Vi)=OD( Vi ) + ID( Vi )

注:用两个数组分别存储顶点表和邻接矩阵
#define INFINITY  INT_MAX             //最大值∞
#define MVNUM      20  //假设的最大顶点数

Typedef struct   //图的定义
{
     VertexType vexs [MAX_VERTEX_NUM ] ;   //顶点表,用一维向量即可
     ArcType arcsAdjMatrix [MVNUM ] [MVNUM ];           //邻接矩阵
     int   Vernum,   arcnum;                                //顶点总数,弧(边)总数
}AMgraph;       

用邻接矩阵生成无向网的算法

Status CreateUDN(AMGraph &G)  //无向网的构造,用邻接矩阵表示
{   cin>>G.vexnum>>G.arcnum; //输入总顶点数,总弧数
    for(i=0; i<G.vexnum; ++i)
        cin >> G.vexs[i];                   //输入顶点值,存入一维向量中
    for(i=0;  i<G.vexnum;  ++i)  //对邻接矩阵n*n个单元初始化
       for(j=0; j<G.vexnum; ++j) 
          G.arcs[i][j] = INFINITY; 
   for(k=0;k<G.arcnum;++k)
   {  //给邻接矩阵有关单元赋初值(循环次数=弧数
        cin >> v1>>v2>>w; //输入弧的两顶点以及对应权值
        i=LocateVex(G,v1); //找到两顶点在矩阵中的位置(n次?
        j=LocateVex(G,v2); G.arcs[i][j].adj=w; //输入对应权值
       G.arcs [i] [j] = w;
       G.arcs[j][i] = G.arcs [i] [j]//无向网是对称矩阵
  } 
return OK;
} // CreateUDN 

邻接表(链式)表示法

对每个顶点vi 建立一个单链表,把与vi有关联的边的信息(即度或出度边)链接起来,表中每个结点都设为3个域;
每个单链表还应当附设一个头结点(设为2个域),存vi信息
每个单链表的头结点另外用顺序存储结构存储

在这里插入图片描述
邻接表与邻接矩阵有什么异同之处?

  1. 联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
  2. 区别:
    ① 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
    ② 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复杂度为O(n+e)。
  3. 用途:邻接矩阵多用于稠密图的存储(e接近n(n-1)/2);而邻接表多用于稀疏图的存储(e<<n2)
    图的邻接表存储表示
#define MAX_VERTEX_NUM 20  //假设的最大顶点数
Typedef  struct  ArcNode {   
      int  adjvex;                         //该弧所指向的顶点位置
      struct  ArcNode *nextarcs; //指向下一条弧的指针
      InfoArc    *info;           //该弧相关信息的指针
} ArcNode;
Typedef   struct  VNode{                 //顶点结构
     VertexType   data;          //顶点信息
     ArcNode   * firstarc;     //指向依附该顶点的第一条弧的指针
}VNode, AdjList[  MAX_VERTEX_NUM ];  

Typedef   struct {                 //图结构
     AdjList   vertics ;         //应包含邻接表
      int  vexnum, arcnum; //应包含顶点总数和弧总数
}ALGraph;  //图结构

十字链表
它是有向图的另一种链式结构。
思路:将邻接矩阵用链表存储,是邻接表、逆邻接表的结合。
1、每条弧对应一个结点(称为弧结点,设5个域)
2、每个顶点也对应一个结点(称为顶点结点,设3个域)

#define MAX_VERTEX_NUM 20

Typedef  struct  ArcBox {               //弧结点结构
   int  tailvex , headvex ;
   struct  ArcBox * hlink , tlink;
   InfoType    *info;
} ArcBox;

Typedef   struct  VexNode{  //顶点结构
    VertexType   data;
     ArcBox   * firstin,*firstout;
}VexNode;  

Typedef   struct { 
      VexNode xlist[  MAX_VERTEX_NUM ];   //表头向量
      int  vexnum, arcnum;
}OLGraph;    //图结构

在这里插入图片描述

邻接多重表
这是无向图的另一种存储结构,当对边操作时,无向图应采用此种结构存储。
1、每条边只对应一个结点(称为边结点),设立6个域;
2、每个顶点也对应一个结点(顶点结点),设立2个域;
在这里插入图片描述

图的遍历

遍历定义:从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。
遍历实质:找每个顶点的邻接点的过程。
图的特点:图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。

深度优先搜索(遍历)

在这里插入图片描述

简单归纳:
1)访问起始点 v;
2)若v的第1个邻接点没访问过,深度遍历此邻接点;
3)若当前邻接点已访问过,再找v的第2个邻接点重新遍历

详细归纳:
在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;
再从 w1 出发,访问与 w1邻接但还未被访问过的顶点 w2;
然后再从 w2 出发,进行类似的访问,…
如此进行下去,直至到达所有的邻接顶点都被访问过为止。
接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

Boolean  visited [MAX];          // 访问标志数组

Void DFSTraverse( Graph  G)
{   // 对图G做深度优先遍历
     for (v=0; v<G.vexnum; ++v)  visited[v] = FALSE;
                                                            // 访问标志数组初始化
     for (v=0; v<G.vexnum; ++v) 
         if (!visited[v])   DFS(G,v);  // 对尚未访问的顶点调用DFS
 }

设图中有 n 个顶点,e 条边)
如果用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,因此遍历全部顶点所需的时间为O(n2)。
如果用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,因此遍历图的时间复杂度为O(n+e)。

结论:
稠密图适于在邻接矩阵上进行深度遍历;
稀疏图适于在邻接表上进行深度遍历。

广度优先搜索(遍历)

在这里插入图片描述

简单归纳:
在访问了起始点v之后,依次访问 v的邻接点;
然后再依次访问这些顶点中未被访问过的邻接点;
直到所有顶点都被访问过为止。
广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。
在这里插入图片描述
BFS算法如何编程?层次遍历应当用队列!

Void BFS ( Graph G, int  v )
{
     cout << v; visited[v] = true;
     InitQueue(Q);
     EnQuene (Q, v);           // v入队列
     while (!QueneEmpty(Q))
     {  
           DeQuene (Q, u);            // 队头元素出队并置为u
           for (w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w))
                if (! Visited [w] )         // w为u的尚未访问的邻接顶点
               {  
                    Visited [w] = TRUE;  Visit(w);
                    EnQuene (Q,w);
                } //if 
      } //while
}  // BFSTraverse

如果使用邻接表来表示图,则BFS循环的总时间代价为 d0 + d1 + … + dn-1 = O(e),其中的 di 是顶点 i 的度。
如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2)。
DFS与BFS之比较:
空间复杂度相同,都是O(n)(借用了堆栈或队列);
时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。

求最小生成树

生成树的特征——n 个顶点的连通网络的生成
树有 n 个顶点、n-1 条边。

求生成树的方法——DFS(深度优先搜索)和
BFS(广度优先搜索)

对于连通图,仅需从图中任一顶点出发,进行DFS或BFS,
便可访问到图中所有顶点.
对于非连通图,需从多个顶点出发进行搜索,而每一次
从一个新的起始点出发进行搜索过程中
得到的顶点访问序 列恰为其各个连通
分量中的顶点集.
在这里插入图片描述

克鲁是卡尔是归并边
普利姆是归并点

Kruskal(克鲁斯卡尔)算法

算法思想:
设G=(V, E)是具有n个顶点的连通网,T=(U, TE)是其最小生成树。
初值:U=V,TE={} 。
对G中的边按权值大小从小到大依次选取。
⑴ 选取权值最小的边(vi,vj),若边(vi,vj)加入到TE后形成回路,则舍弃该边(边(vi,vj) ;否则,将该边并入到TE中,即TE=TE∪{(vi,vj)} 。
⑵ 重复⑴ ,直到TE中包含有n-1条边为止。
特点:将边归并——适于求稀疏网的最小生成树。
故采用邻接表作为图的存储表示。

普利姆(Prim)算法

设:N =(V , E)是个连通网,
另设U为最小生成树的顶点集,TE为最小生成树的边集。

1)初始状态: U ={u0 },( u0 ∈V ), TE={ },
(2)从E中选择顶点分别属于U、V-U两个集合、且权值最小的边( u0, v0),将顶点v0归并到集合U中,边(u0, v0)归并到TE中;
(3)直到U=V为止。此时TE中必有n-1条边,
T=(V,{TE})就是最小生成树。

求最短路径

两种常见的最短路径问题:
一、 单源最短路径—用Dijkstra(迪杰斯特拉)算法
二、所有顶点间的最短路径—用Floyd(弗洛伊德)算法

一、单源最短路径 (Dijkstra算法)

目的: 设一有向图G=(V, E),已知各边的权值,以某指定点v0为源点,求从v0到图的其余各点的最短路径。限定各边上的权值大于或等于0。

算法思想:
先找出从源点v0到各终点vk的直达路径(v0,vk),即通过一条弧到达的路径。
从这些路径中找出一条长度最短的路径(v0,u),然后对其余各条路径进行适当调整:
若在图中存在弧(u,vk),且(v0,u)+(u,vk)<(v0,vk),
则以路径(v0,u,vk)代替(v0,vk)。
在调整后的各条路径中,再找长度最短的路径,依此类推。

1)设A[n][n]为有向网的带权邻接矩阵,A[i][j]表示弧(vi,vj )的权值,S为已找到从源点v0出发的最短路径的终点集合,它的初始状态为{ v0 }.辅助数组dist[n]为各终点当前找到的最短路径的长度,它的初始值为: dist[i]=A[v0 ,i] //即邻接矩阵中第v0行的权值
(2)选择u,使得
dist[u]=min{dist[w] | w∈V-S } // w是S集之外的顶点
// dist[u]是从源点v0到S集外所有顶点的弧中最短的一条
3)对于所有不在S中的终点w,若
dist[u]+ A[u,w]< dist[w] // 即(v0,u)+(u,w)<(v0,w)
则修改dist[w]为: dist[w]= dist[u]+ A[u,w]

(4)重复操作(2)、(3)共n-1次,由此求得从v0到各终点的最短路径。

在这里插入图片描述

问题的提出:已知一个各边权值均大于0的带权有向图,对每一对顶点 vi  vj,希望求出vi 与vj之间的最短路径和最短路径长度。
解决思路:
可以通过调用n次Dijkstra算法来完成,但时间复杂度为O(n^3)。
改进:
Floyd算法(略)

图的应用

① AOV网(Activity On Vertices)—用顶点表示活动的网络
AOV网定义:若用有向图表示一个工程,在图中用顶点表示活动,用弧表示活动间的优先关系。Vi 必须先于活动Vj 进行。则这样的有向图叫做用顶点表示活动的网络,简称 AOV。

② AOE网(Activity On Edges)—用边表示活动的网络
AOE网定义:如果在无环的带权有向图中, 用有向边表示一个工程中的活动,用边上权值表示活动持续时间,用顶点表示事件,则这样的有向图叫做用边表示活动的网络,简称 AOE。

AOV网络
用途:我们经常用有向图来描述一个工程或系统的进行过程。一般来说,一个工程可以分为若干个子工程,只要完成了这些子工程,就可以导致整个工程的完成。
例:AOV网络若用于教学计划的制定,可以解决:
哪些课程是必须先修的,哪些课程是可以并行学习的。

拓扑排序的方法
输入AOV网络。令 n 为顶点个数。
在AOV网络中选一个没有直接前驱的顶点, 并输出之;
从图中删去该顶点, 同时删去所有它发出的有向边;
重复以上 2、3 步, 直到:
全部顶点均已输出,拓扑有序序列形成,拓扑排序完成;或:
图中还有未输出的顶点,但已跳出处理循环。这说明图中还剩下一些顶点,它们都有直接前驱,再也找不到没有前驱的顶点了。这时AOV网络中必定存在有向环。

拓扑排序算法:重复选择没有直接前驱的顶点

AOE网络

用途:常用于大型工程的计划管理。利用AOE网络可以 解决以下两个问题:
(1) 完成整个工程至少需要多少时间(假设网络中没有环)?
(2) 为缩短完成工程所需的时间, 应当加快哪些活动?
或者说,哪些活动是影响工程进度的关键?

问题1:由于在AOE—网中有些活动可以并行地进行,故完成工程的最短时间是从开始点到完成点的最长路径的长度,即关键路径的长度。

一些术语:

1.源点:入度为零的点(工程的开始点,只有一个)。
汇点:出度为零的点(工程的完成点,只有一个)。
2.路径长度:指路径上各活动持续时间之和,
不是路径上弧的数目。
3.关键路径:路径长度最长的路径。
关键活动:满足l(s) = e(s)的活动as

活动as的最早开始时间e(s):
若该活动在弧<vi,vj>上,则:
e(s) = Ve(i)
活动as的最迟开始时间l(s):
在不推迟整个工程完成的前提下,该活动最迟
必须开始进行的时间。
l(s) = Vl(j) — dut (as)

事件vi的最早发生时间Ve(i):
从开始点V1到Vi的最长路径长度。
求法: 从ve(0)=0开始向前递推

ve(j) = Max { ve(i)+dut(<i,j>) } <i,j>∈T
T是所有以第j个顶点为头的弧的集合。

事件vi的最迟发生时间Vl(i):
求法:从vl(n-1)=ve(n-1)起向后递推

vl(i) = Min {vl(j)-dut(<i,j>)} <i,j> ∈S
S是所有以第i个顶点为尾的弧的集合。

要在最短时间内完成一项工程,需要做以下工作:

  1. 画出该工程各个子工程的有向无环网络;
  2. 求出各个顶点(即事件)的ve和vl并找到关键路径;
  3. 根据各顶点的ve和vl值,求出每条弧s上活动的e(s)
    和l(s),由此找到关键活动。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值