关于Hash Table, 总结如下:
散列表能够实现通过key 对元素的快速访问。 而且易于扩展。 对元素能够实现快速访问(搜索等字典操作), 这是Hash Table 较之于链表的优势所在, 二者均易于扩展。 而易于扩展这个dynamic的结构(使用链接法的时候)又是较之于array的优势所在。 因为数组时不易于扩展的。
使用散列表, 我们需要使用Hash function。 散列函数是对关键字和散列表元提供映射的函数。 常见的Hash function 有如下几种:
(1)直接寻址法。 取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)
(2)除法散列法, 关键字k 除以m 取余数, 将关键字k 映射到m个slots 中的一个上, 即 h(k) = k mod m, 一般取m 为素数。
(3)乘法散列法: 包含两步:
step 1:用关键字k 乘上常数A(0<A <1), 并提取kA d的小数部分。
step 2: 用m 乘以这个小数部分的值, 再往下取整,
总之, 散列函数为: h(k) = floor(m(kA mod 1))。 乘法散列的好处就是m 可以是2的某个次幂。 而不只是素数了。
(4) 折叠法: 将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
等等还有好多散列函数。
散列函数的性能直接影响着散列表数据结构的性能。 当散列函数把太少或太多关键字映射到单个散列表元上时,散列表的使用效率会大大降低。
性能最好的散列函数被称为完全散列函数。即保证所有可能的关键字都会分别映射到唯一的散列表元编号上。
通常很难找到一个完全散列函数。 不完全的散列函数就会导致冲突的发生。 冲突是散列表的一个缺点之一。
一般而言, 冲突是无法避免的。 因为关键字的域是无穷的, 而映射到的表中, 表的大小是一定的, 不可能是无穷大的。
既然无法避免冲突, 那么我们就要解决冲突, 解决办法有如下几种:
方法一: 开放寻址法(open addressing):
所有的元素都在散列表中。 不会像链接法那样, 这里没有链表, 也没有链表存放在散列表外。 缺点是散列表可能被插满了。 而且此时的Hash Table也不容易扩展, 即不是dynamic的了。 我们希望我们的散列函数实现均匀散列(uniform hashing), 即每个关键字等可能的插入到slot 中。 但是这只是理想, 通常情况下, 会出现cluste的现象。 出现冲突的时候, 有三种方法用于探查开放寻址中的探查序列:(1)Linear probing(线性探查)(2)二次探查 (3)双重探查。
方法二: 链接法(chaining)。 不难看出此时我们的Hash Table 变成了dynamic的了。 使用允许一个散列表元放置多个元素的方法,这时实际上散列表就是一个链表数组
基于链接法的C++代码如下:
hashtable.h
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include <iostream>
#include <cstdlib>
#include <string>
using namespace std;
class Hash
{
private:
struct item
{
string name;
string drink;
item* next;
item(): name("empty"), drink("empty"), next(nullptr) {}
item(string n, string d): name(n), drink(d), next(nullptr) {}
};
static const int tableSize = 10; // 存储量可改变
item* HashTable[tableSize];
public:
int hashFunction(string key);
Hash();
~Hash();
void AddItem(string name, string drink);
int NumberOfItemsInIndex(int index);
void PrintTable();
void PrintItemsInIndex(int index);
void FindDrink(string name);
void RemoveItem(string name);
};
#endif // HASHTABLE_H
hashtable.cpp
#include "hashtable.h"
Hash::Hash()
{
for (int i = 0; i < tableSize; ++i)
{
HashTable[i] = new item();
HashTable[i]->name = "empty";
HashTable[i]->drink = "empty";
HashTable[i]->next = nullptr;
}
}
Hash::~Hash()
{
}
int Hash::hashFunction(string key)
{
int sum = 0;
int index;
for (size_t i = 0; i < key.size(); ++i)
{
sum += static_cast<int>(key[i]);
}
index = sum % tableSize;
cout << "key[0] = " << key[0] << endl;
cout << "key[0] = " << static_cast<int>(key[0]) << endl;
cout << "key[1] = " << key[1] << endl;
cout << "key[2] = " << key[2] << endl;
cout << "sum = " << sum << endl;
cout << "index = " << index << endl << endl;
return index;
}
void Hash::AddItem(string name, string drink)
{
int index = hashFunction(name);
if (HashTable[index]->name == "empty")
{
HashTable[index]->name = name;
HashTable[index]->drink = drink;
}
else
{
item* p = HashTable[index];
item* n = new item(name, drink);
while (p->next != nullptr)
{
p = p->next;
}
p->next = n;
}
}
int Hash::NumberOfItemsInIndex(int index)
{
int count = 0;
if (HashTable[index]->name == "empty")
{
return count;
}
else
{
count++;
item* p = HashTable[index];
while (p->next != nullptr)
{
count++;
p = p->next;
}
}
return count;
}
void Hash::PrintTable()
{
int number;
for (int i = 0; i < tableSize; ++i)
{
cout << "i = " << i << ": " << endl;
number = NumberOfItemsInIndex(i);
cout << "index = " << i << ": " << endl;
cout << HashTable[i]->name << ", " << HashTable[i]->drink << endl;
cout << "# of items = " << number << endl << endl;
}
}
void Hash::PrintItemsInIndex(int index)
{
item* p = HashTable[index];
if (p->name == "empty")
{
cout << "index = " << index << "is empty";
}
else
{
cout << "index " << index << " contains the following items \n";
while (p != nullptr)
{
cout << p->name << ", " << p->drink <<endl;
p = p->next;
}
}
}
void Hash::FindDrink(string name)
{
int index = hashFunction(name);
bool FindName = false;
string drink;
item* p = HashTable[index];
while (p != nullptr)
{
if (p->name == name)
{
FindName = true;
drink = p->drink;
}
p = p->next;
}
if (FindName)
{
cout << "Favorite drink " << drink << endl;
}
else
{
cout << name << "'s info was not fount in the hash table.\n";
}
}
void Hash::RemoveItem(string name)
{
int index = hashFunction(name);
item* delPtr;
item* p1;
item* p2;
// case0: bucket is empty
if (HashTable[index]->name == "empty" &&
HashTable[index]->drink == "empty")
{
cout << name << " was not found in the hash table" << endl;
}
// case1: only one item contained in the bucket, and that item has matching name
else if (HashTable[index]->name == name &&
HashTable[index]->next == nullptr)
{
HashTable[index]->name = "empty";
HashTable[index]->drink = "empty";
cout << name << " was removed from the hash table" << endl;
}
// case 2: match is located in the first item in the bucket and there are more items in the bucket
else if (HashTable[index]->name == name)
{
delPtr = HashTable[index];
HashTable[index] = HashTable[index]->next;
delete delPtr;
cout << name << " was removed from the hash table" << endl;
}
// case 3: the bucket contains items, but first item is not a match
else
{
p1 = HashTable[index]->next;
p2 = HashTable[index];
while (p1 != nullptr && p1->name != name)
{
p2 = p1;
p1 = p1->next;
}
// case 3.1: no match
if (p1 == nullptr)
{
cout << name << " was not found in the hash table. \n";
}
//case 3.2: match is found
else
{
delPtr = p1;
p1 = p1->next;
p2->next = p1;
delete delPtr;
cout << name << " was removed from the hash table" << endl;
}
}
}
main.cpp
#include "hashtable.cpp"
int main()
{
Hash hash;
string name1 = " ";
string name2 = " ";
hash.PrintTable();
hash.AddItem("Paul", "Locha");
hash.AddItem("Kim", "Iced Mocha");
hash.AddItem("Anni", "Strawberry Smoothy");
hash.AddItem("Sara", "Passion Tea");
hash.AddItem("Mike", "Tea");
hash.AddItem("steve", "Apple cider");
hash.AddItem("Sill", "Root beer");
hash.AddItem("Bill", "Lochs");
hash.AddItem("Susan", "Cola");
hash.AddItem("Joe", "Green Tea");
hash.PrintTable();
hash.PrintItemsInIndex(0);
while (name1 != "exit")
{
cout << "search for: ";
cin >> name1;
if (name1 != "exit")
{
hash.FindDrink(name1);
}
}
cout << endl;
while (name2 != "exit")
{
cout << "Remove: ";
cin >> name2;
if (name2 != "exit")
{
hash.RemoveItem(name2);
}
}
hash.PrintTable();
hash.PrintItemsInIndex(0);
return 0;
}
部分运行结果如下: