Swift 數據結構:圖

這篇文文章主要介紹一種比較複雜的數據結構--圖。主要內容:數組

  • 圖的概念及術語
  • 圖的結構
  • 圖的遍歷
    • 深度優先遍歷
    • 廣度優先遍歷

圖的概念及術語

定義

定義: 由頂點的有窮非空集合和頂點之間邊的集合組成,一般表示爲: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
複製代碼

深度優先遍歷

  1. 從某個頂點(A)出發,訪問頂點並標記爲已訪問。
  2. 訪問 A 的其中一個鄰接點(假設爲 B),若是沒有訪問過,訪問該頂點並標記爲已訪問。
  3. 而後再訪問該頂點(B)的鄰接點(D),重複上述步驟。
// 深度優先遍歷
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的實現用循環(配合隊列)。

相關文章
相關標籤/搜索