哈希表
- 哈希表一般用于将一个大的空间映射成一个比较小的空间,在映射的时候总会有元素冲突(哈希碰撞),这时候如何解决这种元素冲突的问题呢,有两种方法,一种是拉链法,一种是开放寻址法,这也就是哈希表的两种存储结构
拉链法
- 拉链法是一种数组加链表的结构,将映射成的比较小的空间开成一个数组,将每个元素进行哈希,通过链表的方式插在数组下面,如果发生哈希碰撞,通过头插法插入
- 对于删除操作,一般是在对应元素上打一个标记,并不会真的删除
- C++
//N取成尽可能远离2的幂次的质数,一般就是大于数据的第一个质数
int h[N], e[N], ne[N], idx; //记得将h[N]内的元素全部赋成-1
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N; //取模后再加N是为了去除负数
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
// 在哈希表中查询某个数是否存在
bool find(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;
}
- Java
//N取成尽可能远离2的幂次的质数,一般就是大于数据的第一个质数
public static final int N = 100003;
public static int[] h = new int[N]; //记得将h[N]内的元素全部初始化赋成-1
public static int[] e = new int[N];
public static int[] ne = new int[N];
public static int idx;
public static void insert(int x) {
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
public static boolean find(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;
}
- 寻找质数
for (int i = 100000; ; i ++) {
boolean flag = true;
for (int j = 2; j * j <= i; j++) {
if (i % j == 0) {
flag == false;
break;
}
}
if (flag) {
pw.println(i);
break;
}
}
开放寻址法
-
开放寻址法只需要开一个数组,这个数组的长度一般在题目数据范围的2~3倍
-
存储:通过哈希找到索引后,如果该位置有元素,就存放到后一位,以此类推
-
查找:通过哈希找到索引后,如果该位置非空且不为当前元素,就向后一位查找,直到找到该元素或该位置为空为止
-
删除:在该位置上打一个标记,并不会真的删除
-
数组初始化为null = 0x3f3f3f3f
-
在算法竞赛中,我们常常需要用到设置一个常量用来代表“无穷大”。
比如对于int类型的数,有的人会采用INT_MAX,即0x7fffffff作为无穷大。但是以INT_MAX为无穷大常常面临一个问题,即加一个其他的数会溢出。
而这种情况在动态规划,或者其他一些递推的算法中常常出现,很有可能导致算法出问题。
所以在算法竞赛中,我们常采用0x3f3f3f3f来作为无穷大。0x3f3f3f3f主要有如下好处:
0x3f3f3f3f的十进制为1061109567,和INT_MAX一个数量级,即10^9^ 数量级,而一般场合下的数据都是小于10^9^的。 0x3f3f3f3f * 2 = 2122219134,无穷大相加依然不会溢出。
-
在C++中,可以使用
memset(array, 0x3f, sizeof(array))来为数组设初值为0x3f3f3f3f,因为这个数的每个字节都是0x3f。 -
在Java中,可以使用
Arrays.fill(array, 0x3f3f3f3f)来为数组设初值为0x3f3f3f3f
-
-
C++
int h[N]; //记得初始化h[N]内元素全部初始化赋为0x3f3f3f3f
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
- Java
public static int[] h = new int[N];
public static int find(int x) {
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x) {
t++;
if (t == N) {
t = 0;
}
}
return t;
}
字符串哈希
- 字符串前缀哈希法
- h[i]存储字符串前i个字符构成的字串的哈希值
- 将字符串看成P进制的数,P的经验值是131或13331,取这两个值的造成哈希碰撞的概率低
- 将P进制的数变成十进制的数,此时的数非常大,因此要再模上一个数Q,小技巧:取模的数Q用2^64^,这样直接用unsigned long long存储,溢出的结果就是取模的结果
- 这样的哈希方式配上前缀哈希有什么样的好处?
- 可以利用前缀哈希算出任意一个子串的哈希值
- C++
//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数Q用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
- Java
public static final int P = 131; //P的经验值是131或13331,取这两个值的冲突概率低
public static long h = new long[N]; //存储前i个字符组成的字符串的哈希值
public static long p = new long[N]; //存储P的i次方
//初始化
p[0] = 1;
for (int i = 1; i <= n; i++) {
h[i] = h[i - 1] * P + str.charAt(i - 1);
p[i] = p[i - 1] * P;
}
//计算字串str[l ~ r]的哈希值
public static long get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
练习
01 模拟散列表
- 题目
- 题解
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 200003;
public static final int NULL = 0x3f3f3f3f;
public static int[] h = new int[N];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
n = Integer.parseInt(br.readLine());
Arrays.fill(h, NULL);
while (n-- > 0) {
String[] str1 = br.readLine().split(" ");
int x = Integer.parseInt(str1[1]);
int k = find(x);
if (str1[0].equals("I")) {
h[k] = x;
} else {
if (h[k] != NULL) {
pw.println("Yes");
} else {
pw.println("No");
}
}
}
pw.close();
br.close();
}
public static int find(int x) {
int t = (x % N + N) % N;
while (h[t] != NULL && h[t] != x) {
t++;
if (t == N) {
t = 0;
}
}
return t;
}
}
02 字符串哈希
- 题目
- 题解
import java.io.*;
public class Main {
public static final int N = 100010;
public static final int P = 131;
public static long[] h = new long[N];
public static long[] p = new long[N];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
String[] str1 = br.readLine().split(" ");
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
String str2 = br.readLine();
p[0] = 1;
for (int i = 1; i <= n; i++) {
h[i] = h[i - 1] * P + str2.charAt(i - 1);
p[i] = p[i - 1] * P;
}
while (m-- > 0) {
String[] str3 = br.readLine().split(" ");
int l1 = Integer.parseInt(str3[0]);
int r1 = Integer.parseInt(str3[1]);
int l2 = Integer.parseInt(str3[2]);
int r2 = Integer.parseInt(str3[3]);
if (get(l1, r1) == get(l2, r2)) {
pw.println("Yes");
} else {
pw.println("No");
}
}
pw.close();
br.close();
}
public static long get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
}