1. 程式人生 > >資料結構實現 9.2:並查集_樹思想實現(C++版)

資料結構實現 9.2:並查集_樹思想實現(C++版)

資料結構實現 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