Prim算法

来源:互联网 发布:php软件公司源码 编辑:程序博客网 时间:2024/06/05 11:53

-------------------siwuxie095

  

  

  

  

  

  

  

  

Lazy Prim 算法

  

  

在介绍Prim 算法之前,先介绍 Lazy Prim 算法

  

  

看如下实例:

  

  

  

  

这张连通带权无向图中所有边上的权值如下:

  

  

  

  

求最小生成树的过程,其实就是从这张连通带权无向图

的所有边中选出V-1 条边,并且这V-1条边连接了 V

个顶点

  

  

根据切分定理可知,一旦将这个图做一个切分后,相应

横切边中权值最小的那条边就一定属于最小生成树

  

  

  

  

  

0作为起始点,开始切分,逐步将蓝色阵营的顶点

转换到红色阵营中

  

1)将0 加入到红色阵营中

  

  

  

这样一来,就形成了一个切分,相应的就有横切边

  

接下来将最小堆作为辅助数据结构,可以非常快速地找到

横切边中权值最小的那条边

  

将横切边放入到最小堆中,并拿出最小堆中权值最小的边

  

此时,0-7 是权值最小的边,权值为 0.16,且 0、7 分属

不同阵营,一定属于最小生成树

  

  

  

  

2)将 7 加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,1-7 是权值最小的边,权值为 0.19,且 1、7

分属不同阵营,一定属于最小生成树

  

  

  

  

3)将 1 加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,0-2 是权值最小的边,权值为 0.26,且 0、2

分属不同阵营,一定属于最小生成树

  

  

  

  

4)将 2 加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,2-3 是权值最小的边,权值为 0.17,且 2、3

分属不同阵营,一定属于最小生成树

  

  

注意:此时,1-2 和 2-7 实际上已经不是横切边了,

本来应该从最小堆中剔除,不能再作为最小生成树

的候选边,但这里并不急着剔除,而是依然保留在

最小堆中,当二者上移到最小堆的顶端被拿出来时,

就会发现二者的两端同属红色阵营,直接把二者扔

掉即可。这也正是Lazy Prim 算法的之所在

  

  

  

  

5)将 3加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,5-7 是权值最小的边,权值为 0.28,且 5、7

分属不同阵营,一定属于最小生成树

  

  

  

  

6)将 5加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,1-3 是权值最小的边,权值为 0.29,但 1、3

同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 1-5,权值为 0.32,

1、5 同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 2-7,权值为 0.34,

2、7 同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 4-5,权值为 0.35,

4、5 分属不同阵营,一定属于最小生成树

  

  

  

  

7)将 4加入到红色阵营中

  

  

  

这样一来,就形成了一个新的切分,相应的就有新的

横切边

  

将新的横切边放入到最小堆中,并拿出最小堆中权值

最小的边

  

此时,1-2 是权值最小的边,权值为 0.36,但 1、2

同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 4-7,权值为 0.37,

4、7 同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 0-4,权值为 0.38,

0、4 同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 2-6,权值为 0.40,

2、6 分属不同阵营,一定属于最小生成树

  

  

  

  

  

8)将 6加入到红色阵营中

  

  

  

至此,所有蓝色阵营的顶点都已经转换到红色阵营中,

此时,Lazy Prim 算法其实就已经可以结束了

  

但如果是以最小堆中的边为空作为结束依据的话,依

然可以从最小堆中继续拿出权值最小的边,不过,这

之后拿出的边肯定不再是横切边了

  

此时,3-6 是权值最小的边,权值为 0.52,但 3、6

同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 0-6,权值为 0.58,

0、6 同属红色阵营,不是横切边,直接扔掉

  

下一个最小堆中的权值最小的边是 4-6,权值为 0.93,

4、6 同属红色阵营,不是横切边,直接扔掉

  

  

  

  

9)最后

  

  

  

至此,Lazy Prim算法就真正结束了,得到了这张图

的最小生成树

  

  

  

  

  

程序 1

  

Edge.h:

  

#ifndef EDGE_H

#define EDGE_H

  

#include <iostream>

#include <cassert>

using namespace std;

  

  

//边信息:两个顶点和权值

template<typename Weight>

class Edge

