Prim 普利姆算法-Priority Queue 方法

博客介绍了普利姆算法,提及无向网的最小生成树是图的子集,边数比顶点数少1,边权值和最小且无环。还介绍了Lazy Prim算法,其需用优先队列,以边权值为主元素建最小堆,并给出主要代码。

普利姆算法

  1. 最小生成树,Minimum Spanning Tree
    无向网的最小生成树是图的一个子集,在这个子集中,边的数量比顶点数量少1,所有顶点被这些边连接在一起,而且这些边的权值和为最小。值得一提的是,由于最终的结果为树,所以子集中不含有环(cycle).
  2. Lazy Prim 算法
    Lazy Prim算法需要使用优先队列(Priority Queue),队列中的元素为生成边,定义生成边的结构体如下:
typedef struct edge
{
    int from; //start vertex
    int to;   //end vertex
    VRType cost; //weight of cost
}edge;

以边中的权值为Priority Queue的主元素,建立最小堆。

主要代码如下:

typedef edge priority_queue_element;
void mst_prim_lazy(MGraph G, Heap H, int s, edge *mst_edges, int *mst_cost, int (*pf)(priority_queue_element e1, priority_queue_element e2))
{
    int                    i;
    int                    edge_count;
    int                    node_index;
    priority_queue_element arc;

    for(i=0;i<G.vexnum;i++)
    {
        visited[i]=0;
    }
    *mst_cost=0;
    edge_count=0;
    add_edges(G,s,H,pf);

    while(H->size>0 && edge_count!=(G.vexnum-1))
    {
        arc=heap_extract_min(H,pf);
        node_index = arc.to;

        if (!visited[node_index])
        {
            mst_edges[edge_count++]=arc;
            *mst_cost+=arc.cost;
            add_edges(G,node_index,H,pf);
        }
    }

    //need to check if there is MST in the graph
    return;
}

void add_edges(MGraph G,int node_index, Heap H, int (*pf)(priority_queue_element e1, priority_queue_element e2))
{
    int v;
    int w;
    priority_queue_element arc;
    visited[node_index]=1;
    v=node_index;

    for (w = FirstAdjVex(G, G.vexs[v]); w >= 0; w = NextAdjVex(G, G.vexs[v], G.vexs[w]))
    {
        if(!visited[w])
        {
            arc.from=v;
            arc.to=w;
            arc.cost=G.arcs[v][w].adj;
            min_heap_insert(H,arc,pf);
        }
    }
}

int less_than(priority_queue_element e1, priority_queue_element e2)
{
    return (e1.cost<e2.cost);
}

Main 函数如下:

