图遍历,最短路,最小生成树,拓扑序列

图的存储

  • 邻接矩阵
  • 邻接表

图的遍历

邻接矩阵版-DFS

#include <cstdio>
#include <vector>
#define maxn 100

using namespace std;
int n, m;
int u, v, cnt = 0;
vector<int> edges[maxn];
bool visit[maxn] = {false};

void DFS(int idx) {
    visit[idx] = true;
    for (int j = 0; j < edges[idx].size(); j++) {
        if (!visit[edges[idx][j]]) DFS(edges[idx][j]);
    }
}

int main() {
    scanf("%d %d", &n, &m);

    for (int i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        edges[u].push_back(v);
        edges[v].push_back(u);
    }

    for (int i = 0; i < n; i++) {
        if (!visit[i]) {
            cnt ++;
            DFS(i);
        }
    }

    printf("%d", cnt);
    
}

有向图判环🤨

  • visit数组有三个状态,-1表示还未访问,0表示在此轮中已经访问,当此轮中再次访问到为0的结点,说明有环;若无环,则把此轮中访问过的结点置为1,表示已经访问过,无需再次访问。
  • 即以每个结点作为起点,如果从这个起点开始走,成不了环,后续也无需把它考虑进来了。
#include <cstdio>
#include <vector>
#include <cstring>
#define maxn 100

using namespace std;
int n, m;
int u, v, cnt = 0;
vector<int> edges[maxn];
int visit[maxn];

bool DFS(int idx) {
    visit[idx] = 0;
    for (int i = 0; i < edges[idx].size(); i++) {
        int v = edges[idx][i];
        if (visit[v] == 0) {
            return true;
        } else if (visit[v] == -1 && DFS(v)){
            return true;
        }
    }

    visit[idx] = 1;
    return false;
}

int main() {
    scanf("%d %d", &n, &m);
    bool flag = false;

    for (int i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        edges[u].push_back(v);
    }

    memset(visit, -1, sizeof(visit));

    for (int i = 0; i < n; i++) {
        if (visit[i] == -1) {
            flag = DFS(i);
        }
        if (flag) {
            printf("Yes");
            return 0;
        }
    }

    printf("No");
    return 0;
}

BFS

#include <cstdio>
#include <vector>
#include <queue>
#define maxn 100

using namespace std;
int main() {
    int n, m, s;
    int u, v;
    queue<int> q;
    vector<int> edges[maxn];
    int l[maxn];
    bool visit[maxn] = {false};
    scanf("%d %d %d", &n, &m, &s);

    for (int i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        edges[u].push_back(v);
        edges[v].push_back(u);
    }

    q.push(s);
    visit[s] = true;
    int idx, cnt, level = 0;
    while (!q.empty()) {
        cnt = q.size();
        for (int i = 0; i < cnt; i++) {
            idx = q.front();
            l[idx] = level;
            q.pop();
            for (int j = 0; j < edges[idx].size(); j++) {
                v = edges[idx][j];
                if (!visit[v]) {
                    q.push(v);
                    visit[v] = true;
                }
            }
        }
        level ++;
    }

    for (int i = 0; i < n; i++) {
        printf("%d", l[i]);
        if (i != n-1) printf(" ");
    }

    return 0;
}

最短路径

Dijkstra

  • 邻接矩阵版
  • Dijkstra只能处理非负权图
  • 什么时候需要+DFS进行后处理?
#include <iostream>
#define MAX 9999999 // 表示边不存在,用INT32_MAX也可

using namespace std;
int main() {
    int N, M; // 顶点数,边数
    int C1, C2; // 起点,终点
    int A[N][N]; // 记录路径长度

	// 构造邻接矩阵
    cin >> N >> M >> C1 >> C2;
    fill_n(&A[0][0], N * N, MAX); // MAX表示不可达

    for (int i = 0; i < N; i++) cin >> V[i];

    int j, k; // 无向邻接图对称!
    for (int i = 0; i < M; i++) {
        cin >> j >> k >> A[j][k];
        A[k][j] = A[j][k];
    }

    // 最短路径算法
    int visit[N]; // 记录是否在点集中
    int D[N]; // 记录C1到点i的最短路径
    int min, v, w;

    // 初始化(直通路径初始化)
    for (int i = 0; i < N; i++) {
        visit[i] = 0; 
        D[i] = A[C1][i]; // 记录直通的路径长
    }

    visit[C1] = 1; // C1加入已访问节点
    D[C1] = 0; // C1到C1路径长为0
    for (int i = 1; i < N; i++) { // N-1轮
        min = MAX;
        v = -1;
        for (w = 0; w < N; w++) {
            if (!visit[w]) {
                if (D[w] < min) {
                    v = w;
                    min = D[w];
                }
            }
        }
        if (v == -1) break;
        visit[v] = 1;
        for (w = 0; w < N; w++) { //加入w节点,处理D[w]
            if (!visit[w] && A[v][w] != MAX) { // 访问未访问且有直通路径的节点w
                if (min + A[v][w] < D[w]) { // 如果当前记录的最短路径大于经过v的路径
                    D[w] = min + A[v][w]; // 更新D[w]
                } 
            }
        }
    }
}

Bellman-Ford

  • 可以处理负权边

SPFA

  • 优化后的Bellman-Ford

