C++ Prim 算法

2024年8月28日 | 阅读 22 分钟

Prim 算法是一种贪心算法,用于查找连通无向图的最小生成树 (MST)。图的最小生成树是边的子集,它形成一棵树并连接图中的所有顶点,同时最小化总边权重。Prim 算法通过添加权重最小的边来确保生成 MST。

历史

Prim 算法由捷克数学家兼计算机科学家 Robert C. Prim 命名,是图论和计算机科学中的一个基本算法,用于查找连通无向图的最小生成树 (MST)。以下是 Prim 算法的简要历史。

最小生成树的起源

最小生成树的概念可以追溯到 20 世纪初,应用于电气网络设计和运输。

Borůvka 算法 (1926)

Prim 算法的最早前身由捷克数学家 Otakar Borůvka 于 1926 年开发。Borůvka 算法旨在找到最小生成树问题的近似解。

Prim 算法 (1957)

1957 年,美国数学家 Robert C. Prim 独立地重新发现了并发表了后来以他命名的算法。他的工作受到高效电气网络构建的启发。

Edsger W. Dijkstra (1959)

荷兰计算机科学家 Edsger W. Dijkstra 在计算机科学的背景下独立地重新发现了并推广了 Prim 算法。他在 1959 年对该算法的介绍有助于确立其在计算机科学和图论中的重要性。

正确性证明

该算法的正确性由计算机科学家 R.C. Prim 和 Vojtěch Jarník 在 20 世纪 50 年代末和 60 年代初得到严格证明。

广泛采用

Prim 算法因其简单性和效率而被计算机科学和图论广泛采用。它成为解决涉及最小生成树问题的基本算法。

在网络设计中的应用

Prim 算法在网络设计、电路布局和运输规划等各个领域都有应用。

计算机科学教科书

该算法被包含在计算机科学教科书和课程中,进一步促进了其声誉和理解。

后续发展

多年来,人们提出了 Prim 算法的变体和改进,例如 Prim-Jarník 算法和各种数据结构以优化其性能。

Prim 算法仍然是图论和计算机科学中的基本工具,用于解决与网络设计和优化相关的问题。它以其简单性、效率以及在连通无向图中始终找到最小生成树的能力而闻名。

这里是 Prim 算法工作原理的深入解释

输入

  • 一个连通的无向图。
  • 一个起始顶点,用于开始 MST 的构建。

输出

  • 最小生成树。

算法步骤

初始化

  • 创建一个空的集合 MST,用于存储 MST 的边。
  • 创建一个数组 key[],用于跟踪每个顶点的最小边权重。将除起始顶点之外的所有顶点的初始值设置为无穷大,起始顶点设置为 0。
  • 创建一个数组 parent[],用于存储 MST 中每个顶点的父节点。将所有顶点的初始值设置为 -1。

迭代过程

  • 只要还有未包含在 MST 中的顶点,就执行以下步骤
  • 选择一个不在 MST 中且 key[u] 值最小的顶点 u。最初,这将是起始顶点。
  • 将 u 添加到 MST。
  • 对于 u 的每个不在 MST 中的邻居 v,如果边 (u, v) 的权重小于 key[v],则将 key[v] 更新为 (u, v) 的权重,并将 parent[v] 设置为 u。

终止

一旦所有顶点都包含在 MST 中,您就已构建了最小生成树。

结果

MST 中的边集构成了给定图的最小生成树。

注意:Prim 算法通过在每一步贪婪地选择权重最小的边来确保 MST 以可能的最小总边权重构建。

实施

方法 1

输出

Enter the number of vertices: 5
Enter the adjacency matrix of the graph:
0 2 0 1 0
2 0 3 2 5
0 3 0 0 4
1 2 0 0 6
0 5 4 6 0
Edges of Minimum Spanning Tree:
Edge: 0 - 3 Weight: 1
Edge: 1 - 3 Weight: 2
Edge: 3 - 4 Weight: 6
Edge: 1 - 2 Weight: 3

解释

初始化

  • 算法从选择一个任意顶点作为初始顶点开始。该顶点被添加到 MST,其键(连接到它的最小边权重)设置为 0。所有其他顶点的初始键值均标记为无穷大。
  • 使用一种数据结构(通常是优先队列)来跟踪要添加到 MST 的候选边,并按其键值排序。

