边的存储方式


1、vector储存

const int N = 1e5 + 7; //节点数
const int M = 5e5 + 7; //路径数
const ll INF = 1e18;
int u[M], v[M], w[M];
ll d1[N], d2[N];//d1正向图,d2反向图
struct Node {
    int v;
    ll cost;
    bool operator < (Node b)const {
        return cost > b.cost;
    }
};
vector<Node> e[N];
int n, m;

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; ++i) {
        u[i] = read(), v[i] = read(), w[i] = read();
        e[u[i]].push_back(Node{ v[i], w[i] });
        e[v[i]].push_back(Node{ u[i], w[i] });
    }
    return 0;
}

2、前向星保存

const int N = 5e3 + 7; //节点数
const int M = 5e3 + 7; //路径数
const int INF = 0x3f3f3f3f;
int head[N], tot = 0;//前向星变量
struct Node {
    //int u;
    int v, w, next;
} edge[M << 1];

void add(int u, int v, int w) {
    tot++;
    //edge[tot].u = u;
    edge[tot].v = v;
    edge[tot].w = w;
    edge[tot].next = head[u];
    head[u] = tot;
}

dijkstra算法


单源最短路径(不存在负权边),从任意固定起点,前往另一个终点的最短路径算法。这里的是最小堆优化
时间复杂度图片说明

#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 1e5 + 7; //节点数
const int M = 5e5 + 7; //路径数
const ll INF = 1e18;
int u[M], v[M], w[M];
ll d1[N]; //d1正向图
struct Node {
    int v;
    ll cost;
    bool operator < (Node b)const {
        return cost > b.cost;
    }
};
vector<Node> e[N];
int n, m, st, ed;

void dijkstra(int s, ll d[]) {
    priority_queue<Node>  pq;
    fill(d, d + N, INF);
    d[s] = 0;
    pq.push(Node{ s,0 });
    while (!pq.empty()) {
        Node    x = pq.top();
        pq.pop();
        if (d[x.v] < x.cost)    continue; //原先这个节点花费更少 跳过
        for (auto it : e[x.v]) {
            if (d[it.v] > x.cost + it.cost) {
                d[it.v] = x.cost + it.cost;
                pq.push(Node{ it.v,d[it.v] });
            }
        }
    }
}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; ++i) {
        u[i] = read(), v[i] = read(), w[i] = read();
        e[u[i]].push_back(Node{ v[i], w[i] });
        e[v[i]].push_back(Node{ u[i], w[i] });
    }
    st = 1;
    dijkstra(st, d1);
    for (int i = 1; i <= n; ++i) {
        printf("%d->%d : %lld\n", st, i, d1[i]);
    }
    return 0;
}

spfa算法


如果边权为负值,图片说明 算法不能得到正确的结果,这个时候只能选择图片说明 算法,其他边权为正的图不要使用图片说明 ,容易被卡。例题
用途一:带负权值的单源最短路径
用途二:负环的判断,是否存在一个环,边权之和为负数


//判断负环
#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 2e3 + 5;
const int INF = 0x3f3f3f3f;
struct Node {
    int v, w;
};
int n, m, w, st, ed;
vector<Node>e[N];
int d[N];
bool vis[N];
queue<int>q;
int times[N];//记录松弛次数

int spfa(int s) {
    memset(vis, 0, sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    memset(times, 0, sizeof(times));
    while (!q.empty())  q.pop();//多组数据,处理前要全部清空
    q.push(s);
    vis[s] = 1;
    d[s] = 0;
    times[s] = 1;
    while (!q.empty()) {
        int now = q.front();
        q.pop();
        vis[now] = 0;
        for (auto it : e[now])
            if (d[it.v] > d[now] + it.w) {
                d[it.v] = d[now] + it.w;
                ++times[it.v];//松弛次数加1
                if (times[it.v] == n) //如果已经有点松弛次数大于n
                    return 0;        //说明有负环,返回,不需要继续遍历
                if (!vis[it.v]) {
                    q.push(it.v);
                    vis[it.v] = 1;
                }
            }
    }
    return 1;//永远没有点松弛次数大于n,说明无负环
}
int main() {
    int T = read();
    while (T--) {
        n = read(), m = read(), w = read();
        for (int i = 1; i <= m; i++) {
            int u = read(), v = read(), w = read();
            e[u].push_back(Node{ v, w });
            e[v].push_back(Node{ u, w });//大于等于0时双向边
        }
        for (int i = 1; i <= w; ++i) {
            int u = read(), v = read(), w = read();
            e[u].push_back(Node{ v,-w });
        }
        st = 1;
        bool flag = spfa(st); //调用函数,起点为1
        if (flag) {  //不存在负环
            puts("NO");
            //输出各点最短路
            for (int i = 1; i <= n; ++i)
                printf("%d->%d : %d\n", st, i, d[i]);
        }
        else   puts("YES");
        for (int j = 1; j <= n; j++)
            e[j].clear();//清空
    }
    return 0;
}

floyd算法


上面给出的都是单源最短路径算法,如果需要知道任意两点之间的最短路径,需要使用floyd算法


// Floyd算法 O(N^3)  任意两点最短路
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 310; //节点数
int dp[N][N]; //表示从i->j的最短路径
int n, m;

void floyd() {
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 1; i <= n; ++i)    dp[i][i] = 0;
    //dp初始化为邻接矩阵
    for (int i = 1; i <= m; ++i) {
        int u = read(), v = read(), w = read();
        dp[u][v] = min(dp[u][v], w);
    }
    for (int k = 1; k <= n; ++k) //经过编号不超过k的从i->k的最短路径
        //dp[k,i,j]=min(dp[k-1][i][j],dp[k-1][i][k] + dp[k-1][k][j]),
        //k是阶段,i,j是附加状态,再和背包问题一样利用滚动数组节省第一维
        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]);
}

