圖論(三)最小生成樹:Prim算法與Kruskal算法


圖論系列文章

圖論(一) 鄰接表與鄰接矩陣

圖論(二) 最短路徑:Dijkstra算法與Floyd算法

圖論(三)最小生成樹:Prim算法與Kruskal算法

圖論(四)拓撲排序與關鍵路徑


前言

在學習了前兩章的內容後,接下來學習最小生成樹算法。
最小生成樹的意思是將所有的頂點都連通,所有的邊加起來的權值最小的圖(成形爲樹)。
此類算法廣泛用於城市建設中,例如讓所有的城市全部連通,使其造價最小。

爲了防止最小生成樹中沒有環,我們需要使用到一種數據結構:並查集
並查集下標指向該頂點,存放的元素是其根,可以用來幫助我們合併兩個源點不同的集合, 並且也可以當作樹的路徑生成來使用。

  • 並查集
int findRoot(vector<int> &union_find, int i)
{
    if (union_find[i] != -1)
        return findRoot(union_find, union_find[i]);
    return i;
}
vector<int> union_find(_vertexNum, -1); //並查集

接下來我們將使用下圖作爲圖的創建,提前將信息輸入到 graph.conf 中,再讓程序進行讀取。
在這裏插入圖片描述

  • graph.conf
7
10
A B C D E F G
0 1 5
0 2 2
1 3 1
2 3 6
1 4 6
3 4 1
3 5 2
2 5 8
4 6 7
5 6 3

# 第一行 頂點個數
# 第二行 邊個數
# 第三行 頂點的元素
# 往下 邊連接的兩個頂點 以及 權

  • 最小生成樹
    在這裏插入圖片描述

一.Prim算法

Prim算法核心:在已經訪問的頂點中找到權值最小的弧,使用並查集,如果該弧連接的頂點沒有訪問過,那麼加入該頂點到並查集。

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <stack>
#include <queue>
#include <climits>
#include <iomanip>
#include <algorithm>
#include <map>
#include <set>
using namespace std;

/*********************************圖*********************************************/
/*
實現Prim算法,採用鄰接矩陣形式創建圖(無向圖,連通圖, 加權圖):
_matrix 矩陣
*/

using ElemType = char;

/*********************************類申明*********************************************/
struct EdgeNode
{
    int beg;
    int end;
    int weight;
    EdgeNode(int beg, int end, int weight) : beg(beg), end(end), weight(weight) {}
};

struct Comp
{
    bool operator()(EdgeNode *lhs, EdgeNode *rhs)
    {
        return lhs->weight < rhs->weight;
    }
};

class Graph
{
    vector<vector<int>> _matrix;                //鄰接矩陣
    vector<ElemType> _vals;                     //存放頂點元素
    vector<EdgeNode *> _edges;                  //存放邊的信息
    map<int, set<EdgeNode *, Comp>> _vertexMap; //存放相應頂點的邊信息
    int _vertexNum;                             //頂點數量
    int _edgeNum;                               //邊的數量
    vector<int> visited;                        //判斷定點是否訪問過

public:
    Graph();
    void Prim_MST();            //Prim算法,求解最小生成樹
    void printAdjMatrix();      //測試鄰接矩陣是否正確組合
    void printVertexAndEdges(); //測試頂點及其相鄰邊是否正確

    void resetVisited()
    {
        for (int i = 0; i < _vertexNum; i++)
            visited[i] = 0;
    }

private:
    void createGraph();
};

/*********************************類定義*********************************************/
Graph::Graph()
{
    createGraph();
    visited.resize(_vertexNum);
}

void Graph::createGraph()
{
    std::ifstream ifs("graph.conf", ios::in);
    string str;
    //第一行,讀取圖頂點的個數
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _vertexNum;
        _matrix.resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            _matrix[i].resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            for (int j = 0; j < _vertexNum; j++)
                if (i == j)
                    _matrix[i][j] = 0;
                else
                    _matrix[i][j] = INT_MAX;
    }

    //第二行,讀取邊的數量
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _edgeNum;
    }

    //第三行,讀取頂點元素
    {
        getline(ifs, str);
        stringstream ss(str);
        ElemType val;
        for (int i = 0; i < _vertexNum; i++)
        {
            ss >> val;
            _vals.push_back(val);
        }
    }

    //接下來,進行邊的連接
    {
        for (int k = 0; k < _edgeNum; k++)
        {
            getline(ifs, str);
            stringstream ss(str);
            int i, j, weight; //用邊連接的兩個頂點的編號
            ss >> i >> j >> weight;
            _matrix[i][j] = weight;
            _matrix[j][i] = weight;

            //無向圖存儲邊的時候仍然是隻存放一條邊 遵循從小指向大的原則
            EdgeNode *edge = new EdgeNode(i, j, weight);
            _edges.push_back(std::move(edge));
            _vertexMap[i].insert(std::move(edge));
        }
    }

    ifs.close();
}

void Graph::printAdjMatrix()
{
    cout << "-----------------------------------" << endl;
    for (int i = 0; i < _vertexNum; i++)
    {
        for (int j = 0; j < _vertexNum; j++)
        {
            if (INT_MAX == _matrix[i][j])
                cout << "  ∞  ";
            else
                cout << "  " << _matrix[i][j] << "  ";
        }
        cout << endl;
    }
    cout << "-----------------------------------" << endl;
}

void Graph::printVertexAndEdges()
{
    for (int i = 0; i < _vertexNum; i++)
    {
        cout << _vals[i] << " : ";
        set<EdgeNode *, Comp> edges = _vertexMap[i];
        for (auto &e : edges)
        {
            cout << e->beg << "->" << e->end << " (" << e->weight << ")   ";
        }
        cout << endl;
    }
}