迭代过程

当还有未添加到 MST 的顶点时,算法将继续进行

  • 它选择 MST 中不在 MST 中的顶点中键值最小的顶点。该顶点将成为下一个添加到 MST 的顶点。
  • 算法遍历所选顶点的所有边,并检查是否有任何边的权重小于该顶点的键值。如果找到这样的边,则更新相邻顶点的键值和父节点,并将边添加到 MST 候选列表中。
  • 上述步骤重复执行,直到所有顶点都包含在 MST 中。

终止

当所有顶点都包含在 MST 中时,算法终止。

结果

MST 由算法执行过程中选择的边组成。

Prim 算法的关键思想是逐个顶点地增长 MST,始终选择具有最小键值的顶点,并添加连接到它的最小权重的边。这确保了 MST 是通过添加最小权重的边来构建的,从而保证了最优解。

该算法维护三个主要数据结构

  • 一个集合,用于跟踪已包含在 MST 中的顶点。
  • 一个数组(或其他数据结构),用于存储每个顶点的键值。
  • 一个数据结构(例如优先队列),用于根据键值高效地选择要添加到 MST 的下一个顶点。

通过迭代地选择顶点和更新键值,Prim 算法可以高效且最优地构建给定图的最小生成树。

方法 2

输出

Enter the number of vertices: 5
Enter the number of edges: 7
Enter the edges and their weights (from to weight):
0 1 2
0 3 1
1 2 3
1 3 2
1 4 5
2 4 4
3 4 6
Edges of Minimum Spanning Tree:
Edge: 0 - 3 Weight: 1
Edge: 3 - 1 Weight: 2
Edge: 1 - 2 Weight: 3
Edge: 1 - 4 Weight: 5

解释

用于图和边的自定义类

  • 我们定义两个自定义类 Edge 和 Graph 来表示图及其边。
  • Edge 存储目标顶点 (to) 和边的权重。
  • Graph 包含顶点数 (V) 和邻接列表 (adj) 来表示图的边。

用于将边添加到图的函数 (addEdge)

  • 在 Graph 类中,我们有一个成员函数 addEdge,它允许我们将边添加到图中。
  • 它接受源顶点 (from)、目标顶点 (to) 和边权重作为参数,并将边添加到邻接列表中。此外,它为无向图添加反向边。

Prim 算法 (primMST 函数)

  • 此函数使用 Prim 算法查找图的最小生成树 (MST)。
  • 它接受 Graph 对象作为参数。

初始化

  • 我们初始化数组来跟踪键值(最小边权重)、父顶点以及一个布尔数组 inMST 来跟踪顶点是否已包含在 MST 中。
  • priority_queue (pq) 用作最小堆,以选择权重最小的边。

主算法循环

  • 我们通过将第一个顶点(顶点 0)添加为初始顶点开始,其键值为 0。
  • 算法一直迭代,直到所有顶点都包含在 MST 中。
  • 在每次迭代中,它从最小堆 (pq) 中选择键值最小的顶点 u。

顶点包含

选定的顶点 u 被标记为包含在 MST 中,方法是将 inMST[u] 设置为 true。

更新相邻顶点

  • 算法遍历 u 的所有相邻顶点,并检查是否有边的权重小于该顶点的当前键值。
  • 如果找到这样的边,它会更新该相邻顶点的键值和父顶点。
  • 然后将相邻顶点添加到最小堆中以供进一步考虑。

终止

算法一直进行,直到所有顶点都包含在 MST 中。

打印 MST

算法完成后,我们通过遍历父数组并显示所选的边及其权重来打印 MST 边。

主函数

在 main 函数中,我们获取用户输入的顶点数和边数,创建一个 Graph 对象,使用 addEdge 函数向其添加边,最后调用 primMST 来查找并打印 MST。

示例

示例 1:邻接矩阵

输出

Enter the number of vertices: 5
Enter the adjacency matrix:
0 2 0 1 0
2 0 3 2 5
0 3 0 0 4
1 2 0 0 6
0 5 4 6 0
Edge   Weight
0 - 3   1
3 - 1   2
1 - 2   3
0 - 4   5

解释

  • 初始化

选择一个起始顶点作为最小生成树 (MST) 的初始节点。

创建数据结构来跟踪 MST

