图的基本算法
图的表示
1、邻接表
2、邻接矩阵
这两种方法都可表示有向图和无向图,邻接表适合表示稀疏图、邻接矩阵不管有多少条边,它需要的空间都是n*n,所以适合表示稠密图。如果经常需要判断两个给定顶点是否存在边,适合用邻接矩阵,因为邻接表需要从某点出发沿着链表一个个搜看是否找到另一个点,但邻接矩阵可以一下子定位到某个坐标,如[i,j]如果为1表示i与j有边相连。
广度优先搜索
利用队列实现而不是递归,可参照树的层次遍历
深度优先搜索
利用递归
拓扑排序
当且仅当有向无回路图才有拓扑排序,所以往往可以通过判断能否拓扑排序来判断一个有向图有没有回路(环)
生成树
具体见下面内容
最短路径
具体见下面内容
匹配问题
具体见下面内容
网络流问题
具体见下面内容
生成树——最小生成树
求
加权连通图
的最小生成树算法:
kruskal算法(
克鲁斯卡尔算法
):
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
prim算法(
普里姆算法
):
假设V是图中顶点的集合,E是图中边的集合,TE为最小生成树中的边的集合,则prim算法通过以下步骤可以得到最小生成树:
1:初始化:U={u0},TE={f}。此步骤设立一个只有结点u0的结点集U和一个空的边集TE作为最小生成树的初始形态,在随后的算法执行中,这个形态会不断的发生变化,直到得到最小生成树为止。
2:在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u0,v0),将此边加进集合TE中,并将此边的非U中顶点加入U中。此步骤的功能是在边集E中找一条边,要求这条边满足以下条件:首先边的两个顶点要分别在顶点集合U和V-U中,其次边的权要最小。找到这条边以后,把这条边放到边集TE中,并把这条边上不在U中的那个顶点加入到U中。这一步骤在算法中应执行多次,每执行一次,集合TE和U都将发生变化,分别增加一条边和一个顶点,因此,TE和U是两个动态的集合,这一点在理解算法时要密切注意。
3:如果U=V,则算法结束;否则重复步骤2。可以把本步骤看成循环终止条件。我们可以算出当U=V时,步骤2共执行了n-1次(设n为图中顶点的数目),TE中也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边。
总结:
克鲁斯卡尔算法是从所有剩下的边中找一条权重最小的边,且该边不能使之前选中的边形成环,也就是找出不能有回路的权重最小边加到之前的集合中。
普里姆算法是从剩下的所有节点中找出一个与已选的节点相邻的节点,且该节点与已选的节点相连的边的权重最小。
最短路径——单源最短路径
也就是从图中
某点出发到其余所有点
的最短路径。
Dijkstra算法(迪杰斯特拉算法):
是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题,当然也可以解决无向图的最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V
中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。
例如,对下图中的有向图,应用Dijkstra算法计算从源顶点1到其它顶点间最短路径的过程列在下表中。
最短路径——每对顶点间的最短路径
Floyd(弗洛伊德算法):
路径矩阵
通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
从图的带权邻接矩阵A=[a(i,j)] n×n开始,递归地进行n次更新,即由矩阵D(0)=A,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)的i行j列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵,同时还可引入一个后继节点矩阵path来记录两点间的最短路径。
采用的是(松弛技术),对在i和j之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3);
状态转移方程
其状态转移方程如下: map[i,j]:=min{map[i,k]+map[k,j],map[i,j]}
map[i,j]表示i到j的最短距离,K是穷举i,j的断点。
时间复杂度为O(n^3);空间复杂度为O(n^2),因为有n个源,需要用n*n矩阵来存放这些源和源到其它点的最短路径
算法过程
1、从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2、对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i,j]=d,d表示该路的长度;否则G[i,j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i,j]表示从Vi到Vj需要经过的点,初始化D[i,j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i,j] = min( G[i,j], G[i,k]+G[k,j] ),如果G[i,j]的值变小,则D[i,j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。
比如,要寻找从V5到V1的路径。根据D,假如D(5,1)=3则说明从V5到V1经过V3,路径为{V5,V3,V1},如果D(5,3)=3,说明V5与V3直接相连,如果D(3,1)=1,说明V3与V1直接相连。
Johnson算法:
Floyd算法空间复杂度为O(n^2),因为它要建立一个n*n矩阵,所以常用于稠密图,但如果边很少时(稀疏图),Johnson算法更合适。
Johnson算法对每个点使用Dijkstra算法,Dijkstra算法用于求单源。
匹配问题——稳定婚姻问题
问题:
有n位男士和n位女士,每人都对每个异性有一个排序,代表对他们的喜欢程度,现希望给每位男士找一位不同的女士做配偶,使得每人恰好有一个异性配偶,如果男士u和女士v不是配偶但喜欢对方的程度都大于各自的当前配偶,则称他们是一个不稳定对。如果一个配对方案中存在不稳定对,则可能会破坏原来的配对,现在问题是找出一个不含不稳定对的匹配方案。
思路:
解决稳定婚姻的经典算法为“求婚-拒绝算法”,即每个男士按照自己喜欢程度从高到低依次给每位女士求婚,直到有一个接受他,女士每次遇到比当前配偶差的女士时拒绝他,遇到更喜欢的男士就抛弃当前配偶接受新的男士,被抛弃的男士继续按照列表向剩下的女士依次求婚,直到所有人都有配偶。
证明算法的正确性:
首先注意到一点,每位女士的配偶越来越好,而每位男士的配偶越来越差。
算法执行完后:
假设男士u和女士v形成不稳定对,那么该男士一定向该女士求过婚,因为该男士对目前的配偶喜欢程度排在女士v的后面(否则怎么形成不稳定对呢),女士v没有选择男士u,后者选择后抛弃了男士u,这都说明了女士v有了更好的对象,这就与不稳定对的定义矛盾了,因为女士v更喜欢当前配偶。这说明了该算法产生的配对都是稳定对。
下面只要证明该算法可以使每位男士都能找到配偶便可证明算法是正确的,也即证明算法能成功结束。
该算法一定能结束,因为不存在循环,每个男士都按照列表顺序从头到尾求婚。关键是最后会不会有男士找不到配偶的呢?假如男士u没有配偶,那肯定也存在一个女士v没有配偶,男士u按照喜欢的列表分别求婚的时候,一定也对女士v求过婚,而女士v当时还没有配偶(否则最后不会落单了,因为只会换更好的),那么女士v一定接受该男士,这与他们两最终单身矛盾了。
该算法时间复杂度为O(n2)