zoukankan      html  css  js  c++  java
  • hash学习笔记

    学长来讲了hash,感觉好香

    哈希算法

    算法概况

    hash是一种思想 是一种广义的算法 不懂的可以百度

    而我们常将哈希的思想用在字符串中 用于O(1) 判断给出的两个字符串是否相等(预处理完的情况下)

    例题&算法

    这里结合例题理解一下:
    给出两个字符串A与B 每次询问给出l,r,s,t 判断A[l...r] 与 B[s...t]是否相等

    如果我们用裸的暴力的话 显然对于每次询问我们都要用O(n) 的效率 所以对于m次询问效率是O(nm)但是我们用hash可以很轻松的解决这个问题
    关于一个字符串我们很显然是无法直接O(1)比较的 但是对于一个数字可以 所以我们可不可以把一个字符串转化为数字呢?
    答案是肯定的

    • 首先我们存入一个字符串 然后通过把字符串转化为ASCII码值 然后就可以把字符串转化为数字了
    • 因此我们定义一个数组f[i]表示当前字符串的第i位 的前缀和 (类似前缀和),那么f[i] 的求法如下
    for(int i = 1;i <= strlen(s1+1);++i){
    		f1[i] = f1[i-1] * base + s1[i] - 'a' + 1;
    }
    
    • 注意我们这里用到了base base其实是自己定义的一个进制位 可以自己随意定义,根据题目自己定义,大小自己定,为了防止出现两个不同的字符串存成相同的值

    • 因此这样我们就处理出了前缀和,那么对于取区间的问题 显然不能像普通的前缀和那样直接减 所以我们又要用到一个pw数组,用来辅助我们求区间和

    pw[0] = 1;
    for(int i = 1;i <= strlen(s1+1);++i){
    	pw[i] = pw[i-1] * base;
    }
    

    下面给出例题代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5+10;
    const int base = 223;
    int pw[maxn],f1[maxn],f2[maxn];
    char s1[maxn],s2[maxn];
    
    int main(){
    	scanf("%s%s",s1+1,s2+1);
    	for(int i = 1;i <= strlen(s1+1);++i){
    		f1[i] = f1[i-1] * base + s1[i] - 'a' + 1;
    	}
    	pw[0] = 1;
    	for(int i = 1;i <= strlen(s1+1);++i){
    		pw[i] = pw[i-1] * base;
    	}
    	for(int i = 1;i <= strlen(s2+1);++i){
    		f2[i] = f2[i-1] * base + s2[i] - 'a' + 1;
    	}
    	int m;scanf("%d",&m);
    	while(m--){
    		int l1,r1,l2,r2;scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    		if((f1[r1] - f1[l1 - 1] * pw[r1 - l1 + 1]) == (f2[r2] - f2[l2 - 1] * pw[r1 - l1 + 1]))printf("YES
    ");
    		else printf("fake u**
    ");
    	}
    	return 0;
    }
    

    哈希表

    算法概况:

    关于一个较大的值域 我们希望在较短的时间内完成查询操作 但是值域太大导致不能用计数数组 (你又懒得用离散化) 就可以用哈希表来处理了

    例题导入

    对于这样一个数列 {1,75,324,43,1353,91,40}
    我们想要查询一个数字 可以选择开一个数组a[7] 然后将这几个数字都存进去
    然后如果要查询的话 显然会是O(n) 的效率 如果加上各种奇淫巧技应该还可以再优化(log_n)
    但是我们也可以选择开一个数组a[1353] 然后将数值作为下标 数组保存出现几次
    这样我们要查询某个数字出现次数就是O(1)的效率
    显然我们的效率提高了很多 但是看看我们的空间 ……………………………………(泪目就完了)
    所以这时我们哈希表闪亮登场

    • 我们一开始将数值作为下标 很显然我们有很多地方是没有被用到的 比如a[2] a[3] 都是浪费的空间
    • 那么我们可以选择让这个数值取上一个数的模 然后将这个新值作为数组下标
    • 举个栗子: 我们这个题可以选择将7作为模数 取完模之后显然数值范围为0~6 而0很特殊 所以我们避免掉0的情况 让数值取模之后+1
    • 这样我们的数值范围就变成了1~7 然后开始存数
    • 1存进a[1],75存进a[5],324存进a[2],43存进a[1] 诶 a[1]存了俩值 vvvvvvvvv
    • 所以显然这样出现了两个不同的数值x,y 但是会出现x ≡ y (mod p) 这就是我们常说的哈希冲突
    • 如何处理冲突呢? 我们选择不再将数组存数值 而是存一个链表(和最短路的基本一样 木得学过最短路的同鞋emmm 你想起啥来看哈希的)
    • 所以我们的算法模板就有了

    板子

    背景

    • 你有一个序列(别问,问就是我给你的),里面有n个数,有m次查询,询问当前数在序列中出现了多少次
    • 第一行输入两个整数n,m 表示有n个数 ,m次询问
    • 第二行输入 n 个数字 表示这个序列中的数字
    • 第三行输入 m 个数字 询问当前数字在序列中出现次数
    • 请根据每次询问给出对应的答案
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5+10;
    const int mod = 223;//模数
    
    struct node{//邻接表
    	int val,cnt,next;
    }a[maxn];
    
    int head[maxn],tot;
    
    void add(int x){
    	int now = x % mod + 1,u = head[now];//now为哈希操作之后对应的值 u表示链头
    	for(;u;u = a[u].next)//遍历整个链表 
    		if(a[u].val == x){//如果找到当前值
    			a[u].cnt++;break;
    		}
    	if(!u){//如果链表遍历完仍然为找到对应的值
    		a[++tot].val = x;//开个新点
    		a[tot].cnt = 1;
    		a[tot].next = head[now];
    		head[now] = tot;
    	}
    }
    
    int ask(int x){
    	int now = x % mod + 1;
    	for(int i = head[now];i;i = a[i].next)if(a[i].val == x)return a[i].cnt;//查找到了对应的值
    	return 0;//没有查找到对应的值
    }
    
    int main(){
    	int n,m;scanf("%d%d",&n,&m);
    	while(n--){
    		int x;scanf("%d",&x);
    		add(x);
    	}
    	while(m--){
    		int x;scanf("%d",&x);
    		printf("%d
    ",ask(x));
    	}
    	return 0;
    }
    
    如初见 与初见
  • 相关阅读:
    angularjs中的页面访问权限设置
    Html页面head标签元素的意义和应用场景
    脚本引用中的defer和async的用法和区别
    自适应页面中如何使用雪碧图
    网页颜色分辨测试小游戏的js化辨别及优化
    jQuery1.9及其以上版本中动态元素on绑定事件无效解决方案
    Data URL简介及Data URL的利弊
    浏览器调试:事件定位与源码查找
    简述ES5 ES6
    移动端中pagehide、pageshow的应用
  • 原文地址:https://www.cnblogs.com/HISKrrr/p/13341381.html
Copyright © 2011-2022 走看看