如何理解“图”?
图(Graph)是一种非常重要的数据结构,它可以用来表示各种复杂的关系和网络。理解图的基本概念很重要,因为它是许多算法和应用程序的基础。
简单地说,图由两个基本元素组成:
- 节点(Vertex或Node):图中的基本单元,表示某个对象或实体。
- 边(Edge):连接两个节点的线,表示两个节点之间的关系或联系。
图可以分为以下几种类型:
- 无向图:两个节点之间的边没有方向,表示两个节点之间是双向关系。
- 有向图:两个节点之间的边有方向,表示两个节点之间是单向关系。
- 加权图:每条边都有一个权重或成本,用来表示两个节点之间的距离、时间等信息。
图还可以表示更复杂的关系,例如:
- 社交网络:用户是节点,好友关系是边。
- 地图:城市是节点,道路是边。
- 网络拓扑:路由器是节点,网线是边。
- 依赖关系:软件模块是节点,依赖关系是边。
理解图的概念后,可以使用各种图算法来解决实际问题,例如:
- 最短路径算法:寻找两个节点之间的最短路径。
- 拓扑排序:对有向无环图(DAG)中的节点进行排序。
- 连通性算法:判断图中是否存在连通分量。
- 染色算法:判断图是否是二分图。
因此图是一种强大的抽象数据模型,可以用来表达各种复杂的关系和网络。
如何在内存中存储图
图的存储方式主要有两种:邻接矩阵和邻接表。
-
邻接矩阵(Adjacency Matrix):
- 使用二维数组来表示图中节点之间的连接关系。
- 对于无向图,矩阵的元素a[i][j]和a[j][i]都表示节点i和节点j之间存在边。
- 对于有向图,只有a[i][j]表示从节点i到节点j存在边。
- 优点是访问任意两个节点之间的连接关系的时间复杂度是O(1)。
- 缺点是存储空间浪费,因为大部分时候图都是稀疏的,矩阵中大部分元素都是0。
-
邻接表(Adjacency List):
- 使用一个列表(数组、链表等)来存储每个节点的所有邻接节点。
- 对于无向图,如果节点i和节点j之间有边,则节点i的邻接表中会包含节点j,节点j的邻接表中也会包含节点i。
- 对于有向图,只有从节点i到节点j有边时,节点i的邻接表中才会包含节点j。
- 优点是存储空间相对更加高效,只需要存储实际存在的边。
- 缺点是访问任意两个节点之间的连接关系需要遍历整个邻接表,时间复杂度为O(|V|)。
Swift代码:
- 使用字典(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)
}
}
- 使用二维数组实现邻接矩阵:
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"]
}
}