哈希表一些操作的讲解
xingshuyan000 · · 题解
题解:P11615 【模板】哈希表
这是一道哈希表的模板题,我这篇题解将会详细讲解哈希表及相关操作。
题目传送门:洛谷 P11615
(其实我是现学现用的,刚刚学会)
声明:这篇题解中的部分内容引用来自百度百科或 OI-wiki。
哈希表
哈希表也叫散列表。
我们先来看一下百度百科上对于哈希表的定义:
散列表(Hash table,也叫哈希表),是根据关键码值(key-value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数,存放记录的数组叫做哈希表。
所谓的以 key-value 的形式存储数据,是指任意的键值 key(以下用
下面举几个以 key-value 的形式存储数据的例子:
就是类似于上面这样的。
哈希函数
定义: 要让键值对应到内存中的位置,就要为键值计算索引,也就是计算这个数据应该放到哪里。这个根据键值计算索引的函数就叫做哈希函数,也称散列函数。
其实,哈希函数就是一个映射,这个映射可以把一个
当 key 值是一个字符串时,我们需要用到字符串哈希,这里可以移步至 P3370 这道题,那里面的题解会有字符串哈希的讲解,我在这儿就不多讲了。
哈希冲突
经过前面的讲解,我们容易发现一个问题,如果对同一个数取模,可能会出现不同的键值被映射到了同一个数上面,这就叫做哈希冲突。比如,当模数为
处理哈希冲突的方法一:拉链法
拉链法又称为开散列法。
首先,我们开一个一维数组,大小为你的模数,比如你现在要对
拉链法的大致结构如下图,以上面的
(突然感觉有点像一堆糖葫芦)
我们一般用到的哈希表操作是添加操作和查询操作,很少会用到删除操作。
添加操作:要求你插入一个数
查询操作:要求你查找一个数
删除操作:要求你把这个哈希表里的某个数
拉链法的 C++ 核心代码(一般模板,不针对本题):
//拉链法
const int N = 1e7 + 19; //大于1e7最小的质数,可以直接写个程序跑一下
struct Hash{
int h[N]; //哈希表
int e[N], ne[N], idx = 0; //这是链表的东西
void insert(int x) //插入一个数x
{
int k = (x % N + N) % N; //k是x对应的哈希值
//为什么要 (x % N + N) % N 呢?因为处理的时候
//可能会遇到x是负数的情况,如果对负数取模,
//结果也是负数,而我们要的是非负整数值,所以
//通过这样把取模结果转化为非负数
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++; //链表的基本操作
return;
}
bool find(int x) //查找x
{
int k = (x % N + N) % N; //计算x的哈希值
for(int i = h[k]; i != -1; i = ne[i])
if(e[i] == x) return true; //如果找到了,返回true
return false; //遍历完了都没找到,返回false
}
void init()
{
memset(h, -1, sizeof(h)); //初始化,让h中的初始值都为-1
return;
}
};
处理哈希冲突的方法二:开放寻址法
开放寻址法,也是一种常用的处理哈希冲突的方法,其基本思想就是,寻找哈希表中的下一个空位,然后把元素直接扔到这个空位当中。
开放寻址法只需要开一个一维数组即可,不需要再手动模拟链表了,在形式上看来会简单一点。一般来说,开放寻址法开的一维数组大小是数据量的
我们可以这么理解开放寻址法:把开放寻址法的数组当成一个巨大的食堂,数组中的每个位置当成食堂中的餐桌,每个餐桌只能容纳一个人。我们去食堂买完饭以后,肯定需要去找地方坐,那么按照一般思路,我们就会从第一个餐桌开始,往后依次寻找第一个空闲的餐桌。如果第
同样,开放寻址法对应三种操作:添加、查询和删除。
添加操作:要求你往哈希表中插入一个数
查询操作:要求你在哈希表中查找一个数
删除操作:要求你删除哈希表中的一个数
开放寻址法的 C++ 核心代码(一般模板,不针对本题):
//开放寻址法
const int N = 1e7 + 19;
struct Hash_table{
const int null = 0x3f3f3f3f; //相当于一个空指针
int h[N]; //哈希表
void init()
{
memset(h, 0x3f, sizeof(h));
return;
}
int find(int x) //在哈希表中查找x
{
int k = (x % N + N) % N; //x的哈希值
while(h[k] != null && h[k] != x) //如果一直没找到x
{
k ++;
if(k == N) k = 0; //如果找到了最后一个,就要从第一个开始,让k=0
}
return k; //返回x的位置(找到)或x应当存在的位置(未找到)
}
void insert(int x) //插入x
{
int k = find(x);
h[k] = x;
return;
}
};
注意:在 C++ 中,函数名不能定义为 hash,因为 hash 是 C++ 中的保留字,如果定义为了 hash,容易编译错误。
针对本题的分析
其实这道题也就是个哈希表的模板题。他让你维护一个从
它让你找
时间复杂度为
不过请注意,本题的输入数据量可以达到
CODE
我发现我的代码跟某个题解的代码重复了,不过声明一下,我没有抄袭哈,这是我自己写的。
测试点所用最常时间:1.03s;最大内存:166.86MB。可以完美通过本题。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
namespace fast_IO
{
char buf[1 << 23], *p1 = buf, *p2 = buf;
char __getchar()
{
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
template <typename T>
T read()
{
T x = 0;
char ch = __getchar();
while(!isdigit(ch)) ch = __getchar();
while(isdigit(ch)) {x = x * 10 + (ch ^ 48); ch = __getchar();}
return x;
}
}
const int N = 1e7 + 19;
struct Hash_table{
LL h[N], kk[N];
bool exist[N];
void init() //其实这个函数要不要都行
{
memset(h, 0, sizeof(h));
memset(kk, 0, sizeof(kk));
memset(exist, false, sizeof(exist));
return;
}
void insert(LL x, LL y)
{
int k = x % N; //因为输入的都是正数,所以就不用再 +N 了
while(exist[k] == true)
{
if(kk[k] == x) break;
k ++;
if(k == N) k = 0;
}
kk[k] = x, h[k] = y;
exist[k] = true;
return;
}
LL find(LL x)
{
int k = x % N;
while(exist[k] == true)
{
if(kk[k] == x) return h[k];
k ++;
if(k == N) k = 0;
}
return 0;
}
};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int n;
n = fast_IO::read<int>();
Hash_table hash_table;
hash_table.init();
LL sum = 0;
for(int i = 1; i <= n; i ++)
{
LL x, y;
x = fast_IO::read<LL>(), y = fast_IO::read<LL>();
LL ans = i * hash_table.find(x);
sum += ans;
hash_table.insert(x, y);
}
cout << sum << "\n";
return 0;
}