带你拿捏哈希表

92 阅读54分钟

​向Carl老师学习,用最简单的话,讲述最复杂的知识。( •̀ ω •́ )✧

什么是哈希表?

灵魂一问,这是正常人看到后,都会产生的疑问,就好比你是谁。

初学者可以将其看成一个数组

大家都知道数组是怎么存数据的!

通过下标(0~N,是一串连续的数字),将对应数据为其赋值。

哈希表大致就是这样存储的。

既然如此,那为啥它不叫数组,而叫哈希表(⊙_⊙)?

因为哈希表计算计算下标的时候,是通过一个名为 哈希函数 的工具,将key值(也就是输入内容,字符串也好,数字也好) 通通转化为一个特定的下标。而这个被计算出来的数据,功能就相当于数组中的下标!

(哈希表就是数组的拓展。也就是说,若没有数组,自然也就没有哈希表!)

为什么需要哈希表?

看到这,那肯定有人要问,哈希表 取个下标还要计算,多么麻烦呐?为啥不用数组?

假如我们有一组数据,某位工程师每年某个月的收入情况

20171-- 10000
20182-- 13000
20194-- 14000
202012-- 20000

你拿数组怎么存?

怎么存?

在上方说过,你可以通过哈希函数(hashFunction),将他们转化为特定的数字。

20171--> 11
20182--> 13
20194--> 14
202012--> 20

这样就能直接实现存入。

反过来操作,就能实现查找!完美的实现了时间复杂度为O(1)的操作。

存在了一起怎么办?

思维严密的人,在看到这种通过计算,而得出的下标时,肯定会有疑惑!

万一计算出了相同的下标怎么办?

这里就涉及到了哈希碰撞,就是计算结果相同。

其实现在有多种解决方案,而在本篇博客中,主要详细讲解两种方法。

拉链法

想必链表大家都学过了,当你遇到这种问题时,如图

这样通过索引,就能链接下标相同,但是数值不同的数据。

线性探测法

这个就是当两个下标相同时,自动往后移动,直到遇到空位。

常见的3种哈希结构

  • 数组
  • set (集合)
  • map (映射)

1、数组

这里就不多说了,想必大家都懂。

2、set(集合)

官方主要有3种,set,multiset,unordered_set

在这里,先说unordered_set,unordered_set的底层,就是咱们上述所说的哈希表。查询速率为O(1),基础用法。

#include <iostream>
#include <unordered_set>
using namespace std;
int main(){
    // 构建
    unordered_set<int> uset;
    // 增
    uset.insert(3);
    // 删
    uset.erase(3);
    uset.clear();
    // 查
    int size = uset.size();
    bool isEmpty = uset.empty();
    for(auto it = uset.begin(); it!=uset.end(); ++it){
        cout<<*it<<endl;
    }
    for(int i:uset) cout<<i<<endl;

    return 0;
}

其中,set与multiset的底层实现是红黑树,可能大家现在对“红黑树”还不够了解,只需知道,这俩底层不是用哈希表实现的就行,自然查询速率也就不是O(1),而是O(log N),这里就不做详细的解释啦。

3、map(映射)

同样大致也只有这3种,跟set其实相差不大。

map(映射),也主要分为map、multimap、unordered_map。

咱们这里也先说unordered_map,他的底层同unordered_set一样是哈希表。

#include <iostream>
#include <unordered_map>
using namespace std;
int main(){
    // 默认构造
    unordered_map<int,int> umap;
    // 构造并初始化
    unordered_map<int,string> myMap1 = {{1,"one"},{2,"two"}};
    // 构造并指定初始容量
    unordered_map<int,string> myMap2(10);
    // 构造,并直接复制
    unordered_map<int,string> myMap3 = myMap2;

    // 插入
    myMap2.insert({2,"2"});
    // 访问
    cout<<myMap2[2];
    // 删除
    myMap2.erase(2);
    // 查找元素
    auto it = myMap2.find(2);
    if(it!=myMap2.end()){
        cout<<"存在改元素"<<endl;
    }
    return 0;
}

​今天就学到这里喽!( •̀ ω •́ )✧