哈希表原理以及STL使用示例


title: 哈希表原理以及STL使用示例
tags: 算法


哈希简介

让我们来就哈希表做一个最简单的介绍

哈希表是一个通过key-value对(键-值对)实现快速查找数据的简单数据结构

key是我们将对象插入哈希表时提取的特征,value则是我们需要储存的值。

如果把哈希表理解成一座公寓,那么key在我看来,可以意译为“钥匙”,一把钥匙只能打开一扇门,所以一个key值只能对应一个value。

使用哈希表,就是利用key值实现对数据(value)的快速查询。

接下来,我们来看看哈希表在具体操作中如何使用。

STL实操案例

统计相似字符串对的数目

力扣2506.统计相似字符串对的数目

在此题中,由于数据规模较小,直接暴力枚举也未尝不可。

但是如果用哈希表快速查询有没有相似的字符串,则可将时间复杂度大大较少。

常规思路:

二层循环遍历字符串数组,一个一个找相似字符串对
时间复杂度 O ( m n 2 ) O(mn^2) O(mn2)

大致思路:

对每个字符串提取不同字母的集合,这个集合就可以理解为对象的key,而我们要储存的value就是同一个集合出现的次数。

这样,只需要对每个字符串提取key值,就可以通过哈希表实现 O ( 1 ) O(1) O(1)的访问效率。
时间复杂度 O ( m n ) O(mn) O(mn)

粗略来看,哈希表的本质是一种空间换时间的策略,通过提前申请的哈希桶空间,只需要看key值有没有对应的value就知道有没有出现过该key值。

核心思路与桶排序很像,但哈希函数又保证了无论key值是什么类型,哈希桶空间总能维持在合适的大小。

另外,我们可以对提取出来的字母集合做一个初步哈希处理,可把这些字母进行状态压缩,通过它们的有无产生一个最大大小为26位的状态数作为key值。

具体代码

class Solution {
public:
    int similarPairs(vector<string>& words) {
        int len=words.size();
        int sum=0;
        unordered_map<int,int> hash;
        for(int i=0;i<len;i++){
            int sta=0;
            for(char c: words[i]){
                sta=sta|(1<<(c-'a'));
            }
            sum+=hash[sta];//每新增一个,都会和前面所有组队
            hash[sta]++;
            
        }
        return sum;
    }
};

STL标准模板库的哈希表操作

在此仅介绍两种较为常用的哈希类,读者如有兴趣,可自行查阅

std::unordered_map

