最小生成树——Prim/Kruskal

来源:互联网 发布:全屏画图软件 编辑:程序博客网 时间:2024/05/20 06:26

本篇先用邻接矩阵示例。

假定图中有如下数据成员以及成员函数:

//邻接矩阵表示法#ifndef GRAPH_H#define GRAPH_H#include "edge.h"//template <typename T>class Graph {public:    using T = char;    ...    void PrimMST(const int &index);     //从点开始。    void KruskalMST();                  //从边开始。private:    const int& getWeight(const int &indexA, const int &indexB);    struct Vertex {        T data;        bool isVisited;    };    int capacity;       //图中的顶点数    Vertex *pVertex;    //顶点数组    int *pMatrix;       //邻接矩阵    //边类,及边数组,为MST服务。将取出的边依次保存在pEdge中。    Edge *pEdge;};#endif

基本的建图、加点、加边、遍历,仅补充构造函数的实现,其余略过。

<span style="color:#800080;">Graph</span><span style="color:#000000;">::</span><span style="color:#000000;">Graph</span><span style="color:#000000;">(</span><span style="color:#808000;">const</span><span style="color:#c0c0c0;"> </span><span style="color:#808000;">int</span><span style="color:#c0c0c0;"> </span><span style="color:#000000;">&</span><span style="color:#000000;">capacity</span><span style="color:#000000;">):</span><pre style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style="color:#c0c0c0;">    </span><span style="color:#800000;">capacity</span><span style="color:#000000;">(</span><span style="color:#000000;">capacity</span><span style="color:#000000;">),</span>

    pVertex(new Vertex[capacity]()),
    pMatrix(new int[capacity * capacity]()),
    pEdge(new Edge[capacity - 1]()) {}  /*初始化列表,同时进行值初始化。*/

其中的Edge类,定义如下:

#ifndef EDGE_H#define EDGE_H#include <climits>struct Edge {    Edge(const int &indexA=0, const int &indexB=0, const int &weight=INT_MAX);    int indexA;    int indexB;    int weight;};bool operator<(const Edge &x, const Edge &y);#endif // EDGE_H

其实现文件重载了 <操作符,为生成最小堆服务。

#include "edge.h"Edge::Edge(const int &indexA, const int &indexB, const int &weight):    indexA(indexA),    indexB(indexB),    weight(weight) {}bool operator<(const Edge &x, const Edge &y) {    return x.weight > y.weight;}

Prim算法的主要原理:

1. 从一个给定的顶点出发,与之相邻的边为待选边;

2. 从待选边中选出权值最小的边;

3. 由最小边得到另一个顶点。若存在,形成回路,舍弃;否则,加入边集合,把另一个顶点加入点集合;

4. 重复1,直到 n-1 条边都被放入边集合(此时,n个点也都被放入了点集合)。


Kruskal算法的主要原理:

1. 将所有的边放入待选边集合;

2. 选出权值最小的边,放入已选边集合,同时把两个点放入对应的集合(点集可能有多个);

3. 重复2,若形成闭环,抛弃。直到所有的边(n-1)都纳入已选边集合中(同时,所有的点都在同一个点集中,非空点集只剩一个)。


实现文件如下:

void Graph::PrimMST(const int &index) {    vector<int> vertexVec;              //点集合    vertexVec.reserve(capacity);        //预设容量为capacity    priority_queue<Edge> edgeMinHeap;   //备选边集合。    vertexVec.push_back(index);         //加入第一个点,标记为已访问。    pVertex[index].isVisited = true;    int edgeCnt(0);                     //已选边数量,初始化为0.    while(edgeCnt < capacity - 1) {        int tmp(vertexVec.back());      //新加入的顶点索引。        //遍历该点所有未选的边,加入备选边集合。        for(int i(0); i < capacity; ++i) {            int weight(getWeight(tmp, i));            if(weight > 0 && !pVertex[i].isVisited)                edgeMinHeap.emplace(Edge(tmp, i, weight));                //edgeMinHeap.emplace(tmp, i, weight);        }        //选出最小边,若构成回路(边的另一个顶点已被访问过),舍弃。        int nextVertexIndex(edgeMinHeap.top().indexB);        if(!pVertex[nextVertexIndex].isVisited) {            //加入顶点和边,并把顶点标记为已访问。            vertexVec.push_back(nextVertexIndex);            pVertex[nextVertexIndex].isVisited = true;            pEdge[edgeCnt++] = edgeMinHeap.top();        }        edgeMinHeap.pop();    }}/* * 第一步:取出所有的边 * 第二步:从所有边中取出组成最小生成树的边*/void Graph::KruskalMST() {    priority_queue<Edge> edgeMinHeap;    for(int i(0); i < capacity; ++i) {      //取出所有的边,放在一个最小堆中。        //取上三角或者下三角的数值都可以,这里遍历上三角。        for(int k(i + 1); k < capacity; ++k) {            int weight(getWeight(i, k));            if(weight > 0)                edgeMinHeap.emplace(Edge(i, k, weight));        }    }    vector<unordered_set<int>> vertexSets;  //顶点集合的集合。    int edgeCnt(0);                         //已选边数,初始化为0    //1. 算法结束条件    while(edgeCnt < capacity - 1) {        //2. 取出最小边        Edge minEdge(edgeMinHeap.top());        edgeMinHeap.pop();        //3. 找出最小边连接的点。        int indexA(minEdge.indexA);        int indexB(minEdge.indexB);        //4. 找出顶点所在的集合。        int ALabel(-1);     //表示A点所在的集合的索引。        int BLabel(-1);        for(unsigned i(0); i < vertexSets.size(); ++i) {            if(ALabel == -1 && vertexSets[i].count(indexA) > 0)                ALabel = i;            if(BLabel == -1 && vertexSets[i].count(indexB) > 0)                BLabel = i;        }        //5. 根据点所在的集合的不同,做不同的处理        if(ALabel == -1 && BLabel == -1) {      //都不在已有的集合中,新建一个集合。            unordered_set<int> newSet;            newSet.emplace(indexA);            newSet.emplace(indexB);            vertexSets.push_back(newSet);        }        else if(ALabel == -1 && BLabel != -1)   //把A点加入B的集合中。            vertexSets[BLabel].emplace(indexA);        else if(ALabel != -1 && BLabel == -1)   //把B点加入A的集合中。            vertexSets[ALabel].emplace(indexA);        else if(ALabel != BLabel) {             //合并两个集合。            if(ALabel < BLabel) {               //合并到靠前的集合,删除靠后的集合,效率高些。                vertexSets[ALabel].insert(vertexSets[BLabel].begin(), vertexSets[BLabel].end());                vertexSets.erase(vertexSets.begin() + BLabel);            } else {                vertexSets[BLabel].insert(vertexSets[ALabel].begin(), vertexSets[ALabel].end());                vertexSets.erase(vertexSets.begin() + ALabel);            }        } else continue;                        //在同一个集合的情况,说明这条边构成了回路,舍弃。        pEdge[edgeCnt++] = minEdge;    }}

邻接矩阵适合用于表示稠密图,邻接表适合用于表示稀疏图。

Prim算法从点开始,适合用于求出稠密图的最小生成树;

Kruskal算法从边开始,适合于求出稀疏图的最小生成树。


图篇的内容要详谈,刨去代码不算,随便写点就能有几千字。

挺费功夫的,暂时是没这精力了。


0 0
原创粉丝点击