Hash算法

一、理解

        把任意长度的输入通过散列算法(mod)变换成固定长度的输出,把原有的数映射成一个对应的散列值。散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,可能会造成冲突。

二、存储结构

        开放寻址法

        思路:利用hash函数从hash数组中找到对应 应该存放x的位置(若hash表中已有x,就不用再存)。

        解决冲突:当用hash函数求出来的位置上有值,就依次往后查找,找到的第一个空位就是该值的位置,如果找到了数组尾部,就从头开始。

核心代码:

static int find(int x){//找x应该存放的位置
     int k = (x%N+N)%N;//保证取余是正数
     while(h[k]!=x&&h[k]!=Integer.MIN_VALUE){
     //如果值相同就不用在找另一个位置存放该值,退出,如果该位置为空,说明x应该放到这个位置,退出
         k++;
         if(k==N)k=0;
    //如果找到数组的结尾,就从头开始(这里数组的长度是要插入的数的个数的2~3倍,不存在找不到空位,死循环的问题)
    //例如:操作数不超过100000,最好设数组长度N为200003(最大质数)
     }
     return k;//返回该点应该存放的位置
}

例题:840. 模拟散列表 - AcWing题库

        拉链法

        (形似邻接表)数组+链表

         思路:利用hash函数求出该值的散列值,将该值插入以该散列值为头结点的链表中。

插入操作

        (用数组来表示单链表的方式,e[ ]存x,ne[ ]存x指针所指向的下一次值的下标,没有值就是-1)

static void insert(int x){
    int k = (x%N+N)%N;//求散列值
    e[idx] = x;
    ne[idx] = p[k]; p[k] = idx++;
}

查找操作

        从该值的散列值的链表的头结点的指针指向的位置p[k]开始找,若指针指向的下标小于等于0,链表结束。

static boolean find(int x){
        int k = (x%N+N)%N;
        for(int i=p[k];i>0;i=ne[i]){//这里条件为i>=0是因为如果当此时i的下标就是我们第一个插入的下标是0
            if(e[i]==x)
                return true;
            // else if(i==0) return false;但是我们也可以开始时将idx设为1,就避免了此情况
        }
        return false;
    }

三、字符串哈希方式(字符串前缀哈希法)

 思路:将字符串用一个P进制的数表示,实现不同的字符串映射到不同的数字。

        如何定义一个前缀的哈希值:形如字符串X1X2X3····Xn 的P进制表示为

ec3c17eb264047a092f69bfd27dc9b27.png

                (把任何一个字符串都映射成0~Q-1之间的一个数) 

         其中P取131、13331;Q取2^64;(经验值)

                因为这里Q取2^64,数组应该用long来表示

        例如:“abcdabc”        用h[ ]存储字符串哈希值的前缀和

             注意:   h[0] = 0;//不能映射成0,例如:如果a->0,则aa->0,不能区分

                           h[1] = "a"的哈希值

                           h[2] = "ab"的哈希值

                           .....

1d7880d30d12492a8a32fc210c000199.png

 前缀和公式:h[i] = h[i-1]*P +s.charAt(i-1)//该字符的ASCLL码

区间和公式:s[i] = h[r] - h[l-1]*p[r-l+1]

(这里h[ ] 表示字符串的哈希值的前缀和,s是输入的字符串,p[ ] 存储的是P的幂 p[i] = P^i)

 841. 字符串哈希 - AcWing题库

 

字符串哈希(题)

        给定一个长度为 nn 的字符串,再给定 mm 个询问,每个询问包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,请你判断 [l1,r1][l1,r1] 和 [l2,r2][l2,r2] 这两个区间所包含的字符串子串是否完全相同。

        字符串中只包含大小写英文字母和数字。

输入格式

        第一行包含整数 n 和 m,表示字符串长度和询问次数。

        第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。

        接下来 m 行,每行包含四个整数 l1,r1,l2,r2表示一次询问所涉及的两个区间。

        注意,字符串的位置从 1 开始编号。

输出格式

        对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

        每个结果占一行。

数据范围

        1≤n,m≤100000

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

 代码实现:

import java.util.*;

class Main{
    static final int N = 100010,P=131;//P是进制数,经验值
    static int n,m;
    static long[] h = new long[N];//存放前缀和
    static long[] p = new long[N];//存放P的多少次幂
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        String st = in.nextLine();//读取换行

        String s = in.next();//读入字符串
        p[0] = 1;
        for(int i=1;i<=n;i++){
            h[i] = h[i-1]*P+s.charAt(i-1);//求前缀和
            p[i] = p[i-1]*P;//因为这个题需要用到P^(r-l+1),所以设置一个表示P的幂的数组
        }
        while(m-->0){
            int l1,r1,l2,r2;
            l1 = in.nextInt();
            r1 = in.nextInt();
            l2 = in.nextInt();
            r2 = in.nextInt();
            if(get(l1,r1)==get(l2,r2)) System.out.println("Yes");
            else System.out.println("No");
            
        }
    }
    static long get(int l,int r){
        return h[r]-h[l-1]*p[r-l+1];//求区间和
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值