紅黑樹,雜湊表...嘔心瀝血完成的幾種常見的符號表實現

Bazinga_ovo發表於2021-07-11

符號表

符號表是一種通過把一個鍵(key)和一個值(value)聯絡起來,在呼叫時通過查詢鍵來對鍵對應的值進行操作的資料結構(如c++中的map)。
符號表的主要操作有增,刪,改,查四種,也可以對其進行擴充套件操作。下面,就對幾種符號表的實現及部分擴充套件操作進行簡要的介紹。

符號表的雙陣列實現

顧名思義,通過兩個陣列,一個存放key,一個存放value,來實現符號表。為了便於資料查詢,要保證陣列內資料的有序性。
這裡給出完整程式碼:

//符號表(雙陣列實現版)
template<typename K,typename V>
class signTable{
	vector<K> keyV;
	vector<V> valueV;
	size_t n = 0;
public:
	signTable() = default;
	size_t size() { return n; }
	bool is_empty() { return n == 0; }
	K max() { return is_empty() ? NULL : keyV[n - 1]; }
	K min() { return is_empty() ? NULL : keyV[0]; }
	K select(size_t r) { return (r < n) ? keyV[r] : NULL; }
	size_t rank(K key);
	size_t rank(K lo, K hi);
	void insert(K key, V value);
	V get(K key);
	void dele(K key);
	K ceiling(K key);
	K floor(K key);
	vector<K> cut_key(K lo, K hi);
};
template<typename K, typename V>
size_t signTable<K, V>::rank(K key) {
	if (is_empty()) { return 0; }
	int lo = 0, hi = n - 1, mid;
	while (lo <= hi) {
		mid = lo + (hi - lo) / 2;
		if (key < keyV[mid]) { hi = mid - 1; }
		else if (key > keyV[mid]) { lo = mid + 1; }
		else { return mid; }
	}
	return lo;
}
template<typename K, typename V>
size_t signTable<K, V>::rank(K lo,K hi) {
	if (hi <= lo) { return 0; }
	else { return rank(hi) - rank(lo); }
}
template<typename K, typename V>
void signTable<K, V>::insert(K key, V value) {
	size_t temp = rank(key);
	if (temp == n) {
		keyV.push_back(key);
		valueV.push_back(value);
		++n;
	}
	else if (keyV[temp] == key) { valueV[temp] = value; }
	else {
		keyV.push_back(keyV[n - 1]);
		valueV.push_back(valueV[n - 1]);
		for (int i = n - 1; i > temp; --i) {
			keyV[i] = keyV[i - 1];
			valueV[i] = valueV[i - 1];
		}
		keyV[temp] = key;
		valueV[temp] = value;
		++n;
	}
}
template<typename K, typename V>
V signTable<K, V>::get(K key) {
	size_t temp = rank(key);
	if (temp == n || keyV[temp] != key) { return NULL; }
	return valueV[temp];
}
template<typename K, typename V>
void signTable<K, V>::dele(K key) {
	size_t temp = rank(key);
	if (temp == n || keyV[temp] != key) { return; }
	while (temp < n - 1) {
		valueV[temp] = valueV[temp + 1];
		keyV[temp] = keyV[++temp];
	}
	valueV.pop_back();
	keyV.pop_back();
	--n;
}
template<typename K, typename V>
K signTable<K, V>::ceiling(K key) {
	size_t temp = rank(key);
	if (temp == n) { return NULL; }
	return keyV[temp];
}
template<typename K, typename V>
K signTable<K, V>::floor(K key) {
	size_t temp = rank(key);
	if (temp == 0 && keyV[temp] != key) { return NULL; }
	if (temp == n || keyV[temp] != key) { return keyV[temp - 1]; }
	return key; 
}
template<typename K, typename V>
vector<K> signTable<K, V>::cut_key(K lo, K hi) {
	vector<K> temp;
	K temp1 = ceiling(lo);
	if (!temp1) { return temp; }
	size_t temp2 = rank(floor(hi));
	for (size_t i = rank(temp1); i <= temp2; ++i) {
		temp.push_back(keyV[i]);
	}
	return temp;
}