Floyd

  • 三重循环,k一定在最外层
#include <cstdio>
#include <algorithm>
#define maxn 100
#define inf 999999

using namespace std;
int main() {
    int n, m;
    int a[maxn][maxn];
    scanf("%d %d", &n, &m);

    int u, v, w;
    fill(&a[0][0], &a[0][0] + maxn*maxn, inf);
    for (int i = 0; i < m; i++) {
        scanf("%d %d %d", &u, &v, &w);
        a[u][v] = a[v][u] = w;
    }

    for (int i = 0; i < n; i++) {
        a[i][i] = 0;
    }

    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (a[i][k] != inf && a[k][j] != inf && a[i][j] > a[i][k] + a[k][j]) {
                    a[i][j] = a[i][k] + a[k][j];
                }
            }
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (a[i][j] == inf) printf("-1");
            else printf("%d", a[i][j]);

            if (j != n-1) printf(" ");
        }
        printf("\n");
    }
}

最小生成树

Prim算法

  • 和Dijkstra的思路很像,只有D[i]的含义不同
  • Dijkstra中,D[i]代表顶点i与起点的最短距离
  • Prim中,D[i]代表顶点i与集合的最短距离
#include <cstdio>
#include <algorithm>
#define maxn 100
#define inf 999999

using namespace std;
int main() {
    int n, m;
    int a[maxn][maxn];
    int D[maxn];
    bool visit[maxn];
    int u, v, weight;
    scanf("%d %d", &n, &m);

    fill(&a[0][0], &a[0][0] + maxn*maxn, inf);
    fill(D, D + maxn, inf);
    fill(visit, visit + maxn, false);

    for (int i = 0; i < m; i++) {
        scanf("%d %d %d", &u, &v, &weight);
        a[u][v] = a[v][u] = weight;
    }

    D[0] = 0;
    int mini;
    for (int i = 0; i < n; i++) {
        mini = inf;
        v = -1;
        for (int u = 0; u < n; u++) {
            if (!visit[u] && D[u] < mini) {
                mini = D[u];
                v = u;
            }
        }


        if (v == -1) {
            printf("-1");
            return 0;
        }
        visit[v] = true;

        for (int u = 0; u < n; u++) {
            if (!visit[u] && a[v][u] != inf) {
                if (a[v][u] < D[u]) { // 就素这里不一样
                    D[u] = a[v][u];
                }
            }
        }
    }

    int sum = 0;
    for (int v = 1; v < n; v++) {
        sum += D[v];
    }

    printf("%d", sum);
}

Kruskal算法

  • 并查集
  • emm,我选择prim
#include <cstdio>
#include <algorithm>
#define maxn 100

using namespace std;
struct Edge {
    int v, u;
    int weight;
} edges[maxn];

int father[maxn];
int n, m;

void init() {
    for (int i = 0; i < n; i++) {
        father[i] = i;
    }
}

int findFather(int x) {
    int a = x;
    while (x != father[x]) {
        x = father[x];
    }

    while (a != father[a]) {
        int temp = a;
        a = father[a];
        father[temp] = x;
    }

    return x;
}

void Union(int a, int b) {
    a = findFather(a);
    b = findFather(b);

    if (a != b) {
        father[a] = b;
    }
}

bool cmp(Edge e1, Edge e2) {
    return e1.weight < e2.weight;
}

int main() {
    int cnt = 0, sum = 0;
    scanf("%d %d", &n, &m);

    for (int i = 0; i < m; i++) {
        scanf("%d %d %d", &edges[i].v, &edges[i].u, &edges[i].weight);
    }

    init();

    sort(edges, edges + m, cmp);

    for (int i = 0; i < m; i++) {
        if (findFather(edges[i].v) != findFather(edges[i].u)) {
            Union(edges[i].u, edges[i].v);
            sum += edges[i].weight;
            cnt ++;
            if (cnt == n-1) break;
        }
    }

    if (cnt == n-1) printf("%d", sum);
    else printf("-1");
}

拓扑排序

  • 拓扑排序判环,输出拓扑排序也可,这里使用优先队列输出字典序最小的一条
  • 找到当前入度0的结点,入队,删去与该结点相连的边(清空edges[i],对应邻点入度矩阵-1)
  • 反复上一步,直到队列空
  • 检查ans.size(),若<n则说明存在环,若==n则正确
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#define maxn 100

using namespace std;
int main() {
    int n, m, v, u;
    vector<int> edges[maxn];
    vector<int> ans;
    priority_queue<int, vector<int>, greater<int> > q;
    int indegree[maxn];
    scanf("%d %d", &n, &m);

    memset(indegree, 0, sizeof(indegree));

    for (int i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        edges[u].push_back(v);
        indegree[v] ++;
    }

    for (int i = 0; i < n; i++) {
        if (indegree[i] == 0) {
            q.push(i);
        }
    }

    int x;
    while (!q.empty()) {
        x = q.top();
        ans.push_back(x); // 利用优先队列的性质,在pop的时候加入ans!
        q.pop();
        for (int i = 0; i < edges[x].size(); i++) {
            v = edges[x][i];
            indegree[v] --;
            if (indegree[v] == 0) {
                q.push(v);
            }
        }
        edges[x].clear();
    }

    if (ans.size() == n) printf("Yes");
    else printf("No");
}
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值