Prim堆优化

这段代码实现了一个Prim算法的版本,用于寻找加权无向图的最小生成树。程序首先初始化所有节点的距离为最大值,然后通过优先级队列进行迭代,每次选取距离最小的节点并更新与其相邻的节点距离,直到所有节点都被包含在最小生成树中。最后,如果找到n个节点则输出最小生成树的总权重,否则输出'NO'表示无法构建最小生成树。
#include<bits/stdc++.h>
#define R register int//优化,可忽略
#define P pair <int,int>
using namespace std;
int k,n,m;
int cnt,sum,dis[10005],vis[10005];
vector <int> E[200005];//存点
vector <int> W[200005];//存边
inline void add(int u,int v,int w) {
	E[u].push_back(v);//存点
	W[u].push_back(w);//存边
}
priority_queue <P,vector<P>,greater<P> > q;//递减
inline void prim() {//神似dijskra
	memset(dis,127,sizeof(dis));//初始化
	dis[1]=0;//初始化
	q.push(make_pair(dis[1],1));//初始化
	while(!q.empty()&&cnt<n) {
		int d=q.top().first,u=q.top().second;
		q.pop();
		if(vis[u]) continue;//访问过就不访问了
		cnt++;
		sum+=d;//有就加
		vis[u]=1;
		for(R i=0; i<E[u].size(); i++)//dijskra这里是循环找中界点,而这里则直接找与该点相邻的边,刷新一遍距离
		   if(W[u][i]<dis[E[u][i]]){
			dis[E[u][i]]=W[u][i];
               		q.push(make_pair(dis[E[u][i]],E[u][i]));
    		}
	}
}
int main() {
	scanf("%d%d",&n,&m);
	for(R i=1; i<=m; i++) {
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	prim();
	if (cnt==n)printf("%d",sum);
	else printf("NO");
}

<think>好的,我现在要详细理解并实现Prim算法的优化版本。首先,我得回忆一下Prim算法的基本思想。Prim算法用于生成一个带权无向图的最小生成树(MST),其核心思想是从一个起始顶点开始,逐步扩展,每次选择连接已选顶点集合和未选顶点集合的最小权重的边,并将该边连接的顶点加入已选集合,直到所有顶点都被包含。 不过,传统的Prim算法时间复杂度是$O(V^2)$,对于边数较多的图来说,这可能不够高效。优化的目的应该是将时间复杂度降低到$O(E \log V)$,尤其是在稀疏图中表现更好。那我需要弄清楚优化具体是如何实现的。 首先,的作用应该是快速找到当前连接已选集合和未选集合的最小权重边。在传统实现中,这一步可能需要遍历所有边来找到最小边,而(优先队列)可以自动维护这些边的权重顺序,从而每次取出顶元素即为当前的最小边。 接下来,我需要考虑如何用优先队列来优化。根据引用[2]提到的,优化Prim和Dijkstra的优化类似。Dijkstra算法中使用优先队列来维护到各点的最短距离,每次取出距离最小的点进行松弛操作。同理,Prim算法中应该维护当前各个未选顶点到已选集合的最小边权重,每次取出权重最小的那个顶点加入已选集合,并更新其邻接点的权重。 这里可能需要一个辅助数组来记录每个顶点到已选集合的最小权重,类似于Dijkstra中的距离数组。引用[3]中提到,辅助数组的作用是记录V-U(未选顶点集合)中的点与U(已选集合)之间的最小边开销,并且在每次加入新顶点后,用该顶点的邻接信息更新这些开销。如果新的边权重比数组中记录的更小,则更新数组并将该边加入优先队列。 那具体实现步骤应该是怎样的呢?大致步骤如下: 1. 初始化一个优先队列(最小),用于存储边及其权重,按权重从小到大排序。 2. 选择一个起始顶点,将其加入已选集合,并将其所有邻接边加入优先队列。 3. 从优先队列中取出权重最小的边,如果该边连接的顶点未被选中,则将该顶点加入已选集合,并将此边加入最小生成树。 4. 遍历该顶点的所有邻接边,若邻接顶点未被选中且新的边权重小于该顶点当前记录的最小权重,则更新该顶点的最小权重,并将该边加入优先队列。 5. 重复步骤3-4,直到所有顶点都被加入已选集合。 需要注意的是,为了避免重复处理同一个顶点,需要一个标记数组来记录顶点是否已被选中。另外,辅助数组用来维护每个顶点到已选集合的最小权重,当某个顶点被处理时,其对应的最小权重边即被确定,之后不需要再更新。 接下来,我需要考虑代码结构。比如,边的数据结构如何定义?优先队列如何比较权重?如何避免重复添加边? 根据引用[2]中的代码示例,优先队列存储的是边结构体,包含目标顶点和权重。按照边的权重从小到大排序。每次取出顶元素,检查目标顶点是否已加入生成树,若未加入,则处理该顶点,并将其邻接边加入队列。同时,辅助数组可能在这里用来记录各顶点的当前最小权重,如果新的边权重比当前记录的小,才进行更新和入队操作。 这里可能存在一个问题:同一个顶点可能在优先队列中出现多次,但只有最小的权重那次是有效的。因此,在处理顶点时,需要先检查该顶点是否已经被处理过,如果是,则跳过。这与Dijkstra算法中的处理方式类似。 举个例子,假设顶点B已经被处理过,并且此时队列中还有一条到顶点B的边,权重更大。这时候取出这条边时,发现B已经被处理,就跳过,这样保证了每次处理的都是当前最小的有效边。 现在,关于辅助数组,是否需要维护每个顶点的当前最小权重?是的。引用[3]中提到,辅助数组会记录每个未选顶点到已选集合的最优边开销,每次加入新顶点后,用该顶点的邻接信息去更新这些开销。如果发现更小的权重,则更新数组,并将对应的边加入优先队列。因此,辅助数组的作用是避免重复计算每个顶点到已选集合的最小边。 在代码实现中,可能使用一个数组dist[],其中dist[v]表示顶点v到已选集合的最小边权重。初始时,所有dist[v]设为无穷大,起始顶点的dist设为0。优先队列中存储的是这些dist值对应的边。当处理顶点u时,遍历其所有邻接顶点v,如果v未被选中,且边u-v的权重小于dist[v],则更新dist[v]为这个权重,并将该边加入队列。 需要注意的是,优先队列中可能已经存在多个关于顶点v的条目,但它们的权重可能较大,此时新的更小权重的边入队后,旧的条目在后续处理时会被跳过,因为此时顶点v可能已经被处理,或者新的更小权重已经被应用。 现在,我需要考虑具体的代码实现。例如,使用C++的话,可以这样写: - 定义边的结构体,包含目标顶点和权重。 - 使用优先队列(通常用priority_queue,但默认是最大,需要重载比较运算符或使用greater来变成最小)。 - 使用一个bool数组inMST来标记顶点是否已加入生成树。 - 使用dist数组记录各顶点的当前最小权重。 代码的大致结构可能如下: 初始化dist数组为无穷大,dist[start] = 0; 优先队列初始放入起点,权重为0; while 队列不为空: 取出顶元素u; 如果u已经在生成树中,跳过; 将u加入生成树; 遍历u的所有邻接边v和权重w: 如果v不在生成树中,且w < dist[v]: dist[v] = w; 将v和w加入优先队列; 累计dist数组中的值得到MST的总权重,或者记录边。 不过,这里是否应该将边加入队列的时候,同时记录这条边的前驱节点?或者只需要记录顶点和权重? 可能不需要,因为dist数组已经保存了到该顶点的最小权重,而具体的边可以在生成树时通过其他方式记录,比如parent数组。 此外,总权重可以通过将dist数组中所有顶点的值相加得到,前提是图是连通的,否则无法生成MST。 现在,关于正确性,如何确保优化Prim算法正确生成MST? 因为每次选择的是连接已选集合和未选集合的最小权重边,这符合Prim算法的贪心策略。优化只是加速了选择最小边的过程,不影响正确性。同时,辅助数组dist的更新保证了每次都是当前最优的选择。 接下来,可能需要考虑如何处理图的表示。通常,图可以用邻接表或邻接矩阵。邻接表更节省空间,尤其是对于稀疏图。在优化的情况下,邻接表更适合,因为需要遍历每个顶点的邻接边。 在代码中,邻接表可以用vector<vector<pair<int, int>>>来表示,每个顶点对应一个vector,存储邻接顶点和权重。 现在,针对引用中的内容,引用[2]的代码示例中使用了优先队列,并且每次将边压入队列,其中边包含目标顶点和权重。这与上述思路一致。 另外,引用[3]提到辅助数组的更新机制,即在每次新顶点加入后,用该顶点的邻接信息更新未选顶点的开销。这对应于代码中处理顶点u后,遍历其邻接顶点v,并更新它们的dist值。 现在,可能存在的一个疑问是,为什么在每次更新dist[v]时,直接将新的边加入优先队列,而不是只保留最小的那个?这是因为优先队列中可能存在多个关于v的条目,但只有最小的那个会被先处理,处理之后v就被标记为已加入生成树,后续更大的权重条目会被跳过。所以即使队列中存在旧的较大的权重,也不会影响正确性,但可能会影响效率,因为队列中会有冗余元素。不过,这通常不影响总的时间复杂度,因为每个边最多被处理一次,时间复杂度仍然是O(E log E),而E最多是V^2,所以对于稀疏图来说,这比O(V^2)更好。 总结实现步骤: 1. 初始化数据结构:邻接表、优先队列、dist数组、inMST数组。 2. 选择起始顶点,设置其dist为0,并加入队列。 3. 循环取出队列中的最小权重边对应的顶点u: a. 如果u已加入MST,跳过。 b. 将u加入MST,并记录其边(如果需要)。 c. 遍历u的所有邻接边v,如果v不在MST中,且边的权重小于dist[v],则更新dist[v]并加入队列。 4. 重复直到所有顶点加入MST。 现在,考虑具体的代码实现,以C++为例: #include <iostream> #include <vector> #include <queue> #include <climits> using namespace std; typedef pair<int, int> pii; // 存储顶点和权重 vector<int> prim_optimized(vector<vector<pii>>& graph, int start) { int n = graph.size(); vector<int> dist(n, INT_MAX); vector<bool> inMST(n, false); priority_queue<pii, vector<pii>, greater<pii>> pq; // 最小 dist[start] = 0; pq.push({0, start}); while (!pq.empty()) { int u = pq.top().second; int weight = pq.top().first; pq.pop(); if (inMST[u]) continue; inMST[u] = true; for (auto& edge : graph[u]) { int v = edge.first; int w = edge.second; if (!inMST[v] && w < dist[v]) { dist[v] = w; pq.push({w, v}); } } } return dist; } int main() { int n = 5; vector<vector<pii>> graph(n); // 示例图的邻接表,例如: graph[0].push_back({1, 2}); graph[0].push_back({3, 6}); graph[1].push_back({0, 2}); graph[1].push_back({2, 3}); graph[1].push_back({3, 8}); graph[1].push_back({4, 5}); graph[2].push_back({1, 3}); graph[2].push_back({4, 7}); graph[3].push_back({0, 6}); graph[3].push_back({1, 8}); graph[3].push_back({4, 9}); graph[4].push_back({1, 5}); graph[4].push_back({2, 7}); graph[4].push_back({3, 9}); vector<int> mst_dist = prim_optimized(graph, 0); int total = 0; for (int d : mst_dist) { if (d != INT_MAX) { total += d; } } cout << "MST总权重: " << total << endl; return 0; } 需要注意的是,这里返回的dist数组中的每个元素代表该顶点连接到MST的最小边权重。总权重即为所有顶点的dist之和,除了起始点的dist是0。不过,这里可能存在一个问题:起始点的dist被设为0,但其他顶点的dist是连接到MST的边的权重。因此,总权重应该是sum(dist) - dist[start]。或者,在初始化时,是否应该将dist[start]设为0,而其他顶点的dist初始化为无穷大,每次加入顶点时,将对应的边权重累加? 可能这里存在误解。在传统的Prim算法中,dist数组记录的是每个顶点到已选集合的最小边权重。MST的总权重是sum(dist)(假设所有顶点都被正确加入)。例如,起始顶点的dist为0,其他顶点的dist是它们被加入MST时所对应的边的权重。因此,总权重是所有顶点的dist之和,包括起始顶点,但起始顶点的dist为0,不影响总和。因此,上述代码中总权重计算正确。 但需要确保图是连通的,否则可能存在顶点的dist仍为INT_MAX,导致总和错误。在实际应用中,需要处理图不连通的情况。 现在,关于这个代码的正确性,假设邻接表是正确的,那么运行后的总权重应该正确。例如,示例中的图如果顶点0、1、2、3、4构成的图的MST总权重应该是:0-1(2),1-2(3),1-4(5),0-3(6)吗?或者可能我示例中的图可能有不同的MST结构? 可能需要手动计算一下示例图的MST。例如: 顶点0连接1(2)和3(6)。 顶点1连接0(2)、2(3)、3(8)、4(5)。 顶点2连接1(3)、4(7)。 顶点3连接0(6)、1(8)、4(9)。 顶点4连接1(5)、2(7)、3(9)。 构造MST的步骤如下: 从顶点0开始: dist[0] = 0,加入MST。 处理顶点0的邻接边: 顶点1的边权重2,更新dist[1]=2,入队。 顶点3的边权重6,更新dist[3]=6,入队。 队列中现在有(2,1)和(6,3)。取出最小的(2,1),将顶点1加入MST。 处理顶点1的邻接边: 顶点0已加入,跳过。 顶点2:当前dist[2]是无穷大,权重3更小,更新dist[2]=3,入队。 顶点3:当前dist[3]=6,而边权重8更大,不更新。 顶点4:当前dist[4]无穷大,权重5更小,更新dist[4]=5,入队。 队列现在有(3,2)、(5,4)、(6,3)。取出最小的(3,2),加入MST。 处理顶点2的邻接边: 顶点1已加入,跳过。 顶点4的边权重7,当前dist[4]=5,7更大,不更新。 队列剩下(5,4)和(6,3)。取出(5,4),加入MST。 处理顶点4的邻接边: 顶点1已加入。 顶点2已加入。 顶点3的边权重9,当前dist[3]=6,不更新。 队列剩下(6,3),取出,加入MST。 处理顶点3的邻接边: 顶点0和1已加入,其他边权重更大,不更新。 此时所有顶点都已加入。总权重是0(0)+2(1)+3(2)+5(4)+6(3)=16。 而代码中的计算会得到sum(dist) =0+2+3+6+5=16,正确。 因此,代码是正确的。 现在,关于优化的正确性,是否每个顶点的dist值都是最小的可能值?是的,因为每次选择的是当前最小的边,并且通过优先队列维护了这些候选边。每当一个顶点被加入MST时,其邻接顶点的dist值会被更新为可能更小的边权重,从而保证后续的选择总是最小的。 另外,关于代码中的优先队列存储的是(权重,顶点),这是因为pair在优先队列中的比较是先比较第一个元素,再第二个。这样,权重作为第一个元素可以确保队列按权重从小到大排序。 总结来说,Prim算法的优化版本通过优先队列快速选择当前最小边,并利用辅助数组记录各顶点的最小权重,从而将时间复杂度优化到O(E log V),适用于稀疏图。</think>### Prim算法优化实现详解 #### 算法核心思想 Prim算法通过贪心策略构造最小生成树(MST),每次选择连接**已选顶点集合**(U)和**未选顶点集合**(V-U)的最小权重边,并将对应顶点加入U。优化通过优先队列快速获取最小边,将时间复杂度从$O(V^2)$优化至$O(E \log V)$[^1][^3]。 #### 实现步骤 1. **初始化数据结构**: - `dist[]`:记录每个顶点到U的最小边权重,初始为无穷大,起始点设为0。 - `inMST[]`:标记顶点是否已加入MST。 - **优先队列**:按权重升序排列,存储候选边。 2. **从起始点开始**: ```cpp dist[start] = 0; priority_queue<pii, vector<pii>, greater<pii>> pq; pq.push({0, start}); // 格式:(权重, 顶点) ``` 3. **循环处理队列**: - 取出顶元素,若顶点未加入MST,则标记为已加入。 - 遍历该顶点的邻接边,更新邻接顶点的最小权重并加入队列。 ```cpp while (!pq.empty()) { int u = pq.top().second; pq.pop(); if (inMST[u]) continue; inMST[u] = true; for (auto& edge : graph[u]) { int v = edge.first, w = edge.second; if (!inMST[v] && w < dist[v]) { dist[v] = w; pq.push({w, v}); } } } ``` 4. **计算总权重**: ```cpp int total = accumulate(dist.begin(), dist.end(), 0); ``` #### 代码示例(C++) ```cpp #include <iostream> #include <vector> #include <queue> #include <climits> using namespace std; typedef pair<int, int> pii; // (邻接顶点, 权重) vector<int> prim_optimized(vector<vector<pii>>& graph, int start) { int n = graph.size(); vector<int> dist(n, INT_MAX); vector<bool> inMST(n, false); priority_queue<pii, vector<pii>, greater<pii>> pq; dist[start] = 0; pq.push({0, start}); while (!pq.empty()) { int u = pq.top().second; pq.pop(); if (inMST[u]) continue; inMST[u] = true; for (auto& edge : graph[u]) { int v = edge.first, w = edge.second; if (!inMST[v] && w < dist[v]) { dist[v] = w; pq.push({w, v}); } } } return dist; } int main() { // 示例图的邻接表(顶点0~4) vector<vector<pii>> graph = { {{1,2}, {3,6}}, // 0 {{0,2}, {2,3}, {3,8}, {4,5}}, // 1 {{1,3}, {4,7}}, // 2 {{0,6}, {1,8}, {4,9}}, // 3 {{1,5}, {2,7}, {3,9}} // 4 }; vector<int> mst_weights = prim_optimized(graph, 0); int total = 0; for (int w : mst_weights) total += w; cout << "MST总权重: " << total << endl; // 输出16 return 0; } ``` #### 关键点说明 - **优先队列**:确保每次取出最小权重边,时间复杂度$O(\log E)$[^2]。 - **辅助数组`dist[]`**:动态维护顶点到U的最小边权重,避免重复计算[^3]。 - **去重机制**:通过`inMST[]`标记已处理顶点,跳过队列中的无效条目。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值