{

  

private:

  

int a, b;//边的两个顶点ab(如果是有向图,就默认从顶点a指向顶点b

Weight weight;//边上的权值

  

public:

  

Edge(int a,int b, Weight weight)

{

this->a = a;

this->b = b;

this->weight = weight;

}

  

  

//默认构造函数

Edge(){}

  

  

~Edge(){}

  

  

int v(){return a; }

  

  

int w(){return b; }

  

  

Weight wt() {return weight; }

  

  

//知道边的一个顶点x,返回另一个顶点

int other(int x)

{

assert(x == a || x == b);

return x == a ? b : a;

}

  

  

//友元函数重载

friend ostream &operator<<(ostream &os,const Edge &e)

{

os << e.a <<"-" << e.b << ": " << e.weight;

return os;

}

  

  

booloperator<(Edge<Weight> &e)

{

return weight < e.wt();

}

  

  

booloperator<=(Edge<Weight> &e)

{

return weight <= e.wt();

}

  

  

booloperator>(Edge<Weight> &e)

{

return weight > e.wt();

}

  

  

booloperator>=(Edge<Weight> &e)

{

return weight >= e.wt();

}

  

  

booloperator==(Edge<Weight> &e)

{

return weight == e.wt();

}

};

  

  

#endif

  

  

  

SparseGraph.h:

  

#ifndef SPARSEGRAPH_H

#define SPARSEGRAPH_H

  

#include"Edge.h"

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//稀疏图 -邻接表

template<typename Weight>

class SparseGraph

{

  

private:

  

int n, m;//n m分别表示顶点数和边数

bool directed;//directed表示是有向图还是无向图

vector<vector<Edge<Weight> *>> g;//g[i]里存储的就是和顶点i相邻的所有边指针

  

public:

  

SparseGraph(int n,bool directed)

{

this->n = n;

this->m =0;

this->directed = directed;

//g[i]初始化为空的vector

for (int i =0; i < n; i++)

{

g.push_back(vector<Edge<Weight> *>());

}

}

  

  

~SparseGraph()

{

  

for (int i =0; i < n; i++)

{

for (int j =0; j < g[i].size(); j++)

{

delete g[i][j];

}

}

}

  

  

int V(){return n; }

int E(){return m; }

  

  

void addEdge(int v,int w, Weight weight)

{

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

g[v].push_back(new Edge<Weight>(v, w, weight));

//1)顶点v不等于顶点w,即不是自环边

//2)且不是有向图,即是无向图

if (v != w && !directed)

{

g[w].push_back(new Edge<Weight>(w, v, weight));

}

  

m++;

}

  

  

//hasEdge()判断顶点v和顶点w之间是否有边

//hasEdge()的时间复杂度:O(n)

bool hasEdge(int v,int w)

{

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

for (int i =0; i < g[v].size(); i++)

{

if (g[v][i]->other(v) == w)

{

return true;

}

}

  

return false;

}

  

  

void show()

{

  

for (int i =0; i < n; i++)

{

cout <<"vertex " << i << ":\t";

for (int j =0; j < g[i].size(); j++)

{

cout <<"{to:" << g[i][j]->w() << ",wt:" << g[i][j]->wt() << "}\t";

}

cout << endl;

}

}

  

  

  

//邻边迭代器(相邻,即 adjacent

//

//使用迭代器可以隐藏迭代的过程,按照一定的

//顺序访问一个容器中的所有元素

class adjIterator

{

private:

  

SparseGraph &G;//图的引用,即要迭代的图

int v;//顶点v

int index;//相邻顶点的索引

  

public:

  

adjIterator(SparseGraph &graph,int v) : G(graph)

{

this->v = v;

this->index =0;

}

  

  

//要迭代的第一个元素

Edge<Weight> *begin()

{

//因为有可能多次调用begin()

//所以显式的将index设置为0

index =0;

//如果g[v]size()不为0

if (G.g[v].size())

{

return G.g[v][index];

}

  

return NULL;

}

  

  

//要迭代的下一个元素

Edge<Weight> *next()

{

index++;

if (index < G.g[v].size())

{

return G.g[v][index];

}

  

return NULL;

}

  

  

//判断迭代是否终止

bool end()

{

return index >= G.g[v].size();

}

};

};

  

  

#endif

  

  

  

DenseGraph.h:

  

#ifndef DENSEGRAPH_H

#define DENSEGRAPH_H

  

#include"Edge.h"

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//稠密图 -邻接矩阵

template<typename Weight>

class DenseGraph