函式詳細解釋:

  1. rank(K key) :使用二分查詢,找出所提供的key在表中的秩,如果表中沒有key,則返回大於key的最小值(如果不存在則是表尾)的秩。
  2. rank(K lo, K hi) :兩個key的秩的差值。
  3. insert(K key, V value) :插入和修改操作,如果key已存在,則修改value(表中key具有唯一性);key不存在則插入,在插入時仍保證表的有序性。
  4. get(K key) :查詢操作,返回key對應的value,如果key不存在則返回NULL。
  5. dele(K key) :刪除操作,刪除後仍保證表的有序性。
  6. ceiling(K key) :天花板,返回大於等於key的最小值的秩(不存在返回Null)。
  7. floor(K key) :地板,返回小於等於key的最大值的秩(不存在返回Null)。
  8. cut_key(K lo, K hi) :切片,返回一個由大於等於lo小於等於hi的秩組成的vector。

rank通過二分查詢來獲取秩,而幾乎所有操作都是通過rank提供的秩來實現的,這也保證了程式的高效性。雙陣列符號表的查詢效能可以達到O(lgN)級別,但它的插入操作仍是O(N)級別。

符號表的二叉搜尋樹實現

用二叉樹實現的符號表,每個節點存放key,value,n(以它為根節點的樹的節點數)和指向左右樹的智慧指標(防止記憶體洩漏)。通過遞迴,自頂向下地進行查詢,保證較低的時間複雜度。

這裡給出完整程式碼:

