1.鏈表與鄰接表:樹與圖的存儲(chǔ)
我們將結(jié)構(gòu)體和指針結(jié)合來實(shí)現(xiàn)鏈表
struct Node { ? ?int val; ? ?Node * next; }; ? new Node;//這樣創(chuàng)建結(jié)點(diǎn)是相當(dāng)慢的
我們算法主要是用數(shù)組來模擬鏈表,這樣效率會(huì)高一些。
數(shù)組模擬單鏈表
鄰接表:存儲(chǔ)圖和樹
實(shí)現(xiàn)一個(gè)單鏈表,鏈表初始為空,支持三種操作:
-
向鏈表頭插入一個(gè)數(shù)
-
刪除第k個(gè)插入的數(shù)后面的數(shù)
-
在第k個(gè)前面插入一個(gè)數(shù)
#include<iostream>
using namespace std;
?
const int N = 100010;
?
//head為頭結(jié)點(diǎn)的下標(biāo)
//e[i]表示節(jié)點(diǎn)i的值
//ne[i]表示節(jié)點(diǎn)i的節(jié)點(diǎn)next指針
//idx存儲(chǔ)當(dāng)前已經(jīng)使用到的點(diǎn)的位置
?
int head, idx, e[N], ne[N];
?
void init()
{
? ?head = -1;
? ?idx = 0;
}
?
//將x插入到頭結(jié)點(diǎn)
void add_to_head(int x)
{
? ?e[idx] = x;
? ?ne[idx] = head;
? ?head = idx;
? ?idx++;
}
?
//將x插入到k的節(jié)點(diǎn)位置
void add(int k, int x)
{
? ?e[idx] = x;
? ?ne[idx] = ne[k];
? ?ne[k] = idx;
? ?idx++;
}
?
//將k后面的點(diǎn)去掉
void remove(int k)
{
? ?ne[k] = ne[ne[k]];
}
?
int main()
{
? ?int m;
? ?cin >> m;
? ?
? ?init();
? ?
? ?while(m--)
? {
? ? ? ?int k, x;
? ? ? ?char op;
? ? ? ?
? ? ? ?cin >> op;
? ? ? ?if(op == 'H')
? ? ? {
? ? ? ? ? ?cin >> x;
? ? ? ? ? ?add_to_head(x);
? ? ? }
? ? ? ?else if(op == 'D')
? ? ? {
? ? ? ? ? ?cin >> k;
? ? ? ? ? ?if(k == 0) head = ne[head];
? ? ? ? ? ?remove(k-1);
? ? ? }
? ? ? ?else
? ? ? {
? ? ? ? ? ?cin >> k >> x;
? ? ? ? ? ?add(k-1, x);
? ? ? }
? }
? ?
? ?for(int i = head;i != -1;i = ne[i])
? {
? ? ? ?cout << e[i] << ' ';
? }
? ?return 0;
}
數(shù)組模擬雙鏈表的模板:
#include<iostream>
using namespace std;
?
const int N = 100010;
?
int m;
int e[N], l[N], r[N], idx;
?
void init()
{
? ?//0表示左端點(diǎn),1表示右端點(diǎn)
? ?r[0] = 1, l[1] = 0;
? ?idx = 2;
}
?
//在下標(biāo)的k的右邊插入x
void add(int k, int x)
{
? ?e[idx] = x;
? r[idx] = r[k];
? ?l[idx] = k;
? ?l[r[k]] = idx;
? ?r[k] = idx;
}
?
//刪除第k個(gè)點(diǎn)
void remove(int k)
{
? ?r[l[k]] = r[k];
? ?l[r[k]] = l[k];
}
2.棧與隊(duì)列:單調(diào)隊(duì)列、單調(diào)棧
棧是先進(jìn)后出,隊(duì)列是先進(jìn)先出。
數(shù)組實(shí)現(xiàn)棧:
#include<iostream> using namespace std; ? const int N = 100010; ? int stk[N], tt; ? //插入stk[++tt] = x; ? //彈出tt--; ? //判斷棧是否為空 if(tt>0) 不為空,否則為空 ? //棧頂 stk[tt];
數(shù)組實(shí)現(xiàn)隊(duì)列
#include<iostream> using namespace std; ? //在隊(duì)尾插入元素,在隊(duì)頭彈出元素 int q[N], hh, tt = -1; ? //判斷隊(duì)列是否為空 if(hh <= tt) not empty; else empty; ? //取出隊(duì)頭、隊(duì)尾元素 q[hh]; q[tt];
單調(diào)棧例題:
給定一個(gè)長度為 N
的整數(shù)數(shù)列,輸出每個(gè)數(shù)左邊第一個(gè)比它小的數(shù),如果不存在則輸出 -1
。
int tt; for(int i = 1;i <= n; i++) { ? ?while(tt && check(q[tt], i)) t--; ? ?stk[++tt] = i; }
題目代碼:
#include<iostream>
using namespace std;
?
const int N = 1e5 + 10;
?
int n;
int q[N], tt;
?
int main()
{
? ?scanf("%d", &n);
? ?for(int i = 0;i < n; i++)
? {
? ? ? ?int x;
? ? ? ?scanf("%d",&x);
? ? ? ?while(tt && stk[tt] >= x) tt--;
? ? ? ?if(tt) printf("%d ",stk[tt]);
? ? ? ?else printf("-1 ");
? ? ? ?
? ? ? ?stk[++tt] = x;
? }
? ?return 0;
}
只有出棧和入棧兩次操作,所以它的時(shí)間復(fù)雜度為O(n)
單調(diào)隊(duì)列例題:
移動(dòng)窗口的例題,k為窗口大小且k=3,打印出三個(gè)數(shù)中最小數(shù),如果窗口下數(shù)不足,則輸出-1
1 3 -1 -3 5 3 6 7
由上面的例子我們可以知道在3 -1 -3這個(gè)序列中3絕對不可能為最小值,-3的生命周期要比3長。我們要優(yōu)化該形式,可以用單調(diào)隊(duì)列的形式,將不滿足的點(diǎn)刪掉。
我們的隊(duì)列里存的不是值而是下標(biāo)。
int hh = 0, tt = 1; for(int i = 0;i < n; i++) { ? ?if(hh <= tt && check_out(q[hh])) hh++; ? ?while(hh <= tt && check(q[tt],i)) tt--; ? ?q[++tt] = i; }
本題代碼
#include<iostream>
using namespace std;
?
const int N = 1e6 + 10;
?
int n,k;
int a[N], q[N];
?
int main()
{
? ?scanf("%d%d", &n, &k);
? ?
? ?for(int i = 0;i < n; i++)
? {
? ? ? ?scanf("%d", &a[i]);
? }
? ?
? ?int hh = 0, tt = -1;
? ?for(int i = 0;i < n; i++)
? {
? ? ? ?//判斷隊(duì)頭是否已經(jīng)超出滑出窗口
? ? ? ?if(hh <= tt && i - k + 1 > q[hh]) hh++;
? ? ? ?while(hh <= tt && a[q[tt]] >= a[i]) tt--;
? ? ? ?q[++tt] = i;
? ? ? ?if(i >= k - 1)
? ? ? ? ? ?printf("%d ", a[q[hh]]);
? }
? ?printf("");
? ?
? ?return 0;
}
3.kmp算法
KMP算法是一種字符串匹配算法,用于在一個(gè)字符串中查找另一個(gè)字符串出現(xiàn)的位置。它的全名是Knuth-Morris-Pratt算法,是由Donald Knuth、James H. Morris和Victor S. Pratt三人共同發(fā)明的。
S[N],p[M]我們?nèi)绻帽┝λ惴ǎ闼刈龇ǎ﹣碜龅脑?,代碼如下:
for(int i = 1;i <= n; i++) { ? ?bool flag = true; ? ?for(int j = 1;j <= m; j++) ? { ? ? ? ?if(s[i] != p[j]) ? ? ? { ? ? ? ? ? ?flag = false; ? ? ? ? ? ?break; ? ? ? } ? } }
KMP算法的思想是在匹配過程中,盡量利用已經(jīng)匹配過的信息來快速跳過不匹配的位置,從而達(dá)到減少匹配次數(shù)和提高匹配效率的目的。具體來說,它通過預(yù)處理出一個(gè)next數(shù)組,來記錄匹配模式串中每個(gè)位置上最長的相同前綴和后綴的長度。在匹配過程中,如果發(fā)現(xiàn)當(dāng)前字符不匹配,就可以利用next數(shù)組中記錄的信息,快速地將模式串向右移動(dòng)一定的距離,而無需重新從頭開始匹配。
KMP算法的時(shí)間復(fù)雜度為O(m+n),其中m和n分別為匹配串和模式串的長度。相比于暴力匹配算法,KMP算法可以在很大程度上減少匹配的次數(shù),從而提高匹配的效率。但是,KMP算法的實(shí)現(xiàn)比較復(fù)雜,需要對next數(shù)組進(jìn)行正確的計(jì)算和處理,因此在實(shí)際應(yīng)用中需要仔細(xì)考慮其實(shí)現(xiàn)方式和適用場景。
next[i] = j, p[1, j] = p[i - j + 1, i]
acwing的KMP算法例題
給定一個(gè)模式串S,以及一個(gè)模板串P,所有字符串中只包含大小寫英文字母以及阿拉伯?dāng)?shù)字。模板串P在模式串S中多次作為子串出現(xiàn)。求出模板串P在模式串S中所有出現(xiàn)的位置的起始下標(biāo)。
輸入樣例:3 aba 5 ababa 輸出:0 2
代碼:
#include<iostream>
using namespace std;
?
const int N = 1e4 + 10, M = 1e5 + 10;
?
int n, m;
char p[N], s[M];
int ne[N];
?
int main()
{
? ?cin >> n >> p + 1 >> m >> s + 1;
? ?
? ?//求next的過程
? ?for(int i = 2, j = 0;i <= n; i++)
? {
? ? ? ?while(j && p[i] != p[j + 1]) j = ne[j];
? ? ? ?if(p[i] == p[j + 1]) j++;
? ? ? ?ne[i] = j;
? }
? ?
? ?//kmp的匹配過程
? ?for(int i = 1, j = 0;i <= m; i++)
? {
? ? ? ?while(j && s[i] != p[j + 1]) j = ne[j];
? ? ? ?if(s[i] == p[j + 1]) j++;
? ? ? ?if(j == n)
? ? ? {
? ? ? ? ? ?//匹配成功
? ? ? ? ? ?printf("%d ",i - n);
? ? ? ? ? ?j = ne[j];
? ? ? }
? }
? ?return 0;
}
4.Trie樹
Trie(又稱前綴樹或字典樹)是一種基于樹結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu),用于快速地檢索和插入字符串。Trie的每個(gè)節(jié)點(diǎn)代表一個(gè)字符串的前綴,從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的路徑上表示一個(gè)完整的字符串。
Trie的特點(diǎn)是它的每個(gè)節(jié)點(diǎn)都有多個(gè)子節(jié)點(diǎn),每個(gè)子節(jié)點(diǎn)代表一個(gè)字符。根節(jié)點(diǎn)沒有父節(jié)點(diǎn),每個(gè)非葉子節(jié)點(diǎn)的子節(jié)點(diǎn)數(shù)等于它的編號(hào)(從0開始)減去1。
在Trie中,插入一個(gè)字符串的操作是從根節(jié)點(diǎn)開始,按照字符串的字符順序依次遍歷每個(gè)字符,如果當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)中沒有對應(yīng)字符,就新建一個(gè)子節(jié)點(diǎn),并將當(dāng)前節(jié)點(diǎn)移動(dòng)到該子節(jié)點(diǎn)。如果已經(jīng)遍歷到字符串的末尾,則說明該字符串已經(jīng)成功插入到Trie中。
Trie的另一個(gè)常見操作是查找一個(gè)字符串是否存在于Trie中。從根節(jié)點(diǎn)開始,按照字符串的字符順序依次遍歷每個(gè)字符,如果當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)中沒有對應(yīng)字符,則說明該字符串不存在于Trie中。如果已經(jīng)遍歷到字符串的末尾,且當(dāng)前節(jié)點(diǎn)是一個(gè)葉子節(jié)點(diǎn),則說明該字符串存在于Trie中。
Trie的主要優(yōu)勢是它可以快速地檢索和插入字符串,時(shí)間復(fù)雜度為O(m),其中m為字符串的長度。但是,由于Trie的每個(gè)節(jié)點(diǎn)都需要存儲(chǔ)多個(gè)子節(jié)點(diǎn),因此它的空間復(fù)雜度比較高,適用于數(shù)據(jù)規(guī)模較小的情況。在大數(shù)據(jù)處理和文本處理等領(lǐng)域,Trie通常被用來實(shí)現(xiàn)詞典和關(guān)鍵字過濾等功能。
trie字符串統(tǒng)計(jì):
維護(hù)一個(gè)字符串集合,支持兩種操作:
-
‘lx’向集合中插入一個(gè)字符串x
-
‘Qx’詢問一個(gè)字符串在集合中出現(xiàn)了多少次
一共有N次操作,輸入的字符串總長度不超過105,字符串僅包含小寫英文字母。
#include<iostream>
using namespace std;
?
const int N = 1e5 + 10;
?
int son[N][26], cnt[N], idx;//下標(biāo)是0的點(diǎn),既是根節(jié)點(diǎn),有是空節(jié)點(diǎn)
int str[N];
?
void insert(char str[])
{
? ?int p = 0;
? ?for(int i = 0;str[i]; i++)
? {
? ? ? ?int u = str[i] - 'a';
? ? ? ?if(!son[p][u]) son[p][u] = ++idx;
? ? ? ?p = son[p][u];
? }
? ?
? ?cnt[p]++;
}
?
int query(char str[])
{
? ?int p = 0;
? ?for(int i = 0;str[i]; i++)
? {
? ? ? ?int u = str[i] - 'a';
? ? ? ?if(!son[p][u]) return 0;
? ? ? ?p = son[p][u];
? }
? ?
? ?return cnt[p];
}
?
int main()
{
? ?int n;
? ?scanf("%d", &n);
? ?while(n--)
? {
? ? ? ?char op[2];
? ? ? ?scanf("%s%s", op, str);
? ? ? ?if(op == 'I') insert(str);
? ? ? ?else printf("%d\n", query(str));
? }
? ?return 0;
}
最大異或?qū)?/strong>
5.并查集
并查集是一種樹型的數(shù)據(jù)結(jié)構(gòu),用于處理一些不相交集合的合并及查詢問題。
-
將兩個(gè)集合合并
-
詢問兩個(gè)元素是否在一個(gè)集合當(dāng)中。
基本原理:每隔幾何用一棵樹來表示,樹根的編號(hào)就是一整個(gè)集合的編號(hào)。每個(gè)節(jié)點(diǎn)存儲(chǔ)它的父節(jié)點(diǎn),p[x]表示x的父節(jié)點(diǎn)。
問題1:如何判斷樹根:if(p[x] == x);
問題2:如何求x的集合編號(hào):while(p[x] != x) x = p[x];
問題3:如何合并兩個(gè)集合:px是x的集合編號(hào),py是y的集合編號(hào)。p[x] = y
基本上來說是O(1)的時(shí)間復(fù)雜度
合并集合:
一共有n個(gè)數(shù),編號(hào)是1~n,最開始每個(gè)數(shù)各自在一個(gè)集合中。
現(xiàn)在要進(jìn)行m個(gè)操作,操作一共有兩種
-
將編號(hào)為a,b的兩個(gè)數(shù)所在集合進(jìn)行合并,如果兩個(gè)數(shù)已經(jīng)在集合中,即忽略這個(gè)操作
-
詢問編號(hào)為a,b的兩個(gè)數(shù)是否在同一個(gè)集合中
#include<iostream>
using namespace std;
?
const int N = 1e5 + 10;
?
int n, m;
int p[N];
?
int find(int x)//返回x的祖宗結(jié)點(diǎn)+路徑壓縮
{
? ?if(p[x] != x) p[x] = find(p[x]);
? ?return p[x];
}
?
void merge(int a, int b)
{
? ?p[find(a)] = find(b);
}
?
int main()
{
? ?scanf("%d%d", &n, &m);
? ?
? ?for(int i = 1;i <= n; i++)
? p[i] = i;
? ?
? ?while(m--)
? {
? char op[2];
? ? ? ?int a, b;
? ? ? ?scanf("%s%d%d", op, &a, &b);
? ? ? ?
? ? ? ?if(op[0] == 'M')
? ? ? ? ? ?merge(a, b);
? ? ? ?else
? ? ? {
? ? ? ? ? ?if(find(a) == find(b))
? ? ? ? ? ? ? ?printf("Yes\n");
? ? ? ? ? ?else
? ? ? ? ? ? ? ?printf("No\n");
? ? ? }
? }
? ?
? ?return 0;
}
近乎O(1)的時(shí)間
連通塊中點(diǎn)的數(shù)量:
給定一個(gè)包含n個(gè)點(diǎn)(編號(hào)為1~n)的無向圖,初始時(shí)圖中沒有邊。
現(xiàn)在要進(jìn)行m個(gè)操作,操作一共有三個(gè):
-
“Cab”,在點(diǎn)a和點(diǎn)b之間連一條邊,a和b可能相等
-
“Q1ab”,詢問點(diǎn)a和點(diǎn)b是否在同一連通塊內(nèi),a和b可能相等
-
“Q2ab”,詢問點(diǎn)a所在的聯(lián)通快中點(diǎn)的個(gè)數(shù)。
#include<iostream>
using namespace std;
?
const int N = 1e5 + 10;
?
int n, m;
int p[N], size[N];
?
int find(int x)
{
? ?if(p[x] != x) p[x] = find(p[x]);
? ?return p[x];
}
?
void merge(int a, int b)
{
? ?p[find(a)] = find(b);
}
?
int main()
{
? ?scanf("%d%d", &n, &m);
? ?
? ?for(int i = 1;i <= n; i++)
? {
? ? ? ?p[i] = i;
? ? ? ?size[i] = 1;
? }
? ?
? ?while(m--)
? {
? ? ? ?char op[5];
? ? ? ?int a, b;
? ? ? ?
? ? ? ?scanf("%s", op);
? ? ? ?
? ? ? ?if(op[0] == 'C')
? ? ? {
? ? ? ? ? ?scanf("%d%d", &a, &b);
? ? ? ? ? ?if(find(a) == find(b)) continue;
? ? ? ? ? ?size[find(b)] += size[find(a)];
? ? ? ? ? ?merge(a, b);
? ? ? }
? ? ? ?else if(op[1] == '1')
? ? ? {
? ? ? ? ? ?scanf("%d%d", &a, &b);
? ? ? ? ? ?if(find(a) == find(b))
? ? ? ? ? ? ? ?printf("Yes\n");
? ? ? ? ? ?else
? ? ? ? ? ? ? ?printf("No\n");
? ? ? }
? ? ? ?else
? ? ? {
? ? ? ? ? ?scanf("%d", &a);
? ? ? ? ? ?printf("%d\n", size[find(a)]);
? ? ? }
? ? ? ?
? }
? ?return 0;
}
食物鏈
動(dòng)物王國中有三類動(dòng)物A,B,C,這三類動(dòng)物的食物鏈構(gòu)成了有趣的環(huán)形。
A吃B, B吃C,C吃A。
現(xiàn)有N個(gè)動(dòng)物,以1-N編號(hào)。
每個(gè)動(dòng)物都是A,B,C中的一種,但是我們并不知道它到底是哪一種。
有人用兩種說法對這N個(gè)動(dòng)物所構(gòu)成的食物鏈關(guān)系進(jìn)行描述:
第一種說法是”1 X Y”,表示X和Y是同類。
第二種說法是”2 X Y”,表示X吃Y。
此人對N個(gè)動(dòng)物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。
當(dāng)一句話滿足下列三條之一時(shí),這句話就是假話,否則就是真話。
1) 當(dāng)前的話與前面的某些真的話沖突,就是假話; 2) 當(dāng)前的話中X或Y比N大,就是假話; 3) 當(dāng)前的話表示X吃X,就是假話。
你的任務(wù)是根據(jù)給定的N和K句話,輸出假話的總數(shù)。
#include<iostream> using namespace std; ? int main() { ? ?return 0; }
6.堆
如何手寫一個(gè)堆?
堆是維護(hù)數(shù)與集合。
堆是一種數(shù)據(jù)結(jié)構(gòu),它滿足以下兩個(gè)條件:
-
完全二叉樹:堆的總是一棵完全二叉樹,即除了最后一層外,其他層都是滿的,并且最后一層的節(jié)點(diǎn)都集中在左側(cè)。
-
堆性質(zhì):對于每個(gè)節(jié)點(diǎn)X,其父節(jié)點(diǎn)的值小于等于(或大于等于,稱為最小堆)其子節(jié)點(diǎn)的值。
堆通常用于實(shí)現(xiàn)優(yōu)先隊(duì)列,其中每個(gè)節(jié)點(diǎn)代表一個(gè)事件或任務(wù),節(jié)點(diǎn)的值表示任務(wù)的優(yōu)先級(jí)。通過將任務(wù)按照優(yōu)先級(jí)從上到下放置在堆中,可以保證優(yōu)先級(jí)最高的任務(wù)總是最先被執(zhí)行。
在實(shí)現(xiàn)上,堆可以使用數(shù)組來表示。對于一個(gè)節(jié)點(diǎn)X,其父節(jié)點(diǎn)和子節(jié)點(diǎn)的位置可以通過簡單的數(shù)學(xué)計(jì)算來確定。例如,對于一個(gè)最小堆,節(jié)點(diǎn)X的父節(jié)點(diǎn)和左子節(jié)點(diǎn)的位置可以通過以下公式計(jì)算:
-
父節(jié)點(diǎn)位置:parent(X) = (X-1)/2
-
左子節(jié)點(diǎn)位置:left_child(X) = 2*X + 1
堆也可以用來實(shí)現(xiàn)優(yōu)先級(jí)調(diào)度、任務(wù)調(diào)度等應(yīng)用。
-
插入一個(gè)數(shù)
heap[++size] = x; up(size);
-
求集合當(dāng)中的最小值
heap[1];
-
刪除最小值 用最后一個(gè)元素覆蓋根節(jié)點(diǎn)
heap[1] = heap[size];size--;down(1);
-
刪除任意元素
heap[k] = heap[size];size--;
-
修改任意元素
heap[k] = x; down(k); up(k);
838.堆排序:
輸入一個(gè)長度為n的整數(shù)數(shù)列,從小到大輸出前m小的數(shù)。
輸入格式:第一行包含整數(shù)n和m,第二行包含n個(gè)整數(shù),表示整數(shù)數(shù)列。
輸出格式:共一行,包含m個(gè)整數(shù),表示整數(shù)數(shù)列中前m小的數(shù)。
5 3 \n 4 5 1 3 2
1 2 3
代碼:
#include<iostream>
#include<algorithm>
?
using namespace std;
?
const int N = 1e5 + 10;
?
int n, m;
int h[N], size;
?
void down(int u)
{
? ?int t = u;
? ?if(u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
? ?if(u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
? ?if(u != t)
? {
? ? ? ?swap(h[u], h[t]);
? ? ? ?down(t);
? }
}
?
int main()
{
? ?scanf("%d%d", &n, &m);
? ?for(int i = 1;i <= n; i++)
? ? ? ?scanf("%d", &h[i]);
? ?size = n;
? ?
? ?for(int i = n / 2; i; i--)
? down(i);
? ?
? ?while(m--)
? {
? ? ? ?printf("%d ", h[1]);
? ? ? ?h[1] = h[size];
? ? ? ?size--;
? ? ? ?down(1);
? }
? ?
? ?return 0;
}
839.模擬堆:
維護(hù)一個(gè)集合,初始時(shí)集合為空,支持如下幾種操作:
-
"lx",插入一個(gè)數(shù)x;
-
“PM”,輸出當(dāng)前集合中的最小值;
-
“DM”,刪除當(dāng)前集合的最小值(當(dāng)最小值不唯一時(shí),刪除最早插入的最小值);
-
“Dk”,刪除第k個(gè)插入的數(shù);
-
“Ckx”,修改第k個(gè)插入的數(shù),將其變?yōu)閤;
現(xiàn)在要進(jìn)行N次操作。對于所有第2個(gè)操作,輸出當(dāng)前集合的是小值。
#include<iostream>
#include<algorithm>
#include<string.h>
?
using namespace std;
?
const int N = 1e5 + 10;
?
int h[N], ph[N], hp[N], size;
?
void heap_swap(int a, int b)
{
? ?swap(ph[hp[a]], ph[hp[b]]);
? ?swap(hp[a], hp[b]);
? ?swap(h[a], h[b]);
}
?
void down(int u)
{
? ?int t = u;
? ?if(u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
? ?if(u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
? ?if(u != t)
? {
? ? ? ?heap_swap(u, t);
? ? ? ?down(t);
? }
}
?
void up(int u)
{
? ?while(u / 2 && h[u / 2] > h[u])
? {
? ? ? ?heap_swap(u / 2, u);
? ? ? ?u /= 2;
? }
}
?
int main()
{
? ?int n, m = 0;
? ?scanf("%d", &n);
? ?
? ?while(n--)
? {
? ? ? ?char op[10];
? ? ? ?int k, x;
? ? ? ?
? ? ? ?scanf("%s", op);
? ? ? ?if(!strcmp(op, "I"))
? ? ? {
? ? ? ? ? ?scanf("%d", &x);
? ? ? ? ? ?size++;
? ? ? ? ? ?m++;
? ? ? ? ? ?ph[m] = size, hp[size] = m;
? ? ? ? ? ?h[size] = x;
? ? ? ? ? ?up(size);
? ? ? }
? ? ? ?else if(!strcmp(op, "PM")) printf("%d\n", h[1]);
? ? ? ?else if(!strcmp(op, "DM"))
? ? ? {
? ? ? ? ? ?heap_swap(1, size);
? ? ? ? ? ?size--;
? ? ? ? ? ?down(1);
? ? ? }
? ? ? ?else if(!strcmp(op, "D"))
? ? ? {
? ? ? ? ? ?scanf("%d", &k);
? ? ? ? ? ?k = ph[k];
? ? ? ? ? ?heap_swap(k, size);
? ? ? ? ? ?size--;
? ? ? ? ? ?down(k), up(k);
? ? ? }
? ? ? ?else
? ? ? {
? ? ? ? ? ?scanf("%d%d", &k, &x);
? ? ? ? ? ?k = ph[k];
? ? ? ? ? ?h[k] = x;
? ? ? ? ? ?down(k), up(k);
? ? ? }
? }
? ?
? ?return 0;
}
7.Hash表
哈希表是一種使用哈希函數(shù)組織數(shù)據(jù),以支持快速插入和搜索的數(shù)據(jù)結(jié)構(gòu)。
哈希表可以用來實(shí)現(xiàn)動(dòng)態(tài)集合。它支持以下操作:
-
插入:將一個(gè)元素插入到哈希表中。
-
刪除:將一個(gè)元素從哈希表中刪除。
-
搜索:在哈希表中查找一個(gè)元素。
哈希表的實(shí)現(xiàn)基于哈希函數(shù),它將鍵映射到存儲(chǔ)桶(bucket)中。每個(gè)存儲(chǔ)桶是一個(gè)鏈表,鏈表中的每個(gè)節(jié)點(diǎn)都包含一個(gè)鍵值對。當(dāng)插入一個(gè)元素時(shí),哈希函數(shù)將鍵映射到一個(gè)存儲(chǔ)桶,然后將元素添加到該存儲(chǔ)桶的鏈表中。當(dāng)搜索一個(gè)元素時(shí),哈希函數(shù)將鍵映射到相應(yīng)的存儲(chǔ)桶,然后在該存儲(chǔ)桶的鏈表中查找元素。
哈希表有多種實(shí)現(xiàn)方式,其中最常用的是開放地址法和相聯(lián)數(shù)組法。開放地址法在哈希表中使用一個(gè)數(shù)組來存儲(chǔ)存儲(chǔ)桶,當(dāng)發(fā)生哈希沖突時(shí),它使用線性探測、二次探測、雙重散列等方法來找到空槽位。相聯(lián)數(shù)組法在哈希表中直接使用一個(gè)數(shù)組來存儲(chǔ)鍵值對,當(dāng)發(fā)生哈希沖突時(shí),它將新元素添加到數(shù)組的末尾。
哈希表在計(jì)算機(jī)科學(xué)中廣泛應(yīng)用,例如在數(shù)據(jù)庫、操作系統(tǒng)、網(wǎng)絡(luò)通信等領(lǐng)域。它能夠提供常數(shù)級(jí)別的查找效率,比其他數(shù)據(jù)結(jié)構(gòu)更高效。
7.1哈希表的存儲(chǔ)結(jié)構(gòu)
1.開放尋址法 2.拉鏈法
840.模擬散列表
維護(hù)一個(gè)集合,支持如下幾種操作:
-
“l(fā)x”,插入一個(gè)數(shù)
-
“Qx”,詢問數(shù)x是否在集合中出現(xiàn)過
現(xiàn)在要進(jìn)行N次操作,對于每個(gè)詢問操作輸出對應(yīng)的結(jié)果
一般情況下,對哈希函數(shù)進(jìn)行取模,但是由于取模的原因,可能會(huì)有沖突,若干不同的數(shù)可能會(huì)映射在同一個(gè)位置。
開放尋址法:
#include<iostream>
#include<cstring>
?
using namespace std;
?
cosnt int N = 200003, null = 0x3f3f3f3f;
?
int h[N];
?
int find(int n)
{
? ?int t = (x % N + N) % N;
? ?
? ?while(h[t] != null && h[t] != x)
? {
? ? ? ?t++;
? ? ? ?if(t == N)
? ? ? ? ? ?t = 0;
? }
? ?
? ?return t;
}
?
int main()
{
? ?int n;
? ?scanf("%d", &n);
? ?merset(h, 0x3f, sizeof(h));
? ?
? ?while(n--)
? {
? ? ? ?char op[2];
? ? ? ?int x;
? ? ? ?scanf("%s%d", op, &x);
? ? ? ?
? ? ? ?int k = find(x);
? ? ? ?if (op == 'I')
? ? ? {
? ? ? ? ? ?h[k] = x;
? ? ? }
? ? ? ?else
? ? ? {
? ? ? ? ? ?if (h[k] != null)
? ? ? ? ? ? ? ?puts("Yes");
? ? ? ? ? ?else
? ? ? ? ? ? ? ?puts("No");
? ? ? }
? }
? ?return 0;
}
拉鏈法:
#include<iostream>
#include<cstring>
?
using namespace std;
?
const int N = 100003;
?
int h[N], e[N], ne[N], idx;
?
void insert(int x)
{
? ?int k = (x % 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;
? else
? ? ? ? ? ?return false;
}
?
int main()
{
? ?int n;
? ?scanf("%d", &n);
? ?
? ?memset(h, -1, sizeof(h));
? ?
? ?while(n--)
? {
? ? ? ?char op[2];
? ? ? ?int x;
? ? ? ?scanf("%s%d", op, &x);
? ? ? ?
? ? ? ?if(op == "I") insert(x);
? ? ? ?else
? ? ? {
? ? ? ? ? ?if(find(x)) puts("Yes");
? ? ? ? ? ?else puts("No");
? ? ? }
? }
? ?return 0;
}
7.2.字符串前綴哈希方法
str = "ABCDEFADCYUGHJJDFDGKHJK"
預(yù)處理:特殊h[0] = 0
h[1] = "A"的對應(yīng)的哈希值,后面的一樣 h[2] = "AB" h[3] = "ABC".....
-
不能映射為0
-
Rp足夠好,不產(chǎn)生沖突
841.字符串哈希
給定個(gè)長度為的字符串,再給定m個(gè)詢問,每個(gè)詢問包含四個(gè)整數(shù)l1,r1,l2,r2,請你判斷[l1,r1]和[l2,r2]這兩個(gè)區(qū)間所包含的字符串子串是否完全相同。
字符串中只包含大小寫英文字母和數(shù)字。
#include<iostream>
using namespace std;
?
typedef unsigned long long ULL;
?
const int N = 100010;
?
int n, m;
char str[N];
ULL h[N], p[N];
?
ULL get(int l, int r)
{
? ?return h[r] - h[l - 1] * p[r - l + 1];
}
?
int main()
{
? ?scanf("%d%d%s", &n, &m, str + 1);
? ?
? ?p[0] = 1;
? ?for(int i = 1;i <= n; i++)
? {
? ? ? ?p[i] = p[i-1] * p;
? ? ? ?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(l1, r1) == get(l2, r2))
? ? ? ? ? ?puts("Yes");
? ? ? ?else
? ? ? ? ? ?puts("No");
? }
? ?return 0;
}
8.C++STL使用技巧
vector:變長數(shù)組,倍增的思想
string:字符串,substr(),c_str()
queue:隊(duì)列,push(), front(), pop()
priority_queue:優(yōu)先隊(duì)列,push(), top(),pop()
stack:棧,push(), top(), pop()
deque:雙增隊(duì)列
set, map, multiset和multimap:基于平衡二叉樹,動(dòng)態(tài)維護(hù)有序序列
unorder_set、unorder_map、unorder_multiset、unorder_multimap:基于哈希表
bitset:壓位
list
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
?
using namespace std;
?
int main()
{
? ?vector<int> a(10, 3);
? ?//vector<int> a[10];
? ?a.size();//返回元素個(gè)數(shù)
? ?a.empty();//返回是否為空
? ?a.clear();//清空
? ?a.front();//返回第一個(gè)數(shù)
? ?a.back();//返回最后一個(gè)數(shù)
? ?a.push_back();//尾插
? ?a.pop_back();//尾部釋放
? ?a.begin()/a.end();//vector的迭代器
? ?
? ?for(int i = 0;i < 10; i++)
? ? ? ?a.push_back();
? ?
? ?for(int i = 0;i < a.size(); i++)
? ? ? ?cout << a[i] << ' ';
? ?cout << endl;
? ?
? ?for(vector<int>::iterator i = a.begin();i != a.end(); i++)
? ? ? ?cout << a[i] << ' ';
? ?cout << endl;
? ?
? //支持比較運(yùn)算
? ?vector<int> a(4, 3), b(3, 4);
? ?if(a > b) cout << "a > b" << endl;
? ?
? ?return 0;
}
系統(tǒng)內(nèi)某一程序分配空間時(shí),所需時(shí)間,與空間大小無關(guān),與中間次數(shù)有關(guān)
pair<int, int>:存儲(chǔ)一個(gè)二元組
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
?
using namespace std;
?
int main()
{
? ?pair<int, string>p;
? ?
? ?p.first;
? ?p.second;
? ?//支持比較運(yùn)算
? ?//以first為第一關(guān)鍵詞,以second為第二關(guān)鍵詞
? ?
? ?p = make_pair(10, "yxc");
? ?p = {20, "abc"};
? ?
? ?pair<int, pair<int>>w;
? ?return 0;
}
string
#include<iostream>
#include<cstring>
#include<algorithm>
?
using namespace std;
?
int main()
{
? ?string a = "yxc";
? ?
? ?a += "def";
? ?a += "c";
? ?
? ?cout << a << endl;
? ?cout << a.substr(2, 3) << endl;
? ?
? ?return 0;
}
queue隊(duì)列
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
?
using namespace std;
?
int main()
{
? ?queue<int> q;
? ?
? ?q = queue<int>;
? ?
? ?//優(yōu)先隊(duì)列
? ?priority_queue<int> heap;
? ?heap.clear();
? ?
? ?priority_queue<int, vector<int>, greater<int>> heap;//定義一個(gè)小根堆
? ?return 0;
}
stack棧
deque雙端隊(duì)列
size() empty() clear() front() back() push_front() pop_front() push_back pop_back() begin() end()
缺點(diǎn)就是deque比較慢
set
#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
?
using namespace std;
?
int main()
{
? ?set<int> S;
? ?multiset<int> S1;//可以有重復(fù)元素,set不允許
? ?
? ?S.insert(1);//插入一個(gè)數(shù)
? ?S.find();//查找一個(gè)數(shù)
? ?S.count();//返回某一個(gè)數(shù)的個(gè)數(shù)
? ?S.erase();//1.輸入的是一個(gè)數(shù),就刪除該數(shù)的所有存儲(chǔ) 2.輸入一個(gè)迭代器,就刪除這個(gè)迭代器
? ?S.lower_bound(x);//返回大于等于x的最小的數(shù)的迭代器
? ?S.upper_bound(x);//返回大于x的最小數(shù)的迭代器
? ?
? ?return 0;
}
map/multimap
insert()插入的數(shù)是一個(gè)pair erase()輸入的參數(shù)是pair或者是迭代器 find()
[] 時(shí)間復(fù)雜度是O(nlogn) lower_bound() upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap
哈希表 和上面的類似,增刪查改的時(shí)間復(fù)雜度是O(1)
不支持lower_bound()和upper_bound(),迭代器的++、--
bitset,壓位
bitset<10000>S;
~, &, |, ^, >>, <<, ==, !=,
count()返回有多少個(gè)1
any()判斷是否至少有一個(gè)1
none()判斷是否為0
set()把所有位置都換成1
reset()把所有位都變成0
flip()等價(jià)~ flip(k)把第k位反轉(zhuǎn)。
文章來源:http://www.zghlxwxcb.cn/news/detail-600811.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-600811.html
到了這里,關(guān)于算法競賽備賽之經(jīng)典數(shù)據(jù)結(jié)構(gòu)訓(xùn)練提升,暑期集訓(xùn)營培訓(xùn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!