最小生成樹
最小生成樹的定義:
(1)首先要有一個帶權重值的無向圖,也就是Edge不再僅僅是連接兩個vertex,還具有一個權重值weight
(2)最小生成樹就是這個Graph的一棵子樹,包含了Graph中的所有節點,並且沒有任何的循環和週期
(3)最小生成樹中的所有連接線的權重加起來爲最小
要實現最小生成樹算法,首先需要一個表示帶有權重值的圖的API,之前博客已經寫過無向圖和有向圖的基本API,而帶有權重值的圖的API與之前類似,不過這裏需要將連接線Edge抽象爲一個類
如下爲Edge類
// Edge的Abstraction
class Edge{
private:
int v;
int w;
double weight; // 該edge的權重
public:
// 邊緣上的兩個節點
Edge(){
}
Edge(int _v , int _w , double _weight){
this->v = _v;
this->w = _w;
this->weight = _weight;
}
//返回其中的一個節點v
int either(){
return this->v;
}
//返回另外一個節點w
int other(int vertex){
if(vertex == v){
return this->w;
}
return v;
}
//得到該權重值的大小
double getWeight(){
return this->weight;
}
// 兩條連接線比較大小
int compareTo(Edge that){
if(this->getWeight() > that.getWeight()){
return -1;
}else if (this->getWeight() == that.getWeight()) {
return 0;
}else if (this->getWeight() > that.getWeight()) {
return 1;
}
}
// 重載操作符用於比較兩個線的大小
bool operator <(Edge & that){
return this->getWeight() < that.getWeight();
}
bool operator ==(Edge & that){
return this->getWeight() == that.getWeight();
}
bool operator >(Edge & that){
return this->getWeight() > that.getWeight();
}
};
接下來就是帶有權值的圖了:WeightedEdgesGraph
//*****************帶權連接線的圖API : Weighted edges Graph(Undirected graph)****************************
class WeightedEdgesGraph{
private:
int num_vertex; // Graph中的頂點數量
Bag<Edge> * adj; // 包中裝的都是edge了, 不再是int的節點了
public:
WeightedEdgesGraph(int V){
num_vertex = V;
adj = new Bag<Edge>[num_vertex];
}
void addEdges(Edge e){
int v = e.either();
int w = e.other(v);
adj[v].add(e);
adj[w].add(e);
}
Bag<Edge> adjacent(int v){
return adj[v];
}
int V(){
return num_vertex;
}
int E(){
int num_edges = 0;
for(int i =0 ;i < num_vertex; ++i){
num_edges += adj[i].size();
}
return num_edges;
}
// 返回所有的連接線(有重複連接線)
Bag<Edge> edges(){
Bag<Edge> total_edges;
for(int i =0 ;i < num_vertex; ++i){
for(int j = 0 ;j < this->adj[i].size() ; ++j){
total_edges.add(this->adj[i][j]);
}
}
return total_edges;
}
};
Kruskal最小生成樹算法實現
首先是Kruskal的Minimum spanning tree的算法,該算法使用到了priority queue以及Union-find這兩個數據結構作爲輔助。priority queue用於將edges按從小到大的順序排列,並每次取出權重最小的edge,Union-find用於判斷edge的兩個端點是否構成了一個cycle。
// *************************************Kurskal 的Minimum spanning tree最小生成樹算法**************************************
// UnionFind 並查集算法,可以用於檢測兩個元素是否屬於同一個類
class UnionFind{
private:
int * id;
int N;
int * size; // 用於評估這個並集裏的數量
// 查找根節點
int root(int p){
while (p != id[p]) {
p = id[p];
}
return p;
}
public:
UnionFind(int _N){
this->N = _N;
id = new int[N];
size = new int[N];
for(int i= 0; i < N;i++){
this->id[i] = i;
this->size[i] = 1;
}
}
bool connected(int p , int q){
return root(p) == root(q);
}
void union_(int p ,int q){
if(p == q){
return;
}
int root_p = root(p);
int root_q = root(q);
if(root_p == root_q){
return;
}
int size_p = size[root_p];
int size_q = size[root_q];
if(size_p <= size_q){
id[root_p] = root_q;
size[root_q] += size_p;
}else if (size_p > size_q) {
id[root_q] = root_p;
size[root_p] += root_q;
}
}
};
//Priority Queue
// 從小到大排序的priority queue
template<typename T>
class MinPriorityQueue{
public:
int capacity; // 容量
int N; // 當前元素位置
T * pq;
// 交換元素
void swap(T a[] , int i , int j){
T temp = a[i];
a[i] = a[j];
a[j] = temp;
}
// 比較兩個元素大小
int compare(T a , T b){
if(a < b){
return -1;
}else if (a == b) {
return 0;
}else if (a > b == 1) {
return 1;
}
}
// 將元素游上來
void swim(int k){
// 只要K還沒有swim到頂部, 並且 父元素比k要大,則繼續往上swim
while (k > 1 && compare(pq[k / 2] , pq[k] ) == 1 ) {
swap(pq , k / 2 , k);
k = k / 2;
}
}
// 將元素沉下去
void sink(int k){
while (2 * k + 1 < N) {
int j = 2 * k;
if(j < N && compare(this->pq[j] , this->pq[j + 1]) == 1){
j++;
}
if(compare(this->pq[k] , this->pq[j]) != 1){
break;
}
swap(this->pq , k , j);
k = j;
}
}
// 是否已滿
bool isFull(){
return N == capacity + 1;
}
// 是否爲空
bool isEmpty(){
return N == 1;
}
void resizing(int capacity){
T * old_pq = this->pq;
this->pq = new T[capacity + 1];
this->capacity = capacity;
for(int i =0 ;i < N; i++){
pq[i] = old_pq[i];
}
}
public:
// 構造函數
MinPriorityQueue(){
this->capacity = 1;
this->N = 1;
this->pq = new T[capacity + 1];
}
// 插入元素T
void insert(T item){
if(this->isFull()){
this->resizing(capacity * 2);
}
this->pq[N] = item;
swim(N);
N++;
}
// 取出最小的
T delMin(){
if(isEmpty()){
cout << "Error : the prioiry queue is empty !" << endl;
}else {
T item = this->pq[1];
swap(this->pq , 1 , --N);
sink(1);
return item;
}
}
// 返回priority queue的大小
int size(){
return this->N - 1;
}
};
//Kruskal's Minimum Spanning Tree
class KruskalMST{
private:
MinPriorityQueue<Edge> min_pq;
vector<Edge> mst;
int size_of_edges ;
public:
KruskalMST(WeightedEdgesGraph graph){
Bag<Edge> all_edges = graph.edges();
size_of_edges = 0;
//先將所有Edge插入到min priority queue中, 以方便從小到大排序
for(int i =0 ;i < all_edges.size() ; ++i){
min_pq.insert(all_edges[i]);
}
// 造一個Union-Find
UnionFind UF(graph.V());
while (!min_pq.isEmpty() && this->size_of_edges == graph.V() - 1) {
Edge e = min_pq.delMin(); // 獲取權重值最小的那條edge
int v = e.either(); // edge的一個節點
int w = e.other(v);
if(!UF.connected(v , w)){
mst.push_back(e);
size_of_edges++;
UF.union_(v , w);
}
}
}
// 重載構造函數 ,可直接接受priority qeueue作爲輸入參數
KruskalMST(MinPriorityQueue<Edge> edges_pq){
UnionFind UF(edges_pq.size());
while (!edges_pq.isEmpty() && this->size() < edges_pq.size() - 1) {
Edge e = edges_pq.delMin();
int v = e.either();
int w = e.other(v);
if(!UF.connected(v , w)){
mst.push_back(e);
size_of_edges++;
// 再將v , w連接起來
UF.union_(v , w);
}
}
}
int size(){
return this->size_of_edges;
}
// 返回所有的Minimal spanning tree
vector<Edge> edges(){
return mst;
}
};
Prim最小生成樹
之後又是Prim的最小生成樹,Prim的MST算法並非一上來就把所有的edges按從小到大的順序排列好再一個一個加到MST中。Prim算法實際上是循序漸進的,一次加幾條edge,一個節點一個節點的來,不慌不忙的,不過本質上都差不多。
// *************************************Prim的Minimul spanning tree最小生成樹算法**************************************
class PrimMST{
private:
MinPriorityQueue<Edge> min_pq;
vector<Edge> mst;
int size_of_edges;
bool * marked;
void visit(WeightedEdgesGraph graph , int v){
if(marked[v]){
return;
}
marked[v] = true;
Bag<Edge> edge_of_v = graph.adjacent(v);
for(int i = 0 ;i < edge_of_v.size() ; ++i){
Edge e = edge_of_v[i];
int w = e.other(v);
if(!marked[w]){
min_pq.insert(e);
}
}
}
public:
//構造函數
PrimMST(WeightedEdgesGraph graph){
// 先初始化
size_of_edges = 0;
marked = new bool[graph.V()];
for(int i = 0; i < graph.V() ; ++i){
marked[i] = false;
}
visit(graph , 0);
while ( !min_pq.isEmpty() && size_of_edges < graph.V() - 1) {
Edge e = min_pq.delMin();
int v = e.either();
int w = e.other(v);
if(marked[v] && marked[w]){
continue;
}
mst.push_back(e);
size_of_edges++;
if(!marked[v]){
visit(graph , v);
}
if(!marked[w]){
visit(graph , w);
}
}
}
vector<Edge> edges(){
return mst;
}
};