這篇文文章主要介紹一種比較複雜的數據結構--圖。主要內容:數組
定義: 由頂點的有窮非空集合和頂點之間邊的集合組成,一般表示爲:G(V, E)。其中 G 表示圖,V 是圖中頂點的集合,E 是圖 G 中邊的集合。bash
圖的術語很是多,其它的術語當用到的時候再去了解。這裏介紹幾個比較常見的術語:數據結構
頂點: 表示圖中的一個結點。app
邊: 頂點和頂點之間的連線。若連線無方向則爲無向邊,反之則爲有向邊,也稱爲弧。ui
權: 邊有與之相關的數值,這個數值稱爲權。spa
相鄰頂點: 由一條邊鏈接在一塊兒的頂點。code
度: 相鄰頂點的數量。指向其它頂點的數量稱爲出度, 其它頂點指向本身的數量稱爲入度。cdn
無向圖: 圖中任意兩個頂點之間的邊都是無向邊。blog
有向圖: 圖中任意兩個頂點之間的邊都是有向邊。遞歸
無權圖: 圖中的邊是沒有任何意義,無權重。
帶權圖: 圖中的邊有必定的權重。
連通圖: 圖中任意兩個頂點都是連通的。
散列表的具體概念這裏就很少介紹了,大部分的語言中都提供了散列表的實現。在 Swift 中,散列表的實現就是 字典。
咱們能夠用散列表來實現一個圖結構。
兩個數組來表示圖結構。一個一維數組存儲圖中頂點信息,一個二維數組(鄰接矩陣)存出圖中的邊或弧的信息。
圖中展現了三種圖的鄰接矩陣表示方式。其中:
數字爲 0 或者 ∞ 的表明兩個頂點之間沒有連線,其它數字都表明有連線。在有向圖中,數字 1 表明縱向對應頂點指向橫向對應頂點。在帶權圖中,數字的數值大小則表明這條邊的權。
圖的定義:
class Graph {
var vertexs: [Vertex] = []
var edges: [[Int]] = []
// 這個屬性表示帶權圖中無限大
let Not_Link = INT_MAX
}
複製代碼
數組和鏈表結合來表示圖結構。一個一維數組存儲圖中頂點信息,鏈表則用來表示與某個頂點相鄰的頂點。
圖中展現了三種圖的鄰接矩陣表示方式。其中:
鏈表結點的 index 指向數組中與本身相鄰頂點的下標,next 指向下一個與本身相鄰的頂點,最後一個頂點 next 置爲 nil。在帶權圖中,對鏈表結點的定義擴充了一個 Weight 空間,表示這條邊的權重。
圖的定義:
// 圖頂點
class Vertex {
var data: String?
var link: LinkNode?
}
// 鏈表的結點
class LinkNode {
var data: Int?
var next: LinkNode?
// 帶權圖使用此屬性
var weight: Float?
}
// 圖
class Graph {
var vertexs: [Vertex] = []
var adjList: [Int: LinkNode] = [:]
}
複製代碼
邊集數組是由兩個一維數組構成。一個是存儲頂點信息的,另外一個存儲邊的信息。邊數組中每一個數據元素都由 begin(起點下標),end(終點下標),weight(權) 組成。
邊集數組由於其存儲形式,比較適合存儲有向圖及帶權圖。若存儲無向圖,則對同一條邊須要兩個數據元素來存儲,對空間會形成比較大的浪費。
圖的定義:
class Edge {
var begin: Int
var end: Int
var weight: Float?
init(begin: Int, end: Int) {
self.begin = begin
self.end = end
}
}
class G {
var vertexs: [Vertex] = []
var edges: [Edge]?
}
複製代碼
這裏咱們是利用前面散列表來表示圖。
var graph = [String: [String]]()
graph["A"] = ["B", "C"]
graph["B"] = ["D"]
graph["C"] = ["D", "E"]
graph["D"] = ["G"]
graph["E"] = ["F"]
graph["F"] = ["G"]
複製代碼
上面的代碼建立了一個下圖中的圖結構。
廣度優先遍歷相似於樹的層序遍歷。
從 A 開始,找到全部本身一步能到達的頂點(B,C)加入隊列,查找是不是本身須要的(G),若是不是,再將這些頂點(B,C)一步能到達的頂點(D,E),加入到隊列中。若是找到,直接返回結果,不然就這樣一層一層的找下去,直至隊列爲空。
// 存儲已訪問的頂點
var visited: [String] = []
func visit(_ v: String) {
print(v)
}
// 廣度優先遍歷
func breadthFirstSearch(_ value: String?) {
// 利用數組表示隊列
var queue = [String]()
if let vertex = value {
queue.insert(vertex, at: 0)
}
while queue.count != 0 {
let current = queue.popLast()!
if !visited.contains(current) {
visit(current)
visited.append(current)
}
guard let neighbors = graph[current] else {
continue
}
for data in neighbors {
if !visited.contains(data) {
queue.insert(data, at: 0)
}
}
}
}
----輸出結果:----
A
B
C
D
E
G
F
複製代碼
// 深度優先遍歷
func depthFirstSearch(_ value: String?) {
guard let vertex = value else {
return
}
visit(vertex)
visited.append(vertex)
guard let neighbors = graph[vertex] else {
return
}
for data in neighbors {
if !visited.contains(data) {
depthFirstSearch(data)
}
}
}
----輸出結果:----
A
B
D
G
C
E
F
複製代碼
Depth_First_Search的實現用遞歸,Breadth_First_Search的實現用循環(配合隊列)。