//符號表(二叉搜尋樹實現版)
class BST {	
	struct Node {
		int key;
		int value;
		shared_ptr<Node> left, right;
		int n;
		Node(int key, int value, int n) :key(key), value(value), n(n) {}
	};
	shared_ptr<Node>root;
	int size(shared_ptr<Node> node) { return node ? node->n : 0; }
	int get(shared_ptr<Node> node, int key) {
		if (node == nullptr) { return 0; }
		if (key < node->key) { return get(node->left, key); }
		else if (key > node->key) { return get(node->right, key); }
		else { return node->value; }
	}
	shared_ptr<Node> put(int key, int value, shared_ptr<Node> node) {
		if (node == nullptr) { node = shared_ptr<Node>(new Node(key, value, 1)); }
		else if (key < node->key) { node->left = put(key, value, node->left); }
		else if (key > node->key) { node->right = put(key, value, node->right); }
		else { node->value = value; }
		node->n = size(node->left) + size(node->right) + 1;
		return node;
	}
	shared_ptr<Node> max(shared_ptr<Node> node) {
		if (node->right == nullptr) { return node; }
		else { return max(node->right); }
	}
	shared_ptr<Node> min(shared_ptr<Node> node) {
		if (node->left == nullptr) { return node; }
		else { return min(node->left); }
	}
	shared_ptr<Node> floor(int key, shared_ptr<Node> node) {
		if (node == nullptr) { return node; };
		if (key < node->key) { return floor(key, node->left); }
		else if (key == node->key) { return node; }
		shared_ptr<Node> tem = floor(key,node->right);
		if (tem) { return tem; }
		return node;
	}
	shared_ptr<Node> ceiling(int key, shared_ptr<Node> node) {
		if (node == nullptr) { return node; };
		if (key > node->key) { return floor(key, node->right); }
		else if (key == node->key) { return node; }
		shared_ptr<Node> tem = floor(key, node->left);
		if (tem) { return tem; }
		return node;
	}
	shared_ptr<Node> select(int ran, shared_ptr<Node> node) {
		if (node == nullptr) { return NULL; }
		int t = size(node->left);
		if (ran < t) { return select(ran, node->left); }
		else if (ran > t) { return select(ran - t - 1, node->left); }
		else { return node; }
	}
	int rank(int key, shared_ptr<Node> node) {
		if (node == nullptr) { return NULL; }
		if (key < node->key) { return rank(key, node->left); }
		else if (key > node->key) { return size(node->left) + 1 + rank(key, node->right); }
		else { return size(node->left); }
	}
	shared_ptr<Node> deleteMax(shared_ptr<Node> node) {
		if (node->right == nullptr) { return node->left; }
		node->right = deleteMax(node->right);
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	shared_ptr<Node> deleteMin(shared_ptr<Node> node) {
		if (node->left == nullptr) { return node->right; }
		node->left = deleteMin(node->left);
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	shared_ptr<Node> deleteKey(shared_ptr<Node> node,int key) {
		if (node == nullptr) { return nullptr; }
		if (key < node->key) { node->left = deleteKey(node->left, key); }
		else if (key > node->key) { node->right = deleteKey(node->right, key); }
		else {
			if (node->left == nullptr) { return node->right; }
			if (node->right == nullptr) { return node->left; }
			shared_ptr<Node>tem = min(node->right);
			node->key = tem->key;
			node->value = tem->value;
			node->right = deleteMin(node->right);
		}
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	vector<int>& keys(shared_ptr<Node>node, vector<int>& vk, int l, int r) {
		if (node == nullptr) { return vk; }
		if (l < node->key) { vk = keys(node->left, vk, l, r); }
		if (l <= node->key && r >= node->key) { vk.push_back(node->key); }
		if (r > node->key) { vk = keys(node->right, vk, l, r); }
		return vk;
	}
public:
	int size() { return size(root); }
	int get(int key) { return get(root, key); }
	void put(int key, int value) { root = put(key, value, root); }
	int max() { return max(root)->key; }
	int min() { return min(root)->key; }
	int floor(int key) { return floor(key, root)->key; }
	int ceiling(int key) { return ceiling(key, root)->key; }
	int select(int ran) { return select(ran, root)->key; }
	int rank(int key) { return rank(key, root); }
	void deleteMax() { root = deleteMax(root); }
	void deleteMin() { root = deleteMin(root); }
	void deleteKey(int key) { root = deleteKey(root, key); }
	vector<int>& keys(vector<int>& vk, int l, int r) { return keys(root, vk, l, r); }
};

這裡給出增刪改查和切片的函式實現:

  1. get(shared_ptr *<Node>* node, int key) :查,通過遞迴,一層層往下查詢,如果key不存在則返回Null。
  2. put(int key, int value, shared_ptr<Node> node):增和改,先一層層向下遞迴查詢,如找到key則對value進行修改,如果查到樹底也沒有key,就在樹底增添一個新節點,並逐層遞迴更新節點的n。
  3. deleteKey(shared_ptr<Node> node,int key) :刪,一層層向下遞迴查詢,如找到key,在key只有單子樹時返回子樹,若key有雙子樹,則用key的右子樹的最小值來替換它,然後刪除右子樹的最小值,並逐層遞迴更新節點的n。
  4. keys(shared_ptr<Node>node, vector<int>& vk, int l, int r) :切片,中序遍歷二叉樹,將key大於等於l,小於等於r的節點的key儲存到vector中並返回。

二叉搜尋樹符號表並不困難,但卻可以實現高效的查詢和插入操作。其操作的平均時間複雜度均可達到O(1.39lgN)。它基本實現的良好性依賴於其中的key分佈足夠隨機以消除長路徑,但在實踐應用中,最壞情況仍然會出現,最壞情況時的時間複雜度仍是我們不能接受的。

符號表的紅黑二叉搜尋樹(左偏)實現

在介紹紅黑樹之前,我先簡單介紹一下2-3樹
2-3樹由兩種結點組成:2-結點就是普通的二叉樹節點;3-結點含有兩個鍵和三條連結,左連結指向的2-3樹中的鍵都小於該結點,中連結指向的2-3樹中的鍵都位於該節點的兩個鍵之間,右連結指向的2-3樹的鍵都大於該結點。
指向空樹的連結稱為空連結。

如圖:
在這裡插入圖片描述
一棵完美平衡的2-3樹中所有空連結到根節點的距離都是相同的,而一棵左偏紅黑樹就可以看成是一顆完美平衡的2-3樹。由一個紅色左連結相連的兩個2-結點表示一個3-結點。
在這裡插入圖片描述
在這裡插入圖片描述
紅黑樹的結點與二叉樹不同,在結點類中多了一個color成員,用來表示該結點的父結點與它連結的顏色。
在給出左偏紅黑樹的具體實現之前,還需要知道左偏紅黑樹滿足如下標準

  1. 紅連結均為左連結。
  2. 沒有同時連結兩條紅連結的結點(包括父連結)。
  3. 紅黑樹是黑色平衡的,任意空連結到根節點的路徑上的黑連結數量相等。

這裡給出完整程式碼:

//紅黑二叉搜尋樹(左偏)
class RedBlackBST {
	struct RBNode{
		int key, value;
		shared_ptr<RBNode>left, right;
		bool color;
		int n;
		RBNode(int key,int value,int n,bool color):key(key),value(value),n(n),color(color){}
	};
	shared_ptr<RBNode>root;
	bool isRed(shared_ptr<RBNode>node) {
		if (node == nullptr) { return 0; }
		return node->color;
	}
	shared_ptr<RBNode> min(shared_ptr<RBNode> node) {
		if (node->left == nullptr) { return node; }
		else { return min(node->left); }
	}
	int size(shared_ptr<RBNode> node) { return node ? node->n : 0; }
	shared_ptr<RBNode>rotateLeft(shared_ptr<RBNode>node) {
		shared_ptr<RBNode>temp = node->right;
		node->right = temp->left;
		temp->left = node;
		temp->color = node->color;
		node->color = 1;
		temp->n = node->n;
		node->n = size(node->left) + 1 + size(node->right);
		return temp;
	}
	shared_ptr<RBNode>rotateRight(shared_ptr<RBNode>node) {
		shared_ptr<RBNode>temp = node->left;
		node->left = temp->right;
		temp->right = node;
		temp->color = node->color;
		node->color = 1;
		temp->n = node->n;
		node->n = size(node->left) + 1 + size(node->right);
		return temp;
	}
	void flipColors(shared_ptr<RBNode>node) {
		node->color = 1;
		node->left->color = 0;
		node->right->color = 0;
	}
	int get(shared_ptr<RBNode>node, int key) {
		if (node->value > key) { return get(node->left, key); }
		else if (node->value < key) { return get(node->right, key); }
		return node->value;
	}
	shared_ptr<RBNode>put(shared_ptr<RBNode>node, int key, int value) {
		if (node == nullptr) { return node = shared_ptr<RBNode>(new RBNode(key, value, 1, 1)); }
		if (key < node->value) { node->left = put(node->left, key, value); }
		else if (key > node->value) { node->right = put(node->right, key, value); }
		else {
			node->key = key;
			node->value = value;
		}
		if (isRed(node->right) && (!isRed(node->left)) ) { node = rotateLeft(node); }
		if (isRed(node->left) && isRed(node->left->left)) { node = rotateRight(node); }
		if (isRed(node->left) && isRed(node->right)) { flipColors(node); }
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	shared_ptr<RBNode>deleteMin(shared_ptr<RBNode>node) {
		if (node->left == nullptr) { return node->right; }
		if (isRed(node->left)) { node->left = deleteMin(node->left); }
		else {
			if (isRed(node->right)) { node = rotateLeft(node); }
			else { 
				node->color = 0;
				node->left->color = 1;
				if (node->right) { node->right->color = 1; }
			}
			node->left = deleteMin(node->left);
		}
		if (isRed(node->right) && (!isRed(node->left))) { node = rotateLeft(node); }
		if (isRed(node->left) && isRed(node->left->left)) { node = rotateRight(node); }
		if (isRed(node->left) && isRed(node->right)) { flipColors(node); }
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	shared_ptr<RBNode>deleteMax(shared_ptr<RBNode>node) {
		if (node->right == nullptr) { return node->left; }
		if (isRed(node->right)) { node->right = deleteMax(node->right); }
		else {
			if (isRed(node->left)) { node = rotateRight(node); }
			else {
				node->color = 0;
				node->right->color = 1;
				if (node->left) { node->left->color = 1; }
			}
			node->right = deleteMax(node->right);
		}
		if (isRed(node->right) && (!isRed(node->left))) { node = rotateLeft(node); }
		if (isRed(node->left) && isRed(node->left->left)) { node = rotateRight(node); }
		if (isRed(node->left) && isRed(node->right)) { flipColors(node); }
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
	shared_ptr<RBNode>deleteKey(shared_ptr<RBNode>node, int key) {
		if (node == nullptr) { return nullptr; }
		if (key < node->key) { 
			if (!isRed(node->left)) {
				if (isRed(node->right)) { node = rotateLeft(node); }
				else {
					node->color = 0;
					node->left->color = 1;
					if (node->right) { node->right->color = 1; }
				}
			}
			node->left = deleteKey(node->left, key); 
		}
		else if (key > node->key) { 
			if (!isRed(node->right)) {
				if (isRed(node->left)) { node = rotateRight(node); }
				else {
					node->color = 0;
					node->right->color = 1;
					if (node->left) { node->left->color = 1; }
				}
			}
			node->right = deleteKey(node->right, key); 
		}
		else {
			if (node->left == nullptr) { return node->right; }
			if (node->right == nullptr) { return node->left; }
			shared_ptr<RBNode>tmp = min(node->right);
			node->key = tmp->key;
			node->value = tmp->value;
			node->right = deleteMin(node->right);
		}
		if (isRed(node->right) && (!isRed(node->left))) { node = rotateLeft(node); }
		if (isRed(node->left) && isRed(node->left->left)) { node = rotateRight(node); }
		if (isRed(node->left) && isRed(node->right)) { flipColors(node); }
		node->n = size(node->left) + 1 + size(node->right);
		return node;
	}
public:
	int size() { return  size(root); }
	int get(int key) { return get(root, key); }
	void put(int key, int value) { 
		root=put(root, key, value);
		root->color = 0;
	}
	int min() { return min(root)->key; }
	void deleteMin() { 
		root = deleteMin(root);
		if (root) { root->color = 0; }
	}
	void deleteMax() { 
		root = deleteMax(root);
		if (root) { root->color = 0; }
	}
	void deleteKey(int key) { 
		root = deleteKey(root, key); 
		if (root) { root->color = 0; }
	}
};

給出增刪改查函式實現:

  1. 紅黑樹的改和查與普通的二叉樹一樣:遞迴查詢,key值比較。而紅黑樹的增刪則圍繞三種基礎操作展開:
    左旋(rotateLeft)右旋(rotateRight)顏色轉換(flipColors)

    1. 左旋:當操作結點右連結為紅,左連結為黑時使用,將右子節點的左樹接在操作節點的右子樹上,再將操作節點接在儲存的右子節點的左子樹上,使原來的右子節點作為根節點,從而使紅連結移到左側。
    2. 右旋:當操作結點左連結為紅,右連結為黑時使用,和左連結類似,使紅連結移到右側。
    3. 顏色轉換:當操作結點左右連結均為紅時使用,將左右子節點的顏色置為黑,並將操作結點顏色置為紅。如果用2-3樹解釋,則是為了保證樹的穩定而做的提升高度操作。
      在這裡插入圖片描述
  2. put(shared_ptr<RBNode>node, int key, int value):增,和二叉樹的增幾乎一模一樣,但有兩點不同:1.在最後插入時連結為紅色,以保證該節點的空連結到根節點間的黑色路徑與其他空連結相等。2.要在返回上層前進行結點檢查,看當前節點是否滿足紅黑樹標準,如不滿足,則將紅連結向上傳遞。

  3. deleteKey(shared_ptr<RBNode>node, int key) :刪,仍是一層層向下遞迴查詢,但在遞迴的過程中要保證下一次遞迴到的結點是紅結點。如果下一個遞迴結點不是紅結點,則要根據它的兄弟結點的顏色來判斷是用右旋還是逆向顏色變換(當前結點變黑,兩個位元組點變紅)來使遞迴結點變紅。在找到key後,在key只有單子樹時返回子樹,若key有雙子樹,則用key的右子樹的最小值來替換它,然後刪除右子樹的最小值,並逐層遞迴依據紅黑樹標準來恢復結點顏色並更新節點的n。

紅黑樹符號表雖然實現很困難,但卻可以實現比二叉樹更高效的查詢和插入操作。因為樹是平衡的,所以查詢比二叉樹更快。查詢的內迴圈只會進行一次比較並更新一條連結,非常簡短,其查詢和插入的平均時間複雜度均可看作O(lgN)。

符號表的雜湊表(雜湊表)實現

雜湊表又叫雜湊表,通過雜湊函式來確定key和value的存放位置。C++的functional標頭檔案中已經給出保證分步均勻的雜湊函式演算法。下面主要討論兩種基於不同的碰撞處理的雜湊表實現。

  1. 基於拉鍊法的雜湊表實現

    拉鍊法通過一條儲存連結串列的陣列實現。在雜湊函式將key鍵轉化為陣列的秩後,通過對陣列對應位置儲存的連結串列進行操作來進行資料的增,刪,改,查。

    這裡給出完整程式碼:

//拉鍊法雜湊表(雜湊表)
hash<int> h;
class SparateChainingHashST {
	struct Node {
		int key, value;
		Node* next;
		Node(int key,int value):key(key),value(value),next(nullptr){}
	};
	int sz = 0;
	int cpc;
	vector<Node*>sTable;
	int hash(int key) { return h(key) % cpc; }
public:
	SparateChainingHashST(int cpc = 997);
	void put(int key, int value);
	int get(int key);
	void deleteKey(int key);
	~SparateChainingHashST();
};
SparateChainingHashST::SparateChainingHashST(int cpc) : cpc(cpc) {
	for (int i = 0; i < cpc; ++i) { sTable.push_back(nullptr); }
}
void SparateChainingHashST::put(int key, int value) {
	int rank = hash(key);
	if (!sTable[rank]) {
		sTable[rank] = new Node(key, value);
		++sz;
	}
	else{
		Node* tmp = sTable[rank];
		while(1){
			if (tmp->key == key) {
				tmp->value = value;
				return;
			}
			if (tmp->next) { tmp = tmp->next; }
			else { break; }
		}
		tmp->next = new Node(key, value);
		++sz;
	}
}
int SparateChainingHashST::get(int key) {
	Node* tmp = sTable[hash(key)];
	for (; tmp; tmp = tmp->next) {
		if (tmp->key == key) { return tmp->value; }
	}
	return NULL;
}
void SparateChainingHashST::deleteKey(int key) {
	int rank = hash(key);
	Node* tmp;
	if (tmp = sTable[rank]) {	;
		if (tmp->key == key) {
			sTable[rank] = tmp->next;
			delete tmp;
			--sz;
			return;
		}
		while (tmp->next) {
			if (tmp->next->key == key) {
				Node* tmp2 = tmp->next;
				tmp->next = tmp2->next;
				delete tmp2;
				--sz;
				return;
			}
		}
	}
}
SparateChainingHashST::~SparateChainingHashST() {
	for (int i = 0; i < cpc; ++i) { 
		while (sTable[i]) {
			Node* tmp = sTable[i]->next;
			delete sTable[i];
			--sz;
			sTable[i] = tmp;
		}
		if (sz <= 0) { break; }
	}
}

增刪改查就是在通過雜湊函式找到對應位置連結串列後對連結串列的增刪改查,這裡不再贅述。

  1. 基於線性探測法的雜湊表實現

    線性探測法需要兩個陣列,一個存放key,一個存放value,在key經雜湊函式轉化為陣列的秩後,會根據對應秩處陣列所存的值進行處理。在所存值存在且與key不等的情況下,會對下一個位置的值進行重複判斷。在儲存值為空或儲存值與key相等的情況下,才會進行對key和value的操作。

這裡給出完整程式碼:

//線性探測法雜湊表(雜湊表)(key不為0)
hash<int> h;
class LinearProbingHashST {
	int sz = 0, cpc;
	int* Key, * Value;
	int hash(int key) { return h(key) % cpc; }
	void resize(int ncpc);
public:
	LinearProbingHashST(int cpc = 16) :cpc(cpc),Key(new int[cpc]),Value(new int[cpc]){
		for (int i = 0; i < cpc; ++i) {
			Key[i] = 0;
			Value[i] = 0;
		}
	}
	void put(int key, int value);
	int get(int key);
	void deleteKey(int key);
	~LinearProbingHashST() {
		delete[] Key;
		delete[] Value;
	}
};
void LinearProbingHashST::put(int key, int value) {
	if (sz > cpc / 2) { resize(2 * cpc); }
	int temp;
	for (temp = hash(key); Key[temp]; temp = (temp + 1) % cpc) {
		if (Key[temp] == key) {
			Value[temp] = value;
			return;
		}
	}
	Key[temp] = key;
	Value[temp] = value;
	++sz;
}
int LinearProbingHashST::get(int key) {
	for (int temp = hash(key); Key[temp]; temp = (temp + 1) % cpc) {
		if (Key[temp] == key) { return Value[temp]; }
	}
	return NULL;
}
void LinearProbingHashST::deleteKey(int key) {
	int temp = hash(key);
	while (Key[temp]) {
		if (Key[temp] == key) {
			Key[temp] = 0;
			Value[temp] = 0;
			--sz;
			temp = (temp + 1) % cpc;
			while (Key[temp]) {
				int tkey = Key[temp];
				int tvalue = Value[temp];
				Key[temp] = 0;
				Value[temp] = 0;
				--sz;
				put(tkey, tvalue);
				temp = (temp + 1) % cpc;
			}
			break;
		}
		temp = (temp + 1) % cpc;
	}
	if (sz > 0 && sz < cpc / 8) { resize(cpc / 2); }
}
void LinearProbingHashST::resize(int ncpc) {
	int* tKey = new int[ncpc], * tValue = new int[ncpc];
	for (int i = 0; i < ncpc; ++i) {
		tKey[i] = 0;
		tValue[i] = 0;
	}
	for (int i = 0; i < cpc; ++i) {
		if (Key[i]) {
			int trank = hash(Key[i]);
			int temp;
			for (temp = trank; tKey[temp]; temp = (temp + 1) % cpc) {}
			tKey[temp] = Key[i];
			tValue[temp] = Value[i];
		}
	}
	delete[]Key;
	delete[]Value;
	Key = tKey;
	Value = tValue;
	cpc = ncpc;
}

增刪改查就像上面所說的那樣對陣列進行操作,這裡也不再贅述。
但是要注意一點:通過均攤分析和記憶體使用分析,要保證雜湊表內的元素佔雜湊表容量的1/8到1/2。在這時,線性探測的平均次數在1.5到2.5之間,可以保證雜湊表時間與空間的綜合效能達到最優。

這裡給出調整陣列大小(resize)的函式實現:
新建兩個所需容量陣列用於存放key和value,通過雜湊函式將原有的key和value雜湊到新陣列中。釋放原指標保證陣列的記憶體,並將新陣列頭賦給原指標。設立新的容量值。

雜湊表通過雜湊函式實現了極高的搜尋和查詢效能,達到了驚人的O(1)級別。但資料在雜湊後不再有序,因此雖然雜湊表有著極高的效能,但卻不是所有領域都適用。

相關文章