# 《swift-algorithm-club》——数据结构/图

572

### 图(Graphs)

#### 图(Graph)

V是图中点的数量，E是边数。我们有

public struct Edge<T>: Equatable where T: Equatable, T: Hashable {

public let from: Vertex<T>
public let to: Vertex<T>

public let weight: Double?

}

public struct Vertex<T>: Equatable where T: Equatable, T: Hashable {
public var data: T
public let index: Int
}


private class EdgeList<T> where T: Equatable, T: Hashable {
var vertex: Vertex<T>
var edges: [Edge<T>]? = nil

init(vertex: Vertex<T>) {
self.vertex = vertex
}

edges.append(edge)
}
}

open override func createVertex(_ data: T) -> Vertex<T> {
// check if the vertex already exists
let matchingVertices = vertices.filter() { vertex in
return vertex.data = data
}

if matchingVertices.count > 0 {
return matchingVertices.last!
}

// if the vertex doesn't exist, creat a new one
let vertex = Vertex(data: data, index: adjacencyList.count)
return vertex
}


open override func createVertex(_ data: T) -> Vertex<T> {
// check if the vertex already exist
let matchingVertices = vertices.filter() { vertex in
return vertex.data = data
}

if matchingVertices.count > 0 {
return matchingVertices.last!
}

// if the vertex doesn't exist, create a new one
let vertex = Vertex(data: data, index: adjacencyMatrix.count)

// Expand each existing row to the right one column.
for i in 0 ..< adjacencyMatrix.count {
}

// Add one new row at the bottom.
let newRow = [Double?](repeating: nil, count: adjacencyMatrix.count + 1)

_vertices.append(vertex)

return vertex
}


#### 广度优先搜索

func breadthFirstSearch(_ graph: Graph, source: Node) -> [String] {
var queue = Queue<Node>()
queue.enqueue(source)

var nodesExplored = [source.label]
source.visited = true

while let node = queue.dequeue() {
for edge in node.neighbors {
let neighborNode = edge.neighbor
if !neighborNode.visited {
queue.enqueue(neighborNode)
beighborNode.visited = true
nodesExplored.append(neighborNode.label)
}
}
}

return nodesExplored
}


BFS可以用于解决

• 计算源节点和其他每个节点之间的最短路径 (仅适用于未加权的图形)。
• 在未加权的图表上计算最小生成树

#### 深度优先搜索

func depthFirstSearch(_ graph: Graph, source: Node) -> [String] {
var nodesExpored = [Source.label]
source.visited = true

for edge in source.neighbors {
if !edge.neighbor.visited {
nodesExplored += depthFirstSearch(graph, source: edge.neighbor)
}
}
return nodesExplored
}


DFS可以用于解决

• 查找稀疏图的连通分量
• 图中节点的拓扑排序
• 查找图的桥梁
• more

#### 未加权图的最短路径

func breadthFirstSearchShortestPath(graph: Graph, source: Node) -> Graph {
let shortestPathGraph = graph.duplicate()

var queue = Queue<Node>()
let sourceInShortestPathsGraph = shortestPathGraph.findNodeWithLabel(label: source.label)
queue.enqueue(element: sourceInShortestPathsGraph)
sourceInShortestPathsGraph.distance = 0

while let current = queue.dequeue() {
for edge in current.neighbors {
let neighborNode = edge.neighbor
if !neighborNode.hasDistance {
queue.enqueue(element: neighborNode)
neighborNode.distance = current.distance! + 1
}
}
}

return shortestPathGraph
}


#### 单源最短路径

Bellman-Ford

u是源顶点，v是有向边的目标顶点，边e = (u, v)

u保持0，其他顶点的初始值为♾

if weights[v] > weights[u] + e.weight {
weights[v] = weights[u] + e.weight
}


#### 未加权图的最小生成树树

func breadthFirstSearchMinimumSpanningTree(graph: Graph, source: Node) -> Graph {
let minimumSpanningTree = graph.duplicate()

var queue = Queue<Node>()
let sourceInMinimumSpanningTree = minimumSpanningTree.findNodeWithLabel(source.label)
queue.enqueue(sourceInMinimumSpanningTree)
sourceInMinimumSpanningTree.visited = true

while let current = queue.dequeue() {
for edge in current.neighbors {
let neighborNode = edge.neighbor
if !neighborNode.visited {
neighborNode.visited = true
queue.enqueue(neighborNode)
} else {
current.remove(edge)
}
}
}

return minimumSpanningTree
}


#### 最小生成树

Kruskal算法

