本文已参与「新人创作礼」活动,一起开启掘金创作之路
3.哈希表
哈希表又称为散列表,原理是每个元素都能通过哈希函数得到一个哈希值,通过该哈希值进行访问该元素,如key-value的形式访问,key是哈希值,value是该key映射的元素值。理论上查找一个元素的时间复杂度是O(1)
一般的哈希函数:hash(x) = (x % p + p) %p
p一般取大于最大数据量的第一个质数。
有一组数,个数为 8,1 3 5 7 13 20 50 101 ,则P取11, 11是大于8的第一个质数。 然后分别求出这8个数的哈希值。 hash(1) = 1 hash(3) = 3 hash(5) = 5 hash(7) = 7 hash(13) = 2 hash(20) = 9 hash(50) = 4 hash(101) = 2
发现通过哈希函数有两组值是一样的,这被称为“冲突”,数组的一个位置只能存一个数,要解决冲突,有俩种方法:
(1)链地址法(拉链法)
(2)开放寻址法
链地址法(拉链法)
将数据存储在哈希值对应的单链表中
解决冲突的方法:在同一个key处,采用单链表的结构,插在原有数据的后面。
代码如下
import java.io.*;
import java.util.*;
public class Main{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
static final int N = 100003; //假设数据局最多为100000个
static int[] hash = new int[N];//哈希表表头
static int[] data = new int[N];//所有单链表共用一个数组空间--存数据的数组
static int[] next = new int[N];//next数组
static int idx = 1; // static 标识的数组的自动初始化为0的
public static void insert(int x) {
//头插法
int k = (x % N + N) % N;
data[idx] = x;
next[idx] = hash[k];
hash[k] = idx;
idx++;
}
public static void find(int x) throws IOException{
int k = (x % N + N) % N;
k = hash[k];
boolean flag = false;
while(k != 0) {
if(data[k] == x) {
flag = true;
break;
}
k = next[k];
}
if(flag) {
out.write("find out");
}else {
out.write("No find");
}
out.flush();
}
public static void main(String[] args) throws Exception{
int n = Integer.parseInt(in.readLine());
for(int i = 0; i < n; ++i) {
String[] s = in.readLine().split(" ");
switch(s[0]) {
case "i" : insert(Integer.parseInt(s[1]));break;
case "q" : find(Integer.parseInt(s[1]));break;
}
}
}
}
数据类型使用情况:
使用了三个一维数组
data数组用于存储数据元素。
hash数组用于存储每个哈希值链表的头指针。
next数组用于存储每个数据元素的下一个数据元素在data的位置。
使用了两个方法
insert插入方法采用了头插法,即每次插入的数据放置在链表的头部。
find寻找方法,通过哈希函数计算出哈希值,通过哈希值在hash数组中找到数组的头部,从头部开始在next数组中遍历。
开放寻址法
在拉链法中我们用了单链表来解决冲突,如果不使用单链表应该如何解决冲突呢。,在插入的时候,如果相应的哈希值对应的位置已经被占用了,就往后看,直到找到一个空的位置,这个找新位置又有不同的方法。