无序映射哈希表

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> myMap;
    //插入方法1,插入新键或覆盖原有value
    myMap["apple"]=188;//可以把key值姑且认为是哈希桶的下标
    //实际操作中,key值会被内部哈希函数转成int值,但是使用时,只需把哈希表理解成一个下标可以是任意类型的“桶”即可
    //插入方法2,只插入新键,无法覆盖原有value
    myMap.insert({"banana",288});
    myMap["orange"] = 300;

    std::cout << "apple: " << myMap["apple"] << std::endl;

    // 查找方法1,会返回一个指向对应元素的迭代器,如果没找到,就返回指向末端的迭代器,用于需要修改哈希表的value时
    if (myMap.find("banana") != myMap.end()) {
        std::cout << "banana exists with value " << myMap["banana"] << std::endl;
    }
    //查找方法2,会返回一个整数,找到元素返回1,没找到返回0
    if(myMap.count("strawberry")){
        std::cout<<"strawberry exists with value "<<myMap["orange"]<<std::endl;
    }else{
        std::cout<<"strawberry has been eaten!!!!"<<std::endl;
    }
    // 遍历
    for (const auto &pair : myMap) {//标准库中,哈希表以<键-值对>的形式储存
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

运行结果:

apple: 188
banana exists with value 288
strawberry has been eaten!!!!
orange: 300
banana: 288
apple: 188

大致方法已在注释中标明,不再赘述。
仅提醒一点 : unordered_map储存键值对,通过pair类型储存,调用first得到key值,调用second得到value值

需要注意的时,若插入时不表明value值,则默认是0

//查找方法2,会返回一个整数,找到元素返回1,没找到返回0
    myMap["strawberry"]++;
    if(myMap.count("strawberry")){
        std::cout<<"strawberry exists with value "<<myMap["strawberry"]<<std::endl;
    }else{
        std::cout<<"strawberry has been eaten!!!!"<<std::endl;
    }
strawberry exists with value 1

std::unordered_set

大致操作与上述例子相同,区别在于set不储存键key,只储存值value,用于快速去重。

插入原理

对于每一个key-value对,都应该保证key值如果相同,必然指向同一个value。
上面我们提到,key值的类型可以多样化,并不局限于int类型。实际上,哈希表是利用哈希函数把key值转换成哈希表内部储存对象的哈希桶下标(哈希值)。

为了更好地利用空间,这种转化必然是会发生哈希碰撞的,也就是不同的key,产生同一个哈希值的情况。压缩内存的本质就是把一一映射改成多对一映射。

那么我们如何处理上面的现象,也就是哈希碰撞呢?

答案是,把每个哈希值冲突的key以链式结构存入哈希桶中,也就是以链表或者vector<int>储存

这样的话,只需要在每次哈希值冲突的时候,遍历链式结构,判断有无key值重复即可。

现在我们来看看如何自制简易哈希表以便理解个中缘由

数字哈希

题目

假设现在要输入n个数字,需要判断有几个重复的数字,但是受内存所限,数组空间无法开太大,如何用少于 O ( n 2 ) O(n^2) O(n2)的时间复杂度统计?

思路

这时我们就可以简易模拟一个哈希表。
key值就是num本身,因为要保证单映射。
value值在此并没有什么作用,所以可以理解为该哈希表是unordered_set
此时,哈希函数就是对数组空间取余,同时用vector<int>类型数组防止哈希碰撞。

代码

// #include<bits/stdc++.h>
#include<vector>
#include<iostream>
using namespace std;
const int mod=100;
//此处假设数组空间最多开到100
vector<int> Myhash[mod];
int main(){
    int n;
    cin>>n;
    int ans=0;
    for(int i=0;i<n;i++){
        int num;
        cin>>num;
        vector<int>& v=Myhash[num%mod];
        int flag=0;
        for(auto& a:v)
            if(a==num){
                flag=1;
                break;
            }
        if(flag==0){//此处flag判断可改成函数判断,提前return
            ans++;
            v.push_back(num);
        }
    }
    cout<<ans<<endl;
    return 0;
}

字符串哈希

P3370 【模板】字符串哈希

题目描述

如题,给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。

输入格式

第一行包含一个整数 N N N,为字符串的个数。

接下来 N N N 行每行包含一个字符串,为所提供的字符串。

输出格式

输出包含一行,包含一个整数,为不同的字符串个数。

输入输出样例 #1

输入 #1
5
abc
aaaa
abc
abcc
12345
输出 #1
4

说明/提示

对于 30 % 30\% 30% 的数据: N ≤ 10 N\leq 10 N10 M i ≈ 6 M_i≈6 Mi6 M m a x ≤ 15 Mmax\leq 15 Mmax15

对于 70 % 70\% 70% 的数据: N ≤ 1000 N\leq 1000 N1000 M i ≈ 100 M_i≈100 Mi100 M m a x ≤ 150 Mmax\leq 150 Mmax150

对于 100 % 100\% 100% 的数据: N ≤ 10000 N\leq 10000 N10000 M i ≈ 1000 M_i≈1000 Mi1000 M m a x ≤ 1500 Mmax\leq 1500 Mmax1500

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。

题解:

思路与数字哈希类似,都是unordered_set类型的哈希表,不需要储存value。
但是需要注意的是,对于有序字符串,应该如何哈希处理。
此处提供一种思路:将字符串视作’z’+1进制数或者128进制数,随后将字符串整理为整数并对数组空间取余即可。

代码:

// #include<bits/stdc++.h>
#include<vector>
#include<iostream>
#include<string>
using namespace std;
const int mod=10000;
const int maxK='z'+1;
vector<string> Myhash[mod];
int hashCount(string str){
    int len=str.size();
    int value=0;
    for(const char a:str){
        value=(value*maxK+a)%mod;
    }
    return value;
}
int main(){
    int n;
    cin>>n;
    int ans;
    for(int i=0;i<n;i++){
        string str;
        cin>>str;
        int value=hashCount(str);
        vector<string>& vv=Myhash[value];
        if(vv.size()==0)//哈希不冲突,必定是新的
            vv.push_back(str),ans++;
        else{//哈希冲突,看看是不是新的
            int flag=0;
            for(string a:vv){
                if(a==str){
                    flag=1;
                    break;
                }
            }
            if(flag==0){
                ans++;
                vv.push_back(str);
            }
        }
    }
    cout<<ans;
    return 0;
}
### C++ STL哈希表使用方法 #### 一、基本概念 哈希表(Hash Table)作为一种高效的数据结构,在C++标准模板库(STL)中有`unordered_map``unordered_set`两种主要形式提供其实现[^2]。这些容器允许以接近常数时间复杂度完成插入、删除以及查找操作。 #### 二、创建与初始化 为了定义一个简单的整型键值对存储器,可以如下声明并初始化一个`unordered_map<int, int>`对象: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap; // 插入元素 myMap.insert({1, 10}); myMap.emplace(2, 20); } ``` 上述代码展示了如何向`unordered_map`中添加新条目;其中既可以通过`insert()`成员函数来加入一对键值组合,也可以利用`emplace()`直接构建而无需先构造临时对象[^3]。 #### 三、访问元素 当需要获取某个特定键所对应的价值时,可采用下述方式之一: - 使用角括号运算符 `[]`: 如果指定的关键字不存在,则会自动创建一个新的默认初始状态下的项; - 调用`at()`成员函数:此方法会在找不到给定索引的情况下抛出异常,因此更安全可靠一些。 ```cpp // 访问已存在的键 std::cout << "Value of key 1 is: " << myMap[1] << '\n'; try { auto value = myMap.at(3); // 尝试访问不存在的键 } catch (const std::out_of_range& e) { std::cerr << "Key not found\n"; } ``` #### 四、遍历所有项目 通过迭代器可以轻松地循环处理整个集合内的每一条记录: ```cpp for (auto const& pair : myMap) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } ``` 这段程序片段将会打印出当前映射内所有的键值配对情况。 #### 五、移除元素 要从哈希表里清除掉某些不再需要的信息,可以选择调用`erase()`方法传入具体的目标关键字或者范围区间作为参数执行删除动作。 ```cpp myMap.erase(1); // 移除单个元素 if (!myMap.empty()) { // 检查是否为空 size_t countRemoved = myMap.erase(2); // 返回被删去的数量 std::cout << "Number of elements removed: " << countRemoved << "\n"; } myMap.clear(); // 清空全部内容 ``` 以上即是对C++ STL框架下哈希表——特别是`unordered_map`这一类关联式容器的基础介绍及其典型应用实例说明[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值