6.图

图:由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V, E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

特点:

  • 顶点集合不能为空
  • 边的集合可以为空

各种图定义

  • 无向图:若顶点v1到v2之间的边没有方向,则称这条边为无向边,用无序偶对(v1, v2)来表示。无向图就是图中任意两个顶点之间的边都是无向边。
    算法(三)-图 - 图1
    无向图
  • 有向图:若从顶点v1到v2的边有方向,则称这条边为有向边,也称为弧。用有序偶对来表示,v1称为弧尾,v2称为弧头。有向图就是图中任意两条边都是有向边。
    算法(三)-图 - 图2
    有向图
  • 简单图:在图中,不存在顶点到其自身的边,并且同一条边不重复出现,称为简单图。
    算法(三)-图 - 图3
    非简单图
  • 完全图:如果在无向图中,任意两个顶点之间都存在边,则称为完全图。
    算法(三)-图 - 图4
    完全图
  • 有向完全图:如果任意两个顶点之间都存在互为相反的两条弧,则称为又想完全图。
    算法(三)-图 - 图5
    有向完全图
  • 有很少条边或弧的图称为稀疏图,反之称为稠密图。(数量不一定)
  • 与图的边或者弧相关的数字叫做权,带权的图称为网。
  • 子图:假设有两个图G=(V, {E})和G1=(V1, {E1}),如果V1⊆V,且E1⊆E,则称G1为G的子图。

图的顶点和边之间的关系

算法(三)-图 - 图6

图1

  • 顶点的度:
    对于无向图G=(V,{E}),如果边(V,V1)∈E,则称顶点V和V1互为邻接点,顶点V的度是和V相关联的边的数目,记为TD(V)。如图1,A的度TD(A)=3,TD(B)=2,TD(C)=3,TD(D)=2,则各个顶点的度总和为:3+2+3+2=10,而图1无向图的边数为5,即各个顶点的度的总和的一半。
    对于有向图G=(V,{E}),如果弧∈E,则称顶点V和V1互为邻接点;以顶点V为头的弧的数目称为V的入度,记为ID(V);以顶点V为尾的弧的数目称为V的出度,记为OD(V);则顶点TD(V)=ID(V)+OD(V)。如图1有向图,TD(A)=ID(A)+OD(A)=2+1=3,各顶点的出度和为:1+2+1+0=4,各顶点的入度和为:2+0+1+1=4,有向图的边的总数4,所以,有向图边的总数=各顶点的出度数=各顶点的入度数。
  • 路径:
    从一个顶点到另一个顶点所走过的顶点和边所连成的通道。比如图1的无向图,从B到D有四条路径,分别是(BAD),(BCD),(BACD),(BCAD)。而图1从B到D的路径是有方向的,即(BAD),(BCAD)。路径的长度是路径上的边数或弧的数目。序列中顶点不重复出现的路径称为简单路径。
  • 回路或环:
    点一个顶点和最后一个顶点相同的路径称为回路或者环。其中,除了第一个顶点和最后一个顶点,其余顶点不重复出现的回路,称为简单回路或简单环。下面图1左边就是简单回路,右边并不是简单回路,因为顶点C重复出现了。
    算法(三)-图 - 图7
    图2
  • 连通图:
    在无向图G中,如果从顶点V到顶点V1有路径,则称顶点V和顶点V1是连通的。如果对于图中任意两个顶点都是连通的,则称G是连通图。如图3,左边的图不是连通图,右边的图是连通图。
    算法(三)-图 - 图8
    图3
    算法(三)-图 - 图9
    图4
  • 连通分量:
    无向图中的极大连通子图称为连通分量。
    (1)子图。
    (2)子图是连通的。
    (3)连通的子图含有极大的顶点数。
    (4)具有极大顶点数的连通子图包含依附于这些顶点的所有边。
    比如图3中,左边的图的连通分量为右边的图。
    在有向图G中,如果对于每一对V1,V2∈V,V1≠V2,从V1到V2和从V2到V1都存在路径,则称G是强连通图。有向图中极大强连通子图称做有向图的强连通分量。上图4中左边的图不是强连通图,右边的图是强连通图。
  • 无向图生成树:
    一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。比如图2中左边的无向图,它的连通分量为自身,则它的极小连通子图为,自身去调AC和AD两条边,则构成一个极小连通图,因为再去多一条边就构不成连通图了,这个就是连通图的生成树。
  • 有向树:
    如果一个有向图恰有一个顶点的入度为0,其余顶点的入度都为1,则是一颗有向树。(不需要连通)
  • 生成森林:
    一个有向图的生成森林由若干有向树组成,含有图中全部顶点,但只有足以构成若干课不相交的有向树的弧。
    算法(三)-图 - 图10
    图5

图的抽象数据类型

  1. ADT 图(Graph)
  2. Data
  3. 顶点是有穷且非空集合+边的集合
  4. Operation
  5. ...
  6. addVertex(v):添加顶点
  7. addEdge(v1, v2):添加边
  8. DFSTraverse():深度优先遍历
  9. BFSTraverse():广度优先遍历
  10. ...
  11. EndADT

图的数据存储

邻接矩阵:用两个数组来表示图。一个一维数组储存图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

  • 无向图
    算法(三)-图 - 图11
    邻接矩阵图1

算法(三)-图 - 图12

邻接矩阵图2

信息:
(1)v1的度即为第1行,所有元素之和:1+0+1+0=2。
(2)v1的邻接点即为第1行,所有元素为1的顶点。
(3)判断v1到v2是否有边,只需要判断arc[1][2]==1?就行,所以,v1到v2有边。

  • 有向图
    算法(三)-图 - 图13
    邻接矩阵3
    信息:
    (1)v1的出度为第1行的元素之和:1+0+1+0=2;v1的入度为第1列的元素之和:0+0+1+0=1。
    (2)判断v0到v3是否有弧,只需要判断arc[0][3]==1?就行,所以,v0到v3是有弧的。(v3到v0没有弧,弧是有方向的)
  • 带权有向图
    就是将有向图的邻接矩阵中的0和1换成相应的权值,不存在则设置为∞,自身则设置为0。
    算法(三)-图 - 图14
    邻接矩阵4

邻接表:数组和链表组合的存储方法称为邻接表。

邻接表处理:
(1)图中顶点使用一个一维数组存储。
(2)图中每个顶点v的所有邻接点构成一个线性表。

  • 无向图邻接表:
    算法(三)-图 - 图15
    邻接表图1
  • 有向图邻接表和逆邻接表:
    (1)有向图邻接表记录的是顶点的出度的弧,即弧尾。
    (2)有向图逆邻接表记录的是顶点的入度的弧,即弧头。
    算法(三)-图 - 图16
    邻接表图2
  • 带权邻接表
    算法(三)-图 - 图17
    邻接表图3