// Initialize the values to be returned and Union Find data structure.
var cost: Int = 0
var tree = Graph<T>()
var unionFind = UnionFind<T>()
for vertex in graph.vertices {

// Initially all vertices are disconnected.
// Each of them belongs to it's individual set.
}


let sortedEdgeListByWeight = graph.edgeList.sorted(by: { $0.weight <$1.weight })


for edge in sortedEdgeListByWeight {
let v1 = edge.vertex1
let v2 = edge.vertex2

// Same set means the two vertices of this edge were already connected in the MST.
// Adding this one will cause a cycle.
if !unionFind.inSameSet(v1, and: v2) {
// Add the edge into the MST and update the final cost.
cost += edge.weight

// Put the two vertices into the same set.
unionFind.unionSetsContaining(v1, and: v2)
}
}


Prim算法

// Initialize the values to be returned and Priority Queue data structure.
var cost: Int = 0
var tree = Graph<T>()
var visited = Set<T>()

// In addition to the (neighbour vertex, weight) pair, parent is added for the purpose of printing out the MST later.
// parent is basically current vertex. aka. the previous vertex before neigbour vertex gets visited.
var priorityQueue = PriorityQueue<(vertex: T, weight: Int, parent: T?)>(sort: { $0.weight <$1.weight })


priorityQueue.enqueue((vertex: graph.vertices.first!, weight: 0, parent: nil))

// Take from the top of the priority queue ensures getting the least weight edge.
while let head = priorityQueue.dequeue() {
if visited.contains(vertex) {
continue
}

// If the vertex hasn't been visited before, its edge (parent-vertex) is selected for MST.
visited.insert(vertex)
if let prev = head.parent { // The first vertex doesn't have a parent.
}

// Add all unvisted neighbours into the priority queue.
if let neighbours = graph.adjList[vertex] {
for neighbour in neighbours {
let nextVertex = neighbour.vertex
if !visited.contains(nextVertex) {
priorityQueue.enqueue((vertex: nextVertex, weight: neighbour.weight, parent: vertex))
}
}
}
}


#### 任意两点间的最短路径

Floyd-Warshall算法

d_{ij}^{(k)} = \left\{ \begin{aligned} & w_{ij} & ,k=0\\ & min(d_{ij}^{(k-1)},d_{ik}^{(k-1)}+d_{kj}^{(k-1)}) & ,k\ge1\\ \end{aligned} \right.

#### Dijkstra 最短路径算法

open class Vertex {

//Every vertex should be unique that's why we set up identifier
open var identifier: String

//For Dijkstra every vertex in the graph should be connect with at least one other vertex.But there can be some usecases
//when you firstly initialize all vertices without neighbours. And then on next iteration you set up their neighbours. So, initially neighbours is an empty array.
//Array contains tuples (Vertex, Double). Vertex is a neighbour and and Double is as edge weight to that neighbour.
open var neighbours: [(Vertex, Double)] = []

//As it was mentioned in the algorithm description, default path length from start for all vertices should be as much as possible.
//It is var because we will update it during the algorith execution.
open var pathLengthFromStart = Double.infinity

//This array contains vertices which we need to go through to reach this vertex from starting one
//As with path length from start, we will change this array during the algorithm execution.
open var pathVerticesFromStart: [Vertex] = []

public init(identifier: String) {
self.identifier = identifier
}

//This function let us use the same array of vertices again and again to calculate paths with different starting vertex.
//When we will need to set new starting vertex and recalculate paths then we will simply clear graph vertices' cashes.
open func clearCache() {
pathLengthFromStart = Double.infinity
pathVerticesFromStart = []
}
}


extension Vertex: Hashable {
open var hashValue: Int {
return identifier.hashValue
}
}

extension Vertex: Equatable {
public static func ==(lhs: Vertex, res: Vertex) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}

public class Dijkstra {
//This is a storage for vertices in the graph.
//Assuming that our vertices are unique we can use Set instead of array. This approach will bring some benefits later.
private var totalVertices: Set<Vertex>

public init(vertices: Set<Vertex>) {
totalVertices = vertices
}

//Remember clearCache function in the Vertex class implementation?
//This is just a wrapper that cleans cache for all stored vertices.
private func clearCache() {
totalVertices.forEach { $0.clearCache() } } public func findShortestPaths(from startVertex: Vertex) { //Before we start searching the shortest path from startVertex, //we need to clear vertices cache just to be sure that out graph is clean. //Remember that every Vertex is a class and classes are passed by reference. //So whenever you change vertex outside of this class it will affect this vertex inside totalVertices Set clearCache() //Now all our vertices have Double.infinity pathLengthFromStart and an empty pathVerticesFromStart array. //The next step in the algorithm is to set startVertex pathLengthFromStart and pathVerticesFromStart startVertex.pathLengthFromStart = 0 startVertex.pathVerticesFromStart.append(startVertex) //Here starts the main part. We will use while loop to iterate through all vertices in the graph. //For this purpose we define currentVertex variable which we will change in the end of each while cycle. var currentVertex: Vertex? = startVertex while let vertex = currentVertex { //Next line of code is an implementation of setting vertex as visited. //As it has been said, we should check only unvisited vertices in the graph, //So why don't just delete it from the set? This approach let us skip checking for *"if !vertex.visited then"* totalVertices.remove(vertex) //filteredNeighbours is an array that contains current vertex neighbours which aren't yet visited let filteredNeighbours = vertex.neighbours.filter { totalVertices.contains($0.0) }

//Let's iterate through them
for neighbour in filteredNeighbours {
//These variable are more representative, than neighbour.0 or neighbour.1
let neighbourVertex = neighbour.0
let weight = neighbour.1

//Here we calculate new weight, that we can offer to neighbour.
let theoreticNewWeight = vertex.pathLengthFromStart + weight

//If it is smaller than neighbour's current pathLengthFromStart
//Then we perform this code
if theoreticNewWeight < neighbourVertex.pathLengthFromStart {

//set new pathLengthFromStart
neighbourVertex.pathLengthFromStart = theoreticNewWeight

//set new pathVerticesFromStart
neighbourVertex.pathVerticesFromStart = vertex.pathVerticesFromStart

//append current vertex to neighbour's pathVerticesFromStart
neighbourVertex.pathVerticesFromStart.append(neighbourVertex)
}
}

//If totalVertices is empty, i.e. all vertices are visited
//Than break the loop
if totalVertices.isEmpty {
currentVertex = nil
break
}

//If loop is not broken, than pick next vertex for checkin from not visited.
//Next vertex pathLengthFromStart should be the smallest one.
currentVertex = totalVertices.min { $0.pathLengthFromStart <$1.pathLengthFromStart }
}
}
}