int main(void)
{
    Heap H;
    MGraph G;
    FILE *fp;
    int i;
    int mst_cost=0;
    int s=0;
   

    fp=fopen("UDN.txt","r");
    CreateGraph(&G,fp);
    edge mst_edges[G.vexnum-1];
    init_min_heap(&H, G.arcnum);

    mst_prim_lazy(G,H,s,mst_edges,&mst_cost,less_than);
 
    for(i=0;i<G.vexnum-1;i++)
    {
        printf("-%c-%c-%d-\n", G.vexs[mst_edges[i].from], G.vexs[mst_edges[i].to], mst_edges[i].cost);
    }
    fclose(fp);
    PressEnter;

    return EXIT_SUCCESS;
}
这是一个非常好的问题:**为什么在这个“村村通”问题中我们选择 Kruskal 而不是 Prim 算法?** 其实,**Prim 和 Kruskal 都可以解决最小生成树问题**,都能正确求出本题的解。但选择哪一个更合适,取决于图的**稠密程度、输入格式、实现便捷性**等因素。 下面我们来详细分析:**在本题场景下,Kruskal 更自然;但 Prim 也完全可行**。我们先解释为何很多人(包括上述解法)倾向使用 Kruskal,然后再给出用 Prim 的实现和对比。 --- ### ✅ 一、为什么本题适合用 Kruskal? #### 1. **输入是边列表形式** 题目给的是 `m` 条边,每行一条边(u, v, cost),这正是 Kruskal 算法最直接需要的数据结构 —— 边集合。 > Kruskal:对所有边排序 → 依次尝试加入。 > > 所以读入后直接存为边数组即可,无需额外处理。 而 Prim 算法通常基于邻接表或邻接矩阵,需要先建图,稍显繁琐。 #### 2. **边数较少(稀疏图)** 题目说明:`m ≤ 3n`,例如 n=1000 时 m≤3000,属于典型的**稀疏图**。 - Kruskal 时间复杂度:O(m log m) ≈ O(m log n) - 使用优先队列Prim:O(m log n) 两者理论上相近,但在稀疏图中都高效。不过 Kruskal 实现更简洁。 #### 3. **代码简洁,易写易调试** 并查集 + 排序的 Kruskal 算法逻辑清晰,适合竞赛或考试环境快速编码。 --- ### ✅ 二、那什么时候该用 Prim? | 场景 | 更推荐 | |------|--------| | 图很稠密(如 m ≈ n²) | ✅ Prim(可用邻接矩阵 O(n²) 版) | | 已有邻接矩阵/邻接表 | ✅ Prim | | 内存紧张或需避免排序 | ✅ Prim(尤其数组版) | | 输入是边列表且边少 | ✅ Kruskal | 👉 所以:**如果你已经有图的完整结构(比如邻接表),Prim 很好;但如果输入就是一堆边,Kruskal 更直观。** --- ### ✅ 三、用 Prim 算法实现本题(C++) 下面是使用 **Prim 算法 + 优先队列(堆优化)** 的版本: ```cpp #include <iostream> #include <vector> #include <queue> #include <climits> using namespace std; struct Edge { int to, cost; Edge(int to, int cost) : to(to), cost(cost) {} }; struct PQNode { int node, cost; bool operator<(const PQNode& other) const { return cost > other.cost; // 最小堆 } }; int main() { int n, m; cin >> n >> m; vector<vector<Edge>> graph(n + 1); for (int i = 0; i < m; ++i) { int u, v, cost; cin >> u >> v >> cost; graph[u].push_back(Edge(v, cost)); graph[v].push_back(Edge(u, cost)); } vector<bool> visited(n + 1, false); priority_queue<PQNode> pq; long long totalCost = 0; int edgesUsed = 0; // 从节点 1 开始 pq.push({1, 0}); while (!pq.empty()) { auto curr = pq.top(); pq.pop(); int u = curr.node, cost = curr.cost; if (visited[u]) continue; visited[u] = true; totalCost += cost; edgesUsed++; for (const Edge& e : graph[u]) { if (!visited[e.to]) { pq.push({e.to, e.cost}); } } } // 检查是否全部访问 if (edgesUsed == n) cout << totalCost << endl; else cout << -1 << endl; return 0; } ``` --- ### ✅ 四、两种算法对比总结 | 特性 | Kruskal | Prim(堆优化) | |------|--------|----------------| | 数据结构 | 边列表 | 邻接表 | | 时间复杂度 | O(m log m) | O(m log n) | | 空间复杂度 | O(m) | O(m) | | 适合图类型 | 稀疏图 | 稠密或稀疏均可 | | 实现难度 | 简单(排序+并查集) | 中等(建图+优先队列) | | 是否需要建图 | 否(直接处理边) | 是 | | 判断连通性 | 并查集自然支持 | 看是否访问了 n 个点 | --- ### ✅ 结论:**不是“不能用 Prim”,而是“Kruskal 更顺手”** 在这个问题中: - 输入是边列表; - 图稀疏; - 要求最低成本连通; - 输出 -1 若不连通; 所以 **Kruskal 是更自然的选择**,但 **Prim 同样正确且高效**。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值