10.23 闲话

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组复赛祝好。