key[]:一个数组,用于存储将每个顶点连接到 MST 的最小边权重。将所有值初始化为无穷大,除了起始顶点(设置为 0)。

mstSet[]:一个数组或集合,用于跟踪哪些顶点已包含在 MST 中。将所有值初始化为 false。

  • 迭代过程

重复以下步骤,直到所有顶点都包含在 MST 中

选择一个不在 MST 中且具有最小键值的顶点 u。

将 u 添加到 MST。

如果 u 的邻接顶点尚未在 MST 中,并且到 u 的边权重小于其当前键值,则更新其键值。

  • 终止

当所有顶点都包含在 MST 中时,算法终止。

  • 结果

最小生成树 (MST) 由算法执行过程中选择的边组成。这些边连接所有顶点,同时最小化总边权重。

示例 2:邻接列表

输出

Enter the number of vertices: 5
Enter the number of edges: 7
Enter the edges (from to weight):
0 1 2
0 3 1
1 2 3
1 3 2
1 4 5
2 4 4
3 4 6
Edges of Minimum Spanning Tree:
Edge: 0 - 3 Weight: 1
Edge: 1 - 3 Weight: 2
Edge: 3 - 4 Weight: 6
Edge: 1 - 2 Weight: 3

解释

步骤 1:初始化

  • 从图中任意选择一个起始顶点。
  • 创建数据结构来跟踪最小生成树 (MST) 的构建
  • key[]:一个数组,用于存储将每个顶点连接到 MST 所需的最小边权重。将所有值初始化为无穷大,除了起始顶点(设置为 0)。
  • parent[]:一个数组,用于存储 MST 中每个顶点的父节点。将所有值初始化为 -1。
  • inMST[]:一个布尔数组或集合,用于跟踪哪些顶点已包含在 MST 中。将所有值初始化为 false。

步骤 2:增长 MST

重复以下步骤,直到所有顶点都包含在 MST 中

  • 查找不在 MST 中且键值最小的顶点 u。
  • 将顶点 u 添加到 MST。
  • 更新 u 的所有邻接顶点的键值,前提是它们尚未在 MST 中,并且到该顶点的边权重小于其当前键值。
  • 对于步骤 c 中添加的边,将相邻顶点的父节点更新为 u。
  • 通过将 inMST[u] 设置为 true,将顶点 u 标记为包含在 MST 中。

步骤 3:终止

当所有顶点都包含在 MST 中时,算法终止。

步骤 4:结果

最小生成树 (MST) 由算法执行过程中选择的边组成。这些边连接所有顶点,同时最小化总边权重。

应用

  • 网络设计

Prim 算法常用于网络设计问题,例如计算机网络、电力分配网络和电信网络的 T 设计。它有助于最小化成本,同时确保连通性。

  • 电路设计

在电子电路设计中,Prim 算法可用于优化电路板上组件的布局,从而最小化导线长度和连接。

  • 交通和城市规划

Prim 算法可应用于城市规划,以优化交通路线,例如道路网络、地铁系统或公交路线,以减少行程距离和拥堵。

  • 迷宫生成

它用于生成迷宫,目标是创建连接的迷宫,并具有最少的墙壁或通道。

  • 图像分割

在图像处理和计算机视觉中,Prim 算法可用于将图像分割成具有最小边界成本的区域或组件。

  • 聚类分析

Prim 算法可应用于聚类和层次聚类技术,根据数据点的相似性将它们分组,形成数据点的最小生成树。

  • 生成树算法

Prim 算法是其他算法和数据结构(如 Borůvka 算法和 Kruskal 算法)的基础组件,它们也用于查找最小生成树。

  • 路由协议

在计算机网络中,Prim 算法可用于构建路由表或确定网络路由协议(如 OSPF(开放最短路径优先))中的最佳路径。

  • 能源分配

它可以优化网络中资源的分配,如电力、水或天然气,从而最小化连接长度和基础设施成本。

  • 无线传感器网络

Prim 算法有助于在无线传感器网络中创建高效的通信拓扑,其中能源效率和连通性至关重要。

  • 数据聚类和可视化

Prim 算法可应用于数据聚类和可视化任务,以识别数据集中的聚类或连通分量。

  • 游戏开发

在游戏开发中,Prim 算法可用于生成游戏地图和布局,确保游戏环境是可连接和可导航的。

  • 图算法中的生成树

