码迷,mamicode.com
首页 > 其他好文 > 详细

Hash(蛤席)学习总结

时间:2018-04-13 20:38:20      阅读:196      评论:0      收藏:0      [点我收藏+]

标签:bool   算数运算   line   return   and   hash   和我   超过   i++   

hash:
把复杂问题映射到一个容易维护的值域, 因为值域变简单, 范围变小, 可能会造成两个不同的值被hash函数映射到同一个值上,因此需要处理冲突情况
开散列:建立一个邻接表结构,以hash函数的值域作为表头数组head, 映射后的值相同的原始信息被分在同一类, 构成一个链表接在对应的表头, 链表的节点保存原始信息和统计数据(大概就是拉链式hash??)
hash的两个基本操作
1.计算hash函数的值
2.定位到对应链表依次遍历,比较
例:我们要在一个长度为n的随机整数序列A中统计每个数出现了多少次
一般思路:
直接数组计数
hash思路:
设计一个hash函数为h(x) = (x mod p) + 1, 其中p是一个比较大的质数, 但不超过n。这样,显然,我们把数列A分成了P类, 我们依次考虑数列中的每个数A[i], 定位到hash[h(A[i])]这个表头所指向的链表,如果该链表不包含A[i], 我们就在尾部新插入一个节点A[i], 并在该节点上记录A[i]出现了1次,否则直接找到已经存在的节点A[i],并将其出现次数+1。因为整数序列A是随机的,所以最终所以A[i]会比较均匀的分散在各个表头,整个算法的复杂度接近O(n)

对于非随机数列,我们可以设计更好的hash函数来保证其时间复杂度。同样的,如果我们需要维护的是比大整数复杂得多的某些性质(如是否存在,出现次数),也可以通过hash解决

emmmm....放一道基本水题感受下:

 技术分享图片

emmmm要是x小一点就可以丢到数组那当基本题了,然而很大,显然要是直接用数组会爆内存,所以我们来hash吧,这之后的问题解决就是数学的集合的事情了

丢份丑陋的代码:

#include <bits/stdc++.h>
#define p 2323237
using namespace std;
struct node {
    int v, next, num;
}hash[p];
int n, sum = 0, a, b, bsum = 0;
int lin[p], len = 0;
bool flag1 = 0, flag = 0;
inline int read() {
    int x = 0, y = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
        if(ch == -) y = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 3) + (x << 1) + ch - 0;
        ch = getchar();
    }
    return x * y;
}
inline int getkey(int k) {
return k % p;}
inline void insert(int key, int v) {
    hash[++len].next = lin[key];
    hash[len].v = v;
    hash[len].num = 1;
    lin[key] = len;
}
inline void hash_(int k, int c) {
    int key = getkey(k);
    if(c == 1)
        insert(key, k);
    else {
        for(int i = lin[key]; i; i = hash[i].next)
            if(hash[i].v == k) {
                hash[i].num++;
                flag1 = 1;
                flag = 1;
                sum++;
            }
        if(flag == 0) {
            insert(key, k);
            bsum++;
        }
        flag = 0;
    }
}
int main(){
    for(int i = 1; i <= 2; i++) {
        n = read();
        int x;
        if(i == 1) a = n;
        else b = n;
        for(int j = 1; j <= n; j++) {
            x = read();
            hash_(x, i);
        }
    }
    if(!flag1) cout << "A and B are disjoint" << endl;
    else {
        if(bsum == 0) {
            if(sum == a) 
                cout << "A equals B" << endl;
            else
                cout << "B is a proper subset of A" << endl;
        }
        else if(bsum != 0) {
            if(sum == a)
                cout << "A is a proper subset of B" << endl;
            else 
                cout << "I‘m confused!" << endl;
        }
    }
    return 0;
}

 

字符型hash

字符型hash,即把一个任意长度的字符映射成一个非负整数,并且冲突概率几乎为0
取一固定值P,把字符串看做P进制数,并分配一个大于0的数值, 代表每种字符。 一般来说, 我们分配的数值都远小于P, 例如对于小写字母构成的字符串,可以令:a = 1, b = 2, c = 3, .....z = 26。取一固定值M,将P对M取模,作为该字符的hash值 。
一般的说,我们取P = 131或P = 13331, 此时产生冲突的概率极低,只要hash值相同,我们就可以认为原字符串相等的。但是现实是,我们最好还是直接比较字符串是否相同,不然很容易就挂了.jpg,同样拉链式hash很重要.jpg,不然活该被卡(来自被花式卡死的人的怨念。)
一般我们采用M = 2^64, 即直接使用unsigned long long 类型存储hash值, 在计算时产生算术溢出时相当于直接的2^64取%, 这样可以避免低效的取%运算.jpg
我们也可以多取一些恰当的P和M的值(例如一些大质数,就比如如果你的企鹅号是质数...),多进行几组hash运算,当结果都相同时才认为与原字符串相等,一般来说,再毒瘤的出题人也很难构造出使这个hash产生错误的数据了,如果不行还是挂了,呵呵,出题人这辈子怕是没有rp了。但是如果你只运行1次,emmm,不被卡才怪。
对于字符串的各种操作,可以直接对P进制数进行算数运算反映到hash上

比如我们已知一个字符串S的hash值为Hash(S), 那么在S后添加一个字符c构成新字符S + c的hash值就是
Hash(S + c) = (Hash(S) * P + value[c]) % M。其中乘P相当于P进制下的左移运算, value数组是我们预先处理的字母的映射数组, value[c]就是我们选定的c的代表数值。

再如我们已知字符串S的hash值为Hash(S), 字符串S + T的hash值为Hash(S + T),那么字符串T的hash值
Hash(T) = (Hash(S + T) - Hash(S) * P^length(T)) % M
其中 Hash(S) * P^length(T)相当于把Hash(S)在P进制下再S后补0的方式进行算术左移,是S的左端与S + T的左端对齐,这样进行相减后得到的就是字符串T的hash值Hash(T)
例如: S = "abc", c = "d", T = "xyz", value["a, b, c.....z"] = {1, 2, 3, ....26}
S表示为P进制数为1 2 3
Hash(S) = 1 * P^2 + 2 * P + 3
Hash(S + c) = Hash(S) * P + value[c] = (1 * P^2 + 2 * P + 3) * P + 4 = 1 * P^3 + 2 * P^2 + 3 * P + 4
S + T表示为P进制数为1 2 3 24 25 26
Hash(S + T) = 1 * P^5 + 2 * P^4 + 3 * P^3 + 24 * P^2 + 25 * P + 26
Hash(S) * P^length(T) = (1 * P^2 + 2 * P + 3) * P^3 = 1 * P^5 + 2 * P^4 + 3 * P^3
即Hash(S) * P^length(T)表示为P进制数为 1 2 3 0 0 0
显然相减以后我们就得到了T的hash值
即Hash(T) = 1 * P^5 + 2 * P^4 + 3 * P^3 + 24 * P^2 + 25 * P + 26 - (1 * P^5 + 2 * P^4 + 3 * P^3)
表示为P进制数为 1 2 3 24 25 26 - 1 2 3 0 0 0 = 24 25 26
也就是说运算出来的Hash(T)表示为P进制数为24 25 26
Hash(T) = 24 * P^2 + 25 * P + 26
根据以上两种操作,我们可以通过O(n)的时间预处理字符串甚至所有前缀Hash值,并在O(1)的时间内查询任意子串的hash值

 丢一道题:

技术分享图片

emmm字符串hash,然而切记不要去比较什么hash值,直接比较原字符,顺便把拉链式用上

不然你就会和我一样,WA掉这道题

日常丢代码(emmm刚刚发现博客园是可以插入代码的)

#include <bits/stdc++.h>
#define maxn 500086
#define p 131
#define m 2323237
#define ull unsigned long long
using namespace std;
struct node {
    char c[510];
    int next;
}hash[50010];
char ch[maxn];
int ans[maxn], top = 0;
int n;
int lin[3000010], le = 0;
inline void insert(int key) {
    hash[++le].next = lin[key];
    strcpy(hash[le].c, ch);
    lin[key] = le;
}
inline void hash_(int k, int c) {
    register bool flag = 0;
    for(register int i = lin[k]; i; i = hash[i].next)
        if(strcmp(hash[i].c, ch) == 0) {
            ans[++top] = c;
            flag = 1;
            cout << c << "\n";
        }
    if(!flag) insert(k);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    cin >> n;
    for(register int i = 1; i <= n; ++i) {
        cin >> ch;
        register int len;
        register ull key = 0;
        len = strlen(ch);
        for(register int j = 0; j < len; ++j) {
            register int h = ch[j] - a + 1;
            key = (key * p + h) % m;
        }
        hash_(key, i);
    }
    return 0;
}

 

Hash(蛤席)学习总结

标签:bool   算数运算   line   return   and   hash   和我   超过   i++   

原文地址:https://www.cnblogs.com/ywjblog/p/8822747.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!