//Prim算法的核心:不斷在已經加入的頂點中找到最小的邊,藉此找到下一個頂點
void Graph::Prim_MST()
{
    vector<int> union_find(_vertexNum, -1);             //並查集,用來記錄已加入的節點
    set<EdgeNode *, Comp> merged_edges = _vertexMap[0]; //先加入下表爲0的頂點

    for (int i = 0; i < _vertexNum - 1; i++)
    {
        EdgeNode *min_edge = *merged_edges.begin();
        merged_edges.erase(merged_edges.begin());
        int min_beg = min_edge->beg;
        int min_end = min_edge->end;
        int nextVertex = min_end;
        union_find[min_end] = min_beg;
        set<EdgeNode *, Comp> newEdges = _vertexMap[nextVertex];
        for (auto &e : newEdges)
            merged_edges.insert(e);
    }

    //print union_find
    for (int i = 0; i < _vertexNum; i++)
        cout << _vals[union_find[i]] << "->" << _vals[i] << endl;
}

/*********************************測試函數*********************************************/

//Prim
void test1()
{
    Graph graph;
    graph.printAdjMatrix();
    graph.printVertexAndEdges();
    graph.Prim_MST();
}

int main()
{
    test1();
    return 0;
}

二.Kruskal算法

Kruskal算法核心:不斷加入權值最小的弧,並判斷該弧連接的頂點是否屬於並查集中的同一個根,如果不是,那麼加入該頂點。

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <stack>
#include <queue>
#include <climits>
#include <iomanip>
#include <algorithm>
using namespace std;

/*********************************圖*********************************************/
/*
實現Kruskal算法,採用鄰接矩陣形式創建圖(無向圖,連通圖, 加權圖):
_matrix 矩陣
*/

using ElemType = char;

/*********************************類申明*********************************************/
struct EdgeNode
{
    int beg;
    int end;
    int weight;
    EdgeNode(int beg, int end, int weight) : beg(beg), end(end), weight(weight) {}
};

class Graph
{
    vector<vector<int>> _matrix; //鄰接矩陣
    vector<ElemType> _vals;      //存放頂點元素
    vector<EdgeNode *> _edges;   //存放邊的信息
    int _vertexNum;              //頂點數量
    int _edgeNum;                //邊的數量
    vector<int> visited;         //判斷定點是否訪問過

public:
    Graph();
    void Prim_MST();       //Prim算法,求解最小生成樹
    void Kruskal_MST();    //Kruskal算法,求解最小生成樹
    void printAdjMatrix(); //測試鄰接矩陣是否正確組合

    void resetVisited()
    {
        for (int i = 0; i < _vertexNum; i++)
            visited[i] = 0;
    }

private:
    void createGraph();
};

/*********************************類定義*********************************************/
Graph::Graph()
{
    createGraph();
    visited.resize(_vertexNum);
}

void Graph::createGraph()
{
    std::ifstream ifs("graph.conf", ios::in);
    string str;
    //第一行,讀取圖頂點的個數
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _vertexNum;
        _matrix.resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            _matrix[i].resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            for (int j = 0; j < _vertexNum; j++)
                if (i == j)
                    _matrix[i][j] = 0;
                else
                    _matrix[i][j] = INT_MAX;
    }

    //第二行,讀取邊的數量
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _edgeNum;
    }

    //第三行,讀取頂點元素
    {
        getline(ifs, str);
        stringstream ss(str);
        ElemType val;
        for (int i = 0; i < _vertexNum; i++)
        {
            ss >> val;
            _vals.push_back(val);
        }
    }

    //接下來,進行邊的連接
    {
        for (int k = 0; k < _edgeNum; k++)
        {
            getline(ifs, str);
            stringstream ss(str);
            int i, j, weight; //用邊連接的兩個頂點的編號
            ss >> i >> j >> weight;
            _matrix[i][j] = weight;
            _matrix[j][i] = weight;
            EdgeNode *edge = new EdgeNode(i, j, weight);
            _edges.push_back(std::move(edge));
        }
    }

    ifs.close();
}

void Graph::printAdjMatrix()
{
    cout << "-----------------------------------" << endl;
    for (int i = 0; i < _vertexNum; i++)
    {
        for (int j = 0; j < _vertexNum; j++)
        {
            if (INT_MAX == _matrix[i][j])
                cout << "  ∞  ";
            else
                cout << "  " << _matrix[i][j] << "  ";
        }
        cout << endl;
    }
    cout << "-----------------------------------" << endl;
}

int findRoot(vector<int> &union_find, int i)
{
    if (union_find[i] != -1)
        return findRoot(union_find, union_find[i]);
    return i;
}

//Kruskal求最小生成樹算法 利用並查集依次合併
void Graph::Kruskal_MST()
{
    vector<int> union_find(_vertexNum, -1); //並查集

    std::sort(_edges.begin(), _edges.end(), [&](EdgeNode *lhs, EdgeNode *rhs) {
        return lhs->weight < rhs->weight;
    });

    for (auto &e : _edges)
    {
        int beg = e->beg;
        int end = e->end;

        int root1 = findRoot(union_find, beg);
        int root2 = findRoot(union_find, end);
        if (root1 != root2)
            union_find[end] = beg;
    }

    //  打印連接情況
    for (int i = 0; i < union_find.size(); i++)
        cout << _vals[union_find[i]] << "->" << _vals[i] << endl;
}


/*********************************測試函數*********************************************/

//Kruskal
void test1()
{
    Graph graph;
    graph.printAdjMatrix();
    graph.Kruskal_MST();
}

int main()
{
    test1();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章