10.23 閒話

adsd45666發表於2024-10-24

10.23 閒話 圖論複習

還有2天就複賽了,現在暫時不知道做啥題了,寫一下這兩天覆習的圖論知識。

1.存圖方式

(1.) 鄰接矩陣

沒什麼好說的,最簡單的存圖方式,一眼就會。

定義矩陣陣列 \(a[n][n](n為點的數量數)\)\(a[u][v]=w\) 代表 \(u,v\) 之間存在一條權值為 \(w\) 的路徑。

由於採用二維陣列存圖,導致其在稠密圖中效率低下,會有很多空間被浪費掉,限制了其發揮。

(2.) \(vector\)

動態陣列存圖,比較好寫也比較常用的一種方式,定義的話為 \(vector<資料型別>a[n](n為點的數量)\) 對於 \(a[u][i]=v\) 來說,其代表點 \(u\) 的第 \(i\) 條邊的終點是 \(v\) ,若需儲存其權值,則需要改變其資料型別,使用結構體型別。

儲存權值的方式以及遍歷點 \(i\) 的所有出度的方式。

struct node{
	int id,w; 
};
vector<node>a[maxn];
//向點u壓入一條權值為w的通往v的出度
a[u].push((node){v,w});
for(int j=0;j<a[i].size();j++){
	//所有的a[i][j]即為其所有出度
}

比起鄰接矩陣存圖,其省去了大量的冗餘空間儲存不存在的邊。故其在稠密圖的表現明顯優於鄰接矩陣。

(3.)鏈式前向星

學的不太好,可能講起來會比較抽象。

建立結構體陣列 \(e[m](m為邊的數量)\) 結構體的變數為 \(to(邊的終點),next(其同起點的一個兄弟),w(邊的權值)\) ,與一個 \(head\) 陣列, \(head[i]\) 表示 \(i\) 點的最近一條連邊。

儲存方式及遍歷方法:

struct edge{
    int to,next,w;
}e[m];
int head[n];
void add_edge(int u,int v,int w){  //新增一條由u通向v的權值為w的邊
    tot++;               //tot為當前邊的編號
	e[tot].to=v;         //邊的終點
    e[tot].next=head[u]; //更新其兄弟
    e[tot].w=w;
    head[u]=tot;         //記錄u點的最近一條出度
}
//遍歷點x的所有出度
for(int i=head[x];i;i=e[i].next){   //最早的點的next值為0
    
}

較起前兩種,鏈式前向星由於不用儲存起點,只儲存邊,不由點決定,決定其空間利用率極高,是最省空間的存圖方式。

2.拓撲排序

首先闡述其的定義,在一張 \(DAG(有向無環圖)\) 中,若 \(i,j\) 存在一條由 \(i\) 指向 \(j\) 的邊,則稱 \(j\) 依賴於 \(i\) ,則拓撲排序的目的就是使排序後排在前面的點不依賴於後面的點。

可能有點抽象,形象點來說,就是在一張由有向無環圖中,輸出入度為零的點,同時將其所連的點的入度減一,重複此過程,直到所有的點都已被輸出。這也點出了我們的解決思路,佇列儲存入度為零的點,當佇列非空時,每次取出隊首,遍歷其所有出度,將其能到達的點的入度減一,若減為零,則將其加入佇列,否則繼續遍歷。

int idx[maxn];                          //入度陣列
vector<int>e[n];
queue<int>qu;
for(int i=1;i<=n;i++){
    if(idx[i]==0){
        qu.push(i);
        cout<<i<<" ";
    }
}
while(!qu.empty()){
    int op=qu.top();qu.pop();
    for(int i=0;i<e[op].size();i++){
        int k=a[op][i];
        idx[k]--;
        if(idx[k]==0){
            qu.push(k);
            cout<<k<<" ";
        }
    }
}

3.最短路

很經典的圖論問題,也是很常考的知識點。求圖上兩點的最短路,分為全源最短路及單源最短路兩種。

(1.)全源最短路:

全源最短路常使用 \(Floyd\) 演算法,其主要思想是透過列舉中繼點來縮小路徑長度,複雜度為 \(O(n^3)\) 很差,不過相較於對每個點進行一遍尋找單源最短路, \(Floyd\) 演算法還是佔優勢的。

主要採用 \(dp\) 的思想 \(dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j])\) 三維陣列效率有點低下,所以使用滾動陣列,最佳化掉第一維 \(dp[i][j]=max(dp[i][j],dp[i][k]+dp[k][j])\) 當然由於使用了滾動陣列的原因,導致列舉 \(k\) 的迴圈必須在 \(i\) , \(j\) 迴圈的外層。

void flord(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
    }
}
//則dp[i][j] 為i點到j點的最短路

(2.)單源最短路

這裡是 \(dijkstra\) 演算法,一種透過貪心來確定最短路的演算法。只能用於無負邊權值的圖。使用優先佇列儲存點到起點的距離,對於一個已經在優先佇列中的點,若期之前未被遍歷過,則遍歷其所有出度,更新其可到達的點,若更新後距離更短,則更新距離。

struct edge{
    int u,v;
    int w;
};
vector<edge>e[maxn];  //vector存圖
struct node{
    int id,dis;
    bool operator < (const node &x)const{
        return dis<x.dis;
    }
}
int dis[maxn];   //dis記錄點i到起點的距離
bool vis[maxn];  //vis代表是否已被確定 
void dijkstra(){
    for(int i=1;i<=n;i++){
        dis[i]=inf;         
        vis[i]=false;       
    }
    dis[s]=0;
    priority_queue<node>pq; 
    pq.push((node){s,0});
    while(!pq.empty()){
        node u=pq.top();pq.pop();
        if(vis[u.id]) continue;
        vis[u.id]=true;
        for(int i=0;i<e[u].size();i++){
            edge k=e[op.id][i];
            if(vis[k.v]) continue;
            if(dis[k.v]>y.w+u.dis){
                dis[k.v]=y.w+u.dis;
                q.push((node){k.v,dis[y.v]});
            }
        }
    }
}

4.最小生成樹

最小生成樹( \(MST\) ),在圖上,聯通所有點且不含迴路的子圖稱為一顆生成樹,其中邊權和最小的稱為最小生成樹。

這裡使用 \(kruskal\) 演算法,由於需要連線每一個點,所以可以使用貪心的思路,對所有的邊進行排序。選出其中較小的,維護一個並查集,儲存已經加入最小生成樹的點,維護一個點的集合,直到每一條邊都已經遍歷。

struct node{
    int u,v,w;     //邊集陣列u:起點 v:終點 w:權值
}e[maxn];
bool cmp(node a,node b){return a.w<b.w;} //按權值大小排序
int fa[maxn];
int find(int x){
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
void merge(int x,int y){
    int f1=find(x),f2=find(y);
    if(fa!=f2){
        fa[f1]=f2;
    }
}
int kruskal(){
    for(int i=1;i<=n;i++) fa[i]=i;
    int ans=0;
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=m;i++){
        int u=e[i].u,v=e[i].v;
        if(find(u)==find(v)) continue;
        merge(u,v);
        ans+=e[i].w;
    }
    return ans;
}

總結

大概先寫到這,圖論就學到這了,確實不多,還不一定熟練覺得有點懸。

S組複賽祝好。

相關文章