哈希表

哈希表

CSDN

哈希表的作用

哈希表是在键和值之间通过散列函数建立对应关系,将一个庞大的值域映射到一个比较小的空间,并使得元素的查找可以以\(O(1)\)的效率进行

例将\(0\sim 10^9 \to 0\sim 10^5\)

\[Loc(i) = Hash(key_i) \]

常见的散列函数

线性定址法:直接取关键字的某个线性函数作为存储地址

\[Hash(key) = a \times key + b \]

除留余数法:将关键字对某一小于散列表长度的数p取余的结果作为存储地址

\[Hash(key) = key~mod~p \]

平方取中法:对关键字取平方,然后将得到结果的中间几位作为存储地址

折叠法:将关键字分割为几部分,然后将这几部分的叠加和作为存储地址
……

存储结构

地址冲突解决方法:

开放地址法:

  1. 线性探测法:当发生冲突时,就顺序查看下一个存储位置

  2. 平方探测法:以$12,-12,22,-22,\dots $测新的存储位置能否存储元素

  3. 再散列法:利用两个散列函数,当通过第一个散列函数发生冲突时,再利用第二个散列函数计算出地址增量

  4. 伪随机数法: 当发生地址冲突时,加入一个随机数作为地址增量寻找新的存储地址

拉链法:

​ 将具有相同存储地址的关键字链成一单链表

实现

拉链法,散列函数使用除留余数法,模p取不大于表长且最接近表长的素数时效果最好,所以N一般要先测定

int h[N], e[N], ne[N], idx;
// h数组是一排头节点,以元素散列后的值为下标
// e数组模拟单链表,存储原始值,插在h数组的头节点后

void insert(int x) {
	int k = (x % N + N) % N; // 保证k为正数
	e[idx] = x;
	ne[idx] = h[k]; // 头插法,h[x]作为链表头
	h[k] = idx++;
}

bool find(int x) {
	int k = (x % N + N) % N;
	for (int i = h[k]; i != -1; i = ne[i]) {
		if (e[i] == x) return true;
	}
	return false;
}

开放寻址法,数组一般开到需要的2~3倍

const int N = 200003, null = 0x3f3f3f3f; // 0x3f详见Temp.md-无穷大
int h[N];
memset(h, 0x3f, sizeof(h));

// 如果x在哈希表中存在则返回其位置,如果不存在则返回应该存放的位置
int find(int x) {
	int k = (x % N + x) % N;
	while (h[k] != null && h[k] != x) { // null代表为空
		k++;
		if (k == N) k = 0;
	}
	return k;
}

// 因为find()返回的是存在的位置或应该存储的位置,所以插入直接修改进行
int k = find(x);
h[k] = x;

STL库中的哈希表

头文件#include <unordered_map>

声明

unordered_map<elemType_1, elemType_2> var_name; //声明一个没有任何元素的哈希表,
//其中elemType_1和elemType_2是模板允许定义的类型,如要定义一个键值对都为int的哈希表:
unordered_map<int, int> map;

初始化

unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };
//如果知道要创建的哈希表的元素个数时,也可以在初始化列表中指定元素个数
unordered_map<int, int> hmap{ {{1,10},{2,12},{3,13}},3 };

添加元素

通过下标运算添加元素:

mapName[key] = value;
//在相同key中新的值会覆盖旧的值

通过insert()函数添加元素:

napName.insert({key,value});
//在相同key中第二次插入会失败
//试过了,不能一次添加多个键值对...

复制构造,通过其他已初始化的哈希表来初始新的表:

unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int> mapName1(mapName);

STL中哈希表的常用函数

iterator迭代器

begin( )函数:该函数返回一个指向哈希表开始位置的迭代器

unordered_map<int,int>::iterator iterName = mapName.begin();
//申请迭代器,并初始化为哈希表的起始位置

end( )函数:作用于begin函数相同,返回一个指向哈希表结尾位置的下一个元素的迭代器

unordered_map<int, int>::iterator iterName = mapName.end();

cbegin() cend():这两个函数的功能和begin()与end()的功能相同,唯一的区别是cbegin()cend()是面向不可变的哈希表

const unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::const_iterator iterName_a = mapName.cbegin(); 
//注意这里的迭代器也要是不可变的const_iterator迭代器
unordered_map<int, int>::const_iterator iterName_b = mapName.cend();

empty()函数:判断哈希表是否为空,空则返回true,非空返回false

bool isEmpty = mapName.empty();

size()函数:返回哈希表的大小

int size = mapName.size();

erase()函数: 删除某个位置的元素,或者删除某个位置开始到某个位置结束这一范围内的元素, 或者传入key值删除键值对

unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::iterator iter_begin = mapName.begin();
unordered_map<int, int>::iterator iter_end = mapName.end();
hmap.erase(iter_begin);  //删除开始位置的元素
hmap.erase(iter_begin, iter_end); //删除开始位置和结束位置之间的元素
hmap.erase(3); //删除key==3的键值对

at()函数:根据key查找哈希表中的元素

unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
int elem = hmap.at(3);

clear()函数:清空哈希表中的元素

hmap.clear()

find()函数:以key作为参数寻找哈希表中的元素,如果哈希表中存在该key值则返回该位置上的迭代器,否则返回哈希表最后一个元素下一位置上的迭代器

unordered_map<int, int> mapName{ {1,10},{2,12},{3,13} };
unordered_map<int, int>::iterator iter;
iter = mapName.find(2); //返回key==2的迭代器,可以通过iter->second访问该key对应的元素
//if(iter != mapName.end())  cout << iter->second;

bucket()函数:以key寻找哈希表中该元素的储存的bucket编号(unordered_map的源码是基于拉链式的哈希表,所以是通过一个个bucket存储元素)

int pos = mapName.bucket(key);

如果 HashMap 的每个 bucket 里只有一个 Entry 时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 Hash 算法来决定其存储位置;当需要取出一个 Entry 时,也会根据 Hash 算法找到其存储位置,直接取出该 Entry。由此可见:HashMap 之所以能快速存、取它所包含的 Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。

当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。

bucket_count()函数:该函数返回哈希表中存在的存储桶总数(一个存储桶可以用来存放多个元素,也可以不存放元素,并且bucket的个数大于等于元素个数)

int count = mapName.bucket_count();

count()函数: 统计某个key值对应的元素个数, 因为unordered_map不允许重复元素,所以返回值为0或1

int count = mapName.count(key);

通过迭代器遍历哈希表:

unordered_map<int, int> mapName{ {1,10},{2,20},{3,30} };
unordered_map<int,int>::iterator iter = mapName.begin();
for(;iter != mapName.end();iter++) {
  cout << "key: " << iter->first << " value: " << iter->second << endl;
}
posted @ 2023-08-27 23:28  -37-  阅读(15)  评论(0编辑  收藏  举报