十字链表:即把邻接表与逆邻接表结合起来。(有向邻接表的优化)

  • 重新定义顶点
    算法(三)-图 - 图18
    十字链表顶点
    data为数据域,firstin为入边的表头指针,firsetout为出边的表头指针。顶点的入边从firstin开始查询,同理,顶点的出边从firstout开始查询。
  • 重新定义边结点结构
    算法(三)-图 - 图19
    十字链表结点
    tailvex是指弧尾结点的下标,headvex是指弧头结点的下标;headlink是指弧头结点下标和headvex相同的另一个弧头结点下标,同理,taillink是指弧尾结点下标和tailvex相同的另一个弧尾结点下标,没有置为空。

举个例子:

算法(三)-图 - 图20

十字链表图1

v0的入度为:1->0, 2->0,v0的出度为:0->3。

邻接多重表:改造边结点,使删除某一条边不需要遍历两次。

算法(三)-图 - 图21

邻接多重表结点

其中ivex和jvex是与某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。

算法(三)-图 - 图22

邻接多重表

上图总共有5条边,分别是v0->v1,v1->v2,v2->v3,v3->v0,v0-v2,所以,会有5个边结点。然后从v0开始,有三条邻接的边,分别是v0-v1,v0-v2,v0-v3,首先vo指向E(0,1),然后当前的边结点的ivex=0,所以,ilink需要找到下一条顶点为v0的边,顺着边结点往下找,可以找到E(3,0),所以,ilink指向E(3,0),由于,v0还有另外一条边E(0,2),所以,jlink顺着往下找,找到E(0,2),再往下没有边结点了,所以置为空。其他顶点的操作也是相似的过程。

边集数组:边集数组是由两个一位数组组成。一个是存储顶点信息,另一个是存储边的信息。这个数组每个数据元素由一条边的起点下标(begin),终点下标(end)和权(weight)组成。

算法(三)-图 - 图23

边集数组

图的遍历

图的遍历:从图中某一顶点出发访遍图中其余顶点,且使每一个顶点被访问一次。

  • 深度优先遍历(DFS)
    连通图深度优先遍历: 从图中的某个顶点v出发,访问此顶点,然后从v的未被访问的邻接顶点出发,深度优先遍历图,直到图中所有和v有路径相通的顶点被访问到。
    非连通图深度优先遍历:先对一个顶点进行连通图深度优先遍历,若还有没有被访问的顶点,再对该顶点进行连通图深度优先遍历,直到图中所有顶点都被访问到。

算法(三)-图 - 图24

深度优先遍历图1

邻接矩阵存储图,深度优先遍历实现算法

  1. 使用深度优先遍历图1实现深度优先遍历算法
  2. //邻接矩阵
  3. public struct Graph<T: Equatable> {
  4. fileprivate var vexs: [T] = []
  5. fileprivate var arc: [[Int]] = []
  6. public var vertexes: Int {
  7. return vexs.count
  8. }
  9. public var edges: Int {
  10. var count = 0
  11. for items in arc {
  12. for item in items {
  13. if item > 0 {
  14. count += 1
  15. }
  16. }
  17. }
  18. return count
  19. }
  20. public mutating func addVertex(vertext: T) {
  21. vexs.append(vertext)
  22. for i in 0..<arc.count {
  23. var temp = arc[i]
  24. temp.append(0)
  25. arc[i] = temp
  26. }
  27. let newArray = Array(repeating: 0, count: arc.count+1)
  28. arc.append(newArray)
  29. }
  30. public mutating func addEdge(from top1: T, to top2: T) {
  31. let containTop1 = vexs.contains { $0 == top1 }
  32. let containTop2 = vexs.contains { $0 == top2 }
  33. guard containTop1, containTop2 else {
  34. return
  35. }
  36. let row = vexs.index(of: top1)!
  37. let column = vexs.index(of: top2)!
  38. arc[row][column] = 1
  39. }
  40. }
  41. var graphics = Graph<String>()
  42. graphics.addVertex(vertext: "A")
  43. graphics.addVertex(vertext: "B")
  44. graphics.addVertex(vertext: "C")
  45. graphics.addVertex(vertext: "D")
  46. graphics.addVertex(vertext: "E")
  47. graphics.addVertex(vertext: "F")
  48. graphics.addVertex(vertext: "G")
  49. graphics.addVertex(vertext: "H")
  50. graphics.addVertex(vertext: "I")
  51. graphics.addEdge(from: "A", to: "B")
  52. graphics.addEdge(from: "A", to: "F")
  53. graphics.addEdge(from: "B", to: "C")
  54. graphics.addEdge(from: "B", to: "I")
  55. graphics.addEdge(from: "B", to: "G")
  56. graphics.addEdge(from: "C", to: "I")
  57. graphics.addEdge(from: "C", to: "D")
  58. graphics.addEdge(from: "D", to: "I")
  59. graphics.addEdge(from: "D", to: "E")
  60. graphics.addEdge(from: "D", to: "G")
  61. graphics.addEdge(from: "D", to: "H")
  62. graphics.addEdge(from: "E", to: "F")
  63. graphics.addEdge(from: "E", to: "H")
  64. graphics.addEdge(from: "F", to: "G")
  65. graphics.addEdge(from: "G", to: "H")
  66. //深度优先遍历
  67. var visited: [Bool] = []
  68. func DFS(graphics: Graph<String>, i: Int) {
  69. visited[i] = true
  70. print(graphics.vexs[i])
  71. for j in 0..<graphics.vertexes {
  72. if graphics.arc[i][j] == 1, !visited[j] {
  73. DFS(graphics: graphics, i: j)
  74. }
  75. }
  76. }
  77. func DFSTraverse(graphics: Graph<String>) {
  78. for _ in 0..<graphics.vertexes {
  79. visited.append(false)
  80. }
  81. for i in 0..<graphics.vertexes {
  82. if !visited[i] {
  83. DFS(graphics: graphics, i: i)
  84. }
  85. }
  86. }
  87. DFSTraverse(graphics: graphics)
  88. 输出结果:ABCDEFGHI

