日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

离散化与哈希

發布時間:2024/1/18 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 离散化与哈希 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔

文章目錄

  • 前言
  • 一、離散化
    • 如何算出離散化后的值
  • 二、哈希表
    • 處理沖突
      • 1.拉鏈法
      • 2.開放尋址法
      • 3.正增量的二次探測法
        • 題目描述
  • 三、字符串哈希
    • 求字符串前綴的哈希值
    • 求任意字串的哈希值
      • 證明過程如下:
  • 總結


前言

學完hash哈希散列表后,發現跟離散化很像,但又不同,于是便整理一下離散化跟哈希

提示:以下是本篇文章正文內容,根據acwing上y總的講解進行的整理筆記,便于自己日后復習。能力有限,敬請雅正。


一、離散化

我們知道,數組所能開的范圍有限,當數據的范圍很大,比如說達到1e9,但數據量又很小的時候,我們一般考慮用離散化。離散化的過程其實就是將很大的數據映射成對應的下標來解決問題。

舉個例子:

a[i]11000200003000004000000
index01234

將該數組內的值映射成下標,便于更好的解決問題。就是找一個元素x在a[i]中對應的下標。

如何算出離散化后的值

1.首先我們要對待離散化的數組進行去重。

這里我們用c++中的一個庫函數unique來實現去重

這里用vector數組存儲待離散化的元素,便于操作

vector<int> alls;//存儲所有待離散化的值 sort(alls.begin(), alls.end());//將所有值排序 alls.erase(unique(alls.begin(), alls.end()), alls.end());//去掉重復元素

這里我大致解釋一下unique的用法,就是將一個有序的數組中所有重復出現的數組放到數組的尾部,然后返回第一個重復元素的下標,我們將該位置到尾部的所有元素刪除即可。
具體可自行搜索unique庫函數的用法。

2.然后我們用二分求出x對應的離散化的值

int find(int x)//找到第一個大于等于x的位置 {int l = 0, r = alls.size();while (i < r){int mid = l + r >> 1;if (alls[mid] >= x)r = mid;else l = mid + 1;}//返回的下標從1開始映射就返回r + 1, 從0開始映射就返回r,這里從1開始return r + 1;}

我們具體看一個問題:題目鏈接區間和

假定有一個無限長的數軸,數軸上每個坐標上的數都是 0。

現在,我們首先進行 n 次操作,每次操作將某一位置 x 上的數加 c。

接下來,進行 m 次詢問,每個詢問包含兩個整數 l 和 r,你需要求出在區間 [l,r] 之間的所有數的和。

輸入格式
第一行包含兩個整數 n 和 m。

接下來 n 行,每行包含兩個整數 x 和 c。

再接下來 m 行,每行包含兩個整數 l 和 r。

輸出格式
共 m 行,每行輸出一個詢問中所求的區間內數字和。

數據范圍
?109≤x≤109,
1≤n,m≤105,
?109≤l≤r≤109,
?10000≤c≤10000
輸入樣例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
輸出樣例:
8
0
5

我們可以看到,x和,l,r的數據范圍都是1e9級別的,如果直接當成下標來用顯然不可能,所以就需要用到離散化。

題解代碼如下:

#include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std; typedef long long ll; typedef pair<int, int> P; const int N = 1e5 + 6, M = 1e6 + 6, mod = 2e5 + 3; const int inf = 0x3f3f3f3f; //alls來存儲待離散化的元素 //query存儲詢問,l,r就用pair來存儲了 //add因為有x,c兩個元素,所以也用pair存儲了 vector<int> alls; vector<P> query, add; //因為n,m的數據范圍都是1e5級別的,待離散化的值有x,l,r三類所以范圍就開到3 * N int pre[3 * N], a[3 * N]; //求對應離散化的值,返回值是元素映射的下標 int find(int x) {int l = 0, r = alls.size() - 1;while(l < r){int mid = l + r >> 1;if (alls[mid] >= x) r = mid;else l = mid + 1;}return l + 1; } int main() {//因為x,l,r都是要離散化的值 所以都要放入alls數組int n, m; scanf("%d%d", &n, &m);for (int i = 0; i < n; i ++ ){int x, c; scanf("%d%d", &x, &c);alls.push_back(x);add.push_back({x, c});}for (int i = 0; i < m; i ++ ){int l, r; scanf("%d%d", &l ,&r);alls.push_back(l);alls.push_back(r);query.push_back({l, r});}//去重sort(alls.begin(), alls.end());alls.erase(unique(alls.begin(), alls.end()), alls.end());//加值操作for (auto item : add){int idx = find(item.first);a[idx] += item.second;}//因為要求區間和,我們就用前綴和來求for (int i = 1; i <= alls.size(); i ++ ){pre[i] = pre[i - 1] + a[i];}//詢問操作for (auto item : query){int l = find(item.first), r = find(item.second);printf("%d\n", pre[r] - pre[l - 1]);}return 0; }

