《算法导论》读书笔记之第11章 散列表
2016-06-24 07:49
471 查看
本章介绍了散列表(hash table)的概念、散列函数的设计及散列冲突的处理。散列表类似与字典的目录,查找的元素都有一个key与之对应,在实践当中,散列技术的效率是很高的,合理的设计散函数和冲突处理方法,可以使得在散列表中查找一个元素的期望时间为O(1)。散列表是普通数组概念的推广,在散列表中,不是直接把关键字用作数组下标,而是根据关键字通过散列函数计算出来的。书中介绍散列表非常注重推理和证明,看的时候迷迷糊糊的,再次证明了数学真的很重要。在STL中map容器的功能就是散列表的功能,但是map采用的是红黑树实现的,后面接着学习,关于map的操作可以参考:http://www.cplusplus.com/reference/map/。
1、直接寻址表
当关键字的的全域(范围)U比较小的时,直接寻址是简单有效的技术,一般可以采用数组实现直接寻址表,数组下标对应的就是关键字的值,即具有关键字k的元素被放在直接寻址表的槽k中。直接寻址表的字典操作实现比较简单,直接操作数组即可以,只需O(1)的时间。
2、散列表
直接寻址表的不足之处在于当关键字的范围U很大时,在计算机内存容量的限制下,构造一个存储|U|大小的表不太实际。当存储在字典中的关键字集合K比所有可能的关键字域U要小的多时,散列表需要的存储空间要比直接寻址表少的很多。散列表通过散列函数h计算出关键字k在槽的位置。散列函数h将关键字域U映射到散列表T[0....m-1]的槽位上。即h:U->{0,1...,m-1}。采用散列函数的目的在于缩小需要处理的小标范围,从而降低了空间的开销。
散列表存在的问题:两个关键字可能映射到同一个槽上,即碰撞(collision)。需要找到有效的办法来解决碰撞。
3、散列函数
好的散列函数的特点是每个关键字都等可能的散列到m个槽位上的任何一个中去,并与其他的关键字已被散列到哪一个槽位无关。多数散列函数都是假定关键字域为自然数N={0,1,2,....},如果给的关键字不是自然数,则必须有一种方法将它们解释为自然数。例如对关键字为字符串时,可以通过将字符串中每个字符的ASCII码相加,转换为自然数。书中介绍了三种设计方案:除法散列法、乘法散法和全域散列法。
(1)除法散列法
通过取k除以m的余数,将关键字k映射到m个槽的某一个中去。散列函数为:h(k)=k mod m 。m不应是2的幂,通常m的值是与2的整数幂不太接近的质数。
(2)乘法散列法
这个方法看的时候不是很明白,没有搞清楚什么意思,先将基本的思想记录下来,日后好好消化一下。乘法散列法构造散列函数需要两个步骤。第一步,用关键字k乘上常数A(0<A<1),并抽取kA的小数部分。然后,用m乘以这个值,再取结果的底。散列函数如下:h(k) = m(kA mod 1)。
(3)全域散列
给定一组散列函数H,每次进行散列时候从H中随机的选择一个散列函数h,使得h独立于要存储的关键字。全域散列函数类的平均性能是比较好的。
4、碰撞处理
通常有两类方法处理碰撞:开放寻址(Open Addressing)法和链接(Chaining)法。前者是将所有结点均存放在散列表T[0..m-1]中;后者通常是把散列到同一槽中的所有元素放在一个链表中,而将此链表的头指针放在散列表T[0..m-1]中。
(1)开放寻址法
所有的元素都在散列表中,每一个表项或包含动态集合的一个元素,或包含NIL。这种方法中散列表可能被填满,以致于不能插入任何新的元素。在开放寻址法中,当要插入一个元素时,可以连续地检查或探测散列表的各项,直到有一个空槽来放置待插入的关键字为止。有三种技术用于开放寻址法:线性探测、二次探测以及双重探测。
<1>线性探测
给定一个普通的散列函数h':U —>{0,1,.....,m-1},线性探测方法采用的散列函数为:h(k,i) = (h'(k)+i)mod m,i=0,1,....,m-1
探测时从i=0开始,首先探查T[h'(k)],然后依次探测T[h'(k)+1],…,直到T[h'(k)+m-1],此后又循环到T[0],T[1],…,直到探测到T[h'(k)-1]为止。探测过程终止于三种情况:
(1)若当前探测的单元为空,则表示查找失败(若是插入则将key写入其中);
(2)若当前探测的单元中含有key,则查找成功,但对于插入意味着失败;
(3)若探测到T[h'(k)-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。
线性探测方法较容易实现,但是存在一次群集问题,即连续被占用的槽的序列变的越来越长。采用例子进行说明线性探测过程,已知一组关键字为(26,36,41,38,44,15,68,12,6,51),用除余法构造散列函数,初始情况如下图所示:
程序测试结果如下所示:
参考:http://www.cnblogs.com/zhanglanyun/archive/2011/09/01/2161729.html
1、直接寻址表
当关键字的的全域(范围)U比较小的时,直接寻址是简单有效的技术,一般可以采用数组实现直接寻址表,数组下标对应的就是关键字的值,即具有关键字k的元素被放在直接寻址表的槽k中。直接寻址表的字典操作实现比较简单,直接操作数组即可以,只需O(1)的时间。
2、散列表
直接寻址表的不足之处在于当关键字的范围U很大时,在计算机内存容量的限制下,构造一个存储|U|大小的表不太实际。当存储在字典中的关键字集合K比所有可能的关键字域U要小的多时,散列表需要的存储空间要比直接寻址表少的很多。散列表通过散列函数h计算出关键字k在槽的位置。散列函数h将关键字域U映射到散列表T[0....m-1]的槽位上。即h:U->{0,1...,m-1}。采用散列函数的目的在于缩小需要处理的小标范围,从而降低了空间的开销。
散列表存在的问题:两个关键字可能映射到同一个槽上,即碰撞(collision)。需要找到有效的办法来解决碰撞。
3、散列函数
好的散列函数的特点是每个关键字都等可能的散列到m个槽位上的任何一个中去,并与其他的关键字已被散列到哪一个槽位无关。多数散列函数都是假定关键字域为自然数N={0,1,2,....},如果给的关键字不是自然数,则必须有一种方法将它们解释为自然数。例如对关键字为字符串时,可以通过将字符串中每个字符的ASCII码相加,转换为自然数。书中介绍了三种设计方案:除法散列法、乘法散法和全域散列法。
(1)除法散列法
通过取k除以m的余数,将关键字k映射到m个槽的某一个中去。散列函数为:h(k)=k mod m 。m不应是2的幂,通常m的值是与2的整数幂不太接近的质数。
(2)乘法散列法
这个方法看的时候不是很明白,没有搞清楚什么意思,先将基本的思想记录下来,日后好好消化一下。乘法散列法构造散列函数需要两个步骤。第一步,用关键字k乘上常数A(0<A<1),并抽取kA的小数部分。然后,用m乘以这个值,再取结果的底。散列函数如下:h(k) = m(kA mod 1)。
(3)全域散列
给定一组散列函数H,每次进行散列时候从H中随机的选择一个散列函数h,使得h独立于要存储的关键字。全域散列函数类的平均性能是比较好的。
4、碰撞处理
通常有两类方法处理碰撞:开放寻址(Open Addressing)法和链接(Chaining)法。前者是将所有结点均存放在散列表T[0..m-1]中;后者通常是把散列到同一槽中的所有元素放在一个链表中,而将此链表的头指针放在散列表T[0..m-1]中。
(1)开放寻址法
所有的元素都在散列表中,每一个表项或包含动态集合的一个元素,或包含NIL。这种方法中散列表可能被填满,以致于不能插入任何新的元素。在开放寻址法中,当要插入一个元素时,可以连续地检查或探测散列表的各项,直到有一个空槽来放置待插入的关键字为止。有三种技术用于开放寻址法:线性探测、二次探测以及双重探测。
<1>线性探测
给定一个普通的散列函数h':U —>{0,1,.....,m-1},线性探测方法采用的散列函数为:h(k,i) = (h'(k)+i)mod m,i=0,1,....,m-1
探测时从i=0开始,首先探查T[h'(k)],然后依次探测T[h'(k)+1],…,直到T[h'(k)+m-1],此后又循环到T[0],T[1],…,直到探测到T[h'(k)-1]为止。探测过程终止于三种情况:
(1)若当前探测的单元为空,则表示查找失败(若是插入则将key写入其中);
(2)若当前探测的单元中含有key,则查找成功,但对于插入意味着失败;
(3)若探测到T[h'(k)-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。
线性探测方法较容易实现,但是存在一次群集问题,即连续被占用的槽的序列变的越来越长。采用例子进行说明线性探测过程,已知一组关键字为(26,36,41,38,44,15,68,12,6,51),用除余法构造散列函数,初始情况如下图所示:
1 #include <iostream> 2 #include <vector> 3 #include <list> 4 #include <string> 5 #include <cstdlib> 6 #include <cmath> 7 #include <algorithm> 8 using namespace std; 9 10 int nextPrime(const int n); 11 12 template <class T> 13 class HashTable 14 { 15 public: 16 HashTable(int size = 101); 17 int insert(const T& x); 18 int remove(const T& x); 19 int contains(const T& x); 20 void make_empty(); 21 void display()const; 22 private: 23 vector<list<T> > lists; 24 int currentSize; 25 int hash(const string& key); 26 int myhash(const T& x); 27 void rehash(); 28 }; 29 30 template <class T> 31 HashTable<T>::HashTable(int size) 32 { 33 lists = vector<list<T> >(size); 34 currentSize = 0; 35 } 36 37 template <class T> 38 int HashTable<T>::hash(const string& key) 39 { 40 int hashVal = 0; 41 int tableSize = lists.size(); 42 for(int i=0;i<key.length();i++) 43 hashVal = 37*hashVal+key[i]; 44 hashVal %= tableSize; 45 if(hashVal < 0) 46 hashVal += tableSize; 47 return hashVal; 48 } 49 50 template <class T> 51 int HashTable<T>:: myhash(const T& x) 52 { 53 string key = x.getName(); 54 return hash(key); 55 } 56 template <class T> 57 int HashTable<T>::insert(const T& x) 58 { 59 list<T> &whichlist = lists[myhash(x)]; 60 if(find(whichlist.begin(),whichlist.end(),x) != whichlist.end()) 61 return 0; 62 whichlist.push_back(x); 63 currentSize = currentSize + 1; 64 if(currentSize > lists.size()) 65 rehash(); 66 return 1; 67 } 68 69 template <class T> 70 int HashTable<T>::remove(const T& x) 71 { 72 73 typename std::list<T>::iterator iter; 74 list<T> &whichlist = lists[myhash(x)]; 75 iter = find(whichlist.begin(),whichlist.end(),x); 76 if( iter != whichlist.end()) 77 { 78 whichlist.erase(iter); 79 currentSize--; 80 return 1; 81 } 82 return 0; 83 } 84 85 template <class T> 86 int HashTable<T>::contains(const T& x) 87 { 88 list<T> whichlist; 89 typename std::list<T>::iterator iter; 90 whichlist = lists[myhash(x)]; 91 iter = find(whichlist.begin(),whichlist.end(),x); 92 if( iter != whichlist.end()) 93 return 1; 94 return 0; 95 } 96 97 template <class T> 98 void HashTable<T>::make_empty() 99 { 100 for(int i=0;i<lists.size();i++) 101 lists[i].clear(); 102 currentSize = 0; 103 return 0; 104 } 105 106 template <class T> 107 void HashTable<T>::rehash() 108 { 109 vector<list<T> > oldLists = lists; 110 lists.resize(nextPrime(2*lists.size())); 111 for(int i=0;i<lists.size();i++) 112 lists[i].clear(); 113 currentSize = 0; 114 for(int i=0;i<oldLists.size();i++) 115 { 116 typename std::list<T>::iterator iter = oldLists[i].begin(); 117 while(iter != oldLists[i].end()) 118 insert(*iter++); 119 } 120 } 121 template <class T> 122 void HashTable<T>::display()const 123 { 124 for(int i=0;i<lists.size();i++) 125 { 126 cout<<i<<": "; 127 typename std::list<T>::const_iterator iter = lists[i].begin(); 128 while(iter != lists[i].end()) 129 { 130 cout<<*iter<<" "; 131 ++iter; 132 } 133 cout<<endl; 134 } 135 } 136 int nextPrime(const int n) 137 { 138 int ret,i; 139 ret = n; 140 while(1) 141 { 142 int flag = 1; 143 for(i=2;i<sqrt(ret);i++) 144 if(ret % i == 0) 145 { 146 flag = 0; 147 break; 148 } 149 if(flag == 1) 150 break; 151 else 152 { 153 ret = ret +1; 154 continue; 155 } 156 } 157 return ret; 158 } 159 160 class Employee 161 { 162 public: 163 Employee(){} 164 Employee(const string n,int s=0):name(n),salary(s){ } 165 const string & getName()const { return name; } 166 bool operator == (const Employee &rhs) const 167 { 168 return getName() == rhs.getName(); 169 } 170 bool operator != (const Employee &rhs) const 171 { 172 return !(*this == rhs); 173 } 174 friend ostream& operator <<(ostream& out,const Employee& e) 175 { 176 out<<"("<<e.name<<","<<e.salary<<") "; 177 return out; 178 } 179 private: 180 string name; 181 int salary; 182 }; 183 184 int main() 185 { 186 Employee e1("Tom",6000); 187 Employee e2("Anker",7000); 188 Employee e3("Jermey",8000); 189 Employee e4("Lucy",7500); 190 HashTable<Employee> emp_table(13); 191 192 emp_table.insert(e1); 193 emp_table.insert(e2); 194 emp_table.insert(e3); 195 emp_table.insert(e4); 196 197 cout<<"Hash table is: "<<endl; 198 emp_table.display(); 199 if(emp_table.contains(e4) == 1) 200 cout<<"Tom is exist in hash table"<<endl; 201 if(emp_table.remove(e1) == 1) 202 cout<<"Removing Tom form the hash table successfully"<<endl; 203 if(emp_table.contains(e1) == 1) 204 cout<<"Tom is exist in hash table"<<endl; 205 else 206 cout<<"Tom is not exist in hash table"<<endl; 207 //emp_table.display(); 208 exit(0); 209 }
程序测试结果如下所示:
参考:http://www.cnblogs.com/zhanglanyun/archive/2011/09/01/2161729.html
相关文章推荐
- 文章标题
- php 学习笔记之jquery
- php 练习题-留言板
- [leetcode] 341. Flatten Nested List Iterator
- postgresql----INSERT
- linux磁盘分区之parted分区工具
- 很详细的log4j使用教程
- Perfect Squares
- Retrofit分析与实现
- Java一切皆对象
- Kaggle digit-recognizer PCA+SVM
- PHP设置头信息,取得返回头信息
- CMS学习 --- joomla数据库表结构
- javaScript基础:Object类型
- First blog
- Angular 2与TypeScript概览
- 使用 Autofac 进行依赖注入
- 大型互联网技术架构3-分布式存储-I
- 网页常用header头部定义
- 老蔡学堂