zoukankan      html  css  js  c++  java
  • Kruskal算法

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

       

       

       

       

       

       

       

       

       

    Kruskal 算法

       

       

    Prim 算法中,不停地改变切分,同时通过切分寻找

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

       

    在这个过程中,可能有人就会有这样一个想法:如果每

    次都找当前权值最小的那条边(不是横切边中),那么

    它就一定属于最小生成树

       

       

       

    看如下实例:

       

       

       

       

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

       

       

       

       

       

    1-7 是权值最小的边,权值为 0.16,就可以说 1-7 一定

    属于最小生成树

       

    这是因为:总能找到一个切分,使得对于这个切分而言,

    1-7 就是横切边中的权值最小的那条边

       

    采用这样的思路不断去找当前权值最小的边,只要这些

    权值最小的边不构成环,那么这些依次取得的边就一定

    属于最小生成树,这就是 Kruskal 算法的思想

       

       

       

    具体做法:

       

    首先将图中所有的边进行一次排序,时间复杂度:O(E*lgE)

       

    然后每次都取出还未考虑的边中的权值最小的那条边,把它

    加入到最小生成树中,看看是否会形成,如果不会形成环,

    那么它就一定属于最小生成树

       

       

       

    整个过程中比较复杂的,唯一需要处理的就是:怎么判断把

    一个边加入到最小生成树中是否会形成环

       

    其实,这个判断的方式也非常简单,只需要将并查集作为

    助数据结构,就可以很容易地判断出来

       

    在将一条边加入到最小生成树的同时,只要对这条边的两

    个端点要进行一次 Union 操作,后续再加入某一条边时就可

    使用并查集快速判断环

       

       

       

       

       

    程序:

       

    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;

    }

       

       

    bool operator<(Edge<Weight> &e)

    {

    return weight < e.wt();

    }

       

       

    bool operator<=(Edge<Weight> &e)

    {

    return weight <= e.wt();

    }

       

       

    bool operator>(Edge<Weight> &e)

    {

    return weight > e.wt();

    }

       

       

    bool operator>=(Edge<Weight> &e)

    {

    return weight >= e.wt();

    }

       

       

    bool operator==(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 << ": ";

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

    {

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

    }

    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() << " ";

    }

    else

    {

    cout << "NULL ";

    }

    }

    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

       

       

       

    UnionFind.h:

       

    #ifndef UNIONFIND_H

    #define UNIONFIND_H

       

    #include <iostream>

    #include <cassert>

    using namespace std;

       

       

       

    //并查集:Quick Union + rank + path compression

    class UnionFind

    {

       

    private:

    int* parent;

    int* rank; // rank[i]表示以i为根的集合所表示的树的层数

    int count;

       

    public:

    UnionFind(int count)

    {

    this->count = count;

    parent = new int[count];

    rank = new int[count];

    //在初始情况下,并查集里的元素,两两之间互不连接

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

    {

    parent[i] = i;

    rank[i] = 1;

    }

    }

       

       

    ~UnionFind()

    {

    delete []parent;

    delete []rank;

    }

       

       

    int size()

    {

    return count;

    }

       

       

    int find(int p)

    {

       

    assert(p >= 0 && p < count);

       

    // path compression 1

    while (p != parent[p])

    {

    //路径压缩

    parent[p] = parent[parent[p]];

    p = parent[p];

    }

       

    return p;

    }

       

       

    bool isConnected(int p, int q)

    {

    return find(p) == find(q);

    }

       

       

    void unionElements(int p, int q)

    {

       

    int pRoot = find(p);

    int qRoot = find(q);

       

    if (pRoot == qRoot)

    {

    return;

    }

       

    //rank小的那棵树的根节点指向rank大的那棵树的根节点

    if (rank[pRoot] < rank[qRoot])

    {

    parent[pRoot] = qRoot;

    }

    else if (rank[qRoot] < rank[pRoot])

    {

    parent[qRoot] = pRoot;

    }

    // rank[pRoot] == rank[qRoot]

    else

    {

    //可互换

    parent[pRoot] = qRoot;

    rank[qRoot] ++;

    }

       

    }

       

       

    void show()

    {

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

    {

    cout << i << " : " << parent[i] << endl;

    }

    }

    };

       

     

       

       

    //路径压缩:在寻找根的时候,两步一跳,比原来的 Find 操作要快,

    //与此同时,如果下一次要寻找这棵树上某个元素的根节点,由于层

    //数变低,相应的速度也会快很多

       

    #endif

       

       

       

    KruskalMST.h:

       

    #ifndef KRUSKALMST_H

    #define KRUSKALMST_H

       

    #include "Edge.h"

    #include "MinHeap.h"

    #include "UnionFind.h"

    #include <iostream>

    #include <vector>

    using namespace std;

       

       

       

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

    template <typename Graph, typename Weight>

    class KruskalMST

    {

       

    private:

       

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

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

       

    public:

       

    KruskalMST(Graph &graph)

    {

    //使用堆排序(最小堆)

    MinHeap<Edge<Weight>> pq(graph.E());

    //遍历图中所有的边

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

    {

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

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

    typename Graph::adjIterator adj(graph, i);

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

    {

    //对边 e 两端的顶点索引进行比较,只将一端索引更小的边

    //放入最小堆中,避免重复

    if (e->v() < e->w())

    {

    pq.insert(*e);

    }

    }

    }

       

    UnionFind uf = UnionFind(graph.V());

    //只要最小堆不为空,

    while (!pq.isEmpty() && mst.size() < graph.V() - 1)

    {

       

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

    //如果边 e 两端的顶点索引有相同的根,即 相连,

    //那么就不考虑边 e,直接跳过

    if (uf.isConnected(e.v(), e.w()))

    {

    continue;

    }

       

    mst.push_back(e);

       

    uf.unionElements(e.v(), e.w());

    }

       

    mstWeight = mst[0].wt();

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

    {

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

    }

     

    }

       

       

    ~KruskalMST()

    {

       

    }

       

       

    vector<Edge<Weight>> mstEdges()

    {

    return mst;

    }

       

       

    Weight result()

    {

    return mstWeight;

    }

    };

       

       

    #endif

       

       

       

    main.cpp:

       

    #include "SparseGraph.h"

    #include "DenseGraph.h"

    #include "ReadGraph.h"

    #include "KruskalMST.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 Kruskal MST

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

    KruskalMST<SparseGraph<double>, double> kruskalMST(g);

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

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

    {

    cout << mst[i] << endl;

    }

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

       

    system("pause");

    return 0;

    }

       

       

    //Kruskal 算法的时间复杂度:O(E*logE+E*logV),比 Prim 算法的效率要低

       

       

    运行一览:

       

       

       

       

    其中,testG1.txt 的内容如下:

       

       

       

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

       

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

       

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

       

       

       

       

       

       

       

       

       

       

    【made by siwuxie095】

  • 相关阅读:
    [BZOJ1303][CQOI2009]中位数图
    [BZOJ1192][HNOI2006]鬼谷子的钱袋
    9.5题解
    9.3题解
    9.2题解
    9.1题解
    8.29题解
    8.28题解
    8.23<2>题解
    8.23<1>题解
  • 原文地址:https://www.cnblogs.com/siwuxie095/p/7124300.html
Copyright © 2011-2022 走看看