雜湊表(模板,參考用)
雜湊表又叫做散列表,關鍵值通過雜湊函式對映到陣列上,查詢時通過關鍵值直接訪問陣列
雜湊函式指的是關鍵值和儲存位置建立的對應關係,查詢時只要根據這個關係就可以找到目標位置
雜湊表裡,可能存在關鍵字不同但是雜湊地址相同的情況,會產生衝突,一般情況下,衝突是不可避免的,因為關鍵字集合往往比雜湊地址集合大很多
雜湊表的構造方法:
(1)直接定址法:即取關鍵字的指或者關鍵字的某個函式變換值,線性對映到儲存地址上
(2)取餘數法:我們將關鍵字對整數p取的餘數值直接作為儲存地址,整數p一般取小於等於雜湊表長度size的最大質數,如果關鍵字不是整數,比如是一個字串,我們先將其做個轉換,可以先將其做個轉換,然後再對p取餘
下面例舉一個hash變換函式:
int hash(string& value) { //雜湊函式
int code = 0;
for (size_t i = 0; i < value.length(); ++i) {
code = (code * 256 + value[i] + 128) % size; //產生雜湊編碼
}
return code;
}
我們知道有字元範圍是從-128到127,也就是一共有256種。為了減少衝突,
我們可以使用上述構造方法,為了防止出現負數,我們對value[i]加上128,為了防止資料溢位,我們再對其進行取餘操作
衝突的解決
開放地址法:如果發生衝突,那麼就使用某種策略尋找下一儲存地址,直到找到一個不衝突的地址或者找到關鍵字,否則一直按照這種策略繼續尋找。如果衝突次數達到了上限則終止程式,表示關鍵字不在雜湊表裡
1.線性探測法,如果當前的衝突地址為d,那麼接下來幾個探測地址為d+1,d+2,d+3等,
2.線性補償探測法: d+m, d+2*m, d+3*m(m和表長size互質;
3.隨機探測法
4.二次探測法:形成的探測地址為 d + 1平方 d - 1平方 d + 2平方 d - 2平方演算法
演算法的具體實現策略:
(1)用雜湊函式找到字串S的初始位置,初始化衝突次數
(2)從當前位置從後面進行查詢,找到第一個未發生衝突的位置K(當前位置上如果儲存的字串不是S則視為發生衝突) 查詢過程中記錄發生衝突的次數T,如果T大於等於表長,則結束演算法,表示查詢失敗
(3)如果K上的元素就是查詢的字串,則查詢成功,否則查詢失敗
bool search(string& value, int& pos, int& times) {
times = 0; //查詢次數歸零
pos = hash(value); //構建雜湊值
while (elem[pos] != "#" && elem[pos] != value) { //如果沒找到,則繼續進行查詢
times++;
if (times < size) {
pos = (pos + 1) % size; //向下一個方向查詢
}
else {
return false; //需要擴容
}
}
if (elem[pos] == value) {
return true;
}
else {
return false;
}
}
雜湊表的擴容操作:
程式執行時,如果當前元素已經存在於雜湊表中了,就直接返回一個值結束這次插入操作。
當衝突次數小於表長的一半時,我們就可以把字串插入到雜湊表中,如果大於一半,則需要進行重建雜湊表(擴容,防止發生堆聚現象)
雜湊表重建操作的演算法:
- 開闢一段和當前雜湊表等大的臨時空間
- 將原雜湊表中的關鍵字一一複製到臨時陣列中
- 申請一個原空間大小兩倍的新空間,釋放原空間
- 將新空間裡的儲存地址初始化
- 將關鍵字從臨時陣列複製到新的空間,釋放臨時空間
void recreate() { //擴容操作
string* temp_elem = new string[size];
for (int i = 0; i < size; ++i) {
temp_elem[i] = elem[i];
}
int copy_size = size; //保留原有的大小
size = size * 2; //擴大兩倍
delete[] elem;
elem = new string[size]; //擴容
for (int i = 0; i < size; ++i) { //初始化
elem[i] = "#";
}
//插入到表中
for (int i = 0; i < copy_size; ++i) {
if (temp_elem[i] != "#") {
insert(temp_elem[i]);
}
}
delete[] temp_elem;
}
};
整體程式碼如下:
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
class HashTable {
private:
string *elem;
int size;
public:
HashTable() {
size = 2000;
elem = new string[size];
for (int i = 0; i < size; i++) {
elem[i] = "#";
}
}
~HashTable() {
delete[] elem;
}
int hash(string &value) {
int code = 0;
for (size_t i = 0; i < value.length(); i++) {
code = (code * 256 + value[i] + 128) % size;
}
return code;
}
bool search(string &value, int &pos, int ×) {
pos = hash(value);
times = 0;
while (elem[pos] != "#" && elem[pos] != value) {
times++;
if (times < size) {
pos = (pos + 1) % size;
} else {
return false;
}
}
if (elem[pos] == value) {
return true;
} else {
return false;
}
}
int insert(string &value) {
int pos, times;
if (search(value, pos, times)) {
return 2;
} else if (times < size / 2) {
elem[pos] = value;
return 1;
} else {
recreate();
insert(value);
return 0;
}
}
// 請在下面實現重建方法 recreate
void recreate(){
string* temp_elem;
temp_elem = new string[size];
for(int i = 0; i < size; ++i){
temp_elem[i] = elem[i];
}
int copy_size = size;
size = size*2;
delete[] elem;
elem = new string[size];
for(int i = 0; i < size; ++i){
elem[i] = "#";
}
for(int i = 0; i < copy_size;i++){
if(temp_elem[i] != "#"){
insert(temp_elem[i]);
}
}
delete[] temp_elem;
}
};
int main() {
HashTable hashtable;
string buffer;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> buffer;
int ans = hashtable.insert(buffer);
if (ans == 0) {
cout << "recreate while insert!" << endl;
} else if (ans == 1) {
cout << "insert success!" << endl;
} else if (ans == 2) {
cout << "It already exists!" << endl;
}
}
int temp_pos, temp_times;
cin >> buffer;
if (hashtable.search(buffer, temp_pos, temp_times)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
return 0;
}