int main() {
    n = read(), m = read();
    floyd();
    //输出
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j)
            printf("%d ", dp[i][j]);
        puts("");
    }
    return 0;
}

最小生成树,kruskal算法和prim算法


图片说明 算法适用于稠密图,建立一颗最小生成树,而图片说明 算法适用于稀疏图建立最小生成树,特别是完全图的最小生成树建立。两种算法都是基于贪心的思想,一个无向图中权值最小的边一定在最小生成树中。


//kruskal 适用于稀疏图  O(n+m*log2(m))
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 6e3 + 7; //节点
const int M = 6e3 + 7; //路径数
int n, m;
struct Edge {
    int u, v, w;
    bool operator <(const Edge tmp) const {
        return w < tmp.w;
    }
}edge[M];
int father[N];
ll ans = 0; //最小生成树的花费和

int find(int root) {
    int son = root;
    while (root != father[root])
        root = father[root];
    while (son != root) { //路径压缩
        int temp = father[son];
        father[son] = root;
        son = temp;
    }
    return root;
}

void kruskal() {
    for (int i = 1; i <= m; i++) {
        int u = edge[i].u, v = edge[i].v;
        int x = find(u), y = find(v);
        if (x != y) {
            father[x] = y;
            ans += edge[i].w;
        }
    }
}
int main() {
    int T = read();
    while (T--) {
        n = read(), m = read();
        for (int i = 1; i <= n; ++i)
            father[i] = i;
        for (int i = 1; i <= m; ++i)
            edge[i].u = read(), edge[i].v = read(), edge[i].w = read();
        sort(edge + 1, edge + 1 + m);
        kruskal();
    }
    return 0;
}
//无向图最小生成树
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 3e3 + 7;
const int INF = 0x3f3f3f3f;
int e[N][N], vis[N], dis[N];
int n, m;
ll ans;
//prim适合稠密图 O(n^2),特别是完全图最小生成树的求解
void prim() {
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    for (int i = 1; i < n; ++i) {
        int x = 0;
        for (int j = 1; j <= n; ++j)
            if (!vis[j] && (x == 0 || dis[j] < dis[x]))
                x = j;
        vis[x] = 1;
        for (int j = 1; j <= n; ++j)//用u点更新  刚刚新拓展的边
            if (!vis[j])    dis[j] = min(dis[j], e[x][j]);
    }
}

int main() {
    n = read(), m = read();
    //构建邻接矩阵
    memset(e, 0x3f, sizeof(e));
    for (int i = 0; i <= n; ++i)    e[i][i] = 0;
    for (int i = 1; i <= m; ++i) {
        int x = read(), y = read(), z = read();
        e[x][y] = e[y][x] = min(e[x][y], z);
    }
    //求最小生成树
    prim();
    for (int i = 2; i <= n; ++i)    ans += dis[i];
    printf("%lld\n", ans);
    return 0;
}

拓扑排序

拓扑排序是用于有向图判断不存在有向环。入队点数小于总节点数,存在环。
而Kruskal跑并查集是判断无向图存在环。并查集连通块父节点不统一,存在环。
实现思路,首先入度为0的节点入队。
再去跑这个队列,遍历全部入度为0的节点,把他们连接的边去掉,就是终点入度-1,如果终点入度为0了,终点入队。

#include <bits/stdc++.h>
#pragma GCC optimize(2)
#pragma GCC optimize(3)
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 1e3 + 7;
int in[N];
int n, m; // 节点数,边数
vector<int>edge[N];

void topsort() {
    queue<int>q; //如果要按字典序,使用最小堆即可
    for (int i = 1; i <= n; i++)  //n  节点的总数
        if (in[i] == 0) q.push(i);  //将入度为0的点入队列
    vector<int>ans;   //ans 为拓扑序列
    while (!q.empty()) {
        int p = q.front(); q.pop(); // 选一个入度为0的点,出队列
        ans.push_back(p);
        for (int i = 0; i < edge[p].size(); i++) {
            int y = edge[p][i];
            in[y]--;
            if (in[y] == 0)
                q.push(y);
        }
    }
    if (ans.size() == n) {
        for (int i = 0; i < ans.size(); i++)
            printf("%d ", ans[i]);
        printf("\n");
    }
    else printf("No Answer!\n");   //  ans 中的长度与n不相等,就说明无拓扑序列

}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; ++i) {
        int u = read(), v = read();
        edge[u].push_back(v);
        ++in[v];
    }
    topsort();
    return 0;
}