邻接链表存储图,深度优先遍历实现算法:

  1. class EdgeNode<T: Equatable> {
  2. var index: Int
  3. var nextEdge: EdgeNode<T>?
  4. init(atIndex index: Int) {
  5. self.index = index
  6. }
  7. }
  8. class VertexNode<T: Equatable> {
  9. var data: T
  10. var firstEdge: EdgeNode<T>?
  11. init(_ data: T) {
  12. self.data = data
  13. }
  14. }
  15. class Graphics<T: Equatable> {
  16. var adjList: [VertexNode<T>] = []
  17. var numVertexes: Int {
  18. return adjList.count
  19. }
  20. var numEdges: Int {
  21. var count = 0
  22. for vertex in adjList {
  23. if let edge = vertex.firstEdge {
  24. count += 1
  25. var next = edge.nextEdge
  26. while next != nil {
  27. count += 1
  28. next = next?.nextEdge
  29. }
  30. }
  31. }
  32. return count/2
  33. }
  34. func addVertex(vertex: T) {
  35. let v = VertexNode<T>(vertex)
  36. adjList.append(v)
  37. }
  38. func addEdge(from v1: T, to v2: T) {
  39. let vertex1 = getVertex(data: v1)
  40. let vertex2 = getVertex(data: v2)
  41. guard vertex1 != nil, vertex2 != nil else {
  42. return;
  43. }
  44. let index1 = getVertextIndex(v1)!
  45. let index2 = getVertextIndex(v2)!
  46. vertextAddEdge(vertex: vertex1!, index: index2)
  47. vertextAddEdge(vertex: vertex2!, index: index1)
  48. }
  49. func vertextAddEdge(vertex: VertexNode<T>, index: Int) {
  50. var edgeNode = vertex.firstEdge
  51. guard edgeNode != nil else {
  52. vertex.firstEdge = EdgeNode<T>(atIndex: index)
  53. return;
  54. }
  55. let value = adjList[index].data
  56. if adjList[(edgeNode?.index)!].data == value {
  57. return
  58. }
  59. while edgeNode?.nextEdge != nil {
  60. edgeNode = edgeNode?.nextEdge
  61. if adjList[(edgeNode?.index)!].data == value {
  62. return
  63. }
  64. }
  65. edgeNode?.nextEdge = EdgeNode<T>(atIndex: index)
  66. }
  67. func getVertex(data: T) -> VertexNode<T>? {
  68. for v in adjList {
  69. if v.data == data {
  70. return v
  71. }
  72. }
  73. return nil
  74. }
  75. func getVertextIndex(_ data: T) -> Int? {
  76. for index in 0..<adjList.endIndex {
  77. let vertex = adjList[index]
  78. if vertex.data == data {
  79. return index
  80. }
  81. }
  82. return nil
  83. }
  84. }
  85. let graphics = Graphics<String>()
  86. graphics.addVertex(vertex: "A")
  87. graphics.addVertex(vertex: "B")
  88. graphics.addVertex(vertex: "C")
  89. graphics.addVertex(vertex: "D")
  90. graphics.addVertex(vertex: "E")
  91. graphics.addVertex(vertex: "F")
  92. graphics.addVertex(vertex: "G")
  93. graphics.addVertex(vertex: "H")
  94. graphics.addVertex(vertex: "I")
  95. graphics.addEdge(from: "A", to: "B")
  96. graphics.addEdge(from: "A", to: "F")
  97. graphics.addEdge(from: "B", to: "A")
  98. graphics.addEdge(from: "B", to: "C")
  99. graphics.addEdge(from: "B", to: "G")
  100. graphics.addEdge(from: "B", to: "I")
  101. graphics.addEdge(from: "C", to: "B")
  102. graphics.addEdge(from: "C", to: "D")
  103. graphics.addEdge(from: "C", to: "I")
  104. graphics.addEdge(from: "D", to: "C")
  105. graphics.addEdge(from: "D", to: "E")
  106. graphics.addEdge(from: "D", to: "G")
  107. graphics.addEdge(from: "D", to: "H")
  108. graphics.addEdge(from: "D", to: "I")
  109. graphics.addEdge(from: "E", to: "D")
  110. graphics.addEdge(from: "E", to: "F")
  111. graphics.addEdge(from: "E", to: "H")
  112. graphics.addEdge(from: "F", to: "G")
  113. graphics.addEdge(from: "G", to: "B")
  114. graphics.addEdge(from: "G", to: "D")
  115. graphics.addEdge(from: "G", to: "F")
  116. graphics.addEdge(from: "G", to: "H")
  117. var visited: [Bool] = []
  118. func DFS(graphics: Graphics<String>, i: Int) {
  119. visited[i] = true
  120. print(graphics.adjList[i].data)
  121. var edgeNode = graphics.adjList[i].firstEdge
  122. while edgeNode != nil {
  123. if !visited[(edgeNode?.index)!] {
  124. DFS(graphics: graphics, i: (edgeNode?.index)!)
  125. }
  126. edgeNode = edgeNode?.nextEdge
  127. }
  128. }
  129. func DFSTraverse(graphics: Graphics<String>) {
  130. for _ in 0..<graphics.numVertexes {
  131. visited.append(false)
  132. }
  133. for i in 0..<graphics.numVertexes {
  134. if !visited[i] {
  135. DFS(graphics: graphics, i: i)
  136. }
  137. }
  138. }
  139. DFSTraverse(graphics: graphics)
  140. 输出结果:ABCDEFGHI
  • 广度优先遍历(BFS)
    如果深度优先遍历类似于树的前序遍历,那么广度优先遍历则类似于树的层遍历。与深度优先遍历图1为例,则广度优先遍历的顺序则为:
    (1)A(2)BF(3)CIGE(4)DH。

邻接矩阵存储图,广度优先遍历实现算法

  1. var visited: [Bool] = []
  2. func BFSTraverse(graphics: Graph<String>) {
  3. for _ in 0..<graphics.vertexes {
  4. visited.append(false)
  5. }
  6. var result: [String] = []
  7. for i in 0..<graphics.vertexes {
  8. if !visited[i] {
  9. visited[i] = true
  10. print(graphics.vexs[i])
  11. result.append(graphics.vexs[i])
  12. while !result.isEmpty {
  13. result.removeFirst()
  14. for j in 0..<graphics.vertexes {
  15. if graphics.arc[i][j] == 1, !visited[j] {
  16. visited[j] = true
  17. print(graphics.vexs[j])
  18. result.append(graphics.vexs[j])
  19. }
  20. }
  21. }
  22. }
  23. }
  24. }
  25. BFSTraverse(graphics: graphics)
  26. 输出结果:ABFCDIEHG

邻接链表存储图,深度优先遍历实现算法:

  1. var visited: [Bool] = []
  2. func BFSTraverse(graphics: Graphics<String>) {
  3. for _ in 0..<graphics.numVertexes {
  4. visited.append(false)
  5. }
  6. var result: [VertexNode<String>] = []
  7. for i in 0..<graphics.numVertexes {
  8. if !visited[i] {
  9. visited[i] = true
  10. print(graphics.adjList[i].data)
  11. result.append(graphics.adjList[i])
  12. while !result.isEmpty {
  13. let vertex = result.removeFirst()
  14. var edgeNode = vertex.firstEdge
  15. while edgeNode != nil {
  16. if !visited[(edgeNode?.index)!] {
  17. visited[(edgeNode?.index)!] = true
  18. print(graphics.adjList[(edgeNode?.index)!].data)
  19. result.append(graphics.adjList[(edgeNode?.index)!])
  20. }
  21. edgeNode = edgeNode?.nextEdge
  22. }
  23. }
  24. }
  25. }
  26. }
  27. BFSTraverse(graphics: graphics)
  28. 输出结果:ABFCGIEDH

