图论

图论

模板

拓扑排序

  1. 所有入度为0的点入队
  2. 遍历队列中所有入度为0的点,并将它扩展出去的点的度数-1,如果减去后入度为0则入队。
  3. 统计队列中点的个数,如果所有点都入队则存在拓扑排序,否则不可进行拓朴排序。
int q[maxn], front = 1,  rear = 0;
void topsort() {
    for (int i = 1; i <= n; i++) {
        if (!r[i]) {
            q[++rear] = i;
        }
    }
    while (front <= rear) {
        int u = q[front++];
        for (auto v : edge[u]) {
            if (!(--r[v])) {
                q[++rear] = v;
            }
        }
    }
    if (rear == n) {
        for (int i = front; i <= rear; i++) {
            printf("%d%c",q[i]," \n"[i == rear]);
        }
    } else {
        printf("No topsort!");
    }
}

Dijkstra

基于贪心思想,适用于所有边的长度都是非负数的图,当边长都是非负数二队时候,全局最小值不可能再被其他节点更新。

朴素版本(邻接图存图)

时间复杂度\(O(n^2+m)\),其中n是点数,m是边数,多用于稠密图。

  1. 将距离数组除起点外所有点置无穷大,表示目前每个点互不连通。
  2. 循环n次,每次找到距离源点最近且没有被确定过的点。
  3. 用这个点更新其他点的距离。
  4. 标记这个点,表明已经确定该点最短路径。
int g[maxn][maxn], dist[maxn];
bool vis[maxn];
int dijkstra(int Start, int End) {
    memset(dist, 0x3f, sizeof dist);
    memset(vis, false, sizeof vis);
    dist[Start] = 0;
    for (int i = 1; i <= n; i++) {
        int ver = -1;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && (dist[ver] > dist[j] || ver == -1)) {
                ver = j;
            }
        }
        for (int j = 1; j <= n; j++) {
            dist[j] = min(dist[j], dist[ver] + g[ver][j]);
        }
        vis[ver] = true;
    }
    return dist[End];
}

堆优化版

时间复杂度\(O(mlogn)\),m是边数,n是点数,多用于稀疏图。

优化的是找点的过程,将寻找出距离源点最近且未必确定的点从\(O(n^2)\)优化到\(O(n)\)

void add(int a, int b, int c) {
    g[a].push_back({b,c});
}

int dijskra(int Start, int End) {
    memset(dist, 0x3f, sizeof dist);
    memset(vis, false, sizeof vis);
    dist[Start] = 0;
    priority_queue<PII,vector<PII>, greater<PII>> heap;
    heap.push({0,Start});
    while (!heap.empty()) {
        PII u = heap.top();
        heap.pop();
        if (vis[u.second] == true) {
            continue;
        } 
        vis[u.second] = ture;
   		for (auto v : g[u]) {
            if (dist[v.first] > u.first + v.second) {
                dist[v.first] = u.first + v.second;
                heap.push({dist[j], v.first});
            }
        } 
    }
    return dist[End];
} 

Bellman_ford

用来处理负权边和有边数限制的最短路算法,时间复杂度\(O(nm)\),n是点数,m是边数。

  1. for 1 -> n
  2. for 所有边
  3. 松弛操作

有边数限制的条件下,backup的存在可以防止一次更新多条边的情况出现。

for (int i = 0; i < n; i++) {
    memcpy(backup, dist, sizeof dist);
    for (int j = 0; j < m; j++) {
        int u = g[j].a, v = g[j].b, w = g[j].w;
        dist[b] = min(dist[a], backup[a] + w);
    }
}

SPFA

可以处理负权边图和判断负权环,时间复杂度为\(O(m)\),最坏情况会退化到\(O(nm)\)

该算法是对Bellman-ford算法的队列优化版本,解决了Bellman-ford算法中过多的无用操作。

