第二章 資料結構
第二章 資料結構
一、連結串列
1.單連結串列
//head 表示頭結點下標
//e[i] 表示節點i的值
//ne[i] 表示節點i的next指標是多少
//idx 儲存當前已經用到了哪個節點
int head,e[N],ne[N],idx;
//初始化
void init(){
head=-1;
idx=0;
}
//將x插入到頭結點
void add_to_head(int x){
e[idx]=x;
ne[idx]=head;
head=idx++;
}
//將x插到下標為k的節點後面
void add(int k,int x){
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
//將下標為k的點後面的點刪掉
void remove(int k){
ne[k]=ne[ne[k]];
}
2.雙連結串列
//l[i],r[i] 表示i節點左,右指標
int e[N],l[N],r[N],idx;
//初始化
void init(){
r[0]=1;
l[1]=0;
idx=2;
}
//在下標為k的點右面插入一個點
void add(int k,int x){
e[idx]=x;
r[idx]=r[k];
l[idx]=k;
l[r[k]]=idx;
r[k]=idx++;
}
//刪除第k個點
void remove(int k){
r[l[k]]=r[k];
l[r[k]]=l[k];
}
二、棧
//用stk陣列模擬棧 tt為棧頂指標
int stk[N],tt;
//插入x
stk[++tt]=x;
//彈出
tt--;
//判斷是否為空
if(tt>0) not empty
else empty
//獲取棧頂元素
int top=stk[tt];
三、佇列
1.普通佇列
//hh表示隊頭,tt表示隊尾
int q[N],hh,tt;
//插入x
q[++tt]=x;
//彈出
hh++;
//判斷是否為空
if(tt>=hh) not empty
else empty
//取出隊頭元素
int head=q[hh];
//取出隊尾元素
int tail=q[tt];
2.迴圈佇列
hh 表示隊頭,tt表示隊尾的後一個位置
int q[N], hh = 0, tt = 0;
// 向隊尾插入一個數
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 從隊頭彈出一個數
hh ++ ;
if (hh == N) hh = 0;
// 隊頭的值
int head = q[hh];
// 判斷佇列是否為空
if (hh != tt) not empty
else empty
三、KMP
主要思想:尋找模式串中以下標 i 為結尾的最長相等子前字尾
//s[]是長文字,p[]是模式串,ne[i]儲存以p[i]結尾的模式串中最長相等子前字尾的長度
//n,m為s[],p[]的長度
char s[N],p[M],ne[M];
int n,m;
//求模式串的ne陣列:
for (int i = 2, j = 0; i <= m; i ++ ){
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
//尋找子串
for (int i = 1, j = 0; i <= n; i ++ ){
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m){
j = ne[j];
...//根據題目發揮
}
}
四、Trie樹
int son[N][26], cnt[N], idx;
// 0號點既是根節點,又是空節點
// son[p][u]儲存樹中每個節點的子節點
// cnt[p]儲存以每個節點結尾的單詞數量
// 插入一個字串
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];
}
五、並查集
AcWing 836. 合併集合
AcWing 837. 連通塊中點的數量
AcWing 240. 食物鏈
基本原理:
把每個集合用一棵樹表示,樹的根節點就是集合的編號,且每個節點都儲存它的父節點,用 p[x] 表示 x 節點的父節點(定義根節點的父節點是它本身)。
Q&A:
Q1:如何判斷樹根?
A1:if(p[x]==x)
Q2:如何求x的集合編號?
A2:while(p[x]!=x) x=p[x];
Q3:如何合併兩個集合
A3:將一棵樹的根節點連在另一棵樹的任意一個節點上
p[x]=y;//x,y是兩棵樹的根節點
優化:路徑壓縮:
當遍歷一棵樹時,將其所有節點的父節點都賦值為這棵樹的根節點,即可將時間複雜度近似降為
O
(
1
)
O(1)
O(1)。
(1)樸素並查集:
//儲存每個點的根節點
int p[N];
//返回x的根節點+路徑壓縮
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
//合併a和b所在的兩個集合
p[find(a)] = find(b);
//判斷是否在同一集合中
if(find(a) == find(b))
(2)維護集合size的並查集:
//p[]儲存每個點的根節點, size[]只有根節點的有意義,表示根節點所在集合中的點的數量
int p[N], size[N];
//返回x的根節點
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
//初始化,假定節點編號是1~n
for (int i = 1; i <= n; i ++ ){
p[i] = i;
size[i] = 1;
}
//合併a和b所在的兩個集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)維護到根節點距離的並查集:
//p[]儲存每個點的根節點, d[x]儲存x到p[x]的距離
int p[N], d[N];
//返回x的根節點
int find(int x){
if (p[x] != x){
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
//初始化,假定節點編號是1~n
for (int i = 1; i <= n; i ++ ){
p[i] = i;
d[i] = 0;
}
//合併a和b所在的兩個集合:
p[find(a)] = find(b);
d[find(a)] = distance; //根據具體問題,初始化find(a)的偏移量
六、堆
AcWing 838. 堆排序
AcWing 839. 模擬堆
利用陣列儲存一個完全二叉樹,其中下標為x的節點左兒子為 2 x 2x 2x,右兒子為 2 x + 1 2x+1 2x+1
小根堆:每個節點儲存的數小於等於其兩個子節點(根節點儲存最小的數)
// h[N]儲存堆中的值, h[1]是堆頂,x的左兒子是2x, 右兒子是2x + 1
// ph[k]儲存第k個插入的點在堆中的位置
// hp[k]儲存堆中下標是k的點是第幾個插入的
int h[N], ph[N], hp[N], size;
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
// 交換兩個點,及其對映關係
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] < h[u / 2]){
heap_swap(u, u / 2);
u /= 2;
}
}
//插入一個數
h[++size]=x;
up(x);
//求陣列中最小值
h[1];
//刪除最小值
h[1]=h[size--];
down(1);
//刪除下標為k的數
h[k]=h[size--];
down(k),up(k);
//修改下標為k的數
h[k]=x;
down(k),up(k);
七、雜湊
將一組範圍很大的資料對映到另一個小範圍裡
一般操作:對一個質數取模
1.一般雜湊
(1)拉鍊法
基本思想:插入時存在衝突用連結串列儲存
const int N = 100003;
int h[N], e[N], ne[N], idx;
//向雜湊表中插入一個數
void insert(int x){
//負數取模還是負數,+N後%N變為正數
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;
return false;
}
(2)開放定址法
基本思想:非空就找下一位
當定義一個無窮大(
n
>
1
0
9
n>10^9
n>109)時,經驗是定義為0x3f3f3f3f,可以用memset(h,0x3f,sizeof(h))
賦初始值
//N開到元素個數的2~3倍,null開到資料範圍之外
const int N=200003,null=0x3f3f3f3f;
int h[N];
//如果x在雜湊表中,返回x的下標
//如果x不在雜湊表中,返回x應該插入的位置
int find(int x){
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x){
t ++ ;
if (t == N) t = 0;
}
return t;
}
int main(){
memset(h,0x3f,sizeof(h));
}
2.字串雜湊
AcWing 841. 字串雜湊
基本思想:將字串看成P進位制數,P的經驗值是131或13331,衝突概率低
小技巧:取模的數用
2
64
2^{64}
264,這樣直接用unsigned long long
儲存,溢位後的結果就是取模後的結果,(不過這裡假定RP MAX無衝突)
const int N=100003;
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]儲存字串前k個字母的雜湊值, p[k]儲存 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ ){
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 計運算元串 str[l ~ r] 的雜湊值
ULL get(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
相關文章
- 資料結構 第二章(學習筆記一)資料結構筆記
- PostgreSQL資料庫管理 第二章體系結構SQL資料庫
- 第二章 程式結構
- IEC61850教程,第二章:IEC 61850 資料結構資料結構
- 結構化資料、半結構化資料和非結構化資料
- 【資料結構篇】認識資料結構資料結構
- 第二章——資料結構與演算法基礎(佔比較高)資料結構演算法
- 資料結構小白系列之資料結構概述資料結構
- 第二章 處理器結構
- 資料結構?資料結構
- 資料結構資料結構
- 資料結構與演算法-資料結構(棧)資料結構演算法
- 第二章:介面和目錄結構
- 【PHP資料結構】PHP資料結構及演算法總結PHP資料結構演算法
- 資料結構-棧資料結構
- 資料結構-堆資料結構
- 資料結構-集合資料結構
- 資料結構 - 字串資料結構字串
- 資料結構——列表資料結構
- Redis 資料結構Redis資料結構
- 資料結構——樹資料結構
- Redis資料結構Redis資料結構
- 資料結構-樹資料結構
- Java資料結構Java資料結構
- 資料結構---串資料結構
- 資料結構|前言資料結構
- 資料結構——堆資料結構
- spacy資料結構資料結構
- 資料結構-Tree資料結構
- 資料結構:集合資料結構
- 資料結構模板資料結構
- 索引資料結構索引資料結構
- Map 資料結構資料結構
- 資料結構 - 棧資料結構
- 資料結構 - 堆資料結構
- 14 資料結構資料結構
- 資料結構 - 圖資料結構
- [資料結構]堆資料結構