{

  

private:

  

int n, m;//n m分别表示顶点数和边数

bool directed;//directed表示是有向图还是无向图

vector<vector<Edge<Weight> *>> g;//二维矩阵,存储边指针

  

public:

  

DenseGraph(int n,bool directed)

{

this->n = n;

this->m =0;

this->directed = directed;

//二维矩阵:nn列,全部初始化为NULL

for (int i =0; i < n; i++)

{

g.push_back(vector<Edge<Weight> *>(n, NULL));

}

}

  

  

~DenseGraph()

{

for (int i =0; i < n; i++)

{

for (int j =0; j < n; j++)

{

if (g[i][j] != NULL)

{

delete g[i][j];

}

}

}

}

  

  

int V(){return n; }

int E(){return m; }

  

  

//在顶点v和顶点w之间建立一条边

void addEdge(int v,int w, Weight weight)

{

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

//如果顶点v和顶点w之间已经存在一条边,就删掉,

//之后按照传入权值重建一条边,即直接覆盖

if (hasEdge(v, w))

{

delete g[v][w];

  

//如果是无向图,还要删除和主对角线对称的值

if (!directed)

{

delete g[w][v];

}

  

m--;

}

  

g[v][w] =new Edge<Weight>(v, w, weight);

  

//如果是无向图,还要在和主对角线对称处添加值

if (!directed)

{

g[w][v] =new Edge<Weight>(w, v, weight);

}

  

m++;

}

  

  

//hasEdge()判断顶点v和顶点w之间是否有边

//hasEdge()的时间复杂度:O(1)

bool hasEdge(int v,int w)

{

assert(v >=0 && v < n);

assert(w >=0 && w < n);

return g[v][w] != NULL;

}

  

  

void show()

{

  

for (int i =0; i < n; i++)

{

for (int j =0; j < n; j++)

{

if (g[i][j])

{

cout << g[i][j]->wt() <<"\t";

}

else

{

cout <<"NULL\t";

}

}

cout << endl;

}

}

  

  

//邻边迭代器(相邻,即 adjacent

class adjIterator

{

private:

  

DenseGraph &G;//图引用,即要迭代的图

int v;//顶点v

int index;//相邻顶点的索引

  

public:

  

adjIterator(DenseGraph &graph,int v) : G(graph)

{

this->v = v;

this->index = -1;

}

  

  

//要迭代的第一个元素

Edge<Weight> *begin()

{

//找第一个权值不为NULL的元素,即为要迭代的第一个元素

index = -1;

return next();

}

  

  

//要迭代的下一个元素

Edge<Weight> *next()

{

for (index +=1; index < G.V(); index++)

{

if (G.g[v][index])

{

return index;

}

}

  

return NULL;

}

  

  

//判断迭代是否终止

bool end()

{

return index >= G.V();

}

};

};

  

  

#endif

  

  

  

ReadGraph.h:

  

#ifndef READGRAPH_H

#define READGRAPH_H

  

#include <iostream>

#include <string>

#include <fstream>

#include <sstream>

#include <cassert>

using namespace std;

  

  

  

//从文件中读取图的测试用例

template <typename Graph, typename Weight>

class ReadGraph

{

  

public:

ReadGraph(Graph &graph,const string &filename)

{

  

ifstream file(filename);

string line;//一行一行的读取

int V, E;

  

assert(file.is_open());

  

//读取file中的第一行到line

assert(getline(file, line));

//将字符串line放在stringstream

stringstream ss(line);

//通过stringstream解析出整型变量:顶点数和边数

ss >> V >> E;

  

//确保文件里的顶点数和图的构造函数中传入的顶点数一致

assert(V == graph.V());

  

//读取file中的其它行

for (int i =0; i < E; i++)

{

  

assert(getline(file, line));

stringstream ss(line);

  

int a, b;

Weight w;

ss >> a >> b >> w;

assert(a >=0 && a < V);

assert(b >=0 && b < V);

graph.addEdge(a, b, w);

}

}

};

  

  

#endif

  

  

  

MinHeap.h:

  

#ifndef MINHEAP_H

#define MINHEAP_H

  

#include <iostream>

#include <algorithm>

#include <string>

#include <cmath>

#include <cassert>

using namespace std;

  

  

  

//最小堆:索引从0开始

template<typename Item>

class MinHeap

