基本原理
关于横切边和切分定理,点这里 最小生成树理论准备
树中的边是黑色加粗,红色边为横切边,加粗红色为最短横切边,也是将要加入最小生成树的边。
解决三个问题
-
最短横切边怎么找?
用一条优先队列MinPQ来根据权重比较所有边
-
如何保存切分?
通过索引的布尔数组marked,标记已经加入树的节点(白色集合),自己的补集为灰色集合,两者构成一个切分。
-
如何保存树?
- 法一,通过一个队列mst保存最小生成树所有边
- 法二,用一个父亲数组记录边,例如,edgeTo[v]记录着将v连入树中的Edge对象。
过程
-
红色数字列举了横切边的信息【存放在队列中】,按权重排序。
-
选取最小边保存,并出队(delMin)。新节点加入生成树,同时也有新的横切边入队(insert),重新调整得到最小值。
-
值得注意的是随着一些新边的加入,队列中的某些边会失效(两个节点都在MST中,他们之间的边自然无效)。
注意:这里的时间复杂度关键看优先队列的提取最小值和插入的效率如何。记边数为E,因为队列里只存边。插入成本最坏 logE,提取最小值为2logE。而最多插入E条边,删除E次最小边,故而整个Prim算法时间最坏和ElogE成正比。
MST基本API
Prim算法的延时(懒惰)版本
优先队列保存所有的横切边,但会包含失效边。用一条队列来保存最小生成树的边,用一个顶点索引来标记生成树的顶点。
//LazyPrimMST.h
#pragma once
#include<queue>
#include"EdgeWeightGraph.h"
typedef priority_queue<Edge, vector<Edge>, greater<Edge> > MinPQ;
class LazyPrimMST
{
public:
LazyPrimMST(EdgeWeightGraph& G);
queue<Edge>* edges() { return m_mst; }
double weight();
private:
queue<Edge>* m_mst = nullptr;
vector<bool>* m_marked = nullptr;
MinPQ* m_pq=nullptr;
void visit(EdgeWeightGraph& G, int v);//假定G联通
};
void testForLazyPrim();
#include "LazyPrimMST.h"
LazyPrimMST::LazyPrimMST(EdgeWeightGraph& G)
{
m_marked = new vector<bool>(G.V(), false);
m_mst = new queue<Edge>();
m_pq = new MinPQ();
visit(G, 0);//假定G是联通的
while (!m_pq->empty())
{
Edge e = m_pq->top(); m_pq->pop();//取出最小边
int v = e.either();
int w = e.other(v);
if (m_marked->at(v) && m_marked->at(w)) continue;//跳过失效边
m_mst->push(e);//将边加入到树
if (!m_marked->at(v)) visit(G, v);//将顶点加入到树v或w
if (!m_marked->at(w)) visit(G, w);
}
}
double LazyPrimMST::weight()
{
return 0.0;
}
void LazyPrimMST::visit(EdgeWeightGraph& G, int v)
{//标记v,同时将所有 连接v和未被标记的顶点 的边加入m_pq
m_marked->at(v) = true;
for (Edge e : *G.adj(v)) {
if (!m_marked->at(e.other(v))) m_pq->push(e);
}
}
void testForLazyPrim()
{
EdgeWeightGraph G("tinyEWG.txt");
LazyPrimMST mst(G);
queue<Edge>* edges = mst.edges();
while (!edges->empty())
{
Edge e = edges->front(); edges->pop();
out(e.toString()),hh;
}
hh;
}