比较:深度优先遍历适合目标比较明确,而广度优先遍历更适合在不断扩大范围时找到相对最优解的情况。

最小生成树

最小生成树:构造连通网的最小代价生成树称为最小生成树。

寻找最小生成树的两种算法:普里姆算法(Prim)和克鲁斯卡尔算法(Kruskal)。

  • 普里姆算法

假设N是连通网,TE是N上最小生成树中边的集合。算法从U={u0}(u0∈V),TE={}开始。重复执行下述操作:在所有u∈V,v∈V-U的边(u,v)∈E中,找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树,时间复杂度为O(n^2)。通俗来讲,先从v0开始,找到和v0连接的权重最小的边的顶点v1,然后再找{v0,v1}相连的权重最小的边,直到所有顶点都找完。

  1. func miniSpanTree_prim(graphics: Graph<String>) {
  2. var adjvex = Array(repeating: 0, count: graphics.vertexes) //初始化都为0,意味着从v0开始找
  3. var lowcost = graphics.arc[0] //初始化为v0所关联的所有边的权值
  4. //因为v0已经在adjvex里面了,所以从下标1开始寻找
  5. var min: Int
  6. for _ in 1..<graphics.vertexes {
  7. min = Int.max //lowcost下标为0则说明该顶点已被找过,下标为Int.max则意味着,没有该条边
  8. var k = 0 //保存最小值的下标
  9. for j in 1..<graphics.vertexes {
  10. if lowcost[j] != 0, lowcost[j] < min {
  11. min = lowcost[j]
  12. k = j
  13. }
  14. }
  15. print("\(adjvex[k], k)") //adjvex[k]为当前的顶点,k为当前顶点关联权值最小边的顶点的下标
  16. //将找到的最小权值边的下一个顶点加入adjvex,并且将该顶点相关联的较小边加入lowcost
  17. for j in 1..<graphics.vertexes {
  18. if lowcost[j] != 0, graphics.arc[k][j] < lowcost[j] {
  19. lowcost[j] = graphics.arc[k][j]
  20. adjvex[j] = k
  21. }
  22. }
  23. }
  24. }
  25. miniSpanTree_prim(graphics: graphics)
  26. 输出结果:
  27. (0, 1)
  28. (0, 5)
  29. (1, 8)
  30. (8, 2)
  31. (1, 6)
  32. (6, 7)
  33. (7, 4)
  34. (7, 3)
  • 注意点:每添加一个顶点,lowcost数组中除了对应已选则的顶点为0之外,保存已选顶点和未选顶点之间的所有较小的边。
  • 克鲁斯卡尔算法

假设 N= (V,{E})是连通网,则令最小生成树的初始状态为只有 n 个顶点而无边的 非连通图 T={V,{}},图中每个顶点自成一个连通分量。在 E 中选择代价最小的边,若 该边依附的顶点落在 T 中不同的连通分量上,则将此边加入到 T 中,否则舍去此边而 选择下一条代价最小的边。依次类推,直至 T 中所有顶点都在罔一连通分量上为止 ,时间复杂度为O(eloge)。通俗来讲就是,先把所有的边按照权值的大小排序,然后从最小的开始选择,每选择一次需要判断是否和之前已选择的边形成回路,如果形成回路则选择下一条边,直到所有的边都被遍历完,所得就是最小生成树。

  1. struct Edge{
  2. let begin: Int, end: Int, weight: Int
  3. init (begin: Int, end: Int, weight: Int) {
  4. self.begin = begin
  5. self.end = end
  6. self.weight = weight
  7. }
  8. }
  9. func miniSpanTree_Kruskal(graphics: Graph<String>) {
  10. var edges: [Edge] = [] //边集数组
  11. for i in 1..<graphics.vertexes {
  12. let arc = graphics.arc[i-1]
  13. for j in i..<graphics.vertexes {
  14. if arc[j] < Int.max {
  15. let edge = Edge(begin: i-1, end: j, weight: arc[j])
  16. edges.append(edge)
  17. }
  18. }
  19. }
  20. edges.sort{$0.weight < $1.weight}
  21. var parent = Array(repeating: 0, count: graphics.vertexes) //判断是否成环的数组
  22. for i in 0..<edges.count {
  23. let n = find(parent: parent, fedge: edges[i].begin)
  24. let m = find(parent: parent, fedge: edges[i].end)
  25. if n != m { //一条边从两边开始计算,只要最后的点相等,则会构成环
  26. parent[n] = m
  27. print("\(edges[i].begin, edges[i].end) \(edges[i].weight)")
  28. }
  29. }
  30. }
  31. //寻找每个点的路径
  32. func find(parent: [Int], fedge: Int) -> Int {
  33. var result = fedge
  34. while parent[result] > 0 {
  35. result = parent[result]
  36. }
  37. return result
  38. }
  39. miniSpanTree_Kruskal(graphics: graphics)
  • 注意点:parent数组,从下标开始和相应下标数组中的值,形成该顶点的一条路径。
  • 两个算法对比:
    对比两个算法,克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所以对于稀疏图有很大的优势。而普里姆算法对于稠密图,即边数非常多的情况会更 好一些。

最短路径

对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最小的路径,并且,我们称路径上第一个顶点为源点,最后一个顶点为终点。

以下面的网图,寻找最短路径:

算法(三)-图 - 图25

