資料結構

ForgiveMartain發表於2024-11-28

目錄
  • 連結串列和鄰接表
    • 單連結串列
    • 雙連結串列
  • 棧和佇列
    • 佇列
  • Kmp
    • 1.對於next陣列的理解
    • 2.主串與模式串的匹配過程
  • Tire
  • 並查集
  • Harsh表
    • 一般雜湊
      • 拉鍊法
      • 開放定址法
    • 字串雜湊

連結串列和鄰接表

單連結串列

//單連結串列
const int N=10010;
int h,e[N],en[N],idx;
初始化
void init(){
    h=-1;
    idx=0;
}
插入
    //頭插法
   void insert(int x){
    e[idx]=x;
    en[idx]=en[h];
    h=idx++;
}
//任意位置插入
void insert_every(int k,int x){
    e[idx]=x;
    en[idx]=en[k];
    k=idx++;
}
k的前一個位置-1,後一個位置+1;
刪除
    void delete(int k){
    en[k]=en[en[k]];
}

int main(){
//輸出
for(int i=h;i!=-1;i=en[i]){
    cout<<e[i]<<" ";
}
   
}

雙連結串列

using namespace std;
const int N=100010;
int l[N],r[N],e[N];
int k,x,idx;

/*雙連結串列
 *l[i]:表示i位置左邊的點
 *r[i]:表示i位置右邊的點
 *e[i]:表示i位置的值
 *1.初始化
 *2.插入
 *3.刪除
 */

void init() {
 //0表示左端點,1表示右端點
 r[0]=1;l[1]=0;
 idx=2;
}

void insert(int k,int x) {
 e[idx]=x;
 r[idx]=r[k];
 l[idx]=k;
 r[k]=idx++;
}

void remove(int k) {
 r[l[k]]=r[k];
 l[r[k]]=l[k];
}
//輸出i=1說明回到了起點
  for (int i = r[0]; i!= 1; i = r[i]) {
        cout << e[i] << " ";
    }
int main() {
   init();

    // 插入一些節點
    insert(0, 10); // 在左端點後插入值為 10 的節點
    insert(2, 20); // 在新插入的節點後插入值為 20 的節點
    insert(3, 30);

    cout << "插入節點後的雙連結串列:";
    for (int i = r[0]; i!= 1; i = r[i]) {
        cout << e[i] << " ";
    }
    cout << endl;

    // 刪除中間節點
    remove(2);

    cout << "刪除節點後的雙連結串列:";
    for (int i = r[0]; i!= 1; i = r[i]) {
        cout << e[i] << " ";
    }
    cout << endl;

    return 0;
}

案例模擬

   1.初始
   +---+    +---+
   | 0 |<-->| 1 |
   +---+    +---+
   2.插入一個值
   +---+    +---+    +---+
   | 0 |<-->| 2 |<-->| 1 |
   +---+    +---+    +---+
   3.
   +---+    +---+    +---+    +---+
   | 0 |<-->| 2 |<-->| 3 |<-->| 1 |
   +---+    +---+    +---+    +---+
   4.
   +---+    +---+    +---+    +---+    +---+
   | 0 |<-->| 2 |<-->| 3 |<-->| 4 |<-->| 1 |
   +---+    +---+    +---+    +---+    +---+
   5.刪除一個值
   +---+    +---+    +---+    +---+
   | 0 |<-->| 3 |<-->| 4 |<-->| 1 |
   +---+    +---+    +---+    +---+
   
   

棧和佇列

陣列模擬棧
const int N=10010;
int stk[N];棧陣列
int top;//棧頂指標
//判斷棧空
if(top<0);
//入棧
stk[top++]=e;
//出棧
top--;

佇列

陣列模擬佇列
const int N=10010;
int que;佇列陣列
int hh,tt;對頭,隊尾
//判斷佇列是否為空
 if(tt<hh)
//入佇列
     que[++tt]=e;
//出佇列
hh++;
記錄;x=que[hh++];

Kmp

kmp演算法:是一種用於在一個文字字串中查詢一個模式字串的高效演算法。
    const int N=100010,M=10010;
