圖論系列文章
前言
在學習了前兩章的內容後,接下來學習最小生成樹算法。
最小生成樹的意思是將所有的頂點都連通,所有的邊加起來的權值最小的圖(成形爲樹)。
此類算法廣泛用於城市建設中,例如讓所有的城市全部連通,使其造價最小。
爲了防止最小生成樹中沒有環,我們需要使用到一種數據結構:並查集
並查集下標指向該頂點,存放的元素是其根,可以用來幫助我們合併兩個源點不同的集合, 並且也可以當作樹的路徑生成來使用。
- 並查集
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;
}