图的表示及图的搜索(附代码实现)

215 阅读4分钟

如何理解“图”?

图(Graph)是一种非常重要的数据结构,它可以用来表示各种复杂的关系和网络。理解图的基本概念很重要,因为它是许多算法和应用程序的基础。

简单地说,图由两个基本元素组成:

  1. 节点(Vertex或Node):图中的基本单元,表示某个对象或实体。
  2. 边(Edge):连接两个节点的线,表示两个节点之间的关系或联系。

图可以分为以下几种类型:

  1. 无向图:两个节点之间的边没有方向,表示两个节点之间是双向关系。
  2. 有向图:两个节点之间的边有方向,表示两个节点之间是单向关系。
  3. 加权图:每条边都有一个权重或成本,用来表示两个节点之间的距离、时间等信息。

图还可以表示更复杂的关系,例如:

  • 社交网络:用户是节点,好友关系是边。
  • 地图:城市是节点,道路是边。
  • 网络拓扑:路由器是节点,网线是边。
  • 依赖关系:软件模块是节点,依赖关系是边。

理解图的概念后,可以使用各种图算法来解决实际问题,例如:

  • 最短路径算法:寻找两个节点之间的最短路径。
  • 拓扑排序:对有向无环图(DAG)中的节点进行排序。
  • 连通性算法:判断图中是否存在连通分量。
  • 染色算法:判断图是否是二分图。

因此图是一种强大的抽象数据模型,可以用来表达各种复杂的关系和网络。

如何在内存中存储图

图的存储方式主要有两种:邻接矩阵和邻接表。

  1. 邻接矩阵(Adjacency Matrix):

    • 使用二维数组来表示图中节点之间的连接关系。
    • 对于无向图,矩阵的元素a[i][j]和a[j][i]都表示节点i和节点j之间存在边。
    • 对于有向图,只有a[i][j]表示从节点i到节点j存在边。
    • 优点是访问任意两个节点之间的连接关系的时间复杂度是O(1)。
    • 缺点是存储空间浪费,因为大部分时候图都是稀疏的,矩阵中大部分元素都是0。
  2. 邻接表(Adjacency List):

    • 使用一个列表(数组、链表等)来存储每个节点的所有邻接节点。
    • 对于无向图,如果节点i和节点j之间有边,则节点i的邻接表中会包含节点j,节点j的邻接表中也会包含节点i。
    • 对于有向图,只有从节点i到节点j有边时,节点i的邻接表中才会包含节点j。
    • 优点是存储空间相对更加高效,只需要存储实际存在的边。
    • 缺点是访问任意两个节点之间的连接关系需要遍历整个邻接表,时间复杂度为O(|V|)。

Swift代码:

  1. 使用字典(Dictionary)实现邻接表:
struct Graph<T: Hashable> {
    var adjacencyList: [T: [T]] = [:]
    
    // 添加边的操作
    mutating func addEdge(_ from: T, _ to: T) {
        if adjacencyList[from] == nil {
            adjacencyList[from] = []
        }
        adjacencyList[from]?.append(to)
        
        if adjacencyList[to] == nil {
            adjacencyList[to] = []
        }
        adjacencyList[to]?.append(from)
    }
}
  1. 使用二维数组实现邻接矩阵:
struct Graph<T: Hashable & Comparable> {
    private var vertices: [T] = []
    private var adjacencyMatrix: [[Int]] = []
    
    // 添加顶点的操作
    mutating func addVertex(_ vertex: T) {
        vertices.append(vertex)
        adjacencyMatrix.append([Int](repeating: 0, count: vertices.count))
        for row in 0..<adjacencyMatrix.count - 1 {
            adjacencyMatrix[row].append(0)
        }
    }
    
    // 添加边的操作
    mutating func addEdge(_ from: T, _ to: T) {
        guard let fromIndex = vertices.firstIndex(of: from),
              let toIndex = vertices.firstIndex(of: to) else {
            return
        }
        adjacencyMatrix[fromIndex][toIndex] = 1
        adjacencyMatrix[toIndex][fromIndex] = 1
    }
}

总的来说,选择使用哪种方式来表示图,取决于具体的应用场景和需求。邻接矩阵适合于密集图,而邻接表更适合于稀疏图。

示例

//
//  ViewController_Graph.swift
//  Test
//
//  Created by Yin123456 on 2024/8/26.
//

import UIKit

class ViewController_Graph: UIViewController {
    
    // 图的表示方式 - 邻接表
    struct Graph<T: Hashable> {
        var adjacencyList: [T: [T]] = [:]
        
        mutating func addEdge(_ from: T, _ to: T) {
            if adjacencyList[from] == nil {
                adjacencyList[from] = []
            }
            adjacencyList[from]?.append(to)
            
            if adjacencyList[to] == nil {
                adjacencyList[to] = []
            }
            adjacencyList[to]?.append(from)
        }
    }

    func breadthFirstSearch<T: Hashable>(_ graph: Graph<T>, _ start: T) -> [T] {
        var queue = [start]
        var visited = Set<T>()
        var result: [T] = []
        
        while !queue.isEmpty {
            let current = queue.removeFirst()
            
            if !visited.contains(current) {
                visited.insert(current)
                result.append(current)
                
                if let neighbors = graph.adjacencyList[current] {
                    queue.append(contentsOf: neighbors)
                }
            }
        }
        
        return result
    }

    func depthFirstSearch<T: Hashable>(_ graph: Graph<T>, _ start: T) -> [T] {
        var stack = [start]
        var visited = Set<T>()
        var result: [T] = []
        
        while !stack.isEmpty {
            let current = stack.removeLast()
            
            if !visited.contains(current) {
                visited.insert(current)
                result.append(current)
                
                if let neighbors = graph.adjacencyList[current] {
                    for neighbor in neighbors.reversed() {
                        stack.append(neighbor)
                    }
                }
            }
        }
        
        return result
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // 使用示例
        var socialNetwork = Graph<String>()
        socialNetwork.addEdge("user1", "user2")
        socialNetwork.addEdge("user1", "user3")
        socialNetwork.addEdge("user2", "user4")
        socialNetwork.addEdge("user2", "user5")
        socialNetwork.addEdge("user3", "user6")
        socialNetwork.addEdge("user3", "user7")

        let bfsResult = breadthFirstSearch(socialNetwork, "user1")
        print(bfsResult) // ["user1", "user2", "user3", "user4", "user5", "user6", "user7"]
        
        
        let dfsResult = depthFirstSearch(socialNetwork, "user1")
        print(dfsResult) // ["user1", "user2", "user4", "user5", "user3", "user6", "user7"]

    }
    

}