最短路径

  1. var graphics = Graph<String>()
  2. graphics.addVertex(vertext: "v0")
  3. graphics.addVertex(vertext: "v1")
  4. graphics.addVertex(vertext: "v2")
  5. graphics.addVertex(vertext: "v3")
  6. graphics.addVertex(vertext: "v4")
  7. graphics.addVertex(vertext: "v5")
  8. graphics.addVertex(vertext: "v6")
  9. graphics.addVertex(vertext: "v7")
  10. graphics.addVertex(vertext: "v8")
  11. //最短路径
  12. graphics.addEdge(from: "v0", to: "v1", weight: 1)
  13. graphics.addEdge(from: "v0", to: "v2", weight: 5)
  14. graphics.addEdge(from: "v1", to: "v0", weight: 1)
  15. graphics.addEdge(from: "v1", to: "v2", weight: 3)
  16. graphics.addEdge(from: "v1", to: "v3", weight: 7)
  17. graphics.addEdge(from: "v1", to: "v4", weight: 5)
  18. graphics.addEdge(from: "v2", to: "v0", weight: 5)
  19. graphics.addEdge(from: "v2", to: "v1", weight: 3)
  20. graphics.addEdge(from: "v2", to: "v4", weight: 1)
  21. graphics.addEdge(from: "v2", to: "v5", weight: 7)
  22. graphics.addEdge(from: "v3", to: "v1", weight: 7)
  23. graphics.addEdge(from: "v3", to: "v4", weight: 2)
  24. graphics.addEdge(from: "v3", to: "v6", weight: 3)
  25. graphics.addEdge(from: "v4", to: "v1", weight: 5)
  26. graphics.addEdge(from: "v4", to: "v2", weight: 1)
  27. graphics.addEdge(from: "v4", to: "v3", weight: 2)
  28. graphics.addEdge(from: "v4", to: "v5", weight: 3)
  29. graphics.addEdge(from: "v4", to: "v6", weight: 6)
  30. graphics.addEdge(from: "v4", to: "v7", weight: 9)
  31. graphics.addEdge(from: "v5", to: "v2", weight: 7)
  32. graphics.addEdge(from: "v5", to: "v4", weight: 3)
  33. graphics.addEdge(from: "v5", to: "v7", weight: 5)
  34. graphics.addEdge(from: "v6", to: "v3", weight: 3)
  35. graphics.addEdge(from: "v6", to: "v4", weight: 6)
  36. graphics.addEdge(from: "v6", to: "v7", weight: 2)
  37. graphics.addEdge(from: "v6", to: "v8", weight: 7)
  38. graphics.addEdge(from: "v7", to: "v4", weight: 9)
  39. graphics.addEdge(from: "v7", to: "v5", weight: 5)
  40. graphics.addEdge(from: "v7", to: "v6", weight: 2)
  41. graphics.addEdge(from: "v7", to: "v8", weight: 4)
  42. graphics.addEdge(from: "v8", to: "v6", weight: 7)
  43. graphics.addEdge(from: "v8", to: "v7", weight: 4)
  • 迪杰斯特拉(Dijkstra)算法

从源点开始,找到源点到下一个最短路径的顶点。然后,在之前的基础上,再找下一个最短路径的顶点,直到终点。该算法的时间复杂度为O(n^2)。

  1. func shortestPath_Dijkstra(graphics: Graph<String>) {
  2. var final = Array(repeating: 0, count: graphics.vertexes) //final[N]=1,表示求的顶点v0至vN的最短路径
  3. var pathArc = Array(repeating: 0, count: graphics.vertexes) //储存最短路径下标前驱的数组
  4. var shortPathTable: [Int] = [] //表示v0到各个顶点的最短路径长度
  5. for i in 0..<graphics.vertexes {
  6. shortPathTable.append(graphics.arc[0][i])
  7. }
  8. final[0] = 1 //v0-v0不需要求路径
  9. //开始求v0到各个顶点的最短路径
  10. for _ in 1..<graphics.vertexes {
  11. var min = Int.max //当前所知离v0顶点最近的距离
  12. var k = 0
  13. for w in 0..<graphics.vertexes {
  14. if final[w] == 0, shortPathTable[w] < min {
  15. k = w
  16. min = shortPathTable[w]
  17. }
  18. }
  19. final[k] = 1
  20. //修正最短路径
  21. for w in 0..<graphics.vertexes {
  22. if final[w] == 0, min < shortPathTable[w]-graphics.arc[k][w]{
  23. shortPathTable[w] = min + graphics.arc[k][w]
  24. pathArc[w] = k
  25. }
  26. }
  27. }
  28. print(final)
  29. print(pathArc)
  30. print(shortPathTable)
  31. }
  32. shortestPath_Dijkstra(graphics: graphics)
  33. //输出结果
  34. [1, 1, 1, 1, 1, 1, 1, 1, 1]
  35. [0, 0, 1, 4, 2, 4, 3, 6, 7]
  36. [0, 1, 4, 7, 5, 8, 10, 12, 16]
  • 说明:
    刚开始final数组为:[1, 0, 0, 0, 0, 0, 0, 0, 0],因为从v0开始,v0算是找到了最短路径;pathArc数组为:[0, 0, 0, 0, 0, 0, 0, 0, 0];shortestPathTable数组为:[0, 1, 5, ∞, ∞, ∞, ∞, ∞, ∞]。因为v0-v1=1 < v0-v2=5,所以,v0下一个最短路径的顶点是v1,这时候final为[1, 1, 0, 0, 0, 0, 0, 0, 0],那么,既然找到了下一个最短路径的顶点v1,那么,和v1相连的顶点路径的权值和shortestPathTable中的权值比较,取较小者的权值。到此,v0-v1以及和v1相连的顶点的较小权值都得到了,继续重复上面的步骤直到终点,就可以得到从v0-v8各个顶点到v0的最小路径了。
  • 佛洛伊德(Floyd)算法