二、哈希表

哈希表就是集查找、插入和刪除于一身的一種數據結構(算法題里一般只有插入和刪除操作)。哈希的過程就是把一個大的數據范圍映射到一個較小的數據范圍內的過程,這一點跟離散化很像,可以把離散化看成極其特殊的哈希方式。

因為數據量很大,哈希表難免會出現碰撞,就是把兩個不一樣的數映射成一樣的數,key1 != key2 ,hash(key1) == hash(key2)這種情況,所以我們要避免沖突。

盡可能大的避免沖突就是對元素取模,而取模常用較大的素數,這樣可以最大化避免沖突,至于證明過程可以自行百度。

處理沖突

我們通過一道題來分別用拉鏈法和開方尋址法實現。

題目鏈接:模擬散列表

維護一個集合,支持如下幾種操作:

I x,插入一個數 x;
Q x,詢問數 x 是否在集合中出現過;
現在要進行 N 次操作,對于每個詢問操作輸出對應的結果。

輸入格式
第一行包含整數 N,表示操作數量。

接下來 N 行,每行包含一個操作指令,操作指令為 I x,Q x 中的一種。

輸出格式
對于每個詢問指令 Q x,輸出一個詢問結果,如果 x 在集合中出現過,則輸出 Yes,否則輸出 No。

每個結果占一行。

數據范圍
1≤N≤105
?109≤x≤109
輸入樣例:
5
I 1
I 2
I 3
Q 2
Q 5
輸出樣例:
Yes
No

1.拉鏈法

數組與鏈表的結合

假設我們剛開始將11映射到3的位置上,我們就在3的位置處拉出一個鏈,將11加進去;倘若第二次映射,23也映射到了3的位置,我們就繼續把23加進去。這種處理沖突的方法就是拉鏈法。

代碼如下(示例):

#include <iostream> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; typedef pair<int, int> P; const int N = 1e5 + 6, M = 1e6 + 6, mod = 1e5 + 3; const int inf = 0x3f3f3f3f; //我這里采用的是數組模擬單鏈表 //h為哈希數組,e存儲值,ne為next指針,idx為操作到哪個節點 int h[N], e[N], ne[N], idx; //插入操作 void insert(int x) {//c++里負數取模還是負數,為了避免出現負數我們就加mod再取模int k = (x % mod + mod) % mod;//跟單鏈表的頭節點插入一樣e[idx] = x;ne[idx] = h[k];h[k] = idx ++ ; } //查詢操作 bool query(int x) {int k = (x % mod + mod) % mod;//遍歷鏈表for (int i = h[k]; i != -1; i = ne[i]){if (e[i] == x)return true;}return false; } int main() {//這里是將所有頭節點賦為-1memset(h, -1, sizeof h);int n;scanf("%d", &n);while (n -- ){char op[2]; int x;scanf("%s%d", op, &x);if (*op == 'I')insert(x);else{bool flag = query(x);if (flag)printf("Yes\n");elseprintf("No\n");}}return 0; }

2.開放尋址法

基本思路比較容易理解就是只在一個數組中進行操作,防止溢出范圍一般要開到題目范圍的2-3倍。

假如我們求出的哈希值hash(x) == k,我們就在一個數組從第k個位置開始,如果該位置被占用,則依次看下一個位置,直到找到空位置為止。