最小生成树在其他图算法中用作子问题,使 Prim 算法成为计算机科学中的基础概念。

优点

Prim 算法具有多种优点,使其成为在各种应用中查找最小生成树 (MST) 的宝贵选择。

  • 最优性: Prim 算法保证它生成的 MST 是最优的,这意味着它在图中所有可能的生成树中具有最小的总边权重。这种最优性在许多实际场景中至关重要,例如需要最小化成本。
  • 效率: Prim 算法效率很高,尤其适合稠密图。其时间复杂度通常为 O(V^2),其中 V 是顶点数,这使其在解决大规模问题方面具有实用性。通过使用二叉堆或斐波那契堆等更高级的数据结构,它可以达到 O(E + V log V) 的时间复杂度,其中 E 是边数。
  • 易于实现: 该算法相对容易实现和理解。它涉及简单的数??结构,如数组、优先队列或堆,使得程序员和工程师都可以轻松使用。
  • 多功能性: Prim 算法可应用于各种类型的图,包括加权、连通和无向图。它可以处理稠密图和稀疏图,是 MST 问题的多功能选择。
  • 分布式计算: Prim 算法适合分布式和并行计算。在图分布在多个处理器或节点上的场景中,该算法可以适应在这些环境中高效工作。
  • 增量构建: Prim 算法以增量方式构建 MST,可以轻松监控和可视化正在增长的 MST。这在 MST 的分步构建很有信息量的各种应用中很有用。
  • 稀疏图中的复杂度降低: 在稀疏图(其中边数远少于顶点数的平方)中,Prim 算法可能比另一种流行的 MST 算法 Kruskal 算法更快。
  • MST 以外的应用: Prim 算法是其他图算法和数据结构(如最短路径算法和网络路由)的构建块。该算法的变体用于各种应用。
  • 保证连通性: Prim 算法生成的 MST 保证是一棵连通的树,它跨越图中的所有顶点。这一特性在连通性至关重要的应用中至关重要。
  • 简单直观: 该算法的贪心方法,即选择权重最小的边,很容易直观理解。这使其成为教育目的和解决需要简单明了的解决方案的问题的良好选择。

Prim 算法的结合了最优性、效率和易于实现的优点,使其成为解决从网络设计到图像处理再到游戏开发等各个领域各种问题的宝贵工具。

Prim 算法与其他算法的区别

Prim 算法虽然是一种强大且广泛使用的最小生成树算法,但它也有可以解决相同问题的竞争者或替代方案。以下是用于查找最小生成树的 Prim 算法的一些主要竞争者或替代方案:

  • Kruskal 算法

Kruskal 算法是另一种流行的查找最小生成树的算法。它通过按权重对所有边进行排序,然后以升序添加它们来工作,只要它们不产生环路。Kruskal 算法的时间复杂度为 O(E log E),其中 E 是边数,并且通常在图是稀疏的时首选。

  • Borůvka 算法

Borůvka 算法是 Prim 算法和 Kruskal 算法的早期前身。它旨在通过反复将图收缩成更小的组件来找到最小生成树问题的近似解。

  • 反向删除算法

反向删除算法从图中的所有边开始,并反复删除权重最高的边,同时保持连通性。此过程一直持续到图成为生成树。对于稠密图,它的效率可能低于 Prim 算法或 Kruskal 算法。

  • Boruvka-最小生成树算法

该算法是 Borůvka 算法的修改版,旨在高效地找到近似 MST。它将图分成更小的连通分量,并为每个分量计算 MST,然后将这些 MST 连接起来形成最终的 MST。

  • Jarník 算法(也称为 Prim-Jarník 或使用斐波那契堆的 Prim 算法)

Prim 算法的优化版本,它使用斐波那契堆来加速优先队列操作,从而实现更快的实现,时间复杂度为 O(E + V log V)。这种变体对于稠密图尤其有效。

  • 随机算法

随机算法,如随机 Prim 算法和随机 Kruskal 算法,在选择边的过程中引入了随机性。这些算法可以以高概率提供近似 MST,并且在不需要精确解的情况下很有用。

  • Boruvka-Chazelle 算法

Borůvka 算法的改进,它使用斐波那契堆等高效数据结构来提高查找近似 MST 的时间复杂度。