关键公式:D[ v ][ w ] = min { D[ v ][ w ] , D[ V ][0] + D[0][W] },主要思想就是:求v1到v2经过最短路径,有v1->v2,v1->v0->v2,则v1->v2的最短路径为两者最短的路径。以此类推,在其基础上求v1-v3的路径也是一样的。算法时间复杂度为O(n^3)。

  1. //佛洛伊德算法
  2. func ShortestPath_Floyd(graphics: Graph<String>) {
  3. //初始化矩阵
  4. var pathmatirx: [[Int]]
  5. var shortPathTable: [[Int]] = graphics.arc
  6. var rowValue: [Int] = []
  7. for i in 0..<graphics.vertexes {
  8. rowValue.append(i)
  9. }
  10. pathmatirx = Array(repeating: rowValue, count: graphics.vertexes)
  11. //开始算法
  12. for k in 0..<graphics.vertexes {
  13. for v in 0..<graphics.vertexes {
  14. for w in 0..<graphics.vertexes {
  15. if shortPathTable[v][k] < Int.max, shortPathTable[k][w] < Int.max, shortPathTable[v][w] > shortPathTable[v][k] + shortPathTable[k][w] {
  16. shortPathTable[v][w] = shortPathTable[v][k] + shortPathTable[k][w]
  17. pathmatirx[v][w] = pathmatirx[v][k]
  18. }
  19. }
  20. }
  21. }
  22. //打印最短路径
  23. var k = 0
  24. for v in 0..<graphics.vertexes {
  25. for w in v+1..<graphics.vertexes {
  26. print("v\(v)-v\(w) weight: \(shortPathTable[v][w])")
  27. k = pathmatirx[v][w]
  28. print("path: \(v)")
  29. while k != w {
  30. print(" -> \(k)")
  31. k = pathmatirx[k][w]
  32. }
  33. print(" -> \(w)")
  34. }
  35. print("\n")
  36. }
  37. }
  38. ShortestPath_Floyd(graphics: graphics)
  39. //输出结果
  40. [0, 1, 4, 7, 5, 8, 10, 12, 16] //v0达到各顶点的最短路径
  41. [1, 0, 3, 6, 4, 7, 9, 11, 15]
  42. [4, 3, 0, 3, 1, 4, 6, 8, 12]
  43. [7, 6, 3, 0, 2, 5, 3, 5, 9]
  44. [5, 4, 1, 2, 0, 3, 5, 7, 11]
  45. [8, 7, 4, 5, 3, 0, 7, 5, 9]
  46. [10, 9, 6, 3, 5, 7, 0, 2, 6]
  47. [12, 11, 8, 5, 7, 5, 2, 0, 4]
  48. [16, 15, 12, 9, 11, 9, 6, 4, 0]
  49. //前驱矩阵
  50. [0, 1, 1, 1, 1, 1, 1, 1, 1]
  51. [0, 1, 2, 2, 2, 2, 2, 2, 2]
  52. [1, 1, 2, 4, 4, 4, 4, 4, 4]
  53. [4, 4, 4, 3, 4, 4, 6, 6, 6]
  54. [2, 2, 2, 3, 4, 5, 3, 3, 3]
  55. [4, 4, 4, 4, 4, 5, 7, 7, 7]
  56. [3, 3, 3, 3, 3, 7, 6, 7, 7]
  57. [6, 6, 6, 6, 6, 5, 6, 7, 8]
  58. [7, 7, 7, 7, 7, 7, 7, 7, 8]
  • 说明:获取v0->v8的最短路径,则P[0][8] = 1,P[1][8] = 2,P[2][8] = 4,P[4][8] = 3,P[3][8] = 6,P[6][8] = 7,P[7][8] = 8,所以,v0->v8的最短路径为:v0->v1->v2->v4->v3->v6->v7-V8。
  • 两个算法总结:
    如果只需求其中两个顶点之间的最短路径,采用迪杰斯特拉算法;如果需要求所有顶点到所有顶点的最短路径,可以采用佛洛依德算法,虽然,两个算法计算所有顶点到所有顶点的最短路径的时间复杂度都是O(n^3),但是佛洛依德算法比较简洁,不易出错。

拓扑结构

在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图表示的网,称为AOV网(Activity On vertex Network)。假设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1, v2, v3, …… , Vn,满足若从顶点vi到vj有一条路径,则中顶点序列中顶点vi必须在vj之前,我们称这样的顶点序列为一个拓扑序列。拓扑排序,就是对一个有向图构造拓扑序列的过程。拓扑序列的主要作用是确定一个有向图确定的有序序列,比如:iOS开发使用拓扑序列来整理整个工程的文件依赖,比如检查循环依赖,比如确认从那个依赖文件开始编译等等。两个特点:1.无环有向图 2.两个顶点之间的方向只有一个。

算法(三)-图 - 图26

AVO图.png

算法(三)-图 - 图27

AVO图链表表示.png

  1. extension Int {
  2. static func += (left: inout Int, right: Int) {
  3. left = left + right
  4. }
  5. static func -= (left: inout Int, right: Int) {
  6. left = left - right
  7. }
  8. }
  9. //边表结点
  10. class EdgeNode {
  11. var adjex = 0
  12. var weight = 1
  13. var next: EdgeNode?
  14. }
  15. //顶点表结点
  16. class VertexNode {
  17. var inEdge = 0
  18. var data = 0
  19. var firstEdge: EdgeNode?
  20. }
  21. class GraphAdjList {
  22. var adjList: [VertexNode] = []
  23. var numEdges = 0
  24. var numVertexes : Int {
  25. return adjList.count
  26. }
  27. }
  28. let graphiAdjList = GraphAdjList()
  29. //V0
  30. let v0 = VertexNode()
  31. v0.inEdge = 0
  32. v0.data = 0
  33. let v0E11 = EdgeNode()
  34. v0E11.adjex = 11
  35. let v0E5 = EdgeNode()
  36. v0E5.adjex = 5
  37. let v0E4 = EdgeNode()
  38. v0E4.adjex = 4
  39. v0.firstEdge = v0E11
  40. v0E11.next = v0E5
  41. v0E5.next = v0E4
  42. //V1
  43. let v1 = VertexNode()
  44. v1.inEdge = 0
  45. v1.data = 1
  46. let v1E8 = EdgeNode()
  47. v1E8.adjex = 8
  48. let v1E4 = EdgeNode()
  49. v1E4.adjex = 4
  50. let v1E2 = EdgeNode()
  51. v1E2.adjex = 2
  52. v1.firstEdge = v1E8
  53. v1E8.next = v1E4
  54. v1E4.next = v1E2
  55. //V2
  56. let v2 = VertexNode()
  57. v2.inEdge = 2
  58. v2.data = 2
  59. let v2E9 = EdgeNode()
  60. v2E9.adjex = 9
  61. let v2E6 = EdgeNode()
  62. v2E6.adjex = 6
  63. let v2E5 = EdgeNode()
  64. v2E5.adjex = 5
  65. v2.firstEdge = v2E9
  66. v2E9.next = v2E6
  67. v2E6.next = v2E5
  68. //V3
  69. let v3 = VertexNode()
  70. v3.inEdge = 0
  71. v3.data = 3
  72. let v3E13 = EdgeNode()
  73. v3E13.adjex = 13
  74. let v3E2 = EdgeNode()
  75. v3E2.adjex = 2
  76. v3.firstEdge = v3E13
  77. v3E13.next = v3E2
  78. //V4
  79. let v4 = VertexNode()
  80. v4.inEdge = 2
  81. v4.data = 4
  82. let v4E7 = EdgeNode()
  83. v4E7.adjex = 7
  84. v4.firstEdge = v4E7
  85. //V5
  86. let v5 = VertexNode()
  87. v5.inEdge = 3
  88. v5.data = 5
  89. let v5E12 = EdgeNode()
  90. v5E12.adjex = 12
  91. let v5E8 = EdgeNode()
  92. v5E8.adjex = 8
  93. v5.firstEdge = v5E12
  94. v5E12.next = v5E8
  95. //V6
  96. let v6 = VertexNode()
  97. v6.inEdge = 1
  98. v6.data = 6
  99. let v6E5 = EdgeNode()
  100. v6E5.adjex = 5
  101. v6.firstEdge = v6E5
  102. //V7
  103. let v7 = VertexNode()
  104. v7.inEdge = 2
  105. v7.data = 7
  106. //V8
  107. let v8 = VertexNode()
  108. v8.inEdge = 2
  109. v8.data = 8
  110. let v8E7 = EdgeNode()
  111. v8E7.adjex = 7
  112. v8.firstEdge = v8E7
  113. //V9
  114. let v9 = VertexNode()
  115. v9.inEdge = 2
  116. v9.data = 9
  117. let v9E11 = EdgeNode()
  118. v9E11.adjex = 11
  119. let v9E10 = EdgeNode()
  120. v9E10.adjex = 10
  121. v9.firstEdge = v9E11
  122. v9E11.next = v9E10
  123. //V10
  124. let v10 = VertexNode()
  125. v10.inEdge = 1
  126. v10.data = 10
  127. let v10E13 = EdgeNode()
  128. v10E13.adjex = 13
  129. v10.firstEdge = v10E13
  130. //V11
  131. let v11 = VertexNode()
  132. v11.inEdge = 2
  133. v11.data = 11
  134. //V12
  135. let v12 = VertexNode()
  136. v12.inEdge = 1
  137. v12.data = 12
  138. let v12E9 = EdgeNode()
  139. v12E9.adjex = 9
  140. v12.firstEdge = v12E9
  141. //V13
  142. let v13 = VertexNode()
  143. v13.inEdge = 2
  144. v13.data = 13
  145. graphiAdjList.adjList.append(v0)
  146. graphiAdjList.adjList.append(v1)
  147. graphiAdjList.adjList.append(v2)
  148. graphiAdjList.adjList.append(v3)
  149. graphiAdjList.adjList.append(v4)
  150. graphiAdjList.adjList.append(v5)
  151. graphiAdjList.adjList.append(v6)
  152. graphiAdjList.adjList.append(v7)
  153. graphiAdjList.adjList.append(v8)
  154. graphiAdjList.adjList.append(v9)
  155. graphiAdjList.adjList.append(v10)
  156. graphiAdjList.adjList.append(v11)
  157. graphiAdjList.adjList.append(v12)
  158. graphiAdjList.adjList.append(v13)
  159. func toPologicalSort(gl: GraphAdjList) {
  160. var count = 0, top = 0, k = 0, getTop = 0
  161. var stack: [Int] = []
  162. //将有向图所有入度为0的顶点入栈
  163. for i in 0..<gl.numVertexes {
  164. if gl.adjList[i].inEdge == 0 {
  165. stack.append(i)
  166. top += 1
  167. }
  168. }
  169. //开始算拓扑序列
  170. while top != 0 {
  171. getTop = stack.removeLast()
  172. top -= 1
  173. print("vertex: \(gl.adjList[getTop].data)")
  174. count += 1
  175. var edgeNode = gl.adjList[getTop].firstEdge
  176. while edgeNode != nil {
  177. k = (edgeNode?.adjex)!
  178. gl.adjList[k].inEdge -= 1
  179. if gl.adjList[k].inEdge == 0 {
  180. stack.append(k)
  181. top += 1
  182. }
  183. edgeNode = edgeNode?.next
  184. }
  185. }
  186. if count < gl.numVertexes {
  187. print("Error")
  188. } else {
  189. print("OK")
  190. }
  191. }
  192. toPologicalSort(gl: graphiAdjList)
  193. //输出结果
  194. vertex: 3
  195. vertex: 1
  196. vertex: 2
  197. vertex: 6
  198. vertex: 0
  199. vertex: 4
  200. vertex: 5
  201. vertex: 8
  202. vertex: 7
  203. vertex: 12
  204. vertex: 9
  205. vertex: 10
  206. vertex: 13
  207. vertex: 11
  • 拓扑排序并不是唯一的
  • 拓扑排序简要说明:

