首页 > 其他 > 详细

图论学习笔记

时间:2019-08-14 11:04:03      阅读:70      评论:0      收藏:0      [点我收藏+]

最小生成树

Kruskal

\(kruskal\),一种求最小生成树的算法,其思想与贪心有些相似,具体做法为:、

将边按照边权由小到大排序,每次拿出权值最小的一条边,看它连接的两个顶点是否在同一个连通块中(可以用并查集维护),如果在的话就不使用他,否则就加入生成树,一直加入到边数为\(n - 1\)结束

如何证明?

要我说我也说不明白,还是直接上图吧
技术分享图片

图片来自:https://blog.csdn.net/weixin_43272781/article/details/83589394

复杂度:\(O(mlogm)\)

代码实现:

#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
    for( ; isdigit(c); c = getchar()) x = (x <<3) + (x << 1) + (c ^ 48);
    return x * f;
}

const int N = 5011;
const int M = 200011;

struct node {
    int x, y, val;
} a[M];

int n, m, k, fa[N], ans = 0;

bool cmp(node a, node b) {
    return a.val < b.val;
}

inline int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int main() {
    n = read(), m = read();
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= m; i++) {
        a[i].x = read(), a[i].y = read(), a[i].val = read();
    }
    stable_sort(a + 1, a + 1 + m, cmp);
    for(int i = 1; i <= m; i++) {
        int dx = find(a[i].x), dy = find(a[i].y);
        if(find(dx) != find(dy)) {
            fa[dx] = fa[dy];
            ans += a[i].val;
            k++;
        }
        if(k == n - 1) break;
    }
    cout << ans << '\n';
    return 0;
}

Prim

同样是一种求最小生成树的算法,也用到了贪心的思想,具体做法是:

随便找一个点作为一棵树的根节点,然后找出和它相邻的边(这里邻接表存储,用一遍循环即可),使用一条边扩展这个树,要求这条边一个顶点在树中,另一 个顶点不在树中,并且这条边的权值要求最小。重复以上的步骤,同时维护一个\(dis\)数组,表示为已用点到未用点的最短距离。

证明:\(Prim\)算法之所以是正确的,主要基于一个判断:对于任意一个顶点\(v\),连接到该顶点的所有边中的一条最短边\((v, v_j)\)必然属于最小生成树(即任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)

复杂度:\(O(elog_2v)\)

来自:https://www.cnblogs.com/bcoier/p/10293059.html

代码实现如下:

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;

inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
    for( ; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
    return x * f;
}

const int INF = 1e10 + 11;
const int N = 5011;
const int M = 200011;

struct node {
    int to, next, val;
} e[M << 1];

int head[N], dis[N], cnt;
int n, m, k, now = 1, ans = 0;
bool vis[N];

inline void add(int from, int to, int val) {
    e[++cnt].to = to;
    e[cnt].val = val;
    e[cnt].next = head[from];
    head[from] = cnt;
}

int main() {
    n = read(), m = read();
    for(int i = 1; i <= m; i++) {
        int x = read(), y = read(), w = read();
        add(x, y, w), add(y, x, w);
    }
    for(int i = 2; i <= n; i++) dis[i] = INF;
    for(int i = head[1]; i; i = e[i].next) dis[e[i].to] = min(dis[e[i].to], e[i].val);
    while(++k < n) {
        int minn = INF;
        vis[now] = 1;
        for(int i = 1; i <= n; i++) {
            if(!vis[i] && minn > dis[i]) {
                minn = dis[i];
                now = i;
            }
        }
        ans += minn;
        for(int i = head[now]; i; i = e[i].next) {
            int v = e[i].to;
            if(dis[v] > e[i].val && !vis[v]) {
                dis[v] = e[i].val;
            }
        }
    }
    cout << ans << '\n';
    return 0;
}

练习题

USACO08 watering hole

建立一个超级源点,将它往每个点建一条边权为\(wi\)的边,跑最小生成树即可

洛谷1967 NOIP2013 货车运输