代碼如下(示例):

#include <iostream> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; typedef pair<int, int> P; const int N = 2e5 + 6, M = 1e6 + 6, mod = 2e5 + 3; const int inf = 0x3f3f3f3f; int h[N]; //返回的是應該存儲的位置下標 int find(int x) {int k = (x % mod + mod) % mod;while (h[k] != inf && h[k] != x){k ++;//如果超出范圍就從頭開始找if (k > N) k = 0;}return k; } int main() {//用無窮來約定空位置memset(h, inf, sizeof h);int n;scanf("%d", &n);while (n -- ){char op[2]; int x;scanf("%s%d", op, &x);int idx = find(x);if (*op == 'I')h[idx] = x;else{if (h[idx] == x)printf("Yes\n");elseprintf("No\n");}}return 0; }

個人感覺開放尋址法還是比拉鏈法簡單的

3.正增量的二次探測法

這種處理沖突的方法我是在做題的時候遇見的,我也看不出這種方法有什么優勢,既然出現就了解一下。

假設哈希數組的長度為msize,用哈希的方式插入元素x,同時用正增量的二次探測解決沖突,每次的位置idx = (x + i * i)% msize

bool f = false; for (int i = 0; i < msize; i ++ ) {idx = (x + i * i) % msize;//如果可以插入if (!h[idx]){h[idx] = x;f = true;break;} }

題目描述

將一個由若干個不同正整數構成的整數序列插入到一個哈希表中,然后輸出輸入數字的位置。

哈希函數定義為 H(key)=key%TSize,其中 TSize 是哈希表的最大大小。

利用只具有正增量的二次探測法來解決沖突。

注意,哈希表的大小最好是素數,如果用戶給出的最大大小不是素數,則必須將表大小重新定義為大于用戶給出的大小的最小素數。

輸入格式
第一行包含兩個整數 MSize 和 N,分別表示用戶定義的表的大小以及輸入數字的數量。

第二行包含 N 個不同的正整數,數字之間用空格隔開。

輸出格式
在一行中,輸出每個輸入數字的相應位置(索引從 0 開始),數字之間用空格隔開,行尾不得有多余空格。

如果無法插入某個數字,則輸出 -。

數據范圍
1≤MSize≤104,
1≤N≤MSize,
輸入數字均在 [1,105] 范圍內

樣例
輸入樣例:
4 4
10 6 4 15
輸出樣例:
0 1 4 -

代碼如下:

#include <iostream> #include <algorithm> #include <cstring> #include <vector> #include <iomanip> #include <cstdlib> #include <ctime> using namespace std; typedef long long ll; typedef pair<int,int> PII; typedef unsigned long long ull; const int N = 2e5 + 6, mod = 2e5 + 3, P = 131; const int inf = 0x3f3f3f3f; int a[N], h[N], n, m,cnt; //我這里用的埃氏素數篩找的素數 bool isprime[N]; void Prime(int n) {isprime[1] = true;for (int i = 2; i * i<= n ; i ++ ){if (!isprime[i]){for (int j = i * i; j <= n; j += i )isprime[j] = true;}} } int main() {Prime(2e5);scanf("%d%d", &n, &m);while(isprime[n]) n ++;while (m -- ){int x; scanf("%d", &x);int idx = x % n;if (!h[idx])h[idx] = x;else{bool f = false;//正增量的二次探測for (int i = 0; i < n; i ++ ){idx = (x + i * i) % n;if (!h[idx]){f = true;h[idx] = x;break;}}if (!f) idx = -1;}if (idx == -1) printf("-");else printf("%d", idx);cnt ++;if (cnt != n)printf(" ");}return 0; }

三、字符串哈希

字符串哈希又叫字符串前綴哈希法

快速判斷兩個字符串是否相同。y總表示是一種非常牛逼的做法,讓KMP望而卻步

相比于KMP算法,字符串哈希還是比較容易理解的,而且除了KMP算法求循環節之外,基本上都可以用字符串哈希代替KMP算法來實現。

求字符串前綴的哈希值

將一個字符串看成一個P進制的數,最后轉化成10進制數

假如我們要求一個字符串ABCD的哈希值,ABCD對應1234

ABCD
1234

我們要求的就是字符串的前綴哈希值
h[0] = 0
h[1] = 'A’的哈希值
h[2] = 'AB’的哈希值
h[3] = 'ABC’的哈希值
h[4] = 'ABCD’的哈希值

ABCD的哈希值 h[4] =(1234)p = 1 * P3 + 2 * P 2 + 3 * P1 + 4 * P0

因為轉化后的數字可能很大,所以我們要對所求的哈希值對Q取模

通過這樣一種方式就可以把任意一個字符串映射成一個0~Q-1的數了

注意:
1.不能將字母映射成0,一般從1開始映射,不然會使不同的字符串的哈希值相同

2.有這樣一組經驗值,當P = 131 或 13331 Q = 264,且我們RP足夠好,在99.99%的情況下不存在沖突,這可不是我說的

3.因為Q = 264,所以我們用unsigned long long來存儲所有的哈希值,就不需要對Q取模了。因為會溢出,所以就等價于取模了

求任意字串的哈希值

我們可以利用我們所求得的前綴哈希用一個公式算出來任意一個子串的哈希值

假設我們已知兩個前綴哈希值h[R],h[L - 1], 目標是求得L~R的哈希值

即h[L~R] = h[R] -h[L-1] * PR-L+1

證明過程如下:


h[R] = 1 * PR-1 + 2 * PR-2 + …+ R * P0
h[L-1] = 1 * PL-2 + 2 * PL-2 + …+ (L-1) * P0
要求的L~R的哈希值, 我們需要將h[L-1] * PR-L+1 = 1 * PR-1 + 2 * PR-2 + …+ (L-1) * PR-L+1
h[L~R] = L * PR-L + (L+1) * PR-L-1 + …+ R * P0 = h[R] - h[L-1] * PR-L+1

其實本質就是進制轉換

接下來上一個模板題用代碼實現一下


給定一個長度為 n 的字符串,再給定 m 個詢問,每個詢問包含四個整數 l1,r1,l2,r2,請你判斷 [l1,r1] 和 [l2,r2] 這兩個區間所包含的字符串子串是否完全相同。

字符串中只包含大小寫英文字母和數字。

輸入格式
第一行包含整數 n 和 m,表示字符串長度和詢問次數。

第二行包含一個長度為 n 的字符串,字符串中只包含大小寫英文字母和數字。

接下來 m 行,每行包含四個整數 l1,r1,l2,r2,表示一次詢問所涉及的兩個區間。

注意,字符串的位置從 1 開始編號。

輸出格式
對于每個詢問輸出一個結果,如果兩個字符串子串完全相同則輸出 Yes,否則輸出 No。

每個結果占一行。

數據范圍
1≤n,m≤105
輸入樣例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
輸出樣例:
Yes
No
Yes

代碼如下:

#include <iostream> #include <algorithm> #include <cstring> #include <vector> #include <iomanip> #include <cstdlib> #include <ctime> using namespace std; typedef long long ll; typedef unsigned long long ull; const int P = 131; ull h[N], p[N]; //p數組就是用來存儲P的多少次方 char str[N]; //得到任意字串的哈希值 ull get_hash(int l, int r) {return h[r] - h[l - 1] * p[r - l + 1]; } int main() {int n, m; scanf("%d%d%s", &n, &m, str + 1);//預處理p[0] = 1;for (int i = 1; i <= n; i ++ ){//存儲P的次方p[i] = p[i - 1] * P;//求字符串前綴的哈希值//str[i]只要不是0,多少都可以//這里直接將字符映射成其對應的ascall碼值h[i] = h[i - 1] * P + str[i];}while (m -- ){int l1, r1, l2, r2;scanf("%d%d%d%d", &l1, &r1, &l2, &r2);if (get_hash(l1, r1) == get_hash(l2, r2))printf("Yes\n");elseprintf("No\n");}return 0; }

總結

學了哈希字符串有種想忘掉KMP的想法

總結

以上是生活随笔為你收集整理的离散化与哈希的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。