|
| 1 | +## 1. 二分图最大匹配简介 |
| 2 | + |
| 3 | +> **二分图最大匹配(Maximum Bipartite Matching)**:图论中的一个重要问题。在二分图中,我们需要找到最大的匹配数,即最多可以有多少对顶点之间形成匹配。 |
| 4 | +
|
| 5 | +- **二分图**:图中的顶点可以被分成两个独立的集合,使得每条边的两个端点分别属于这两个集合。 |
| 6 | +- **匹配**:一组边的集合,其中任意两条边都没有共同的顶点。 |
| 7 | +- **最大匹配**:包含边数最多的匹配。 |
| 8 | + |
| 9 | +### 1.1 应用场景 |
| 10 | + |
| 11 | +二分图最大匹配在实际应用中有广泛的应用: |
| 12 | + |
| 13 | +1. **任务分配**:将任务分配给工人,每个工人只能完成一个任务 |
| 14 | +2. **婚姻匹配**:将男生和女生进行配对 |
| 15 | +3. **网络流问题**:可以转化为最大流问题求解 |
| 16 | +4. **资源分配**:将资源分配给需求方 |
| 17 | +5. **学生选课**:将学生与课程进行匹配 |
| 18 | +6. **网络路由**:将数据包与可用路径进行匹配 |
| 19 | + |
| 20 | +### 1.2 优化方法 |
| 21 | + |
| 22 | +1. **使用邻接表**:对于稀疏图,使用邻接表可以显著减少空间复杂度 |
| 23 | +2. **双向搜索**:同时从左右两侧进行搜索,可以减少搜索次数 |
| 24 | +3. **预处理**:对图进行预处理,去除不可能形成匹配的边 |
| 25 | +4. **贪心匹配**:先进行贪心匹配,减少后续搜索的复杂度 |
| 26 | +5. **并行处理**:对于大规模图,可以使用并行算法提高效率 |
| 27 | + |
| 28 | +## 2. 匈牙利算法 |
| 29 | + |
| 30 | +### 2.1 匈牙利算法基本思想 |
| 31 | + |
| 32 | +匈牙利算法(Hungarian Algorithm)是求解二分图最大匹配的经典算法。其基本思想是: |
| 33 | + |
| 34 | +1. 从左侧集合中任选一个未匹配的点开始 |
| 35 | +2. 尝试寻找增广路径 |
| 36 | +3. 如果找到增广路径,则更新匹配 |
| 37 | +4. 重复以上步骤直到无法找到增广路径 |
| 38 | + |
| 39 | +### 2.2 匈牙利算法实现代码 |
| 40 | + |
| 41 | +```python |
| 42 | +def max_bipartite_matching(graph, left_size, right_size): |
| 43 | + # 初始化匹配数组 |
| 44 | + match_right = [-1] * right_size |
| 45 | + result = 0 |
| 46 | + |
| 47 | + # 对左侧每个顶点尝试匹配 |
| 48 | + for left in range(left_size): |
| 49 | + # 记录右侧顶点是否被访问过 |
| 50 | + visited = [False] * right_size |
| 51 | + |
| 52 | + # 如果找到增广路径,则匹配数加1 |
| 53 | + if find_augmenting_path(graph, left, visited, match_right): |
| 54 | + result += 1 |
| 55 | + |
| 56 | + return result |
| 57 | + |
| 58 | +def find_augmenting_path(graph, left, visited, match_right): |
| 59 | + # 遍历右侧所有顶点 |
| 60 | + for right in range(len(graph[left])): |
| 61 | + # 如果存在边且右侧顶点未被访问 |
| 62 | + if graph[left][right] and not visited[right]: |
| 63 | + visited[right] = True |
| 64 | + |
| 65 | + # 如果右侧顶点未匹配,或者可以找到新的匹配 |
| 66 | + if match_right[right] == -1 or find_augmenting_path(graph, match_right[right], visited, match_right): |
| 67 | + match_right[right] = left |
| 68 | + return True |
| 69 | + |
| 70 | + return False |
| 71 | +``` |
| 72 | + |
| 73 | +### 2.3 匈牙利算法时间复杂度 |
| 74 | + |
| 75 | +- 匈牙利算法的时间复杂度为 O(VE),其中 V 是顶点数,E 是边数 |
| 76 | +- 使用邻接矩阵存储图时,空间复杂度为 O(V²) |
| 77 | +- 使用邻接表存储图时,空间复杂度为 O(V + E) |
| 78 | + |
| 79 | + |
| 80 | +## 3. Hopcroft-Karp 算法 |
| 81 | + |
| 82 | +### 3.1 Hopcroft-Karp 算法基本思想 |
| 83 | + |
| 84 | +Hopcroft-Karp 算法是求解二分图最大匹配的一个更高效的算法,时间复杂度为 O(√VE)。其基本思想是: |
| 85 | + |
| 86 | +1. 同时寻找多条不相交的增广路径 |
| 87 | +2. 使用 BFS 分层,然后使用 DFS 寻找增广路径 |
| 88 | +3. 每次迭代可以找到多条增广路径 |
| 89 | + |
| 90 | + |
| 91 | +### 3.2 Hopcroft-Karp 算法实现代码 |
| 92 | + |
| 93 | +```python |
| 94 | +from collections import deque |
| 95 | + |
| 96 | +def hopcroft_karp(graph, left_size, right_size): |
| 97 | + # 初始化匹配数组 |
| 98 | + match_left = [-1] * left_size |
| 99 | + match_right = [-1] * right_size |
| 100 | + result = 0 |
| 101 | + |
| 102 | + while True: |
| 103 | + # 使用 BFS 寻找增广路径 |
| 104 | + dist = [-1] * left_size |
| 105 | + queue = deque() |
| 106 | + |
| 107 | + # 将未匹配的左侧顶点加入队列 |
| 108 | + for i in range(left_size): |
| 109 | + if match_left[i] == -1: |
| 110 | + dist[i] = 0 |
| 111 | + queue.append(i) |
| 112 | + |
| 113 | + # BFS 分层 |
| 114 | + while queue: |
| 115 | + left = queue.popleft() |
| 116 | + for right in graph[left]: |
| 117 | + if match_right[right] == -1: |
| 118 | + # 找到增广路径 |
| 119 | + break |
| 120 | + if dist[match_right[right]] == -1: |
| 121 | + dist[match_right[right]] = dist[left] + 1 |
| 122 | + queue.append(match_right[right]) |
| 123 | + |
| 124 | + # 使用 DFS 寻找增广路径 |
| 125 | + def dfs(left): |
| 126 | + for right in graph[left]: |
| 127 | + if match_right[right] == -1 or \ |
| 128 | + (dist[match_right[right]] == dist[left] + 1 and \ |
| 129 | + dfs(match_right[right])): |
| 130 | + match_left[left] = right |
| 131 | + match_right[right] = left |
| 132 | + return True |
| 133 | + return False |
| 134 | + |
| 135 | + # 尝试为每个未匹配的左侧顶点寻找增广路径 |
| 136 | + found = False |
| 137 | + for i in range(left_size): |
| 138 | + if match_left[i] == -1 and dfs(i): |
| 139 | + found = True |
| 140 | + result += 1 |
| 141 | + |
| 142 | + if not found: |
| 143 | + break |
| 144 | + |
| 145 | + return result |
| 146 | +``` |
| 147 | + |
| 148 | +### 3.3 Hopcroft-Karp 算法复杂度 |
| 149 | + |
| 150 | +- **时间复杂度**:O(√VE),其中 V 是顶点数,E 是边数 |
| 151 | +- **空间复杂度**:O(V + E) |
| 152 | +- **优点**: |
| 153 | + 1. 比匈牙利算法更高效 |
| 154 | + 2. 适合处理大规模图 |
| 155 | + 3. 可以并行化实现 |
| 156 | +- **缺点**: |
| 157 | + 1. 实现相对复杂 |
| 158 | + 2. 常数因子较大 |
| 159 | + 3. 对于小规模图可能不如匈牙利算法 |
| 160 | + |
| 161 | +### 3.4 Hopcroft-Karp 算法优化 |
| 162 | + |
| 163 | +1. **双向 BFS**:同时从左右两侧进行 BFS,减少搜索空间 |
| 164 | +2. **动态分层**:根据当前匹配状态动态调整分层策略 |
| 165 | +3. **预处理**:使用贪心算法进行初始匹配 |
| 166 | +4. **并行化**:利用多线程或分布式计算提高效率 |
| 167 | + |
| 168 | +## 4. 网络流算法 |
| 169 | + |
| 170 | +### 4.1 网络流算法实现步骤 |
| 171 | + |
| 172 | +二分图最大匹配问题可以转化为最大流问题来求解。具体步骤如下: |
| 173 | + |
| 174 | +1. 添加源点和汇点 |
| 175 | +2. 将二分图转化为网络流图 |
| 176 | +3. 使用最大流算法求解 |
| 177 | + |
| 178 | +### 4.2 网络流算法实现代码 |
| 179 | + |
| 180 | +```python |
| 181 | +from collections import defaultdict |
| 182 | + |
| 183 | +def max_flow_bipartite_matching(graph, left_size, right_size): |
| 184 | + # 构建网络流图 |
| 185 | + flow_graph = defaultdict(dict) |
| 186 | + source = left_size + right_size |
| 187 | + sink = source + 1 |
| 188 | + |
| 189 | + # 添加源点到左侧顶点的边 |
| 190 | + for i in range(left_size): |
| 191 | + flow_graph[source][i] = 1 |
| 192 | + flow_graph[i][source] = 0 |
| 193 | + |
| 194 | + # 添加右侧顶点到汇点的边 |
| 195 | + for i in range(right_size): |
| 196 | + flow_graph[left_size + i][sink] = 1 |
| 197 | + flow_graph[sink][left_size + i] = 0 |
| 198 | + |
| 199 | + # 添加二分图中的边 |
| 200 | + for i in range(left_size): |
| 201 | + for j in graph[i]: |
| 202 | + flow_graph[i][left_size + j] = 1 |
| 203 | + flow_graph[left_size + j][i] = 0 |
| 204 | + |
| 205 | + # 使用 Ford-Fulkerson 算法求解最大流 |
| 206 | + def bfs(): |
| 207 | + parent = [-1] * (sink + 1) |
| 208 | + queue = deque([source]) |
| 209 | + parent[source] = -2 |
| 210 | + |
| 211 | + while queue: |
| 212 | + u = queue.popleft() |
| 213 | + for v, capacity in flow_graph[u].items(): |
| 214 | + if parent[v] == -1 and capacity > 0: |
| 215 | + parent[v] = u |
| 216 | + if v == sink: |
| 217 | + return parent |
| 218 | + queue.append(v) |
| 219 | + return None |
| 220 | + |
| 221 | + def ford_fulkerson(): |
| 222 | + max_flow = 0 |
| 223 | + while True: |
| 224 | + parent = bfs() |
| 225 | + if not parent: |
| 226 | + break |
| 227 | + |
| 228 | + # 找到增广路径上的最小容量 |
| 229 | + v = sink |
| 230 | + min_capacity = float('inf') |
| 231 | + while v != source: |
| 232 | + u = parent[v] |
| 233 | + min_capacity = min(min_capacity, flow_graph[u][v]) |
| 234 | + v = u |
| 235 | + |
| 236 | + # 更新流量 |
| 237 | + v = sink |
| 238 | + while v != source: |
| 239 | + u = parent[v] |
| 240 | + flow_graph[u][v] -= min_capacity |
| 241 | + flow_graph[v][u] += min_capacity |
| 242 | + v = u |
| 243 | + |
| 244 | + max_flow += min_capacity |
| 245 | + |
| 246 | + return max_flow |
| 247 | + |
| 248 | + return ford_fulkerson() |
| 249 | +``` |
| 250 | + |
| 251 | +### 4.3 网络流算法复杂度 |
| 252 | + |
| 253 | +- **时间复杂度**: |
| 254 | + 1. Ford-Fulkerson 算法:O(VE²) |
| 255 | + 2. Dinic 算法:O(V²E) |
| 256 | + 3. ISAP 算法:O(V²E) |
| 257 | +- **空间复杂度**:O(V + E) |
| 258 | + |
| 259 | +## 5. 算法复杂度分析 |
| 260 | + |
| 261 | +1. **匈牙利算法** |
| 262 | + - 时间复杂度:O(VE) |
| 263 | + - 优点:实现简单,容易理解 |
| 264 | + - 缺点:对于大规模图效率较低 |
| 265 | + - 适用场景:小规模图,需要快速实现 |
| 266 | + |
| 267 | +2. **Hopcroft-Karp 算法** |
| 268 | + - 时间复杂度:O(√VE) |
| 269 | + - 优点:效率更高,适合大规模图 |
| 270 | + - 缺点:实现相对复杂 |
| 271 | + - 适用场景:大规模图,需要高效算法 |
| 272 | + |
| 273 | +3. **网络流算法** |
| 274 | + - 时间复杂度:O(VE²) 或 O(V²E) |
| 275 | + - 优点:可以处理更复杂的问题,如带权匹配 |
| 276 | + - 缺点:实现复杂,常数较大 |
| 277 | + - 适用场景:带权匹配,复杂约束条件 |
0 commit comments