char S[M],P[N];
int Ne[N];
int m,n;


//用於:匹配問題
int main(){
    cin >> n >> P+1 >> m >> S+1 ;

    //求next的過程P為小的
    for(int i=2,j=0;i<=n;i++){
        while(j&&P[i]!=P[j+1]) j=Ne[j];//快速回溯 ,不斷回溯直到找到相等的,或者是j=0 
        if(P[i]==P[j+1])j++;//更新匹配長度 
        Ne[i]=j;//將當前的匹配長度 j 儲存在 next 陣列的對應位置 i,表示在模式字串中位置 i 之前的最長相同字首和字尾的長度。
    }


    //kmp的匹配過程S為大的
    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-j+1);
   	j=Ne[j];//目的是利用已經計算好的模式字串的next陣列來快速確定下一次匹配的起始狀態。
   }
    }


1.對於next陣列的理解

求出部分模式串的next值:其實就是求最長公共前字尾
拿ABABC舉例
    主要找最後一個字元
	第一部:A  只有A自並沒有公共前字尾 故next[0]=0
	第二部:AB 字首為A,字尾為B,並不相等 next[1]=0
    第三部:ABA 最長相等的公共前字尾,前字尾為A,next[2]=1
    第四部:ABAB 最長相等的公共前字尾,前字尾為AB,next[3]=2
    第五部:ABABC 並沒有公共前字尾,next[4]=0 ;

2.主串與模式串的匹配過程

在匹配過程中,KMP演算法並沒有出現和BF演算法一樣匹配過程,主串沒有和模式串一起移動,而是採用了只動模式串的做法
    
    1.在建立了next回溯陣列後,與主串進行匹配。
    
    2.一旦匹配出現了問題後,根據匹配出現問題的地方,結合next陣列,進行跳轉。
    
    3.模式串全部匹配後,則結束匹配。
    
    

Tire

Tire也可以稱它為字首樹
    1.用於快速儲存和查詢一個單詞
   那空間換時間
    
    const int N=10010;
    int son[N][26],idx;//用數字儲存字母
	bool cnt[N];//記錄末尾
	char str[N];//需要進行操作的字串
    
void insert(char Str[]){
    int p=0;//p為根節點
    for(int i=0;Str[i];i++){
        char u=Str[i]-'a';//將其轉化為數字,進行儲存
        if(!son[p][u]) son[p][u]=++idx;//idx為記錄它的深度
        p=son[p][u];//不斷的往下跑
    }
    cnt[p]=true;//最後記錄最後的一個單詞,表明有這個單詞
}


bool search(char Str[]){
        int p=0;//p為根節點
    for(int i=0;Str[i];i++){
        char u=Str[i]-'a';//將其轉化為數字,進行儲存
        if(!son[p][u]) return false;
        p=son[p][u];//不斷的往下跑
    }
    return true;
}
    

並查集

img

/*並查集的作用:
 * 1.將兩個集合合併
 * 2.查詢兩個元素是否在同一個集合當中
 *3.統計集合中的個數,只看根節點的size
 *
 *
 * 並查集的基本原理:每個集合有一個樹表示。樹根的編號就是集合的編號。每個節點儲存他的父節點。p[x]表示他的父節點
 *
 * 問題:
 * 1.如何判斷根節點:if(p[x]=x)
 * 2.如何求X的集合編號:while(p[x]!=x)x=p[x];
 * 3.如何將兩個集合合併:p[x]=y;
 *
 * */


const int N=10010;
int p[N],x;
int size[N];
    
    //路徑壓縮
int find(x){
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}
    
int main(){
    //1~n的編號
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        p[i]=i;//初始化父節點,使每個都指向自己
        size[i]=1;//初始化時都為1
    }
    while(m--){
        char op[10];
        int a,b;
        cin>>op;
        if(op[0]=='M'){
            cin>>a>>b;
           size[find(b)]+=size[find(a)];//先加
            p[find(a)]=find[b];//再合併
 
        }else if(op[0]=='Q'){
            if(op[1]=='a'){
                cin>>a>>b;
            if(find(a)==find(b)){
                puts("在一個連通塊中");
            }
            else{
                puts("不在");
            }
        }else{
            cin>>a;
            cout<<size(find(a))<<endl;
        }
        }
        else{
            
        }
        
    }
}

