資料結構實現 9.2:並查集_樹思想實現(C++版)
阿新 • • 發佈:2018-11-27
資料結構實現 9.2:並查集_樹思想實現(C++版)
1. 概念及基本框架
9.1 我們利用陣列的結構實現了並查集,這一節我們利用樹的思想來實現並查集。(但本質上還是陣列,只是邏輯上的樹形結構)
如上圖,不同的顏色表示不同的集合,裡面的字母表示元素儲存的資料。下面給出並查集的基本介面。
class UnionFind{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
//是否連線
virtual bool isConnected(int p, int q) = 0;
//聯合元素
virtual void unionElements(int p, int q) = 0;
};
我們使用樹結構來實現並查集,這裡我們忽略每個元素的資料,用每個元素的階表示元素本身,用陣列中存的數字表示元素所屬集合。那麼並查集的實現過程如下:
1.將每個元素初始化成自身,在下面的建構函式中實現。
2.聯合,將需要聯合的元素中儲存的數字統一為一個。
3.查詢,只要陣列中儲存的數字是一樣的,那麼認為兩個元素屬於同一個集合。
class QuickUnion : public UnionFind{
public:
QuickUnion(int size){
m_data = new int[size];
//m_num = new int[size];
m_rank = new int[size];
for (int i = 0; i < size; ++i){
//每個結點指向自己
m_data[i] = i;
//m_num[i] = 1;
m_rank[i] = 1;
}
m_size = size;
}
...
private:
int *m_data;
int m_size;
//int *m_num; //以i為根的樹結點個數
int *m_rank; //結點的高度
};
m_data 用來表示陣列。
m_size 表示並查集大小。
其他變數後面會詳細敘述。接下來我們就對並查集的聯合、查詢以及一些其他基本操作用程式碼去實現。
2. 基本操作程式實現
2.1 聯合操作
class QuickUnion : public UnionFind{
public:
...
//聯合元素
void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
/*
if (m_num[pRoot] > m_num[qRoot]){
m_data[qRoot] = pRoot;
m_num[pRoot] += m_num[qRoot];
}
else{
m_data[pRoot] = qRoot;
m_num[qRoot] += m_num[pRoot];
}
*/
if (m_rank[pRoot] > m_rank[qRoot]){
m_data[qRoot] = pRoot;
}
else if (m_rank[pRoot] > m_rank[qRoot]){
m_data[pRoot] = qRoot;
}
else{
m_data[qRoot] = pRoot;
m_rank[pRoot] = m_rank[qRoot] + 1;
}
}
...
};
聯合元素時,9.1 中需要遍歷一遍陣列,才能需要聯合的元素都聯合起來。如果我們利用樹形結構,那麼我們只需要把一棵樹的根連線到需要聯合的樹上面,這樣會大大減小時間複雜度。那麼,問題在於,把誰的根連線到誰的根上面,我們可以新增一個輔助的樹高度的資料,然後將樹高小的樹併到樹高大的樹上面。
注:程式中被註釋掉的是依據樹大小決定並的操作的,不建議這樣做。連線到根節點可以實現路徑壓縮,實質都是為了減小樹高度。
2.2 查詢操作
class QuickUnion : public UnionFind{
public:
...
//是否連線
bool isConnected(int p, int q){
return find(p) == find(q);
}
...
private:
int find(int index){
if (index < 0 || index >= m_size){
cout << "訪問越界!" << endl;
throw 0;
}
while (index != m_data[index]){
m_data[index] = m_data[m_data[index]];
index = m_data[index];
}
return index;
}
...
};
用於查詢兩個元素是否被聯合在一起。
2.3 其他操作
並查集還有一些其他的操作,包括 並查集大小 等的查詢操作。
class QuickUnion : public UnionFind{
public:
...
int size(){
return m_size;
}
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "QuickUnion: " << "Size = " << m_size << endl;
cout << '[';
for (int i = 0; i < m_size; ++i){
cout << m_data[i];
if (i != m_size - 1){
cout << ',';
}
}
cout << ']' << endl;
}
...
};
3. 演算法複雜度分析
3.1 聯合操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
unionElements | O(logn) | O(logn) |
因為要遍歷一次陣列,所以聯合操作的時間複雜度為 O(logn) 。
3.2 查詢操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
isConnected | O(1) | O(1) |
總體情況:
操作 | 時間複雜度 |
---|---|
並 | O(logn) |
查 | O(1) |
4. 完整程式碼
程式完整程式碼(這裡使用了標頭檔案的形式來實現類)如下:
虛擬函式介面 程式碼如下:
#ifndef __UNIONFIND_H__
#define __UNIONFIND_H__
class UnionFind{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
//是否連線
virtual bool isConnected(int p, int q) = 0;
//聯合元素
virtual void unionElements(int p, int q) = 0;
};
#endif
並查集 類程式碼:
#ifndef __QUICKFIND_H__
#define __QUICKFIND_H__
#include "UnionFind.h"
class QuickFind : public UnionFind{
public:
QuickFind(int size){
m_data = new int[size];
for (int i = 0; i < size; ++i){
m_data[i] = i;
}
m_size = size;
}
int size(){
return m_size;
}
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "QuickFind: " << "Size = " << m_size << endl;
cout << '[';
for (int i = 0; i < m_size; ++i){
cout << m_data[i];
if (i != m_size - 1){
cout << ',';
}
}
cout << ']' << endl;
}
//是否連線
bool isConnected(int p, int q){
return find(p) == find(q);
}
//聯合元素
void unionElements(int p, int q){
if (p == q){
return;
}
for (int i = 0; i < m_size; ++i){
if (m_data[i] == m_data[q]){
m_data[i] = m_data[p];
}
}
}
private:
int find(int index){
if (index < 0 || index >= m_size){
cout << "訪問越界!" << endl;
throw 0;
}
return m_data[index];
}
private:
int *m_data;
int m_size;
};
#endif