Prim算法是图论中的一种经典算法,主要用于在加权连通图中搜索最小生成树(MST)。最小生成树是一个连通图的子图,它包含图中所有的顶点,但只有足够的边使得这个子图成为一个树,且这些边的权重之和最小。下面详细阐述Prim算法实现最小生成树的原理及其起源发展。
目录
Prim算法的原理
Prim算法的实现基于贪心策略,其基本原理如下:
-
初始化:
- 选择一个起始顶点,将其加入最小生成树中,并初始化最小生成树的边集合为空。
- 创建一个数组(如 dist ),用于存储图中所有顶点到当前最小生成树的距离(或最小边权)。初始时,除了起始顶点外,其他顶点到最小生成树的距离都设置为无穷大(或某个足够大的数)。
-
选择边:
- 在所有未加入最小生成树的顶点中,选择与当前最小生成树中的顶点连接且权重最小的边。这通常通过遍历 dist 数组来实现,找到其中最小的非零值。
-
更新最小生成树:
- 将选择的边的另一端顶点加入最小生成树中,同时将该边添加到最小生成树的边集合中。
- 更新 dist 数组:对于新加入的顶点,遍历其所有相邻顶点,如果通过新加入的顶点可以使得相邻顶点到最小生成树的距离变小,则更新 dist 数组中的相应值。
-
重复步骤:
- 重复步骤 2 和 3 ,直到所有顶点都加入最小生成树中,即 dist 数组中的所有值都为 0。
Prim算法的起源与发展
-
发现者:
- Prim算法最初由捷克数学家沃伊捷赫·亚尔尼克(Vojtěch Jarník)在1930年发现。
- 1957年,美国计算机科学家罗伯特·普里姆(Robert C. Prim)独立发现了该算法。
- 1959年,艾兹格·迪科斯彻(Edsger W. Dijkstra)也再次发现了该算法。
-
命名:
- 由于有多位数学家独立发现了该算法,因此在不同场合下,Prim算法也被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。
-
应用与发展:
- Prim算法在解决加权连通图的最小生成树问题方面具有广泛的应用。它特别适用于稠密图(即边的数量相对较多的图),因为在这种情况下,Prim算法的效率通常优于其他算法(如Kruskal算法)。
- 随着计算机科学的不断发展,Prim算法也得到了不断的优化和改进。例如,可以使用二叉堆或斐波那契堆等数据结构来优化查找最小边权的过程,从而降低算法的时间复杂度。
Prim算法的 C++实现
问题:
#include <iostream> // 引入输入输出流库
#include <vector> // 引入向量容器库
#include <climits> // 引入常量定义库,用于获取INT_MAX
using namespace std; // 使用标准命名空间
const int INF = INT_MAX; // 定义无穷大常量,用于表示不可达的边
// 定义图结构(邻接矩阵)
typedef struct {
int NumOfVex; // 图的顶点数
int NumOfArc; // 图的边数
vector< vector<int> > AdjacencyMatrix; // 邻接矩阵,用于存储图的边和权重
} Graph;
// 创建图
void GraphCreate(Graph &G) {
cout << "请输入图的点数:" << endl; // 提示用户输入图的顶点数
cin >> G.NumOfVex; // 读取用户输入的顶点数
G.AdjacencyMatrix.resize(G.NumOfVex, vector<int>(G.NumOfVex, INF)); // 初始化邻接矩阵,所有元素设为INF
cout << "请输入图的边数:" << endl; // 提示用户输入图的边数
cin >> G.NumOfArc; // 读取用户输入的边数
for (int k = 0; k < G.NumOfArc; k++) { // 遍历每一条边
int a, b, w; // 定义变量a, b存储边的两个顶点,w存储边的权重
cout << "请依次输入边的两个点及权值:" << endl; // 提示用户输入边的信息
cin >> a >> b >> w; // 读取用户输入的边的信息
a--; b--; // 调整为0索引,因为用户可能习惯从1开始计数
G.AdjacencyMatrix[a][b] = w; // 设置邻接矩阵中对应边的权重
G.AdjacencyMatrix[b][a] = w; // 无向图,添加对称边
}
}
// 打印图的信息
void GraphPrint(Graph &G) {
cout << "图的点数:" << endl; // 输出图的顶点数
cout << G.NumOfVex << endl; // 输出顶点数
cout << "图的边数:" << endl; // 输出图的边数
cout << G.NumOfArc << endl; // 输出边数
cout << "图邻接矩阵边和权值:" << endl; // 输出邻接矩阵的标题
for (int i = 0; i < G.NumOfVex; i++) { // 遍历邻接矩阵的每一行
for (int j = 0; j < G.NumOfVex; j++) { // 遍历邻接矩阵的每一列
cout << G.AdjacencyMatrix[i][j] << " "; // 输出矩阵元素
}
cout << endl; // 输出行结束符
}
}
// Prim算法求最小生成树的总权重
int Prim(Graph &G) {
vector<bool> book(G.NumOfVex, false); // 标记数组,记录顶点是否已被加入最小生成树
vector<int> dist(G.NumOfVex, INF); // dist数组,记录从已加入最小生成树的顶点到未加入顶点的最短边权重
dist[0] = 0; // 初始时,将第一个顶点到自身的距离设为0
for (int i = 1; i < G.NumOfVex; i++) { // 遍历每一个顶点
int Temp = INF; // 临时变量,用于记录当前找到的最短边权重
int Flag = -1; // 标记变量,用于记录当前找到的最短边的顶点
for (int j = 0; j < G.NumOfVex; j++) { // 遍历所有顶点
if (!book[j] && dist[j] < Temp) { // 如果顶点未被标记且到该顶点的距离小于当前最短距离
Temp = dist[j]; // 更新最短距离
Flag = j; // 更新最短距离的顶点标记
}
}
if (Flag == -1) {
return INF; // 如果找不到下一个点,返回正无穷表示构建失败(理论上不应该发生,除非图不连通)
}
book[Flag] = true; // 将找到的顶点标记为已加入最小生成树
for (int j = 0; j < G.NumOfVex; j++) { // 更新dist数组
if (!book[j] && G.AdjacencyMatrix[Flag][j] < dist[j]) { // 如果顶点未被标记且通过新加入的顶点到达该顶点的距离更短
dist[j] = G.AdjacencyMatrix[Flag][j]; // 更新最短距离
}
}
}
int res = 0; // 初始化最小生成树的总权重为0
for (int i = 0; i < G.NumOfVex; i++) { // 遍历dist数组
if (dist[i] != INF) { // 忽略未连接的顶点(即dist值为INF的顶点)
res += dist[i]; // 累加已连接顶点的最短边权重
}
}
return res; // 返回最小生成树的总权重
}
int main() {
Graph G; // 定义图变量G
GraphCreate(G); // 创建图G
GraphPrint(G); // 打印图G的信息
int Distance = Prim(G); // 调用Prim算法求最小生成树的总权重
cout << "最小生成树的总权重为: " << Distance << endl; // 输出最小生成树的总权重
system("pause"); // 暂停程序,等待用户输入,以便查看结果
return 0; // 程序结束
}
在命运之书里,我们同在一行字之间。——莎士比亚《罗密欧与朱丽叶》