img

/*
 * heap陣列,up(size)
 * 下標:從一開始
 * 堆:完全二叉樹
 * 小根堆:他並未是完全從小到大的,他的儲存結構是數,不能夠直接用迴圈輸出
 * 1.插入一個數:heap[++size]=x;up(size);
 * 2.求集合當中的最小值:heap[1];
 * 3.刪除最小元素:heap[1]=heap[size];size--;down(1);
 * 4.刪除任意一個值:heap[k]=heap[size];size--;down(k);up(k);
 * 5.修改任意一個元素:heap[k]=x;up(k),down(k)
 * */


const int N=10010;
int h[N],size;

//用遞迴來實現
void down(int t){
    int u=t;//開始位置
    //先從左邊開始
    if(2*u<=size&&h[2*u]<h[t])t=2*u
    //再從右邊開始
    if(2*u+1<=size&&h[2*u+1]<h[t])t=2*u+1;
    if(t!=u){
        swap(h[u],h[t]);
        down(t);
    }
}

//用迴圈實現
void up(int t){
    while(2/t&&h[2/t]>h[t]){
        swap(h[2/t],h[t]);
        t=2/t;
    }
    
}


int main(){
    
    cin>>n>>m;
    for(int i=1;i<=n;i++){scanf("%d",&h[i]);}
    
    //建堆
for(int i=n/2;i;i--){down(i)}    
    
    while(m--){
        char op[10];
        int c,k;
    scanf("%s",op);
        //1.插入
    if(op[0]=='I'){
        cin>>c;
        //新增原理:先將size++,讓後up(size)一下
        h[++size]=c;
        up(size);
    }    
        //2.查詢最小的數
        else if(op[0]=='Q'){
            if(op[1]=='a'){
                cout<<h[1]<<endl;
            }
        }
        //3.刪除
        else if(op[0]=='D'){
            //刪除第一個元素
            if(op[1]=='a'){
                h[1]=h[size];
                size--;
                down(1);
            }else if(op[1]=='b'){
                //刪除任意位置的數
                cin>>k;
                h[k]=h[size];
                size--;
                up(k);down(k);
            }
        }
        //修改任意位置的數
        else{
            cin>>c>>k;
            h[k]=c;
            down(k);up(k);
}
    }
}

Harsh表

一般雜湊

拉鍊法

1.高效的資料儲存與檢索

2.資料去重
可以將10的九次方的數轉換為10的五次方

const int N=10010;
int h[N],e[N],ne[N],idx;

//拉鍊法實現
void insert(int x){
    int k=(x%N+N)%N;//將k對映為0到N-1的範圍
	e[idx]=x;
	ne[idx]=h[k]
	h[k]=idx;
  	idx++;
    
}

bool search(int x){
    int k=(x%N+N)%N;//將k對映為0到N-1的範圍
    for(int i=h[k];i!=-1;i=ne[i]){
        if(e[i]==x)return true;
    }
    return false;
}



int main(){
memset(h,-1,sizeof h);
}

開放定址法

//開放定址發
    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;
}

字串雜湊

用於快速檢索兩端字串是否相等

str="ABCIBNUOCTYULJ"
    h[0]=0;
	h[1]="A"的雜湊值
    h[2]="AB"的雜湊值
       .
       .
       .
       .
    以此類推  
        
         (A B C D)
         (1,2,3,4)p
        (1*p的三次方+...)modQ
        
        注意點: 1.不能對映為0
        	    2.Rps是足夠好
        
        用一個P進位制來表示
P最好為131或13331數學中證明過:99.99%沒有衝突
        
        最好再模上個2的64次方:可以用unsigned long long 溢位的部分就是取模的數
        
    
        
typedef unsigned long long ULL;
const int N=10010,P=131;

ULL h[N],p[N];
int n,m;
char str[N];
ULL get(int l,int r){
    return h[r]-h[l-1]*p[l-r+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;
}

相關文章