1.先找到所有入度为0的顶点,并依次进栈。
2.从栈顶开始,出栈;然后,遍历该栈顶元素的链表,每个顶点的入度减1,判断每个顶点的入度是否为0,如果为0则入栈,否则跳过。
3.判断栈是否还有元素,如果栈不为空,则回到第2步,否则,结束。

关键路径

拓扑排序解决一个工程能否顺利进行的问题,那么,关键路径就是解决工程完成需要的最短时间问题。在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为 AOE网 (Activity On Edge Network) 。 我们把AOE网中没有人边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。

算法(三)-图 - 图28

AOE图.png

算法(三)-图 - 图29

AOE链表显示.png

  1. //V0
  2. let v0 = VertexNode()
  3. v0.inEdge = 0
  4. v0.data = 0
  5. let v0E2 = EdgeNode()
  6. v0E2.adjex = 2
  7. v0E2.weight = 4
  8. let v0E1 = EdgeNode()
  9. v0E1.adjex = 1
  10. v0E1.weight = 3
  11. v0.firstEdge = v0E2
  12. v0E2.next = v0E1
  13. //V1
  14. let v1 = VertexNode()
  15. v1.inEdge = 1
  16. v1.data = 1
  17. let v1E4 = EdgeNode()
  18. v1E4.adjex = 4
  19. v1E4.weight = 6
  20. let v1E3 = EdgeNode()
  21. v1E3.adjex = 3
  22. v1E3.weight = 5
  23. v1.firstEdge = v1E4
  24. v1E4.next = v1E3
  25. //V2
  26. let v2 = VertexNode()
  27. v2.inEdge = 1
  28. v2.data = 2
  29. let v2E5 = EdgeNode()
  30. v2E5.adjex = 5
  31. v2E5.weight = 7
  32. let v2E3 = EdgeNode()
  33. v2E3.adjex = 3
  34. v2E3.weight = 8
  35. v2.firstEdge = v2E5
  36. v2E5.next = v2E3
  37. //V3
  38. let v3 = VertexNode()
  39. v3.inEdge = 2
  40. v3.data = 3
  41. let v3E4 = EdgeNode()
  42. v3E4.adjex = 4
  43. v3E4.weight = 3
  44. v3.firstEdge = v3E4
  45. //V4
  46. let v4 = VertexNode()
  47. v4.inEdge = 2
  48. v4.data = 4
  49. let v4E7 = EdgeNode()
  50. v4E7.adjex = 7
  51. v4E7.weight = 4
  52. let v4E6 = EdgeNode()
  53. v4E6.adjex = 6
  54. v4E6.weight = 9
  55. v4.firstEdge = v4E7
  56. v4E7.next = v4E6
  57. //V5
  58. let v5 = VertexNode()
  59. v5.inEdge = 1
  60. v5.data = 5
  61. let v5E7 = EdgeNode()
  62. v5E7.adjex = 7
  63. v5E7.weight = 6
  64. v5.firstEdge = v5E7
  65. //V6
  66. let v6 = VertexNode()
  67. v6.inEdge = 1
  68. v6.data = 6
  69. let v6E9 = EdgeNode()
  70. v6E9.adjex = 9
  71. v6E9.weight = 2
  72. v6.firstEdge = v6E9
  73. //V7
  74. let v7 = VertexNode()
  75. v7.inEdge = 2
  76. v7.data = 7
  77. let v7E8 = EdgeNode()
  78. v7E8.adjex = 8
  79. v7E8.weight = 5
  80. v7.firstEdge = v7E8
  81. //V8
  82. let v8 = VertexNode()
  83. v8.inEdge = 1
  84. v8.data = 8
  85. let v8E9 = EdgeNode()
  86. v8E9.adjex = 9
  87. v8E9.weight = 3
  88. v8.firstEdge = v8E9
  89. //V9
  90. let v9 = VertexNode()
  91. v9.inEdge = 2
  92. v9.data = 9
  93. let graphiAdjList = GraphAdjList()
  94. graphiAdjList.adjList.append(v0)
  95. graphiAdjList.adjList.append(v1)
  96. graphiAdjList.adjList.append(v2)
  97. graphiAdjList.adjList.append(v3)
  98. graphiAdjList.adjList.append(v4)
  99. graphiAdjList.adjList.append(v5)
  100. graphiAdjList.adjList.append(v6)
  101. graphiAdjList.adjList.append(v7)
  102. graphiAdjList.adjList.append(v8)
  103. graphiAdjList.adjList.append(v9)
  104. //etv(earliest time of vertex) => 顶点V的最早发生时间
  105. //ltv(latest time of vertex) => 顶点V的最迟发生时间
  106. //ete(earliest time of edge) => 弧a的最早发生时间
  107. //lte(latest time of edge) => 弧a的最迟发生时间
  108. var etv: [Int] = Array(repeating: 0, count: graphiAdjList.numVertexes)
  109. var ltv: [Int] = []
  110. var stack2: [Int] = []
  111. var top2 = 0
  112. func TopologicalSort(gl: GraphAdjList) {
  113. var count = 0, top = 0, k = 0, getTop = 0
  114. var stack: [Int] = []
  115. //将有向图所有入度为0的顶点入栈
  116. for i in 0..<gl.numVertexes {
  117. if gl.adjList[i].inEdge == 0 {
  118. stack.append(i)
  119. top += 1
  120. }
  121. }
  122. //开始算拓扑序列
  123. while top != 0 {
  124. getTop = stack.removeLast()
  125. top -= 1
  126. count += 1
  127. /////////////////////
  128. stack2.append(getTop) //添加入度为0的顶点
  129. top2 += 1
  130. ////////////////////
  131. var edgeNode = gl.adjList[getTop].firstEdge
  132. while edgeNode != nil {
  133. k = (edgeNode?.adjex)!
  134. gl.adjList[k].inEdge -= 1
  135. if gl.adjList[k].inEdge == 0 {
  136. stack.append(k)
  137. top += 1
  138. }
  139. /////////////////////////////////////////////
  140. if etv[getTop]+(edgeNode?.weight)! > etv[k] { //顶点的最早发生时间,即到达这个顶点之前最大的花费时间
  141. etv[k] = etv[getTop]+(edgeNode?.weight)!
  142. }
  143. /////////////////////////////////////////////
  144. edgeNode = edgeNode?.next
  145. }
  146. }
  147. if count < gl.numVertexes {
  148. print("Error")
  149. } else {
  150. print("OK")
  151. }
  152. }
  153. func CriticalPath(gl: GraphAdjList) {
  154. var getTop = 0, k = 0
  155. var ete = 0, lte = 0
  156. TopologicalSort(gl: gl) //求拓扑序列,计算数组etv和stack2
  157. // etv: [0, 3, 4, 12, 15, 11, 24, 19, 24, 27]
  158. //stack2: [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9]
  159. for _ in 0..<gl.numVertexes {
  160. ltv.append(etv[gl.numVertexes-1]) //初始化ltv为27
  161. }
  162. while top2 != 0 {
  163. getTop = stack2.removeLast()
  164. top2 -= 1
  165. var edgeNode = gl.adjList[getTop].firstEdge
  166. while edgeNode != nil {
  167. k = (edgeNode?.adjex)!
  168. if ltv[k]-(edgeNode?.weight)! < ltv[getTop] {
  169. ltv[getTop] = ltv[k]-(edgeNode?.weight)! //取最小值,才不会影响该顶点到达其他顶点的时间,也就是最迟发生的时间
  170. }
  171. edgeNode = edgeNode?.next
  172. }
  173. }
  174. for j in 0..<gl.numVertexes {
  175. var edgeNode = gl.adjList[j].firstEdge
  176. while edgeNode != nil {
  177. k = (edgeNode?.adjex)!
  178. ete = etv[j]
  179. lte = ltv[k]-(edgeNode?.weight)!
  180. if ete == lte {
  181. print("<\(gl.adjList[j].data), \(gl.adjList[k].data) length: \((edgeNode?.weight)!)>")
  182. }
  183. edgeNode = edgeNode?.next
  184. }
  185. }
  186. }
  187. 输出结果:
  188. <0, 2 length: 4>
  189. <2, 3 length: 8>
  190. <3, 4 length: 3>
  191. <4, 7 length: 4>
  192. <7, 8 length: 5>
  193. <8, 9 length: 3>
  • 关键点:
    1.etv的计算:比如计算v9点最早开始的时间,那么需要计算终点是v9的所有点的权值,取最大的权值,是因为,在这个点开始,v9之前的所有活动都刚刚结束,也就是说保证v9之前所有活动完成之后才是v9最早开始的时间。
    算法(三)-图 - 图30
    etv计算公式.png