{

  

private:

Item *data;

int count;

int capacity;

  

  

//私有函数,用户不能调用

void shiftUp(int k)

{

//如果新添加的元素小于父节点的元素,则进行交换

while (k >0 && data[(k - 1) / 2] > data[k])

{

swap(data[(k -1) / 2], data[k]);

k = (k -1) / 2;

}

}

  

  

//也是私有函数,用户不能调用

void shiftDown(int k)

{

//只要当前节点有孩子就进行循环

while (2 * k +1 < count)

{

//在此轮循环中,data[k]data[j]交换位置

int j =2 * k + 1;

  

// data[j]data[2*k]data[2*k+1]中的最小值

if (j +1 < count && data[j + 1] < data[j])

{

j++;

}

  

if (data[k] <= data[j])

{

break;

}

  

swap(data[k], data[j]);

k = j;

}

}

  

  

public:

  

MinHeap(int capacity)

{

data =new Item[capacity];

//计数器,即序列号,这里索引等于序列号减一

count =0;

this->capacity = capacity;

}

  

  

~MinHeap()

{

delete []data;

}

  

  

int size()

{

return count;

}

  

  

bool isEmpty()

{

return count ==0;

}

  

  

//向最小堆中添加新元素,新元素放在数组末尾

void insert(Item item)

{

//防止越界

assert(count <= capacity);

  

//索引从0开始

data[count] = item;

count++;

  

//新加入的元素有可能破坏最小堆的定义,需要通过

//Shift Up操作,把索引为count-1的元素尝试着向上

//移动来保持最小堆的定义

shiftUp(count -1);

}

  

  

//取出最小堆中根节点的元素(最小值)

Item extractMin()

{

//首先要保证堆不为空

assert(count >0);

  

//取出根节点的元素(最小值)

Item ret = data[0];

  

//将第一个元素(最小值)和最后一个元素进行交换

swap(data[0], data[count -1]);

  

//count--后,被取出的根节点就不用再考虑了

count--;

  

//调用Shift Down操作,想办法将此时的根节点(索引为0

//向下移动,来保持最小堆的定义

shiftDown(0);

  

return ret;

}

  

  

public:

  

//在控制台打印测试用例

void testPrint()

{

  

//限制:只能打印100个元素以内的堆,因为控制台一行的字符数量有限

if (size() >=100)

{

cout <<"Fancy print can only work for less than 100 int";

return;

}

  

//限制:只能打印类型是int的堆

if (typeid(Item) !=typeid(int))

{

cout <<"Fancy print can only work for int item";

return;

}

  

cout <<"The Heap size is: " << size() << endl;

cout <<"data in heap: ";

for (int i =0; i < size(); i++)

{

cout << data[i] <<" ";

}

cout << endl;

cout << endl;

  

int n = size();

int max_level =0;

int number_per_level =1;

while (n >0)

{

max_level +=1;

n -= number_per_level;

number_per_level *=2;

}

  

int max_level_number =int(pow(2, max_level -1));

int cur_tree_max_level_number = max_level_number;

int index =0;

for (int level =0; level < max_level; level++)

{

string line1 = string(max_level_number *3 - 1,' ');

  

int cur_level_number = min(count -int(pow(2, level)) +1,

int(pow(2, level)));

  

bool isLeft =true;

  

for (int index_cur_level =0; index_cur_level < cur_level_number;

index++, index_cur_level++)

{

putNumberInLine(data[index], line1, index_cur_level,

cur_tree_max_level_number *3 - 1, isLeft);

  

isLeft = !isLeft;

}

cout << line1 << endl;

  

if (level == max_level -1)

{

break;

}

  

  

string line2 = string(max_level_number *3 - 1,' ');

for (int index_cur_level =0; index_cur_level < cur_level_number;

index_cur_level++)

{

putBranchInLine(line2, index_cur_level, cur_tree_max_level_number *3 - 1);

}

  

cout << line2 << endl;

  

cur_tree_max_level_number /=2;

}

}

  

  

  

private:

  

void putNumberInLine(int num, string &line,int index_cur_level,

int cur_tree_width,bool isLeft)

{

  

int sub_tree_width = (cur_tree_width -1) / 2;

  

int offset = index_cur_level * (cur_tree_width +1) + sub_tree_width;

  

assert(offset +1 < line.size());

  

if (num >=10)

{

line[offset +0] = '0' + num /10;

line[offset +1] = '0' + num %10;

}

else

{

if (isLeft)

line[offset +0] = '0' + num;

else

line[offset +1] = '0' + num;

}

}

  

  

void putBranchInLine(string &line,int index_cur_level, int cur_tree_width)

{

  

int sub_tree_width = (cur_tree_width -1) / 2;

  

int sub_sub_tree_width = (sub_tree_width -1) / 2;

  

int offset_left = index_cur_level * (cur_tree_width +1) + sub_sub_tree_width;

  

assert(offset_left +1 < line.size());

  

int offset_right = index_cur_level * (cur_tree_width +1) + sub_tree_width

+1 + sub_sub_tree_width;

  

assert(offset_right < line.size());

  

line[offset_left +1] = '/';

line[offset_right +0] = '\\';

}

};

  

  

  

