1. 程式人生 > >補充:需要掌握的演算法

補充:需要掌握的演算法

01揹包和完全揹包

使用一維陣列的版本:
01揹包:

int main() {
    int n,m;
    while(cin>>n>>m) {
        vector<int> weight(n+1,0);//物品的重量
        vector<int> value(n+1,0);//物品的價值
        vector<int> dp(m+1,0);//一維陣列,存放用n個產品裝容量為i的揹包的解
        for(int i=0;i<n;i++)
            cin>>weight[i+1
]>>value[i+1];//輸入 for(int i=1;i<=n;i++) //這裡是逆序 //用j>=weight[i]作為判斷條件,省略了一個if語句 for(int j=m;j>=weight[i];j--) { dp[j]=max(dp[j],dp[j-weight[i]]+value[i]); cout<<dp[m]<<endl; } return 0; }

完全揹包:
只需要將上面的逆序改為順序

for(int i=1;i<=n;i++)
        //這裡變成了順序
        //這裡設j從weight[i]開始,省略了一個if語句
        for(int j=weight[i];j<=m;j++)
                dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);

O(nlogn)的排序

快排(單向掃描版)

int partition(int a[], int l, int r) {
    int x = a[l];//選擇a[l]作為選定元素
    int i = l;
    int
j = l+1; for (; j <= r; j++) if (a[j] < x) //如果a[j]小於x,就將它交換到前面去 swap(&a[++i], &a[j]); swap(&a[l], &a[i]);//最終將a[l]交換到正確的位置上 return i; } void quick(int a[],int l,int r) { if (l>=r) return;//如果子區間為0或1個元素,就可以結束遞迴呼叫 int i = partition(a,l,r); quick(a,l,i-1); quick(a,i+1,r); }

歸併排序

int b[maxN]; //輔助陣列

void partition(int a[],int l,int r) {
    int i,j,k;
    int m = (l+r)/2;
    //將排好序了的兩個子陣列:a[1~m]和a[m+1~r]放入輔助陣列b中
    for (i=l;i<=r;i++) b[i]=a[i];

    //i和j分別指向兩個子陣列的開頭
    i=l;
    j=m+1;
    k=l;
    while(i<=m && j<=r) {
        if (b[i]<b[j])
            a[k++]=b[i++];
        else 
            a[k++]=b[j++];  
    }
    //如果後半個數組已經全部處理完了
    while(i<=m) 
        a[k++]=b[i++];
    //如果前半個陣列已經全部處理完了
    while(j<=r)
        a[k++]=b[j++];
}

void merge(int a[],int l,int r)  {
        //當子序列只有1個元素或為空時,返回
        if (l>=r) return;

        int m = (l+r)/2;
        merge(a,l,m);
        merge(a,m+1,r);
        partition(a,l,r);
    }

堆排序

堆本質上是完全二叉樹,用陣列(vector)表示。首先定義一個數組,用來表示堆。

int N=0;
vector<int> heap(100005,0);

上浮和插入操作:

void Up(int k) {
    while(k>1 && heap[k/2]<heap[k]) {//是否到根節點了,或者父節點比當前節點小
        swap(heap[k/2],heap[k]);
        k=k/2;
    }
}

void insert(int weight) {
    N++;
    heap[N]=weight;
    Up(N);
}

下沉和刪除操作:

void Down(int k) {
    int j;
    while(2*k<=N) {//如果沒到達樹底
        j=2*k;
        if(j<N && heap[j+1]>heap[j]) 
            j++;//找到子節點中較大的那個

        if(heap[k]>heap[j]) 
            break;//如果父節點比子節點都大,退出
        swap(heap[k],heap[j]);
        k=j;
    }
}

int deleteMax() {
    swap(heap[N],heap[1]);
    N--;
    Down(1);
    return heap[N+1];
}

堆排序:

void PQsort(int a[],int l,int r) {
    int k;
    for (k=l;k<=r;k++) insert(a[k]);
    for (k=r;k>=l;k--) a[k]=deleteMax();
}

迪傑斯特拉演算法

int main() {
    int n,m,s,t;//分別是節點數、邊的條數、起點、終點
    while(cin>>n>>m>>s>>t) {
        vector<vector<int>> edge(n+1,vector<int>(n+1,0));//鄰接矩陣
        vector<int> dis(n+1,0);//從起點出發的最短路徑
        vector<int> book(n+1,0);//某結點已經被訪問過

        for(int i=1;i<=n;i++)//初始化鄰接矩陣
            for(int j=1;j<=n;j++)
                if(i!=j) edge[i][j]=INT_MAX;

        int u,v,length;
        for(int i=0;i<m;i++) {//讀入每條邊,完善鄰接矩陣
            cin>>u>>v>>length;
            if(length<edge[u][v]) {//如果當前的邊長比已有的短,則更新鄰接矩陣
                edge[u][v]=length;
                edge[v][u]=length;
            }
        }
        for(int i=1;i<=n;i++)//初始化dis陣列
            dis[i]=edge[s][i];
        book[s]=1;//把起點先標記一下

        //演算法核心!!!先確定沒訪問過的節點中,離起點最近的,然後鬆弛
        for(int i=1;i<=n;i++) 
        {
            int min=INT_MAX;
            int index=0;
            for(int j=1;j<=n;j++) {
            {
                    min=dis[j];
                    index=j;
                }
            }
            book[index]=1;//標記這個剛剛被確定了的節點
            if(index==t) break;//如果已經到終點了,就直接結束

            for(int i=1;i<=n;i++) 
            {//從剛被確定的節點出發,鬆弛剩下的節點
                if(book[i]==0 && edge[index][i]<INT_MAX && dis[i] > dis[index]+edge[index][i]) 
                    dis[i] = dis[index]+edge[index][i];
            }
        }

        cout<<dis[t]<<endl;
    }
    return 0;
}

並查集

#include<iostream>  
using namespace std;  

int  pre[1050];     //儲存節點的直接父節點

//查詢x的根節點 
int find(int a){  
    if(pre[a]!=a)  
        pre[a]=find(pre[a]);//路徑壓縮,本結點更新為根結點的子結點  
    return pre[a];  
} 
//連線兩個連通塊
void join(int x,int y) {  
    int fx=Find(x),fy=Find(y);  
    if(fx!=fy) pre[fy]=fx;  
}   

int main() {  
    int N,M,a,b,i,j,ans=0;  
    while(scanf("%d%d",&N,&M) && N) {
        //初始化pre陣列
        for(i=1;i<=N;i++) pre[i]=i;  
        //根據連通情況,構建pre陣列
        for(i=1;i<=M;i++) {  
            scanf("%d%d",&a,&b);  
            join(a,b);
        }  

    for(i=1;i<=N;i++) 
        if(pre[i]==i) ans++; //計算連通子圖的個數ans

    cout<<ans;
    return 0;  
}

拓撲排序

若用鄰接表表示圖的資訊,則有:

int in[100001]={0};//記錄每個節點的入度
vector<int> edge[100001]; //鄰接表

int main() {
    int n,m;//節點數和順序關係的數量

    while(cin>>n>>m) {
        int from,to;
        int i,k;
        int index;
        int cnt=0;

        for(i=1;i<=m;i++) {//初始化每個節點的入度
            cin>>from>>to;
            edge[from].push_back(to);
            in[to]++;
        }

        for(k=1;k<=n;k++) 
        {
            index=0;//當前這輪確定了的節點編號

            bool valid = false;
            for(i=1;i<=n;i++) {
                if(in[i]==0) {//若某個未確定節點的入度為0
                    index=i;
                    cnt++;
                    in[i]--;
                    valid = true;
                    break;
                }
            }
            if(!valid) {//如果在未確定的節點中,找不到入度為0的
                cout<<"Wrong"<<endl;
                break;
            }

            for(auto p : edge[index]) {//刪除從節點index出發的邊,將終點的入度減一
                in[p]--;
            }
        }
        if(cnt==n)  cout<<"Correct"<<endl;
    }
    return 0;
}

KMP演算法

本質上,是利用匹配子串的最長公共前後綴的資訊來提高效率。

void makeNext(string P,vector<int> &next) 
{
    int q,k;
    int m = P.size();
    next[0] = 0;
    for (q = 1, k = 0;q < m;q++) 
    {
        while(k > 0 && P[q] != P[k])
            k = next[k-1];
        if (P[q] == P[k])
            k++;
        next[q] = k;
    }
}

int kmp(string T,string P,vector<int> &next) {
    int n=T.size();
    int m=P.size();
    int i=0,q=0;
    int ans=0;
    for (;i < n;i++) {
        while(q > 0 && P[q] != T[i]) 
            //如果失配了,退回q-(q-next[q-1]) = next[q-1]位
            q = next[q-1]; 
        if (P[q] == T[i]) //如果當前字元匹配成功,q後移一位
            q++;
        if (q == m) //如果已經成功匹配一次
            ans++;
    }
    return ans;
}

int main() {
    int N;
    string mo,str;
    cin>>N;  
    while(N--) {  
        cin>>mo>>str;
        vector<int> next(mo.size(),0);
        makeNext(mo,next);//構建部分匹配表
        cout<<kmp(str,mo,next)<<endl;//輸出總的匹配個數
    }  
    return 0;  
}