C++ hash table 的编程实现
2014-08-21 16:26
423 查看
关于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的了。 使用允许一个散列表元放置多个元素的方法,这时实际上散列表就是一个链表数组
程序如下:
code::blocks 新建工程, 一个主程序文件, 一个hash.h 文件, hash.cpp 文件。 主程序如下:
hash.h 文件:
hash.cpp 文件:
根据提示, 运行结果如下:
散列表能够实现通过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的了。 使用允许一个散列表元放置多个元素的方法,这时实际上散列表就是一个链表数组
程序如下:
code::blocks 新建工程, 一个主程序文件, 一个hash.h 文件, hash.cpp 文件。 主程序如下:
#include <iostream> #include <cstdlib> #include <string> #include "hash.h" using namespace std; int main() { Hash Hashy; string name1 = " "; string name2 = " "; Hashy.PrintTable(); Hashy.AddItem("Paul", "Locha"); Hashy.AddItem("Kim", "Iced Mocha"); Hashy.AddItem("Anni", "Strawberry Smoothy"); Hashy.AddItem("Sara", "Passion Tea"); Hashy.AddItem("Mike", "Tea"); Hashy.AddItem("steve", "Apple cider"); Hashy.AddItem("Sill", "Root beer"); Hashy.AddItem("Bill", "Lochs"); Hashy.AddItem("Susan", "Cola"); Hashy.AddItem("Joe", "Green Tea"); Hashy.PrintTable(); Hashy.PrintItemsInIndex(0); while(name1 != "exit") { cout << "search for: "; cin >> name1; if(name1 != "exit") { Hashy.FindDrink(name1); } } while(name2 != "exit") { cout << "Remove: "; cin >> name2; if(name2 != "exit") { Hashy.RemoveItem(name2); } } Hashy.PrintTable(); Hashy.PrintItemsInIndex(0); return 0; }
hash.h 文件:
#ifndef HASH_H #define HASH_H #include <iostream> #include <cstdlib> #include <string> using namespace std; struct item { string name; string drink; item* next; }; class Hash{ public: int hashFunction(string key); 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); private: static const int tableSize = 10; // 修改为40再试试看, 存储会变换 item* HashTable[tableSize]; // in this case, 10 buckets, each bucket contains a pointer that has the ability to point to some item }; #endif // HASH_H
hash.cpp 文件:
#include <iostream> #include <cstdlib> #include <string> #include "hash.h" using namespace std; Hash::Hash() { for (int i = 0; i < tableSize; i++) { HashTable[i] = new item; HashTable[i] -> name = "empty"; HashTable[i] -> drink = "empty"; HashTable[i] -> next = NULL; } } int Hash::hashFunction(string key) { int sum = 0; int index = 0; index = key.length(); // find the length of the string passing to the function for (int i = 0; i < index; i++) { sum += static_cast<int>(key[i]); // 可以修改hash function } // 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; index = sum % tableSize; 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; // HashTable[index] -> next = NULL; } else { item* ptr = HashTable[index]; item* n = new item; n -> name = name; n -> drink = drink; n -> next = NULL; while (ptr -> next != NULL) { ptr = ptr -> next; } ptr -> next = n; } } int Hash::NumberOfItemsInIndex(int index) { int Count = 0; // 为什么注释掉的这个统计的程序会出现问题 // 因为Hash[index]????? // item* ptr = HashTable[index]; // // while (ptr -> name != "empty") { // Count++; // ptr = ptr -> next; // } if (HashTable[index] -> name == "empty") { return Count; } else { Count++; item* ptr = HashTable[index]; while (ptr ->next != NULL) { Count++; ptr = ptr -> next; } } return Count; } void Hash::PrintTable() { int number; //number of items in each bucket for (int i = 0; i < tableSize; i++) { cout << "i = " << i << ": " << endl; // print the first element in the bucket, and we can also see if there are some other items number = NumberOfItemsInIndex(i); cout << "--------------\n"; cout << "index = " << i << ": " << endl; cout << HashTable[i] -> name << endl; cout << HashTable[i] -> drink << endl; cout << "# of items = " << number << endl; cout << "--------------\n"; } } void Hash::PrintItemsInIndex(int index) { item* ptr = HashTable[index]; if(ptr -> name == "empty") { cout << "index = " << index << " is empty"; } else { cout << "index " << index << " conatains the following items \n"; while(ptr != NULL) { cout << "--------------\n"; cout << ptr -> name << endl; cout << ptr -> drink << endl; cout << "--------------\n"; ptr = ptr -> next; } } } void Hash::FindDrink(string name) { int index = hashFunction(name); bool FoundName = false; string drink; item* ptr = HashTable[index]; while(ptr != NULL) { if(ptr -> name == name) { FoundName = true; drink = ptr -> drink; } ptr = ptr ->next; } if(FoundName == true) { cout << "Favorite drink = " << drink << endl; } else { cout << name << "'s info was not found in the hash table. \n"; } } void Hash::RemoveItem(string name) { int index = hashFunction(name); item* delPtr; item* P1; item* P2; // case 0: bucket is empty if (HashTable[index] -> name == "empty" && HashTable[index] -> drink == "empty") { cout << name << " was not found in the hash table. \n"; } //case 1: only one item contained in the bucket, and that item //has matching name else if(HashTable[index] -> name == name && HashTable[index] -> next == NULL) { HashTable[index] -> name = "empty"; HashTable[index] -> drink = "empty"; cout << name << " was removed from the hash table. \n"; } //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. \n"; } // case 3: the bucket contains items, but first item is not a match else { P1 = HashTable[index] -> next; P2 = HashTable[index]; while(P1 != NULL && P1 -> name != name) { P2 = P1; P1 = P1 -> next; } // case 3.1: no match if(P1 ==NULL) { 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. \n"; } } }
根据提示, 运行结果如下:
相关文章推荐
- 仿基因编程原理及其C++实现
- 仿基因编程原理及其C++实现
- CP Socket编程 C/C++实现 (Windows Platform SDK)
- TCP(SOCKETS)编程 C/C++实现
- 抱歉,没分了!!!请教linux下,用C/C++编程,如何知道我要实现某一功能需要用到什么函数
- 仿基因编程原理及其C++实现
- TCP/UDP Socket编程 C/C++实现(Windows Platform SDK)
- TCP Socket编程 C/C++实现 (Windows Platform SDK)
- UDP Socket编程 C/C++实现
- C++ GUI QT 编程(第二版) -第4章 实现功能源码简介_1
- 仿基因编程原理及其C++实现
- 仿基因编程原理及其C++实现
- 仿基因编程原理及其C++实现
- CP Socket编程 C/C++实现 (Windows Platform SDK)
- UDP Socket编程 C/C++实现 (Windows Platform SDK)
- 网络编程概念全面接触(四).C++与Java的实现
- 仿基因编程原理及其C++实现
- 仿基因编程原理及其C++实现
- TCP Socket编程 C/C++实现 (Windows Platform SDK)
- C/C++实现多参数函数编程