#endif

  

  

  

LazyPrimMST.h:

  

#ifndef LAZYPRIMMST_H

#define LAZYPRIMMST_H

  

#include"Edge.h"

#include"MinHeap.h"

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//Lazy Prim算法实现最小生成树

template<typename Graph, typename Weight>

class LazyPrimMST

{

  

private:

  

Graph &G;//图的引用,即要切分的图

MinHeap<Edge<Weight>> pq;//pq 充当一个优先队列,pq priority queue

bool *marked;//切分后的顶点分到另一阵营时,需要进行标记

vector<Edge<Weight>> mst;//属于最小生成树的 V-1条边存储到向量 mst

Weight mstWeight;//最后最小生成树的总权值 mstWeight

  

void visit(int v)

{

//保证顶点 v 属于蓝色阵营,即 false

assert(!marked[v]);

//访问过顶点 v 后转为红色阵营,即 true

marked[v] =true;

  

//注意:声明迭代器时,前面还要加 typename,表明 adjIterator

// Graph 中的类型,而不是成员变量

typename Graph::adjIterator adj(G, v);

//遍历顶点 v 所有的邻边

for (Edge<Weight> *e = adj.begin(); !adj.end(); e = adj.next())

{

//如果顶点 v 的邻边 e对应的另一端的顶点没有被访问过,

//即分属不同阵营,即为横切边,放入优先队列中

if (!marked[e->other(v)])

{

pq.insert(*e);

}

}

}

  

  

public:

  

LazyPrimMST(Graph &graph) :G(graph), pq(MinHeap<Edge<Weight>>(graph.E()))

{

  

marked =newbool[G.V()];

for (int i =0; i < G.V(); i++)

{

marked[i] =false;

}

 

//保证向量 mst 在初始化时为空

mst.clear();

  

// Lazy Prim

visit(0);

//如果优先队列不为空

while (!pq.isEmpty())

{

Edge<Weight> e = pq.extractMin();

//如果取出来权值最小的边的两个端点同属

//红色阵营,就直接把这条边扔掉

if (marked[e.v()] == marked[e.w()])

{

continue;

}

  

//否则,把 e 加入到向量 mst

mst.push_back(e);

//继续访问 e 的蓝色一端的顶点

if (!marked[e.v()])

{

visit(e.v());

}

else

{

visit(e.w());

}

}

  

mstWeight = mst[0].wt();

for (int i =1; i < mst.size(); i++)

{

mstWeight += mst[i].wt();

}

}

  

  

~LazyPrimMST()

{

delete []marked;

}

  

  

vector<Edge<Weight>> mstEdges()

{

return mst;

};

  

  

Weight result()

{

return mstWeight;

};

};

  

  

#endif

  

  

  

main.cpp:

  

#include"SparseGraph.h"

#include"DenseGraph.h"

#include"ReadGraph.h"

#include"LazyPrimMST.h"

#include <iostream>

#include <iomanip>

using namespace std;

  

  

  

int main()

{

  

string filename ="testG1.txt";

int V =8;

  

//稀疏图

SparseGraph<double> g = SparseGraph<double>(V,false);

ReadGraph<SparseGraph<double>,double> readGraph(g, filename);

  

// Test Lazy Prim MST

cout <<"Test Lazy Prim MST:" << endl;

LazyPrimMST<SparseGraph<double>,double> lazyPrimMST(g);

vector<Edge<double>> mst = lazyPrimMST.mstEdges();

for (int i =0; i < mst.size(); i++)

{

cout << mst[i] << endl;

}

cout <<"The MST weight is: " << lazyPrimMST.result() << endl;

  

cout << endl;

  

system("pause");

return0;

}

  

  

//Lazy Prim的时间复杂度:

//

//主要循环都是在 Priority Queue 不为空的情况下,所有的边都会进入一次

//Priority Queue,所以共循环了 E

//

//每次循环有两个主要的操作:

//1extractMin(),时间复杂度是 O(logE)

//2visit(),其中:(a)遍历的部分:如果是邻接表,就是 O(E),如果是

//邻接矩阵,就是 O(V^2),但在邻接矩阵中,通常表达的是稠密图,对于稠密

//图来说,V^2近乎和 E是一个级别的;(b)insert()部分也是 logE 级别的

//

//

//综上,Lazy Prim 的时间复杂度是 O(E*logE)

  

  

运行一览:

  

  

  

  

testG1.txt 的内容如下:

  

  

  

该文件可以分成两个部分:

  

1)第一行:两个数字分别代表顶点数和边数

  

2)其它行:每一行的前两个数字表示一条边,第三个数字表示权值

  

  

  

  

  

  

  

Prim 算法

  

  

Prim 算法是Lazy Prim 算法的优化,Lazy Prim 的主要问题:

  

1)图中所有的边都要进入最小堆,虽然随着切分的改变,红色

阵营中的顶点越来越多,但很多已经在最小堆中的边,其实已经不

再是横切边了

  

2)虽然横切边有很多,但通常只关注权值最小的横切边,尤其

是和每个顶点相连的横切边中权值最小的那条边

  

  

  

基于此,Prim 算法的实现如下:

  

最小索引堆作为辅助数据结构,用来存储和每个顶点相连的横切

边中权值最小的那条边

  

随着切分改变,只要不断更新和每个顶点相连的横切边中权值最小

的那条边即可

  

  

  

  

  

程序 2:(在程序 1的基础上,用MinIndexHeap.h、PrimMST.h

分别替换MinHeap.h、LazyPrimMST.h,修改 main.cpp 即可)

  

MinIndexHeap.h:

  

#ifndef MININDEXHEAP_H

#define MININDEXHEAP_H

  

#include <iostream>

#include <string>

#include <cassert>

#include <algorithm>

using namespace std;

  

  

  

//最小索引堆:索引从0开始

template<typename Item>

class MinIndexHeap

