哈希表

哈希表又称散列表,存储键值对,主要操作有插入,删除,查找。哈希表底层是一个数组,首先将key通过哈希函数计算哈希值,也即数组下标,然后对位置上的元素执行插入删除,查询。三种操作时间复杂度均为 O ( 1 ) O(1) O(1),耗时操作在哈希函数计算和哈希冲突的处理上。

哈希函数(*)

哈希函数的要求是,映射均匀,降低哈希冲突的几率;计算简单,节省时间。

  1. 直接定址法
    使用key的线性函数作为哈希函数,H(key) = a*key + b,其中a和b为常数。
  2. 除留取余法
    key值对不大于数组长度m的数值p取余。H(key) = key % p, (p<=m)。
  3. 数字分析
    当关键字的位数大于数组下标的位数,可以取key值中分布比较均匀的几位作为哈希值。如学号201714378,前四位是入学时间,后四位(编号)可以做哈希值。
  4. 平方取中法
    当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。

哈希冲突

在大量关键字中,哈希函数难免对不同的关键字产生相同的哈地址,也即哈希冲突。

  1. 拉链法
    把地址冲突的键值对用链表存储起来,链表节点存储关键字和值。查找时首先定位到数组位置,然后把待查询的key和链表节点一次比较,如果找到就返回值,如果遍历到链表结束就表示关键字不再数组中。当冲突非常严重,所有元素存在同一条链表时,查找的时间复杂度达到 O ( n ) O(n) O(n),当然也可以提高查询效率用红黑树代替链表存储。插入和删除也要首先做查找操作,不再赘述。
  2. 开放寻址法
    2.1 线性探测法

插入操作:如果出现冲突,就从数组当前位置向后寻找,遇到第一个空闲位置就把键值对存储在该处;如果没有空闲位置继续从数组开始处寻找。
查询操作:从冲突的位置向后寻找,将待查关键字与数组元素依次比较,相等就返回,如果找到空闲位置还没匹配,就表示关键字不再数组中。
删除操作:找到键值对存放位置后,删除元素,并把当前位标记为detected而不能变成空闲位,否则会影响其他元素的查找。(其他元素本来放在当前元素后面,现在由于删除操作在当前位置产生空闲位置,使其他元素的查找在当前位置终止而产生误判)。查找的话遇到detect继续查找。

极端情况下,需要探测整个表,最坏时间复杂度 O ( n ) O(n) O(n)

2.2 二次探测法
探测的步长以 d 2 , d = 1 , 2 , 3... d^2,d=1,2,3... d2,d=1,2,3...递增,探测位置为 a r r [ k + 1 2 ] , a r r [ k + 2 2 ] . . . arr[k+1^2],arr[k+2^2]... arr[k+12],arr[k+22]...。缺点:数组容量为偶数时无法探测整个数组空间。
2.3 双重散列
出现冲突后,再使用一个哈希函数计算哈希地址,并加在当前位置上成为新的哈希地址。 d = ( h a s h 1 ( k e y ) + i ∗ h a s h 2 ( k e y ) ) % m d=(hash1(key) + i*hash2(key))\%m d=(hash1(key)+ihash2(key))%m

拉链法和开放寻址法比较
  1. 拉链法冲突代价更低。冲突后开放寻址要遍历整个数组,而拉链法只遍历当前位置上的链表,复杂度要小很多。
  2. 拉链法内存利用率更高。拉链法的负载因子可以大于1,因为开放寻址的负载因子在接近1时产生大量冲突,所以上限不能太大(必须<=1),造成数组很多空闲位置。

负载因子:又称装载因子, 哈 希 表 中 元 素 总 数 / 数 组 大 小 哈希表中元素总数/数组大小 /

rehash

哈 希 表 中 元 素 总 数 > 负 载 因 子 ∗ 数 组 大 小 哈希表中元素总数> 负载因子 * 数组大小 >时,冲突会非常明显影响查询效率。所以要rehash,首先申请一个两倍容量的新数组,然后把原数组中的键值对拷贝到新数组中。由于全部拷贝影响服务器性能,redis渐进式rehash,定义rehash的键值对索引rehashidx ,初始值-1,每执行一次插入删除或查询操作,rehashidx+1,并把该位置处的键值对拷贝到新数组中。每次查询时都要查询新旧两个哈希表。

stl中unordered_map
template<class Key, class Ty, //分别为键和值的类型
	class Hash = std::hash<Key>, //hash函数,最好是函数对象,方便内联
	class Pred = std::equal_to<Key> // 哈希冲突时的用来区分键值对,最好是函数对象,方便内联
    > class unordered_map

自定义关键字时要定义hash函数,equal_to函数。

struct Edge
{
	int w;
	Edge(int i): w(i){}
};
struct EdgeHash {
	size_t operator()(const Edge& e)
	{
		return hash<int>()(e.from.id) ^ hash<int>()(e.to.id);
	}
};
struct EdgeCmp {
	bool operator()(const Edge& e1, const Edge& e2)
	{
		return e1.from.id == e2.from.id && e1.to.id == e2.to.id;
	}
};

unordered_map<int, Node> nodes;
unordered_set<Edge, EdgeHash, EdgeCmp> edges = //注意模板参数传的是类型,而不是对象EdgeHash();
	{Edge(1), Edge(2), Edge(3)};

参考:
https://zhuanlan.zhihu.com/p/77533501
redis rehash
自定义key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值