2.ltv的计算:由etv可以知道终点的最早开始时间,那么所有点以这个为初始值,因为最迟开始的时间不可能大于终点最早的开始时间。那么,ltv的计算从终点开始,往回推。比如说要计算v8最迟开始的时间,因为v8需要3天时间工作,那么在保证v8工作完成时不影响v9的工作,那么v8最迟开始的工作时间就是v9最早工作时间减去v8需要的时间。比如说v4,经过v4会到达多个顶点,需要保证不影响所达到的多个的工作时间,那么v4开始的时间就是v4所经过的过个顶点最早的开始时间分别减去v4所需要的时间,取最小的开始时间,才会不影响之后所有的顶点的最早开始时间。

算法(三)-图 - 图31

ltv计算公式.png

3.ete本来是表示活动 的最早开工时间,是针对弧来说
的。但只有此弧的弧尾顶点vk的事件发生了,它才可以开始,因此 ete=etv[k];lte 表示的是活动的最晚开工时间,但此活动再晚也不能等 vj事件发生才开始,而必须要在vj事件之前发生,所以 lte=ltv[j]-len

总结:

写这个数据结果,断断续续奋战了几个星期,总算一点一点的把它啃完。敲代码的基础很重要,就像盖房子一样,基础不牢固,建不起高楼大厦。后面会继续推出,算法,设计模式,编译原理,操作系统等等一些列的学习笔记。这世界很大,需要保持一点好奇心😄。最后,献上Demo