第二章 資料結構

Blueqwq發表於2020-11-12

一、連結串列

1.單連結串列

AcWing 826. 單連結串列

	//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.雙連結串列

AcWing 827. 雙連結串列

//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];
	}

二、棧

AcWing 828. 模擬棧

	//用stk陣列模擬棧 tt為棧頂指標 
	int stk[N],tt;

	//插入x 
	stk[++tt]=x;

	//彈出 
	tt--;

	//判斷是否為空 
	if(tt>0) not empty
	else empty

	//獲取棧頂元素 
	int top=stk[tt];

三、佇列

1.普通佇列

AcWing 829. 模擬佇列

	//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

AcWing 831. 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樹

AcWing 835. 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.一般雜湊

AcWing 840. 模擬雜湊表

(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];
	}

相關文章