{

  

private:

Item *data;//指向存储元素的数组

int *indexes;//指向存储索引的数组

int *reverse;//指向存储反向索引的数组

int count;

int capacity;

  

  

//私有函数,用户不能调用

void shiftUp(int k)

{

//如果新添加的元素小于父节点的元素,则进行交换

while (k >0 && data[indexes[(k - 1) / 2]] > data[indexes[k]])

{

swap(indexes[(k -1) / 2], indexes[k]);

reverse[indexes[(k -1) / 2]] = (k -1) / 2;

reverse[indexes[k]] = k;

k = (k -1) / 2;

}

}

  

  

//也是私有函数,用户不能调用

void shiftDown(int k)

{

//只要当前节点有孩子就进行循环

while (2 * k +1 < count)

{

//在此轮循环中,data[indexes[k]]data[indexes[j]]交换位置

int j =2 * k + 1;

  

// data[indexes[j]]data[indexes[j]]data[indexes[j+1]]中的最小值

if (j +1 < count && data[indexes[j + 1]] < data[indexes[j]])

{

j +=1;

}

  

if (data[indexes[k]] <= data[indexes[j]])

{

break;

}

  

swap(indexes[k], indexes[j]);

reverse[indexes[k]] = k;

reverse[indexes[j]] = j;

k = j;

}

}

  

  

public:

  

MinIndexHeap(int capacity)

{

data =new Item[capacity];

indexes =newint[capacity];

reverse =newint[capacity];

//初始化reverse数组

for (int i =0; i < capacity; i++)

{

reverse[i] = -1;

}

//计数器,这里索引等于计数器减一

count =0;

this->capacity = capacity;

  

}

  

  

~MinIndexHeap()

{

delete []data;

delete []indexes;

delete []reverse;

}

  

  

int size()

{

return count;

}

  

  

bool isEmpty()

{

return count ==0;

}

  

  

void insert(int i, Item item)

{

//防止越界

assert(count <= capacity);

assert(i >=0 && i <= capacity);

  

data[i] = item;

indexes[count] = i;

reverse[i] = count;

count++;

  

shiftUp(count -1);

}

  

  

//取出最小的data

Item extractMin()

{

//首先要保证堆不为空

assert(count >0);

  

Item ret = data[indexes[0]];

swap(indexes[0], indexes[count -1]);

reverse[indexes[count -1]] = -1;

reverse[indexes[0]] =0;

count--;

shiftDown(0);

return ret;

}

  

  

//取出最小的data对应的index

int extractMinIndex()

{

assert(count >0);

  

//对于外部来说,索引从0开始,所以要减一

int ret = indexes[0];

swap(indexes[0], indexes[count -1]);

reverse[indexes[count -1]] = -1;

reverse[indexes[0]] =0;

count--;

shiftDown(0);

return ret;

}

  

  

Item getMin()

{

assert(count >0);

return data[indexes[0]];

}

  

  

int getMinIndex()

{

assert(count >0);

return indexes[0];

}

  

  

bool contain(int i){

assert(i >=0 && i <= capacity);

//reverse数组在构造函数中都初始化为-1

//所以拿-1做比较

return reverse[i] != -1;

}

  

  

Item getItem(int i)

{

assert(contain(i));

//对于外部来说,索引从0开始,

//对于内部来说,索引从1开始,

//所以要加一

return data[i];

}

  

  

//修改 index 对应的 data

void change(int i, Item newItem)

{

//防止越界和检查i是否在堆中,

//因为有可能已经取出去了

assert(contain(i));

  

data[i] = newItem;

  

//找到indexes[j] = i, j表示data[i]在堆中的位置

//之后尝试着shiftUp(j)一下,shiftDown(j)一下

//看看能不能向上或向下移动以保持堆的性质

int j = reverse[i];

shiftUp(j);

shiftDown(j);

  

//先用O(1)的时间找到位置,再用O(lgn)的时间完成

//Shift UpShift Down,此时,该函数的时间复杂

//度就是O(lgn)级别的,如果有n个堆操作,总时间

//就是O(n*lgn)

//

//加入了反向查找后,性能得到了巨大的提升

}

  

  

public:

  

//在控制台打印测试用例

void testPrint()

{

  

//限制:只能打印100个元素以内的堆,因为控制台一行的字符数量有限

if (size() >=100)

{

cout <<"Fancy print can only work for less than 100 int";

return;

}

  

//限制:只能打印类型是int的堆

if (typeid(Item) !=typeid(int))

{

cout <<"Fancy print can only work for int item";

return;

}

  

cout <<"The Heap size is: " << size() << endl;

cout <<"data in heap: ";

for (int i =0; i < size(); i++)

{

cout << data[i] <<" ";

}

cout << endl;

cout << endl;

  

int n = size();

int max_level =0;

int number_per_level =1;

while (n >0)

{

max_level +=1;

n -= number_per_level;

number_per_level *=2;

}

  

int max_level_number =int(pow(2, max_level -1));

int cur_tree_max_level_number = max_level_number;

int index =0;

for (int level =0; level < max_level; level++)

{

string line1 = string(max_level_number *3 - 1,' ');

  

int cur_level_number = min(count -int(pow(2, level)) +1,

int(pow(2, level)));

  

bool isLeft =true;

  

for (int index_cur_level =0; index_cur_level < cur_level_number;

index++, index_cur_level++)

{

putNumberInLine(indexes[index], line1, index_cur_level,

cur_tree_max_level_number *3 - 1, isLeft);

  

isLeft = !isLeft;

}

cout << line1 << endl;

  

  

if (level == max_level -1)

{

break;

}

  

  

string line2 = string(max_level_number *3 - 1,' ');

for (int index_cur_level =0; index_cur_level < cur_level_number;

index_cur_level++)

{

putBranchInLine(line2, index_cur_level, cur_tree_max_level_number *3 - 1);

}

  

cout << line2 << endl;

  

cur_tree_max_level_number /=2;

}

}

  

  

  

private:

  

void putNumberInLine(int num, string &line,int index_cur_level,

int cur_tree_width,bool isLeft)

{

  

int sub_tree_width = (cur_tree_width -1) / 2;

  

int offset = index_cur_level * (cur_tree_width +1) + sub_tree_width;

  

assert(offset +1 < line.size());

  

if (num >=10)

{

line[offset +0] = '0' + num /10;

line[offset +1] = '0' + num %10;

}

else

{

if (isLeft)

line[offset +0] = '0' + num;

else

line[offset +1] = '0' + num;

}

}

  

  

void putBranchInLine(string &line,int index_cur_level, int cur_tree_width)

{

  

int sub_tree_width = (cur_tree_width -1) / 2;

  

int sub_sub_tree_width = (sub_tree_width -1) / 2;

  

int offset_left = index_cur_level * (cur_tree_width +1) + sub_sub_tree_width;

  

assert(offset_left +1 < line.size());

  

int offset_right = index_cur_level * (cur_tree_width +1) + sub_tree_width

+1 + sub_sub_tree_width;

  

assert(offset_right < line.size());

  

line[offset_left +1] = '/';

line[offset_right +0] = '\\';

}

};

  

  

#endif

  

  

  

PrimMST.h:

  

#ifndef PRIMMST_H

#define PRIMMST_H

  

#include"Edge.h"

#include"MinIndexHeap.h"

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//Prim算法实现最小生成树

template<typename Graph, typename Weight>

class PrimMST

