提示:不乱于心 ,不困于情,不畏将来,不念过往 。如此 ,安好
一、图的基本概念
1.1、图的定义
图G是由顶点集V和边集E构成,记为G=(V,E) 其中V(G)表示图G中顶点的有限个非空集,E(G) 表示图G中顶点之间的关系边集合
V=(v1,v2,v3…vn) 其中|V|表示图G中顶点个数也称为图G的阶 使用|E|来表示图G中边的条数
线性表可以是空表,树可以是空树,但是图不能是空图
1.2、图的一些概念
-
有向图:若是E是有向边(也称为弧)的有限集合时,称图G为有向图,弧是顶点的有序对,记为<v,w> 其中v,w是顶点,v称为弧尾 w称为弧头,<v,w>称为顶点v到w的弧,也称为v邻接于w 或者w邻接于v
-
无向图:若是E是无向边的有限集合时,则称G为无向图,边是顶点的无序对,记为(w,v)或者(v,w) 因为(v,w)=(w,v),其中v,w是顶点 可以说顶点w和顶点v互为邻接点 边(v,w)依附于顶点w和顶点v或者说边(w,v)和顶点v,w 相关联
-
简单图:不存在重复边,不存在顶点到自身的边
-
多重图:若是G中某两个结点之间的边的个数多于一条,又允许顶点之间通过同一个边和自身关联 则G为多重图
-
完全图(简单完全图):对于无向图,|E|的取值范围0 到n(n-1)/2 有n(n-1)/2条边的无向图称为完全图,在完全图中任意两个顶点之间都存在边,
-
-
对于有向图,|E|的取值范围是n(n-1) ,有n(n-1) 条弧的有向图称为完全有向图,在有向完全图中 任意两个点之间都存在两个方向相反的边
-
子图:设有两个图G=(V,E)和G^ =(V^ , E^ ) 若是V^是V的子集, 且E ^ 是E的子集 则称G ^是G的子图
-
连通:在无向图中若是从顶点v到w之间有路径存在 则称w和v是连通的
-
连通图:若是图G中任意两个顶点之间都是连通的,称为G为连通图 ,否则称为非连通图
-
极小连通子图:连通子图和极小连通子图都是在无向图中进行讨论的,一个子图删除任意一条边,子图不再连通
-
连通分量:连通分量的提出是以"整个无向图不是连通图"为前提的,因为如果无向图是连通图,则其无法分解出多个最大连通子图,因为图中所有的顶点之间都是连通的。也就是说连通图只有一个连通分量就是他自己,连通图中极大连通子图称为连通分量 若是图中有n个顶点,并且边数小于n-1 则称此图必然是非连通图,连通分量=极大连通子图
-
强连通图:在有向图中,若是从顶点v到顶点w和从顶点w到v之间都存在通路,尽管路径可能不一样,则称为这两个顶点是强连通的,若图中任意一对顶点之前都是强连通的,则称此图为强连通图,有向图中极大强连通子图称为有向图的强连通分量
关于极大极小连通图的解析 -
生成树:对连通图进行遍历,过程中所经过的边和顶点的组合可看做是一棵普通树,通常称为生成树。连通图的生成树是包含图中所有顶点的一个极小连通子图 若是图中顶点为n 则它的生成树中含有的边是n-1 对于生成树而言,若砍去一个边 则会变成非连通图,若是加上一个边 则会形成一个回路,
亦或者是这样 其实并不唯一
本例子中不止两种!!!生成树 -
生成森林:我们知道,非连通图可分解为多个连通分量,而每个连通分量又各自对应多个生成树(至少是 1 棵),因此与整个非连通图相对应的,是由多棵生成树组成的生成森林
-
度:定义为以该顶点为一个端点的边的数目
-
无向图的度:顶点v的度是指依附于该顶点的边的个数,无向图的全部顶点度的和等于边数的2倍
-
有向图的度:全部顶点的入度之和与出度之和相等,并且等于边数
-
边的权和网:每一个边都可以标上具有某种特定含义的数值,则称数值为该边的权值,这种边上带有权值的图称为带权图,也称为网
-
稠密图:一般当边|E|<|V|log|V| 称为稀疏图
-
路径:两个顶点之间相连的边
-
路径长度:路径上边的数目
-
回路或环:第一个顶点和最后一个顶点相同的路径 若是一个图有n个顶点,并且有大于n-1 条边 则此图一定是有环
-
简单路径:顶点不重复出现 的路径
-
简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路
-
距离:从顶点u出发到顶点v 之间的最短路径若是存在,则称此路径的长度称为从u到v的距离,若是从u到v根本不存在路径,则称该距离为无穷
-
有向图:一个顶点的入度为0,其余顶点的入度均为1 的有向图 称为有向树
二、图的存储及基本操作
2.1、邻接矩阵
2.1.1、邻接矩阵存储
是指使用一个一维数组存储图中的顶点信息 使用一个二维数组来存储图中的边信息(即各个顶点之间的关系) 存储顶点之间关系的二维数组称为邻接矩阵. A[i][j]=1 表示顶点i 与结点v之间是有边的 等于零 则表示没有边 带权图中 若是有边则使用权值存放 若是没有存在边则使用 无穷来表示 邻接矩阵表示法的空间复杂度为O(n2) 其中n为图中顶点数|V| 结构如下图
类如上图中0与1相连 0 也与3 相连 则其中邻接矩阵中 A[0][1] A[0][3] A[1][0] A[3][0]
2.1.2、特点
1、无向图的邻接矩阵一定是一个对称矩阵并且唯一,并且对角线上值一定为零
2、对于无向图而言 邻接矩阵中第i行或者第i列中非零元素的个数恰好等于第i 个顶点的度
3、对于有向图邻接矩阵中第i行中非零元素的个数恰好等于第i个结点的出度
4、使用邻接矩阵存储图很容易确定图中任意两个结点之间是否有边相连 但是若是确定图中有多少边,则必须按行按行或者按列进行遍历
稠密图适合使用邻接矩阵的方式存储
2.1.3、结构体定义
#define MaxInt 32767 //表示极大值∞ 其实就是一种无穷标志
#define MVNum 100 //表示最大顶点数
typedef char VerTexType;//假设顶点的数据结构类型为char
typedef int ArcType;//假设权值类型为整形
typedef struct{
VerTexType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum;//图的当前顶点数
int arcnum;//图的当前边数
}AMGraph;
2.2、邻接表法
2.2.1、结构
当一个图为稀疏图,使用邻接矩阵要浪费大量的存储空间 而图的邻接表法结合了顺序存储和链式存储方式,减少了不必要的浪费
结构:对图G中的每一个顶点建立一个单链表,第i 个单链表中结点表示依附于顶点v的边,这个单链表就称为顶点v的边表,边表的头指针和顶点的数据信息采用顺序存储称为顶点表(当然也可以使用单链表来存储),所以在邻接表中存在两种结点,顶点表结点和边表结点 结构如下图
顶点表(称为表头结点如上图的V0,V1)结构分为两部分,数据域和指针域。数据域用于存储顶点数据信息,指针域用于链接下一个节点,跟之前的单链表是一样的
那么除了表头节点我们还有由表头节点引出来的链表,称为边表,若边表的节点存储的是图不是网,那么仍然可以使用上边图示的存储结构,但是如果存储的是网,那么就可以使用下面的存储结构(adjvex存储与表头节点有关系的顶点的下标,next是连接的链表,info表示的是权重等信息:所以也就需要多带一个信息
2.2.2、特点
若是G为无向图 则需要的存储空间为O(|V|+2|E|) 若是G为有向图,则所需的存储空间为O(|V|+|E|)
对于稀疏图 采用邻接矩阵表示将极大的节省存储空间
在无向图的邻接表中 给定一个顶点 能很快的找到它的所有领边,因为只需要读取它的邻接表即可
在有向图的邻接表表示中,求一个给定顶点的出度只需要计算其中邻接表中的结点个数
求其顶点的入度:
1、遍历整个邻接表中的节点,统计数据域与该顶点所在数组位置下标相同的节点数量,即为该顶点的入度;
2、建立一个逆邻接表,该表中的各顶点链表专门用于存储以此顶点为弧头的所有顶点在数组中的位置下标。
图的邻接表表示并不唯一 因为邻接表表示中,各边表结点的链接次序取决于建立邻接表的算法,以及边的输入次序
2.2.3、结构体
#define MVNum 100 //表示最大顶点数
typedef int VerTexType//顶点数据的数据类型
typedef struct ArcNode{//边结构
int adjvex;//该边所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条边的指针
int info; //和边相关的信息如权重等 若是没有权值也可以省略 省略之后就可以于点结构相统一
}ArcNode;
typedef struct VNode{//顶点结构
VerTexType data;
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNode;
//相当于多个链表 将首结点存放于一个数组中 就像单链表中存放长度一样 这里存放的是点与边的个数
typedef struct{//邻接表
VNode vertices[MVNum];
int vexnum;//图当前的顶点数
int arcnum;//图当前的边数
};
2.3 、十字链表法
与邻接表不同,十字链表法仅适用于存储有向图和有向网。不仅如此,十字链表法还改善了邻接表计算图中顶点入度的问题
十字链表存储有向图(网)的方式与邻接表有一些相同,都以图(网)中各顶点为首元节点建立多条链表,同时为了便于管理,还将所有链表的首元节点存储到同一数组(或链表)中。
在十字链表中,对应于有向图中每一个弧有一个结点,对应的每一个顶点也有一个结点 时间复杂度O(|V|+|E|)
从上图 可以看出,首元节点中有一个数据域和两个指针域(分别用 firstin 和 firstout 表示):
firstin 指针用于连接以当前顶点为弧头的其他顶点构成的链表;
firstout 指针用于连接以当前顶点为弧尾的其他顶点构成的链表;
data 用于存储该顶点中的数据;
由此可以看出,十字链表实质上就是为每个顶点建立两个链表,分别存储以该顶点为弧头的所有顶点和以该顶点为弧尾的所有顶点。
弧结点与结构:
尾域tailvex:弧尾
头域headvex:弧头
链域hlink:指向弧头相同的下一个弧
链域tlink 指向弧尾相同的下一个弧
info域:指向该弧的相关信息
其实相当于是两个链表,相信若是你第一次看一定是一头雾水 我们来分析一下,首先来看图 中的V0,它是有两个入弧(入度) 分别是 v2和v3 我们来看顶点表中V0中入弧指向的是20, 20的弧头指针指向的是30 ,也就是对应的 V2 V3 , V0的出弧指向的是01 01弧尾指向的是02 02 的指向为NULL
或者说一些不算特点的特点:
你看有右边去掉头是不是对应七个 其实也就是七个边, 你连着读一下弧头弧尾的数字,比如 01 指的即是从0 指向1 比如02 就是从0指向2 所以01 弧尾指向的就是 02 因为他们同弧尾,比如01 和31 他们弧头相同 所以01弧头指针指向的就是 31
2.3.1、结构体定义
#define MAX_VERTEX_NUM 20
#define InfoType int//图中弧包含信息的数据类型
#define VertexType int
typedef struct ArcBox{
int tailvex,headvex;//弧尾、弧头对应顶点在数组中的位置下标
struct ArcBox *hlik,*tlink;//分别指向弧头相同和弧尾相同的下一个弧
InfoType *info;//存储弧相关信息的指针
}ArcBox;
typedef struct VexNode{
VertexType data;//顶点的数据域
ArcBox *firstin,*firstout;//指向以该顶点为弧头和弧尾的链表首个结点
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM];//存储顶点的一维数组
int vexnum,arcnum;//记录图的顶点数和弧数
}OLGraph;
2.4、邻接多重表
无向图的存储可以使用邻接表,但在实际使用时,如果想对图中某顶点进行实操(修改或删除),由于邻接表中存储该顶点的节点有两个,因此需要操作两个节点。
为了提高在无向图中操作顶点的效率,本节学习一种新的适用于存储无向图的方法——邻接多重表
邻接多重表是无向图的另外一种链式存储结构
时间复杂度:O(|V|+|E|)
邻接多重表存储无向图的方式,可看作是邻接表和十字链表的结合。同邻接表和十字链表存储图的方法相同,都是独自为图中各顶点建立一张链表,存储各顶点的节点作为各链表的首元节点,同时为了便于管理将各个首元节点存储到一个数组中。各首元节点结构如图 所示:
顶点结构 data域存放该顶点的相关信息 firstedget 指针域 用于指向同顶点有直接关联的存储其他顶点的结点
边结构:
mark为标志域,可以使用标记该条边是否被搜索过,
ivex 和jvex为该边依附的两个顶点在图中的位置
ilink指向下一条依附于顶点ivex 的边
jlink指向下一条依附于顶点jyvex 的边
info为指向和边相关的各种信息的指针域,比如说权
就跟上述的十字链表有点相似, 找v1相关联的时候因为他与第一个数值0 相同所以看link指针也就是 1 ,2 ,3
找V2的时候看它与边01的1相同所以看Jlink所以下一个就是v2下一个没有了 所以也就是NULL
只能存放无向图
2.4.1、结构体定义
#define MAX_VERTEX_NUM 20 //图中顶点的最大个数
#define InfoType int //边含有的信息域的数据类型
#define VertexType int //图顶点的数据类型
typedef enum {unvisited,visited}VisitIf; //边标志域
typedef struct EBox{
VisitIf mark; //标志域
int ivex,jvex; //边两边顶点在数组中的位置下标
struct EBox * ilink,*jlink; //分别指向与ivex、jvex相关的下一个边
InfoType *info; //边包含的其它的信息域的指针
}EBox;
typedef struct VexBox{
VertexType data; //顶点数据域
EBox * firstedge; //顶点相关的第一条边的指针域
}VexBox;
typedef struct {
VexBox adjmulist[MAX_VERTEX_NUM];//存储图中顶点的数组
int vexnum,degenum;//记录途中顶点个数和边个数的变量
}AMLGraph;
三、图的遍历(重点)
3.1、广度优先搜索
类似于二叉树的层序遍历算法
算法思想:首先访问起始结点v 接着由v出发 依次访问各个未被访问过的邻接顶点w1,w2… 然后依次访问这些w1 w2 …的所有未被访问过的的顶点 再从这些顶点出发 访问它们所有未被访问的邻接顶点,直到图中所有的顶点都被访问过为止,利用队列实现搜索
BFS算法的性能分析:
在最坏情况下 空间复杂度为O(|V|)
时间复杂度:
邻接表存储方式:顶点:O(|V|) 边:O(|E|) 总时间复杂度:O(|V|+|E|)
邻接矩阵存储方式:总时间复杂度:O(|V|的平方)
BFS算法可以解决最短路径的问题
广度优先生成树:在广度遍历的过程中,我们可以得到一个遍历树,称为广度优先生成树
唯一性:邻接矩阵存储表示是唯一的,故其广度优先生成树也是唯一的
由于邻接表的存储表示不唯一 故广度优先生成树也不是唯一的
3.2、深度优先搜索(DFS)
算法思想:类似于树的先序遍历,搜索策略尽可能深的搜索一个图,首先访问图中每一个起始顶点v ,然后由v出发,访问与v邻接 且未被访问的任意一个顶点v1 再访问v1 未被访问的任意一个v2 重复上述步骤,当不能再向下访问时,依次退回到最近被访问过的顶点,若是它还有未被访问的邻接顶点 则从该顶点继续上述搜索过程,直到图中所有的顶点都被访问过为止
递归算法:需要一个递归工作栈
DFS的性能分析:空间复杂度:O(|V|) 时间复杂度:邻接矩阵:时间复杂度O(|V|的平方) 邻接链表:找到所有结点的邻接点所需要的时间为O(|E|) 访问所有顶点时间为O(|V|) 总的时间为O(|V|+|E|)
深度优先生成树和生成森林:对连通图调用DFS才能产生深度优先生成树 非连通图产生的是深度优先生成森林,基于邻接表存储的存储的深度优先生成树是不唯一的
3.3、注意
基于邻接矩阵的遍历所得到的DFS序列和BFS序列是唯一的
基于邻接表的遍历所得到的DFS 序列和BFS序列是不唯一的
3.4、图的遍历与图的连通性
图的遍历和图的连通性:图的遍历算法可以用来判断图的连通性,
对于无向图
若是无向图是连通的,则从任意一个结点出发,仅仅需要一次遍历就能访问图中的所有顶点
若无向图是非连通的 则从某一个顶点出发,一次遍历只能够访问到该顶点所在的连通分量的所有的顶点 对于图中其他的连通分量的顶点,则无法通过这次遍历访问
对于有向图
若是图是连通的 从初始点到图中每一个顶点都有路径,则能够访问图中所有的顶点
若图是非连通的,不能访问所有的顶点
四、图的应用
4.1、最小生成树
一个连通图的生成树包含图中的所有顶点,并且只含有尽可能少的边 若是砍去它的一个边,则会生成树变成非连通图 若是给他加一个边,则会形成图中的一条回路
最小生成树:权值之和最小的那棵生成树,称为最小生成树
4.1.1、最小生成树的性质
最小生成树的性质,最小生成树不是唯一的,即最小生成树的树形不唯一 其对应的边的权值之和总是唯一的,并且是最小的, 最小生成树的边数为顶点数减1
4.1.2、最小生成树算法(重点)
Prim算法
概述 开始时从图中任取一个顶点加入树T,此时树中只含有一个顶点,之后选择一个与当前T中顶点集合距离最近的顶点,并将该顶点与相应的边放入T,每一次操作后T中的顶点数和边数都增1 以此类推,直到图中所有的顶点都并入T 得到的T就是最小的生成树,此时T中必然有n-1条边
时间复杂度 O(|V|*)
适用于求解边稠密的图的最小生成树
Kruskal算法
概述:初始时只有n个结点 而无边的非连接图T={V,{}},每一个顶点自成一个连通分量
然后按照边的权值由小到大的顺序,不断选取当前未被选取过且权值最小的边
若是边依附的顶点落在T中的不同的连通分量上,则将此边加入T ,否则舍此边而选择下一条权值最小的边 依次类推,直到T中所有顶点都在一个连通分量
时间复杂度O(log) 适合于边稀疏而顶点多的图
4.2、最短路径
Dijkstra算法求单源最短路径问题
辅助数组
dist[]:记录从源点v0到其他各个顶点当前的最短路径长度,它的初态为:若是从V0 到V1有弧,则dist[i]为弧上的权值,否则设置dist[i]为无穷大
path[]:path[i] 表示从单源到顶点i之间的最短路径的前驱结点
实现过程
1)初始化:集合S初始化为(0) dist[]的初始值dist[i]=arcs[0][i] ,i=1,2,…n-1
2)从顶点集合V-S中选出Vj 满足dist[j]=Min{dist[i]} Vj就是当前求得的一条从v0出发的最短路径的终点
3)修改从V0 出发到集合V-S上任一顶点Vk 可达到的最短的路径长度,若dist[j]+arcs[j][k]<dist[k] 则更新dist[j] +arcs[j][k]
4) 重复2)3)操作共n-1 次 直到所有的顶点都包含S中
时间复杂度O(V*)
不适合边权值存在负数的情况
Floyd 算法求各个顶点之间的最短路径
实现过程 初始时,对于任意两个顶点vi vj 若是它的之间存在边,则以边上的权值作为它们之间的最短路径长度
若它们之间不存在有向边,则以无穷大作为他们之间的最短路径长度
以后逐步尝试在路径中加入顶点k(k=0,1,2,3,4,…n-1) 作为中间顶点
若是增加顶点后,得到的路径比原来的路径长度减少了,则此新路径的代替原路径
时间复杂度O(|V|3次方)
允许图中带有负权值的边,但是不允许带有负权值的边组成的回路
适合带有权无向图
4.3、有向无环图描述表达式
有向无环图:若是一个有向无环图中不存在环,则称为;有向无环图,简称DAG
有向无环图是描述含有公共子式的表达式的有效工具
AOV网(拓扑排序)
用一个有向图表示一个工程的各个子工程以及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先限制关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)
AOE(关键路径)
用一个有向图表示一个工程的各个子工程以及其相互制约关系,以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网 简称为AOE网(Activity On Edge)
4.4、拓扑排序
拓扑排序指的是将有向无环图(又称“DAG”图)中的顶点按照图中指定的先后顺序进行排序。
拓扑排序的方法:
- 在图中选择一个没有前驱的顶点 V;
- 从图中删除顶点 V 和所有以该顶点为尾的弧
如果顶点之间只是具有偏序关系,那么拓扑排序的结果肯定不唯一;如果顶点之间是全序关系,那么拓扑排序得到的序列唯一
所以用来表示某种活动间的优先关系的有向图简称为“AOV 网”。
4.5、逆拓扑排序
集体实现:DFS算法
思想:
1、从AOV 网中选择一个没有后继出度为零的顶点输出
2、从网中删除该顶点和所有以它为终点的有向边
重复1 ,2 直到当前的AOV网为空
4.6、关键路径
这个我也没看过 关键路径 大家自己看应该问题不大吧 xiu 溜了 代码写到这的时候再补充 接下来开始更新代码