哈希表基础
哈希表是一类数据结构(哈希表包含数组、集合和映射,和前两篇文章叙述的字符串、链表平级) 哈希表概念 :类似于Python里的字典类型,哈希表把关键码key值通过哈希函数来和哈希表上的索引对应起来,之后输入key值可直接定位到对应索引位置,然后进行取值哈希表的好处 :主要为查找方便,可快速判断一个元素是否在集合中哈希函数 :即关键码key值和存储位置(索引)的对应关系,一个散列函数,比如把小明映射为0,小李映射为1,如图
哈希碰撞
定义:当哈希函数的映射关系把多个关键码映射到了同一个哈希表索引上时,这种现象称为哈希碰撞,如图所示(哈希碰撞有时候是因为关键码的数量大于哈希表的长度,这时不可避免发生碰撞;但是也可能是哈希函数的对应关系不合理,使得即便仍有空索引,还是把部分关键码分配到了同一索引上)
其实发生哈希碰撞不见得是个坏事,如果是因为关键码的数量大于哈希表的长度,说明此时哈希表的所有索引都被完全利用,没有发生内存浪费 解决方案一:拉链法
当发生冲突时,在对应索引位置通过链表结构储存多个关键码,如图所示
解决方案二:线性探测法
如果是哈希函数的对应关系不合理,使得即便仍有空索引,还是把部分关键码分配到了同一索引上,此时可以利用线性探测法,从发生冲突的索引位置开始,线性查找找到下一个空索引,然后把多余的关键码分配过去,如图所示
常见的三种哈希表结构
数组 set集合
是一种数据结构,常用于元素查找的场景 特点:元素不重复(有的具体类实现会允许元素重复,如multiset)、有序(有些具体类实现是无序的如unordered-set) 具体的类实现
第一种,set:有序,即set类中的元素按值的大小排序;基于红黑树实现 第二种,unordered-set:无序,即set类中的元素不按照值的大小排序,而是根据哈希函数的映射结果来;基于哈希表实现 第三种,multiset:允许元素重复;按照元素值的大小排序;基于红黑树实现 集合类的使用
第一步,引入头文件#include <unordered_set>
和#include <set>
,其中后者同时包含set和multiset类 第二步,创建集合变量,如unordered_set<int> myset;
、set<int> myset;
、multiset<int> myset;
第三步,插入或删除元素,使用myset.insert(value);
或myset.erase(value);
第四步,查找元素,使用myset.find(value)
,如果找到了,find会返回指向该元素的迭代器 (具体见下面对于迭代器的介绍);如果没找到,会返回一个指向集合的 end() 的迭代器。所以通过if myset.find(value) == myset.end()
可以判断集合中是否有对应元素 map映射
迭代器iterator
迭代器是一种类似于指针的接口,其作用类似于容器(如数组、集合)的下标,可以用来遍历访问容器中的元素,并执行各种操作 容器都拥有begin()
和end()
成员,分别为指向第一个元素和末尾元素的下一个元素 (尾后迭代器)的迭代器;如果容器为空,则begin和end返回同一迭代器 对于迭代器的操作
比较大小,直接使用比较运算符比较两个迭代器 移动位置,通过自加、自减运算符移动迭代器的位置 取值,通过解引用*
可以获得迭代器指向的对象内容 使用迭代器遍历容器元素的代码如下
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> myVector = {1, 2, 3, 4, 5};
// 使用迭代器遍历容器
// vector<int>::iterator it 用于创建一个能读取<vector>int 元素的迭代器it,最初指向begin()
// ++it表示迭代器的移动
for (vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
cout << *it << " "; // 通过解引用获取迭代器所指的对象
}
cout << endl;
return 0;
}