提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
前言
本文主要介绍模拟散列表,并用拉链法和开放寻址法来解决哈希冲突。
提示:以下是本篇文章正文内容,下面案例可供参考
一、模拟散列表
维护一个集合,支持如下几种操作:
I x
,插入一个整数 x;Q x
,询问整数 x 是否在集合中出现过;
现在要进行 N 次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数 N,表示操作数量。
接下来 N 行,每行包含一个操作指令,操作指令为 I x
,Q x
中的一种。
输出格式
对于每个询问指令 Q x
,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤N≤100000
−1000000000≤x≤1000000000
二、算法思路
1.散列表
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
计算索引的过程被称为 哈希(hash)。
比如我们有一个从1到10^9个整数,然后我们有10^5个大小的空间,然后我们从1-10^9个数对10^5进行取余运算,然后得到的余数就是在空间中的位置,我们可以知道例如10^5+1在空间中的位置也是1,1在空间中的位置也是1,发生了冲突,这样就是哈希冲突 。我们把对10^5进行取模运算叫做哈希函数。
注:我们对一个数和另一个书进行取模运算,模数尽量是质数且是大于当前数据规模大小的最小的一个质数,这样的冲突是最少的。
2.拉链法
图2.1拉链法示例
拉链法是我们解决哈希冲突所使用的一种方法。比如我们用哈希函数hash(11) = 3就把11链接在3的位置,hash(23)=3也是放入3的位置,此时呢我们就把23链接在11的后面,故我们我们的每一个位置都是一条链表,故这种方法称为拉链法。
一般我们都是我们都是在散列表中进行增加和查找操作,很少进行删除操作;就算进行删除操作,我们也不是直接删除这个数,而是准备一个标志数组即可。
java
//头插法
public static void insert(int x){
//因为负数对正数取余还是负数,所以我们加上一个N变成正数
//k就是我们的哈希值
int k = ( x % N + N ) % N;
e[index] = x;
ne[index] = h[k];
h[k] = index;
index++;
}
public static boolean query(int x){
int k = (x % N + N ) % N;
for(int i = h[k]; i != -1;i = ne[i]){
if(e[i] == x){
return true;
}
}
return false;
}
我们引入一维整型数组h,h[i]为第i个位置单链表的头结点,因为初始链表都为空,都初始化-1,然后我们采用头插法将结点插入链表。具体e数组、ne数组和尾插法、单链表的遍历的详细过程请看这篇博客。https://blog.csdn.net/m0_63267251/article/details/138202017
3.开放寻址法
图3.1开放定址法示例图
开放寻址法解决冲突比如有10^5个数据,那么数组空间得开数据的2到3倍,这样才会减少冲突。
当h(x) = k的时候,然后第k个位置有元素,那么就往后移一位,如果后面一个位置还有元素,就接着往后移,知道有空白位置放即可。
我们查找也是从第k个位置开始,如果当前位置有人且是x那么我们就找到了x;如果当前位置有人但不是x,那我们就往后一位接着找;如果找到最后是一个空位且之前并没有找到x,就说明没有x。
删除操作我们不会直接把x删掉,而是在数组中打一个特殊的标记,标记一下x有没有删除(不常用)。
java
public static int find(int x){
int k = (x % N + N) % N;
while ( h[k] != x && h[k] != nu){
k++;
if(k == N){
k = 0;
}
}
return k;
}
我们设置一个整型变量nu,当数组中的值等于nu的时候表示为空,当数组中的值不等于x且不为空时,往后移,当我们k移到最后一个位置,我们再让k从第一个位置开始查找空白位置,我们数组的空间是比较大的,够放置元素。找到了空白是说明没找到元素返回的空白处的数组下标,找到元素了返回的是找到位置的数组下标。
然后插入元素我们直接让 h[find(x)] = x即可。查询元素如果h[find(x)] == nu则说明没有该元素打印No,如果不等则说明找到该元素打印Yes。
三、代码如下
1.拉链法代码如下:
c
import java.io.*;
import java.util.*;
public class 模拟散列表 {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int N = 100003;
//存储每个链表中第一个结点在e数组中的下标
static int[] h = new int[N];
//存储链表中的值
static int[] e = new int[N];
static int[] ne = new int[N];
//记录链表中新创建的结点
static int index = 0;
public static void main(String[] args)throws Exception {
int n = nextInt();
//头结点下标全部初始化为-1
Arrays.fill(h,-1);
while (n-- > 0){
String[] strs = nextLine().split(" ");
String cmd = strs[0];
int x = Integer.parseInt(strs[1]);
if (cmd.equals("I")){
insert(x);
} else if (cmd.equals("Q")) {
if (query(x)){
pw.println("Yes");
}else {
pw.println("No");
}
}
}
pw.flush();
}
//头插法
public static void insert(int x){
//因为负数对正数取余还是负数,所以我们加上一个N变成正数
//k就是我们的哈希值
int k = ( x % N + N ) % N;
e[index] = x;
ne[index] = h[k];
h[k] = index;
index++;
}
public static boolean query(int x){
int k = (x % N + N ) % N;
for(int i = h[k]; i != -1;i = ne[i]){
if(e[i] == x){
return true;
}
}
return false;
}
public static int nextInt()throws Exception{
st.nextToken();
return (int)st.nval;
}
public static String nextLine()throws Exception{
return br.readLine();
}
}
2.开放寻址法代码如下:
java
import java.io.*;
import java.util.Arrays;
public class 开放定址法 {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int N = 200003;
static int[] h = new int[N];
//如果数组中的数为m,则说明该位置为空
static int nu = 0x3f3f3f3f;
public static void main(String[] args) throws Exception{
Arrays.fill(h,nu);
int n = nextInt();
while (n-- > 0){
String[] strs = nextLine().split(" ");
String cmd = strs[0];
int x = Integer.parseInt(strs[1]);
int k = find(x);
if (cmd.equals("I")){
h[k] = x;
} else if (cmd.equals("Q")) {
if(h[k] == nu){
pw.println("No");
} else {
pw.println("Yes");
}
}
}
pw.flush();
}
public static int find(int x){
int k = (x % N + N) % N;
while ( h[k] != x && h[k] != nu){
k++;
if(k == N){
k = 0;
}
}
return k;
}
public static void insert(int x){
int k = (x % N + N) % N;
}
public static int nextInt()throws Exception{
st.nextToken();
return (int)st.nval;
}
public static String nextLine()throws Exception{
return br.readLine();
}
}
3.读入数据
c
5
I 1
I 2
I 3
Q 2
Q 5
3.代码运行结果
java
Yes
No
总结
上述主要介绍了通过拉链发和开放寻址法来解决哈希冲突和模拟散列表的代码和思路。