AOP-Chap23-Hash Tables 哈希表

1 Hash Table Basics

  • hash table实现O(1)
    1. 对key应用一个散列函数,该函数将其转换为一个无符号整数(可能对数据的底层数字结构进行数学计算)
    2. 计算H%N,H是散列函数应用到key的结果,N是数组的大小
    3. 使用结果值在数组中建立索引
  • 以上操作所需时间与number of elements无关,而是取决于size of the key,因为mod时间为常数,indexing an array时间为常数
  • make一个泛型hash table --> 传递一个template parameter来说明如何对键进行hash。这个模板形参可以命名一个类,该类的函数调用操作符(operator())重载以接受对键的const reference并返回unsigned int,并且它通过执行所需的散列操作来做到这一点。然后哈希表可以创建该对象的实例,并使用它
template<typename Key, typename Value, typename Hasher>
class HashMap {
private:
	Hasher hash;
public:
	void add(const Key & k, const Value & v) { 
		unsigned int h = hash(k);
		//...everything else elided
	}	
};

2 Collision Resolution

  • collision --> mod后产生相同的index

2.1 Chaining

  • 使用chaining——maintain一个链表数组。链表中的每个节点都拥有一个键/值对(for a map,or just the item for a set)
//implement a map
template<typename Key, typename Value, typename Hasher>
class HashMap {
private:
	Hasher hash;
	vector<list<pair<Key, Value> > > table;
public:
	void add(const Key & k, const Value & v) { 
		unsigned int h = hash(k);
		h = h % table.size(); 
		table[h].push_front(pair<Key, Value>(k, v));
	}
};			
  • A hash table with external chaining
    请添加图片描述
    请添加图片描述
    在这里插入图片描述
    请添加图片描述
  • 查找一个item时,要搜索一个linked list,是O(N)而不是O(1)
  • bucket --> vector/数组元素的数量
  • 如果bucket的数量是O(N)(并且我们将元素平均分配在它们之间),那么access time是O(1),即O(N)/O(N)
  • 保证每条链都很短,长度基本上都为1,每个bucket都有东西不为空

2.2 Open Addressing

  • open addressing --> 寻找附近未使用的数组索引
  • open addressing 最简单的形式是linear probing --> 顺序尝试索引,直到找到一个开放的索引
  • 可以用不同的步长来执行线性探测,比如每次添加三个。步长是恒定的
  • open addressing的另一种形式 quadratic probing 二次探测 --> 不是每次都使用常数步长,而是每次都增加步长 --> 如果数据聚集在一个区域,算法将更快地走出该集群
  • 在这两种方法中,当查找(或删除)一个项目时,需要考虑它在添加时遇到collision的可能性,即不在初始index的位置。
  • 如果不支持“删除”,可以搜索(遵循相同的探测方案),直到找到所需的键,或者找到一个empty bucket
  • 如果支持“remove”操作,empty bucket不足以表明所需的键不在该点之后——可能在插入时,这个bucket有内容,但随后被删除了。可以继续搜索,直到扫描完整个表;然而这种方法是低效的。可以区分“真正空”的桶和有数据但数据已被删除的桶——当遇到真正空的桶时,则停止。但由于许多插入和删除操作,table的表现可能会下降,因为“真正的空”桶消失了——可以通过定期清理表来解决这个问题:将每个项重新插入到一个新表中,然后重新开始

3 Hashing Functions

  • hash function is valid
    1. hash(x)永远返回相同的值,除非修改x的值
    2. 任何两个obejct a和b, 如果a==b, 那么hash(a)==hash(b)

3.1 A Bad Hash Function for Strings

unsigned hash(const std::string & str) {
	unsigned ans = 0;
	for (std::string::const_iterator it = str.begin();it != str.end(); ++it) {
  		ans += *it;
	}
	return ans;
}	
  • 把每个字母对应的数字相加,有无数重复

3.2 A Better Hash Function for Strings

unsigned hash(const std::string & str) {
	unsigned ans = 0;
	for (std::string::const_iterator it = str.begin();it != str.end(); ++it) {
  		ans = ans * 29 + *it;
	}
	return ans;
}	
  • 29是质数且比26大(小写字母a-z对应1-26)
  • 最终table的bucket数也比第一种大很多

3.3 Cryptographic Hash Functions 加密哈希函数

  • 很难产生重复

3.4 Hash Function Objects

  • write by our own
class StringHasher {
private:
	const int multiplyBy;
public:	
	StringHasher() : multiplyBy(29) {}
	StringHasher(int m) : multiplyBy(m) {}
	unsigned operator() (const std::string & str) const {
		unsigned ans = 0;
		for (std::string::const_iterator it = str.begin(); it!=str.end(); ++it) {
    		ans = ans * multiplyBy + *it;
		}
		return ans; 
	}
};	

4 Rehashing

  • rehashing --> 随着table中元素数量的增加,需要调整table的大小
  • load factor加载因子——表中实际存储的元素数量与bucket总数的比值,通常在0.5-0.8
  • 第一步是分配一个更大的数组(或vector)——不能重用/增加现有的数组。新数组的大小大约是现有数组的两倍,将重散列操作的成本分摊到许多额外的插入操作上。可以精确地将大小翻倍,尽管可能希望使用质数bucket(在这种情况下,将保留一个质数数组用于表示大小,其中一个大约是前一个的两倍,然后在列表中转到下一个大小)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    请添加图片描述
    请添加图片描述请添加图片描述
    在这里插入图片描述

4.1 Hash Table Sizing

  • 选择size翻倍或者质数有tradeoff
  • 如果保持哈希表的大小为2的幂,可以更有效地执行mod操作;如果使用质数,减少引入额外碰撞的机会
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值