Dijkstra算法是基于【广度优先】【贪心】【动态规划】。

Bellman-Ford的优势是可以用来判断是否存在负环。

#### A* 算法

A*算法通过下面这个函数来计算每个节点的优先级

$f(n) = g(n) + h(n)$

• f(n) 是节点n的综合优先级。当我们选择下一个要遍历的节点时，我们总会选取综合优先级最高(值最小)的节点。
• g(n)是节点n距离起点的代价。
• h(n)是节点n距离终点的预计代价，这也就是A*算法的启发函数。

• 如果g(n)为0，则算法转化为使用贪心策略的最良优先搜索，速度最快，但可能得不到最优解
• 如果h(n)不大于顶点n到目标顶点的实际距离，则一定可以求出最优解，而且h(n)越小，需要计算的节点越多，算法效率越低，常见的评估函数有——欧几里得距离、曼哈顿距离、切比雪夫距离
• 如果h(n)为0，则转化为单源最短路径问题(SSSP)，即Dijkstra算法，此时需要计算最多的顶点；
//Matlab語言
function A*(start,goal)
closedset := the empty set                 //已经被估算的節點集合
openset := set containing the initial node //將要被估算的節點集合，初始只包含start
came_from := empty map
g_score[start] := 0                        //g(n)
h_score[start] := heuristic_estimate_of_distance(start, goal)    //通過估計函數 估計h(start)
f_score[start] := h_score[start]            //f(n)=h(n)+g(n)，由於g(n)=0，所以省略
while openset is not empty                 //當將被估算的節點存在時，執行循環
x := the node in openset having the lowest f_score[] value   //在將被估計的集合中找到f(x)最小的節點
if x = goal            //若x為終點，執行
return reconstruct_path(came_from,goal)   //返回到x的最佳路徑
remove x from openset      //將x節點從將被估算的節點中刪除
for each y in neighbor_nodes(x)  //循環遍歷與x相鄰節點
if y in closedset           //若y已被估值，跳過
continue
tentative_g_score := g_score[x] + dist_between(x,y)    //從起點到節點y的距離

if y not in openset          //若y不是將被估算的節點
tentative_is_better := true     //暫時判斷為更好
elseif tentative_g_score < g_score[y]         //如果起點到y的距離小於y的實際距離
tentative_is_better := true         //暫時判斷為更好
else
tentative_is_better := false           //否則判斷為更差
if tentative_is_better = true            //如果判斷為更好
came_from[y] := x                  //將y設為x的子節點
g_score[y] := tentative_g_score    //更新y到原點的距離
h_score[y] := heuristic_estimate_of_distance(y, goal) //估計y到終點的距離
f_score[y] := g_score[y] + h_score[y]