{

  

private:

  

Graph &G;//图的引用,即要切分的图

MinIndexHeap<Weight> ipq;//ipq 充当一个索引优先队列,ipq index priority queue

bool* marked;//切分后的顶点分到另一阵营时,需要进行标记

vector<Edge<Weight>> mst;//属于最小生成树的 V-1条边存储到向量 mst

Weight mstWeight;//最后最小生成树的总权值 mstWeight

vector<Edge<Weight>*> edgeTo;//向量 edgeTo用于存储和每个顶点相连的权值最小的横切边指针

  

void visit(int v)

{

//保证顶点 v 属于蓝色阵营,即 false

assert(!marked[v]);

//访问过顶点 v 后转为红色阵营,即 true

marked[v] =true;

  

//注意:声明迭代器时,前面还要加 typename,表明 adjIterator

// Graph 中的类型,而不是成员变量

typename Graph::adjIterator adj(G, v);

//遍历顶点 v 所有的邻边

for (Edge<Weight> *e = adj.begin(); !adj.end(); e = adj.next())

{

int w = e->other(v);

//如果顶点 v 的邻边 e对应的另一端的顶点 w没有被访问过,

//即分属不同阵营,即 e 为横切边

if (!marked[w])

{

//如果和顶点 w 相连的横切边为空,即之前没有找到过和

//顶点 w 相连的横切边,则对edge[w]进行赋值并插入到

//索引优先队列中

if (!edgeTo[w])

{

edgeTo[w] = e;

ipq.insert(w, e->wt());

}

//如果和顶点 w 相连的横切边不为空,即之前找到过和

//顶点 w 相连的横切边。此时就要判断新的横切边的权

//值和之前找到的横切边的权值的大小,如果小于,就进

//行一次更新

else if (e->wt() < edgeTo[w]->wt())

{

edgeTo[w] = e;

ipq.change(w, e->wt());

}

}

}

  

}

  

  

public:

  

// assume graph is connected

PrimMST(Graph &graph) :G(graph), ipq(MinIndexHeap<double>(graph.V()))

{

  

assert(graph.E() >=1);

  

marked =newbool[G.V()];

for (int i =0; i < G.V(); i++)

{

marked[i] =false;

edgeTo.push_back(NULL);

}

  

//保证向量 mst 在初始化时为空

mst.clear();

  

//Prim

visit(0);

//如果索引优先队列不为空

while (!ipq.isEmpty())

{

int v = ipq.extractMinIndex();

  

//确认该横切边确实是存在的

assert(edgeTo[v]);

  

mst.push_back(*edgeTo[v]);

  

visit(v);

}

  

mstWeight = mst[0].wt();

for (int i =1; i < mst.size(); i++)

{

mstWeight += mst[i].wt();

}

 

}

  

  

~PrimMST()

{

delete []marked;

}

  

  

vector<Edge<Weight>> mstEdges()

{

return mst;

};

  

  

Weight result()

{

return mstWeight;

};

};

  

  

#endif

  

  

  

main.cpp:

  

#include"SparseGraph.h"

#include"DenseGraph.h"

#include"ReadGraph.h"

#include"PrimMST.h"

#include <iostream>

#include <iomanip>

using namespace std;

  

  

  

int main()

{

  

string filename ="testG1.txt";

int V =8;

  

//稀疏图

SparseGraph<double> g = SparseGraph<double>(V,false);

ReadGraph<SparseGraph<double>,double> readGraph(g, filename);

  

// Test Prim MST

cout <<"Test Prim MST:" << endl;

PrimMST<SparseGraph<double>,double> primMST(g);

vector<Edge<double>> mst = primMST.mstEdges();

for (int i =0; i < mst.size(); i++)

{

cout << mst[i] << endl;

}

cout <<"The MST weight is: " << primMST.result() << endl;

  

cout << endl;

  

system("pause");

return0;

}

  

  

//整个过程,其实对图中所有的边都考虑了一遍,不过因为最小索引堆

//的元素个数和图中的顶点数一致,所以,基于堆的操作,变快了一些

//

//与此同时,每次访问到一个顶点时,考察这个顶点的邻边,对于那些

//不是横切边的边,一旦判断出来,也会马上扔掉,所以其实对于Prim

//算法来说,虽然它的时间复杂度是 O(E*logV),好像只是将 logE

//进到了 logV,但其实除了对于堆的改进之外,遍历边的次数其实也更

//小了

//

//因此,整体而言,使用这个最小索引堆以后,整个 Prim 算法的时间

//复杂度的改进还是非常可观的

//

//PSLazy Prim 的时间复杂度:O(E*logE)

  

  

运行一览:

  

  

  

  

其中,testG1.txt 的内容同程序 1

  

  

  

  

  

  

  

  

  

  

【made by siwuxie095】