图总结
一、思维导图
二、重要概念的笔记
1.图的结构定义
Graph=(V, VR)
其中,V={x| x∈某个数据对象},是顶点的有穷非空集合;
VR= {(x,y)|x,y∈V} 或 VR= {<x, y>|x, y∈V&& Path (x, y)}是顶点之间关系的有穷集合,也叫做
(edge)或弧(Arc)集合。
Path (x, y)表示从x到y的一条单向通路,它是有方向的。
有向图:"弧”是有方向的,因此称由顶点集和弧集构成的图为有向图。
无向图:有顶点集和边集构成的图是无向图
2.基本术语
有(无)向网:弧或边带权的图
子图:设图G=(V,{VR}) 和图G'=(V',{VR'}),且V'≦V, VR'≦VR,则称G'为G的子图l
假设图中有n个顶点,e条边,则
•含有e=n(n-1)/2条边的无向图称作完全图;
•含有e=n(n- 1)条弧的有向图称作有向完全图;
•若边或弧的个数e<nlogn,则称作稀疏图,否则称作稠密图。
假若顶点v和顶点w之间存在一条边,
•则称顶点v和w互为邻接点;
•边(v,w)和顶点v和w相关联;
•和顶点v关联的边的数目定义为边的度。
■路径上边的数目称作路径长度。
■简单路径:序列中顶点不重复出现的路径。
■简单回路:序列中第一个顶点和最后一个顶点相同的路径。
若无向图为非连通图,则图中各个极大连通子图称作此图的连通分量
对有向图来说
若任意两个顶点之间都存在一条有向路径, 则称此有向图为强连通图。
否则,其各个强连通子图称作它的强连通分量。
3.图的存储结构
邻接矩阵
顶点信息:记录各个顶点信息的顶点表。
边或弧信息:各个顶点之间关系的邻接矩阵。
设图A=(V, E)是一个有n个顶点的图,则图的邻接矩阵是一个二维数组A.edge[n][n], 定义:如果∈E或(i, j)∈E,A.Edge [i][j]=1否则A.Edge [i][j]=0.
无向图的邻接矩阵是对称的,有向图的邻接矩阵可能是不对称的
#define MaxInt 32767 //表示极大值
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设定点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct { //图的定义
VerTexType vexs [MVNum ] ; //顶点表
ArcType arcs [MVNum][MVNum]; //弧的信息(邻接矩阵)
int vexnum,arcnum; //图当前的顶点数和边数
} AMGraph; //邻接矩阵
邻接表
对图中的每个顶点都建立一个单链表来存储所有依附于该顶点的弧或边
■无向图:第个单链表中的结点表示依附于顶点v;的边。
■有向图:第个单链表中的结点表示以顶点v;为尾的弧。
对图中所有顶点使用一个维数组来存放
单链表中每个结点由三个域组成
■邻接点域(adjvex):指示与顶点v ,邻接的点在图中的位置;
■链域(nextarc):指示下一条边或弧的结点;
■数据域(info):存储和边或弧相关的信息,如权值等。
typedef struct {
AdjList vertices;
int vexnum, arcnum;
} ALGraph;
十字链表
typedef struct ArcBox { //弧的结构表示
int tailvex, headvex;
InfoType *info;
struct Arc Box *hlink, *tlink;
} VexNode ;
typedef struct VexNode { //顶点的结构表示
VertexType data;
ArcBox * firstin, * firstout;
} VexNode;
4.图的遍历
从图中某个顶点出发游历图,访遍图中其余顶点,并且使图中的每个顶点仅被访问一次的过程。
深度优先搜索(DFS)
连通图的深度优先搜索算法描述
void DFS(Graph G, int v) {
//从顶点v出发,深度优先搜索遍历连通图G
访问v; visited[v] = TRUE;
//FirstAdjVex为获得v的第一个邻接点, NextAdjVe为获得下一个邻接点
for(w=FirstAdjVex(G, v); W>=0; W=NextAdjVex(G,v,W))
if (!visited[w]) DFS(G, W);
//对v的尚未访问的邻接顶点w递归调用DFS
} // DFS
非连通图的深度优先搜索算法描述
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);//对尚未访问的顶点调用DFS
}
图的深度优先遍历类似二叉树的先序遍历。
广度优先搜索(BFS)
广度优先遍历图的实质:通过边或弧找邻接点的过程!
算法描述
void BFSTraverse(Graph G){
for (v=0; v<G.vexnum; ++v)
visited[v] = FALSE; //初始化访问标志
InitQueue(Q); //置空的辅助队列Q
for ( v=0; v<G . vexnum;++v )
if ( !visited[v]) {// v尚末访问
访问v; visited[v] = TRUE; //访问v
EnQueue(Q, v); // v入队列
while ( !QueueEmpty(Q)) {
DeQueue(Q, u); // 队头元素出队并置为u
for(W=FirstAdjVex(G, u); w!=0; w=NextAdjVex(G,u,W))
if ( ! visited[w]) {
访问v; visited[w]=TRUE ;
EnQueue(Q, W); //访问的顶点w入队列
}//if
} // while
}
}//BFSTraverse
说明:使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,因此,采用队列来存放已被访问过的顶点!
图的广度优先遍历类似二叉树的层次遍历。
DFS与BFS算法效率比较
■空间复杂度相同,都是O(n)(借用 了堆栈或队列)
■时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径(遍历的顺序)无关
■搜索路径是由存储结构与算法共同决定的
5.最小生成树
最小生成树的MST的性质:
假设N = (V,{E})是一个连通网,U是顶点集V的一个非空子集,若(u,v)是一 条具有最小权值(代价)的边,其中u∈U, v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
普里姆(Prim)算法:
基本思想:
第一步:取图中任意-一个顶点v作为生成树的根;
第二步:往生成树上添加新的顶点w。在添加的顶点w和已经在生成树上的顶点v之间必定存在一条边,并且该边的权值在所有连通顶点v和w之间的边中取值最小;
第三步:继续往生成树上添加顶点,直至生成树上含有n个顶点为止。
设置一个辅助数组closedge,对当前V-U集中的每个顶点,记录和顶点集U中顶点相连接的代价最小的边:
struct {
VertexType adjvex; //U集中的顶点序号
VRType lowcost; //边的权值
} closedge [MAX_VERTEX_NUM] ;
算法描述
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].adj };
closedge[k].lowcost =θ; //初始, U={u}
for (i=0; i<G. vexnum; ++i) {
继续向生成树上添加顶点;
}
k = Min(closedge);/ /求出加入生成树的下一个顶点k
printf(closedge[k] .adjvex, G.vexs[k]);
//输出生成树_上一条边
closedge[k]. lowcost = 0;//第k顶点并入u集
for(j = 0; j < G. vexnum; ++j)
//修改其它顶点的最小边
if (G.arcs[k][j].adj < closedge[j]. l1owcost )
closedge[j]={G.vexs[k], G.arcs[k][j].adj
};
贪心算法
算法原理:以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪心法不要回溯。
算法优点:因为省去 了为寻找解而穷尽所有 可能所必须耗费的大量时间,因此算法效率高。
注意:贪婪算法的精神就是"只顾如何获得眼前最大的利益”,有时不一定是最优解。
克鲁斯卡尔算法:
考虑问题的出发点:为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能小
基本思想:
第一步:构造一个只含n个顶点的子图SG;
第二步:从权值最小的边开始,若它的添加不使SG中产生回路, 则在SG,上加上这条边;
第三步:如此重复,直至加上n-1条边为止。
算法描述
构造非连通图ST=( V,{ } );
k =i= 0; // k计选中的边数
while (k<n-1) {
++i ;
检查边集E中第i条权值最小的边(u,v);
若(u,v)加入ST后不使ST中产生回路,则输出边(u,v);
且k++;
}
比较两种算法:
普里姆算法: O(n2)、 适用于稠密图
克鲁斯卡尔算法: O(eloge)、适用于稀疏图
构造的最小生成树不一定唯一,但最小生成树的权值之和一定是相同的。
6.最短路径
单源点最短路径
给定带权有向图G和源点v,求从v到G中其余各顶点的最短路径。
Dijkstra算法
初始时,令S={Vg},T={其余顶点}, T中顶点对应的距离值:
■若存在<Vg,Vi>,距离值为<Vg, V;>弧上的权值
■若不存在<Vg,Vi>,距离值为∞
从T中选取一个其距离值为最小的顶点W,加入S;
对T中顶点的距离值进行修改: .
■若加进w作中间顶点,从V到V;的距离值比不加W的路径要短,则修改此距离值;
重复上述步骤,直到S中包含所有顶点,即S=V为止。
算法实现
图用带权邻接矩阵存储a[] [];
数组D[]存放当前找到的从源点V。到每个终点的最短路径长度,其初态为图中直接路径权值;
数组path[ ]表示从V。到各终点的最短路径上,此,顶点的前一顶点的序号;若从V。到某终点无路径,则用0作为其前一顶点的序号。
void ShortestPath_ _DIJ(AMGraph G, int v0){
//初始化
n = G.vexnum;
for(v = 0; v<n; ++v){
S[v] = false; //s初始为空集
D[v] = G.arcs[v0][v]; //v0到各个顶点的弧的权值
if (D[v] < MaxInt)
Path[v] = v0; //v0和v之间有弧
else Path[v] = -1; //v0和v之间无弧
}
S[v0] = true; //ve加入s
D[v0] = 0; //源点到源点距离为0
for(i = 1;i < n; ++i){
min = MaxInt;//
for(w=0;W<n;++W)
if(!S[W] && D[w]<min)
{v = W; min = D[w];}//选择一 条当前最短路径
S[v] = true; //将v加入s
for(W = 0; W < n; ++W)/加入v后,v0到其他节点需更新
if(!S[W] & (D[v]+G.arcs[v][w] < D[W]){
D[W] = D[v] + G.arcs[v][W];
Path[W] = V;
}
}
}
Floyd算法
弗洛伊德(Floyd)算法:
时间复杂性也是0(n3),但形式上简单些。
基本思想是:逐个顶点试探
从v到v;的所有可能存在的路径中,选出一条长度最短的路径。
■若<V,Vj>存在,则存在路径{V;,Vj} -路径中不含其它顶点
■若<V,V1>, <V1,Vj>存在,则存在路径{V;,V1,Vj}一路 径中中间顶点序号不大于1
■若{v1,...,v2}, {v2,...,vj}存在, 则存在一条路径{Vi, ....V2, ..j}——路径中中间顶点序号不大于2
...依次类推,则v; 至vj的最短路径应是上述这些路径中,路径长度最小者。
算法实现
图用邻接矩阵存储;
二维数组D[] []存放顶点对之间的最短路径长度;
path[i][j]是从v到v的最短路径上v,前- ~顶点序号。
7.拓扑排序
检查有向图中是否存在回路的方法之一 ,是对有向图进行拓扑排序。
用顶点表示活动,用弧表示活动间的优先关系的有向图称为顶点表示活动的网(Activity On Vertex Network),简称A0V-网。
■在A0V-网中不应该出现有向环。
■对给定的A0V-网需首先判断网中是否有环。
一个AOV-网的拓扑序列不是唯一的
检测是否有环的办法:
对有向图构造其顶点的拓扑有序序列;
若网中所有顶点都在其拓扑有序序列中,则该A0V-网必定不存在环。
在算法中需要用定量的描述替代定性的概念:
■没有前驱的顶点==入度为零的顶点;
■删除顶点及以它为尾的弧==弧头顶点的入度减1。
while (V<>0) {
printf(v); ++m;
W:=FirstAdj(v);
while (W<>0) {
inDegree [w]--;
W: =nextAdj(v, w);
}
取下一个入度为零的顶点v;
}
if m<n printf("图中有回路”);
CountInDegree(G,indegree); //对各顶点求入度
InitStack(S);
for( i=0; i<G.vexnum; ++i)
if (!indegree[i]) Push(S, i); //入度为零的顶点入栈
count =0; //对输出顶点计数
while (!EmptyStack(S)) {
Pop(S, v); ++count; printf(v);
for (w=FirstAdj(v); w; w=NextAdj(G,v,w)){
--indegree(w); // 弧头顶点的入度减一
if (lindegree[w) Push(S, w); /新产生的入度为零的顶点入栈
}
}
if( count<G.vexnum)print("图中有回路” );
8.关键路径
整个工程完成的时间为:从有向图的源点到汇点的最长路径
"关键活动”指的是:该弧上的权值增加将使有向图上的最长路径的长度增加。
几个概念:
事件v的最早发生时间:从开始点v到v;的最长路径长度。用ve(i)表示。
事件v的最迟发生时间:在不推迟整个工期的前提下,事件v;允许的最晚发生时间。用vL(i)表示。
活动ai的最早开始时间:即为ai的弧尾顶点(事件)的最早发生时间。用e(i)表示。
活动ai的最迟开始时间:在保证不推迟整个工程的完成时间的前提下,活动a的最迟开始时间。用l(i)表示。
关键活动: L(i) = e(i)的活动。
求ve(j)和vL(j):
■从ve(源点) = 0开始向前(顺弧的方向)递推:
ve(j) = Max{ve(i) + dut(<i,j>)}
<i,j>∈T, j = 1,2.,..,n-1, T是所有以第j个顶点为弧头的弧的集合。
■从vL(汇点) = ve(汇 点)起向后(逆弧的方向)递推:
vL(i) = Min{vl(j) - dut(<i,j>)}
<i,j>∈S,i = n-2,n-1,...,0, S是所有以第i个顶点为弧尾的弧的集合。
关键路径的算法:
(1)输入e条弧,建立AOE-网。
(2)从源点vg出发,令ve[0] = 0,按拓扑有序求各顶点的最早发生时间vei,如果得到的拓扑序列中顶点个数小于网中顶点数n,说明网中存在环,不能求关键路径,算法终止;否则执行步骤
(3)从汇点v,出发,令vL[n-1] = ve[n-1]; 按逆拓扑有序求其余各顶点的最迟发生时间vL[i] (n-2≥i≥2);
(4)根据各个顶点的ve和vL值,求每条弧s的e(s)和L(s)。若某条弧满足e(s) == L(s),则为关键活动。
算法的实现要点:
■求ve的顺序应该是按拓扑有序的次序;
■求vL的顺序应该是按拓扑逆序的次序;
■对拓扑排序修改:
对ve[i]设初值0;
增加求v;的直接后继v的最早发生时间的操作:
若ve[j]+dut(<j,k>) > ve[k], 则ve[k] =ve[j]+dut(<j,k>)。
为了求逆拓扑序列,只需要把拓扑序列入栈,那么出栈的序列即为逆拓扑序列。
三、疑难问题
1.十字链表不太懂
2.Dijkstra算法看不太懂
3.PTA图着色问题,公路村村通