在这些算法之间进行选择取决于因素,例如特定的问题、图的特性(例如密度)和性能考虑。虽然 Prim 算法以其在稠密图中的简单性和效率而闻名,但 Kruskal 算法通常用于稀疏图。研究人员和工程师根据问题的要求和可用计算资源选择最合适的算法。

缺点

  • 对边权重的敏感性: Prim 算法假定边权重是唯一的。如果存在多条具有相同权重的边,算法的行为可能不可预测,因为它没有明确的方式在等效边之间进行选择。这可能导致具有重复边权重的图产生不同的 MST。
  • 对稀疏图的效率低下: 在顶点数 (V) 远大于边数 (E) 的图中,Prim 算法的效率可能不如 Kruskal 算法等其他算法,后者在这种情况下具有更好的时间复杂度(O(E log E))。
  • 不适用于有向图: Prim 算法是为无向图设计的。它不能直接应用于有向图,因为它依赖于无向边的对称性。
  • 依赖于起始顶点: 起始顶点的选择会影响结果 MST。虽然 MST 的整体结构保持不变,但 MST 中的具体边可能会根据起始顶点而变化。在某些需要唯一 MST 的情况下,这种依赖性可能是一个缺点。
  • 不适用于动态图: 如果图是动态的,并且经常添加或删除边,那么从头开始使用 Prim 算法重新计算整个 MST 可能会效率低下。在这种情况下,专为动态图设计的算法可能更合适。
  • 缺乏并行性: Prim 算法本质上是顺序的,其步骤不适合自然并行化。在需要并行处理的应用中,其他算法或并行变体可能更合适。
  • 内存使用: 该算法需要内存来存储数组、优先队列或堆等数据结构。在某些情况下,内存使用可能是限制因素,尤其对于非常大的图。
  • 仅限于连通图: Prim 算法假定输入图是连通的。如果图不是连通的,算法将分别找到每个连通分量的最小生成树。
  • 不适用于负权重边: Prim 算法不处理具有负权重边的图,因为它假定所有边权重都是非负的。在这种情况下,Dijkstra 算法或 Bellman-Ford 算法可能更合适。

尽管存在这些缺点,Prim 算法在各种实际场景中仍然是查找最小生成树的宝贵工具,尤其是在应用于稠密图和具有唯一边权重时,并且计算效率不是主要考虑因素。研究人员和工程师应仔细考虑其问题的具体要求和数据特征,以选择 MST 计算算法。

总结

Prim 算法概述

  • Prim 算法是一种贪心算法,用于在连通无向图中查找最小生成树 (MST)。
  • MST 是图的边的子集,它连接所有顶点,同时最小化总边权重。
  • Prim 算法逐步构建 MST,从一个初始顶点开始,一次添加一个顶点和边,直到 MST 完成。

Prim 算法的关键步骤

初始化

  • 选择一个任意起始顶点。
  • 初始化数据结构
  • key[]: 将每个顶点连接到 MST 所需的最小边权重。将所有值初始化为无穷大,除了起始顶点(设置为 0)。
  • parent[]: 存储 MST 中每个顶点的父节点。将所有值初始化为 -1。
  • inMST[]: 跟踪哪些顶点包含在 MST 中。将所有值初始化为 false。

迭代过程

重复以下步骤,直到所有顶点都包含在 MST 中

  • 在 MST 中未包含的顶点中找到键值最小的顶点 u。
  • 将 u 添加到 MST。
  • 如果相邻顶点不在 MST 中,并且到 u 的边权重小于其当前键值,则更新它们的键值。
  • 对于步骤 c 中添加的边,将相邻顶点的父节点更新为 u。
  • 通过将 inMST[u] 设置为 true,将顶点 u 标记为包含在 MST 中。

终止

当所有顶点都包含在 MST 中时,算法终止。

结果

最小生成树 (MST) 由算法执行过程中选择的边组成。

图的表示

图可以使用邻接矩阵或邻接列表来表示,具体取决于具体的实现。

应用

  • Prim 算法用于各种应用,包括网络设计、聚类和图像分割。
  • 它在您希望以最小的总边权重连接一组点(顶点)的情况下特别有用。

总之,Prim 算法是在图中查找最小生成树的基本方法。它有效地构建了一棵连接所有顶点同时最小化总边权重的树。对于需要在优化和高效网络设计方面的各种领域的有价值的工具。