建一个最大生成树,然后跑树上倍增

[CODEVS1001][BZOJ1050] 舒适的路线

菜死我了,还没看

最短路

Floyd

这个就不多说了吧……求多源最短路,四行代码搞定,复杂度\(O(N^3)\)

for(int k = 1; k <= n; k++)
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);

另外说一下,用玄学优化是可以让\(floyd\)跑过最短路这个题的!

传送门

//早期代码没空格hhh
#include<bits/stdc++.h>
using namespace std;
long long g[1010][1010];
long long n,m,a,b,s;

int qread()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int main() {
    n=qread();m=qread();
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=n; j++) {
            g[i][j]=0xffffff;
        }
    }
    for(int i=1; i<=m; ++i) {
        a=qread();b=qread();s=qread();
        g[a][b]=s;
    }
    for(int k=1; k<=n; k++)
        for(int i=1; i<=n; i++)
            if(i!=k && g[i][k]!=0xffffff) {
                for(int j=1; j<=n; j++) {
                    if(i!=j&&j!=k&&g[i][j]>g[i][k]*g[k][j]) {
                        g[i][j]=g[i][k]*g[k][j];
                    }
                }
            }
    printf("%lld",g[1][n]%9987);
    return 0;
}

Dijkstra

\(Dijkstra\)用于求单源最短路径,他是比较优秀的最短路算法,但是不能处理负边权,不做详细介绍

普通版本

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
    for( ; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
    return x * f;
}

const int N = 5011;
int n, m, ans;
int dis[N], a[N][N];
bool vis[N];

int main() {
    memset(a, 0x3f, sizeof(a));
    n = read(), m = read();
    for(int i = 1, u, v, w; i <= m; i++) {
        u = read(), v = read(), w = read();
        a[u][v] = a[v][u] = w;
    }
    for(int i = 0; i <= n; i++) dis[i] = a[1][i];
    vis[1] = 1;
    dis[1] = 0;
    for(int i = 1; i <= n; i++) {
        int k = 0;
        for(int j = 1; j <= n; j++) {
            if(!vis[j] && dis[j] < dis[k]) {
                k = j;
            }
        }
        vis[k] = 1;
        for(int j = 1; j <= n; j++) {
            if(dis[j] > dis[k] + a[k][j]) {
                dis[j] = dis[k] + a[k][j];
            }
        }
    }
    cout << dis[n] << '\n';
    return 0;
}

堆优化

priority_queue <pin> Q;
inline void dij(int st) {
    memset(dis, 0x3f, sizeof(dis));
    Q.push(pin(dis[st] = 0LL, st));
    for(; !Q.empty(); ) {
        int x = Q.top().second;
        Q.pop();
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i = head[x]; i; i = e[i].nxt) {
            int y = e[i].to;
            if(dis[y] > dis[x] + e[i].val) {
                dis[y] = dis[x] + e[i].val;
                Q.push(pin(-dis[y], y));
            }
        }
    }
}

SPFA

一个最可怕的最短路算法,因为它的复杂度是错的,甚至很多人都说,\(SPFA\)已经死了,实在是可怕啊

不过\(SPFA\)可以判断负环,这是好的

平常没有负边权还是不要写\(SPFA\),好好写\(Dijkstra\)

SPFA判负环

queue <int> q;
bool inq[N];
int dis[N], len[N];

bool spfa() {//true表示有环 
    for(int i = 2; i <= n; i++) dis[i] = INF;
    len[1] = 1;
    q.push(1);
    inq[1] = true;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = false;
        for(int i = 0; i < e[u].size(); i++) {
            int v = e[u][i];
            int w = W[u][i];
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                len[v] = len[u] + 1;
                if(len[v] >= n) return true;
                if(!inq[v]) q.push(v), inq[v] = true;
            }
        }
    }
    return false;
}

练习题

洛谷P1119 灾后重建

\(floyd\)优化版,按时间走就行了

图论学习笔记

原文:https://www.cnblogs.com/loceaner/p/11350271.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!