模拟散列表

目录

介绍:

1.冲突

2.哈希函数

3.存储结构:

4.离散化:

5.基本操作:O(1)

应用:

一.拉链法

        二.直接寻址法


介绍:

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

1.冲突

存在不同的数,他们的映射是相同的。

2.哈希函数

h(x) = ( x % N + N )% N

(1)N是数据范围

(2)+N再%N可以避免出现负数

(3)N必须是质数,这样可以减少冲突发生的概率

3.存储结构:

根据处理冲突的方法不同,分为拉链法和直接寻址法

4.离散化:

是一种特殊的哈希方法,离散化强调“保序性”即映射之前如果具有小于关系,映射之后仍需要保证小于关系,元素之间的相互关系是没变的,而这里的哈希是一般的哈希,元素之前的相互关系可能改变了

5.基本操作:O(1)

(1)插入一个元素

(2)查找一个元素

(3)修改一个元素

(4)删除一个元素:在执行删除一个元素操作的时候,我们不会真的删除一个元素,而是这只一个布尔类型的数组,标记一下该元素所在的位置就行了。

(5)主要的操作是插入和查找,删除几乎不用


应用:

840. 模拟散列表 - AcWing题库

一.拉链法

解决冲突方式:如果一个元素映射的位置没有存储元素,那么把这个元素放置在该位置下面;如果一个元素映射的位置已经存储了元素,就把这个元素插入到映射位置的后面,这样一个位置下面可能存储了若干个数,像一个拉链,故称为拉链法(其实就是一个邻接表)。

实现方式:邻接表,插入元素和查找元素都和邻接表的插入和查找操作相同。

代码:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100007;

int h[N], e[N], ne[N], idx; //邻接表

void insert(int k)  //邻接表插入操作
{
    //将K映射为P,就在p的位置上插入K
    int p = (k % N + N) % N;    //p是要插入元素的映射位置
    e[idx] = k;
    ne[idx] = h[p];
    h[p] = idx ++;
}

bool query(int k)   //查找一个元素
{
    int p = (k % N + N) % N;    //p是要查找元素的映射位置
    for(int i = h[p]; i != -1; i = ne[i])   //遍历邻接表
    {
        if(e[i] == k)   return true;
    }
    return false;
}

int main()
{
    int n;
    scanf("%d", &n);
    memset(h, -1, sizeof h);  //初始化头节点
    while(n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);    //scanf读入一个字符串可以避免空格造成的输入错误
        if(*op == 'I')  insert(x);
        else
        {
            if(query(x))    puts("Yes");
            else    puts("No");
        }
    }
    return 0;
}


二.直接寻址法

解决冲突方式:如果要插入元素的映射位置没有元素,那么直接将该元素放在当前位置;如果要插入元素的映射位置已经有了元素,那么就以此往后找,如果到了数组末尾还没有找到一个空的位置,就返回数组的头接着找;为了防止整个数组都找了一遍还没有找到一个空的位置,数组大小通常要开数据范围的2~3倍。

实现方式:一个一维数组。区别于链表法的是,直接寻址法每一个位置只存储一个元素,所以我们可以直接通过下标找到某个元素(因此称为直接寻址法),因此我们的插入和查找操作的实现只依靠一个find()函数找到该元素的实际映射位置(哈希函数找到的理论位置未必是该元素的实际存储位置,因为该位置可能已经被其他元素占用了,但一般来说发生冲突的概率是比较小的,即便发生了,也不需要移动态度位置)就可以了。

代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 200003, INF = 0x3f3f3f3f;

int h[N];

int find(int k) //找到元素K在哈希表中实际应该插入的位置
{
    int p = (k % N + N) % N;   //理论应该插入的位置
    while(h[p] != INF && h[p] != k)    //只要当前位置被占用并且占用位置不是我们要存入的值
    {
        p ++;    //线性移动
        if(p == N)  p = 0;    //如果到了末尾就回到起点
    }//while一定会结束,因为我们的数组范围是开了2~3倍的,所以整个数组不会被完全使用,因此while不会无限循环
    return p;
}

int main()
{
    //把哈希表的值设置成一个不可能被映射到的数,以此来判断该位置有没有被使用
    memset(h, 0x3f, sizeof h);    
    int n;
    scanf("%d", &n);
    while(n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        if(*op == 'I')  h[find(x)] = x;
        else
        {
            int t = h[find(x)];
            if(t == INF)    puts("No");
            else    puts("Yes");
        }
    }

    return 0;
}

find()函数:

find的返回值k有两种含义:

  1. 如果k存在哈希表当中,k是下标
  2. 如果k不在哈希表当中,k是要存储的位置


注意:

  • 邻接表的存储模式会存储重复的值,如果一个数重复出现了多次,那么这个数就会重复保存到邻接表中
  • 例如:当我们重复插入一个数2,假设2的哈希映射为1,那么邻接表当中就存储了两个2
  • 直接寻址法不会保存重复的值,如果这个数之前出现过了,那么当下次这个数再出现的时候,find()函数会找到他上一次存储位置的下标并返回,而不再存储一边

posted @   光風霽月  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示