int spfa()
{
    memset( dis,0x3f,sizeof dis);
    dis[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while( q.size() ) 
    {
        int t =  q.front();
        q.pop();
        st[t] = false;

        for( int i = h[t] ; i != -1 ; i = ne[i] )
        {
            int j = e[i];
            if( dis[j]>dis[t]+w[i] )
            {
                dis[j] = dis[t] + w[i];
                if( !st[j] )
                {
                    q.push( j );
                    st[j] = true;
                }
            }
        }

    }

    return dis[n];
}

判断有无负权边,若有会在里面打转转,如果一个点遍历次数大于n次,则说明有负权边。

void add(int a, int b, int c) {
    g[a].push_back({b,c});
}

bool spfa(int Start, int End) {
    queue<PII> q;
    memset(dist, 0x3f, sizeof dist);
    dist[Start] = 0;
    q.push({0,Start});
    vis[Start] = true;
    while (!q.empty()) {
        PII u = q.front();
        q.pop();
        vis[u.second] = false;
        for (auto v : g[u]) {
            if (dist[v.first] > dist[u.second] + v.second) {
                dist[v.first] = dist[u.second] + v.second;
                cnt[v.first] = cnt[u.second] + 1;
                if (cnt[v.first] >= n) {
                	return true;
                }
                if (!vis[v.first]) {
                    q.push({dist[v.first], v.first});
                    vis[v.first] = true;
                }
            }
        }
    }
	return false;
}

Floyd

解决多源汇最短路问题。

本质是动态规划,K是阶段,必须置于外层循环当中。

时间复杂度为\(O(n^3)\)

void floyd() {
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                dist[i][j] = min(dist[i][k]+dist[k][j], dist[i][j]);
            }
        }
    }
}

最小生成树

Prim

基于一种贪心策略,每次将距离集合最近的点加入到集合当中来,使得集合内的连通部分逐渐增大,最后连通整个图并且使得边权和最小。时间复杂度为\(O(n^2)\),多用于稠密图。

int sum;
bool prim() {
    memset(dist, 0x3f, sizeof dist);
    sum = 0;
    for (int i = 0; i < n; i++) {
        int ver = -1;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && (ver == -1 || dist[ver] > dist[j])) {
                ver = j;
            }
        }
        if (i && dist[ver] == inf) {
            return false;
        }
        if (i) {
            sum += dist[ver];
        }
        vis[ver] = true;
        for (int j = 1; j <= n; j++) {
            dist[j] = min(dist[j], g[ver][j]);
        }
    }
    return true;
}

结构体邻接表+堆优化写法

成立条件是tot == n,集合内有图的所有点,表示连通,否则表示不连通。

const int N = 200015, inf = 123456789;

typedef pair<int,int> pii;

int h[N], dist[N], idx, n, m, tot, ans;
bool vis[N];

struct Node {
    int v, w, ne;
}E[N<<1];

priority_queue<pii, vector<pii>, greater<pii>> q;

inline void add(int a, int b,int c) {
    E[idx] = {b,c, h[a]}, h[a] = idx++;
}

void prim() {
    memset(dist, 127, sizeof dist);
    dist[1] = 0;
    q.push(make_pair(0, 1));
    while (!q.empty() && tot < n) {
        pii t = q.top();
        int d = t.first, ver = t.second;
        q.pop();
        if (vis[ver]) continue;
        vis[ver] = true;
        tot++, ans += d;
        for (int i = h[ver]; ~i; i = E[i].ne) {
            if (dist[E[i].v] > E[i].w) 
                {
                    dist[E[i].v] = E[i].w;
                    q.push(make_pair(dist[E[i].v], E[i].v));
                }
        }
    }
}

Kruskal

时间复杂度为\(O(mlogm)\),m为边数。

  1. 对所有边权从小到大进行排序。
  2. 枚举每条边,如果边的两点还未进行联通,则将其联通并将边加入集合中。

需要并查集进行合并操作,当集合内边数少于n-1条时,图不连通。

n个结点的图要进行连通,至少需要n-1条边。

const int N = 200015, inf = 123456789;

typedef pair<int,int> pii;

int dist[N], idx, n, m, tot, ans;
int f[N];
bool vis[N];

struct Node {
    int u, v, w;
    bool operator < (const Node &T) const {
        return w < T.w;
    }
}E[N<<1];


inline int find(int x) {
    if (x != f[x]) f[x] = find(f[x]);
    return f[x];
}

inline bool kruskal() {
    sort(E,E+m, cmp);
    for (int i = 1; i <= n; i++) {
        f[i] = i;
    }
    ans = 0, tot = 0;
    for (int i = 0; i < m; ++i) {
        int u = E[i].u, v = E[i].v;
        ru = find(u), rv = find(v);
        if (ru != rv) {
            ans += E[i].w;
            f[ru] = rv;
            ++tot;
            if (tot == n-1) {
                break;
            }
        }
    }
    if (tot < n-1) {
        return false;
    } else {
        return true;
    }
}
posted @ 2022-04-14 02:27  古雨梨花  阅读(50)  评论(0)    收藏  举报