915图代码

【方便复制】

请结合PPT及讲解达到最佳的复习效果。


以下代码基于无向图。

邻接矩阵转换成邻接表

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 u u u, v v v, w w w,表示点 u u u 和点 v v v 之间存在一条权值为 w w w 的边。

输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
邻接矩阵表示法:
- 1 2 3 
1 - 2 - 
2 2 - 4 
3 - 4 - 

邻接表表示法(括号内为权值):
与1相连的边有:4(3) 3(2) 2(1) 
与2相连的边有:3(2) 1(1) 
与3相连的边有:4(4) 2(2) 1(2) 
与4相连的边有:3(4) 1(3) 
c++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, M = 100010, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];    // 邻接矩阵

struct Node
{
    int id; // 编号
    int w;  // 权值
    Node* next;
    Node(int _id, int _w) : id(_id), w(_w), next(NULL){}
} *head[N];     // 邻接表

void add(int a, int b, int w)
{
    Node* p = new Node(b, w);
    p->next = head[a];
    head[a] = p;
}

void convert()  // 邻接矩阵转换成邻接表
{
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (g[i][j] < INF / 2)
                add(i, j, g[i][j]);
}

int main()
{
    memset(g, 0x3f, sizeof g);
    cin >> n >> m;
    while (m -- )
    {
        int a, b, w;
        cin >> a >> b >> w;
        g[a][b] = g[b][a] = min(g[a][b], w);
    }
    
    puts("邻接矩阵表示法:");
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= n; j ++ )
            if (g[i][j] > INF / 2) cout << '-' << ' ';
            else cout << g[i][j] << ' ';
        cout << endl;
    }
    
    convert();
    
    puts("\n邻接表表示法(括号内为权值):");
    for (int i = 1; i <= n; i ++ )
    {
        printf("与%d相连的边有:", i);
        for (auto p = head[i]; p; p = p->next)
            printf("%d(%d) ", p->id, p->w);
        cout << endl;
    }
    
    return 0;
}

邻接表转换成邻接矩阵

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 u u u, v v v, w w w,表示点 u u u 和点 v v v 之间存在一条权值为 w w w 的边。

输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
邻接表表示法(括号内为权值):
与1相连的边有:4(3) 3(2) 2(1) 
与2相连的边有:3(2) 1(1) 
与3相连的边有:4(4) 2(2) 1(2) 
与4相连的边有:3(4) 1(3) 

邻接矩阵表示法:
- 1 2 3 
1 - 2 - 
2 2 - 4 
3 - 4 - 
c++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, M = 100010, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];    // 邻接矩阵

struct Node
{
    int id; // 编号
    int w;  // 权值
    Node* next;
    Node(int _id, int _w) : id(_id), w(_w), next(NULL){}
} *head[N];     // 邻接表

void add(int a, int b, int w)
{
    Node* p = new Node(b, w);
    p->next = head[a];
    head[a] = p;
}

void convert()  // 邻接表转换成邻接矩阵
{
    for (int i = 1; i <= n; i ++ )
    {
        for (auto p = head[i]; p; p = p->next)
        {
            int j = p->id;  // 与i相邻的点的编号
            g[i][j] = g[j][i] = p->w;
        }
    }
}

int main()
{
    memset(g, 0x3f, sizeof g);
    cin >> n >> m;
    while (m -- )
    {
        int a, b, w;
        cin >> a >> b >> w;
        // 假设没有重边
        add(a, b, w);
        add(b, a, w);
    }

    puts("邻接表表示法(括号内为权值):");
    for (int i = 1; i <= n; i ++ )
    {
        printf("与%d相连的边有:", i);
        for (auto p = head[i]; p; p = p->next)
            printf("%d(%d) ", p->id, p->w);
        cout << endl;
    }    
      
    convert();
    
    puts("\n邻接矩阵表示法:");
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= n; j ++ )
            if (g[i][j] > INF / 2) cout << '-' << ' ';
            else cout << g[i][j] << ' ';
        cout << endl;
    }
    
    return 0;
}

带权值的用邻接表存的有向图的dfs

输入:

4 5
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
#include <iostream>
#include <cstring>

using namespace std;

const int N = 510, M = 100010, INF = 0x3f3f3f3f;

int g[N][N];
bool st[N];

struct Node
{
    int id, w;
    Node* next;
    Node(int _id, int _w) : id(_id), w(_w), next(NULL) {}
}* head[N];

void add(int a, int b, int w)
{
    Node* p = new Node(b, w);
    p->next = head[a];
    head[a] = p;
}

void dfs(int u)
{
    st[u] = true;
    cout << u << ' ';
    
    for (Node* p = head[u]; p; p = p->next)
        if (!st[p->id])
            dfs(p->id);
}

int main()
{
    memset(g, 0x3f, sizeof g);
    int n, m;
    cin >> n >> m;
    while (m -- )
    {
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);
        // g[a][b] = w;
    }
    
    for (int i = 1; i <= n; i ++)
        if (!st[i])
            dfs(i);
    
    return 0;
}

DFS 邻接表

5 5
1 2
1 3
1 4
2 3
3 4
#include <iostream>
#include <cstring>

using namespace std;

const int N = 510;

bool st[N];

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL) {}
}* head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void dfs(int u)
{
    st[u] = true;
    cout << u << ' ';
    
    for (Node* p = head[u]; p; p = p->next)
        if (!st[p->id])
            dfs(p->id);
}

int main()
{
    int n, m;
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        // g[a][b] = w;
    }
    
    for (int i = 1; i <= n; i ++)
    {
        printf("%d: ", i);
        for (Node* p = head[i]; p; p = p->next)
            printf("%d -> ", p->id);
        puts("NULL");
    }
    
    puts("-------------------");
    
    for (int i = 1; i <= n; i ++)
        if (!st[i])
            dfs(i);
    
    return 0;
}

DFS 邻接矩阵

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510;

int n, m;
int g[N][N];
bool st[N];

void dfs(int u)
{
    st[u] = true;
    cout << u << ' ';
    
    for (int i = 1; i <= n; i ++)
        if (g[u][i] && !st[i])
            dfs(i);
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        g[a][b] = 1;
    }
    
    for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= n; j ++)
            cout << g[i][j] << ' ';
        cout << endl;
    }
    puts("----------------");
    
    for (int i = 1; i <= n; i ++)
        if (!st[i])
            dfs(i);
    
    return 0;
}

DFS非递归

#include <iostream>
#include <cstring>
#include <stack>

using namespace std;

const int N = 510;

int n, m;
bool st[N];

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL) {}
}* head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void dfs(int u)
{
    stack<int> s;
    s.push(u);
    st[u] = true;
    
    while (s.size())
    {
        int t = s.top();
        s.pop();
        
        cout << t << ' ';
        for (Node *p = head[t]; p; p = p->next)
        {
            int j = p->id;
            if (!st[j])
            {   
                st[j] = true;
                s.push(j);
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    for (int i = 1; i <= n; i ++)
        if (!st[i])
            dfs(i);
    
    return 0;
}

BFS

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 510;

int n, m;
bool st[N];

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL) {}
}* head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void bfs(int u)
{
    queue<int> q;
    q.push(u);
    st[u] = true;
    
    while (q.size())
    {
        int t = q.front();
        q.pop();
        
        cout << t << ' ';
        for (Node *p = head[t]; p; p = p->next)
        {
            int j = p->id;
            if (!st[j])
            {   
                st[j] = true;
                q.push(j);
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    for (int i = 1; i <= n; i ++)
        if (!st[i])
            bfs(i);
    
    return 0;
}

求拓扑排序

基于BFS

求拓扑排序的基本思路是:

  1. 如果图中含有回路,则说明该图不具有拓扑排序,直接返回失败。可以用 Kahn 算法检测是否含有回路。

  2. 选取入度为 0 的节点,该节点可以第一步输出。

  3. 输出该节点后,将它的邻接节点入度都减 1。

  4. 不断重复选择入度为 0 的节点输出,并更新其他节点入度。

  5. 若最终输出的节点数与总节点数相同,说明找出了一个拓扑序列。

具体算法如下:

  1. 输入图,统计每个节点的入度,建立入度表。

  2. 使用队列存储入度为 0 的节点。

  3. 循环检测队列是否为空:

    - 不空,出队首节点 u ,输出
    - 并更新u的相邻节点v的入度值
    - 如果入度变为0,加入队尾
    
  4. 检查是否输出的节点数等于总节点数,是则找到拓扑序列,否则图含回路。

  5. 返回拓扑排序结果

主要思路是利用入度代表节点的前驱关系,入度为0表示无前驱可输出,更新入度后继续输出下一个节点。
重复此过程找出符合前后关系的拓扑序列。

需要注意的是在更新入度时判断是否产生新的入度为0的节点加入队列,实现层层递推输出关系。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m, q[N];
int d[N];   // 每个点的入度

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL){}
} *head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];      // 头插法
    head[a] = p;
}

bool topsort()
{
    int hh = 0, tt = -1;    

    for (int i = 1; i <= n; i ++)
        if (!d[i]) q[++ tt] = i;

    while (hh <= tt)
    {
        int t = q[hh ++];

        // 在结点t的所有邻边中找所有入度为0的点,然后将其入队
        for (Node* p = head[t]; p; p = p->next)
        {
            int j = p->id;  // t的临边的编号
            if (-- d[j] == 0)
                q[++ tt] = j;
        }
    }

    return tt == n - 1;
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        d[b] ++;
        add(a, b);
    }

    if (topsort()) 
    {
        for (int i = 0; i < n; i ++) cout << q[i] << ' ';
        cout << endl;
    }
    else puts("-1");

    return 0;
}
基于DFS

深度优先遍历判断图中拓扑排序(是否无环)的算法如下:

  1. 选择一个起始节点,将其标记为”正在访问”状态。

  2. 检查当前节点的所有相邻节点:
    * 如果相邻节点为”未访问”状态,则递归地进行深度优先搜索这个节点

  3. 如果在搜索过程中发现这个节点已经被标记为”正在访问”,则说明存在环路,返回false

  4. 如果相邻节点状态为”已访问”,忽略该节点

  5. 所有相邻节点均处理完成后,将当前节点标记为”已访问”状态,并返回上层递归

  6. 如果整个图都搜索完,没有返回false,则说明图中不存在环路,返回true。

主要思路是:

  • 使用”未访问”、”正在访问”和”已访问”三种节点状态进行标记

  • 如果搜索某个节点时发现其状态为”正在访问”,则说明从此节点开始一定存在一个环路

  • 整个深度优先搜索结束没有返回false,则该图不存在环路

通过遍历每个节点及其相邻节点,并检查其访问状态就可以判断是否存在环路。时间复杂度为O(V+E),空间复杂度为O(V)。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m, q[N], top;
int st[N];  // 0表示没有遍历过,1表示在递归当中,2表示已经遍历完了

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL){}
} *head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];      // 头插法
    head[a] = p;
}

bool dfs(int u)     // 如果有环的话返回false
{
    st[u] = 1;      // 标记当前点在递归中

    for (Node* p = head[u]; p; p = p->next)
    {
        int j = p->id;
        if (!st[j])     // 如果没有搜过的话(0)就直接搜
        {
            if (!dfs(j)) return false;  // 如果搜的过程中出现了环,直接false
        }
        else if (st[j] == 1) return false;   // 否则如果发现这个点在递归当中,直接false
    }

    q[top ++] = u;

    st[u] = 2;      // 标记当前点已经遍历完了

    return true;
}

// 基于深搜的话要遍历所有点,因为这个图不一定是联通的
bool topsort()
{
    for (int i = 1; i <= n; i ++)
        if (!st[i] && !dfs(i)) // 如果没遍历的话就遍历它,如果发现有环的话直接false
            return false;
    return true;
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }

    if (topsort())
    {
        // 因为得到的是拓扑排序的逆序序列,所以要倒序输出
        for (int i = n - 1; i >= 0; i -- ) cout << q[i] << ' ';
        cout << endl;
    }
    else puts("-1");

    return 0;
}

屏幕截图 2023-10-22 040344.png

BFS:

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 510;

int n, m;
bool st[N];
int ss = 1, tt = 2;   // s起点,t终点

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL) {}
}* head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void bfs(int u)
{
    queue<int> q;
    q.push(u);
    st[u] = true;
    
    while (q.size())
    {
        int t = q.front();
        q.pop();
        
        if (t == tt)
        {
            printf("存在%d-%d的路径\n", ss, tt);
            return;
        }
        for (Node *p = head[t]; p; p = p->next)
        {
            int j = p->id;
            if (!st[j])
            {   
                st[j] = true;
                q.push(j);
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    bfs(ss);
    
    return 0;
}

DFS:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510;

bool st[N];
int s = 1, t = 2;  // s起点,t终点

struct Node
{
    int id;
    Node* next;
    Node(int _id) : id(_id), next(NULL) {}
}* head[N];

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void dfs(int u)
{
    st[u] = true;
    if (u == t) // 若遍历到了终点
    {
        printf("存在%d-%d的路径", s, t);
        return;
    }
    
    for (Node* p = head[u]; p; p = p->next)
        if (!st[p->id])
            dfs(p->id);
}

int main()
{
    int n, m;
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    for (int i = 1; i <= n; i ++)
    {
        printf("%d: ", i);
        for (Node* p = head[i]; p; p = p->next)
            printf("%d -> ", p->id);
        puts("NULL");
    }
    
    puts("-------------------");
    
    dfs(s);
    
    return 0;
}

问题描述

假设图用邻接表表示,设计一个算法,输出从顶点 i i i 到顶点 j j j 的所有简单路径

输入格式

第一行包含三个整数 n n n m m m k k k

接下来 m m m 行,每行包含两个整数 x x x y y y,表示存在一条从点 x x x 到点 y y y 的有向边 ( x x x, y y y)。

接下来 k k k 行,每行包含两个整数 s s s t t t,表示要求输出顶点 s s s 到顶点 t t t 的所有简单路径。

输入

6 8 3
1 2
1 3
2 4 
3 4
4 5
5 3
2 7
7 4
1 4
2 5
1 5

屏幕截图 2023-10-22 040312.png

输出


1 -> 4 的所有简单路径:
1 3 4 
1 2 7 4 
1 2 4 

2 -> 5 的所有简单路径:
2 7 4 5 
2 4 5 

1 -> 5 的所有简单路径:
1 3 4 5 
1 2 7 4 5 
1 2 4 5 

c++ 代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510;

int n, m, k;
bool st[N];
vector<int> path;

struct Node
{
    int id; // 编号
    int w;  // 权值
    Node* next;
    Node(int _id) : id(_id), next(NULL){}
} *head[N];     // 邻接表

void add(int a, int b)
{
    Node* p = new Node(b);
    p->next = head[a];
    head[a] = p;
}

void dfs(int s, int t)
{
    if (s == t)
    {
        for (auto x : path) printf("%d ", x);
        puts("");
        return ;
    }
    
    for (auto p = head[s]; p; p = p->next)
    {
        int j = p->id;
        if (!st[j])
        {
            st[j] = true;
            path.push_back(j);
            dfs(j, t);
            path.pop_back();
            st[j] = false;
        }
    }
}


int main()
{
    cin >> n >> m >> k;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    while (k --)
    {
        int s, t;
        cin >> s >> t;
        printf("\n%d -> %d 的所有简单路径:\n", s, t);
        path.push_back(s);
        st[s] = true;
        dfs(s, t);  // 输出顶点s到顶点t的所有简单路径
        st[s] = false;
        path.pop_back();
    }
    
    return 0;
}

算法思想

这个代码输出从顶点i到顶点j的所有简单路径的算法思想如下:

  1. 用邻接表表示图,构建邻接表头head,每个节点存储下一个邻接节点指针。

  2. 深度优先搜索dfs函数,递归搜索各条路径。

  3. path向量存储当前路径,st数组标记每个节点是否访问过。

  4. dfs函数入口是s节点,终止条件是找到t节点。

  5. 对s节点的每一个邻接节点p递归搜索:

    - 将p加入path并标记为已访问
    
    - 继续递归dfs搜索下一个节点
    
    - 返回后删除p,恢复未访问状态
    
  6. 每经过一个节点并回溯,输出当前path向量中的路径,即找到一条从s到t的路径。

  7. 重复5-6步骤搜索s节点的所有可能路径,输出所有简单路径。

所以这个算法是使用深度优先搜索和回溯方法,结合path向量和st访问标记数组来记录和输出所有可能的路径,得到从初始节点到目标节点的所有简单路径。


屏幕截图 2023-10-22 040252.png

PRIM算法(Prim’s algorithm)是一个用于计算加权无向图的最小生成树(MST)的贪心算法。

算法步骤:

  1. 从图中选择任意一个顶点作为第一个最小生成树中的顶点。

  2. 不断地添加图中与当前最小生成树顶点之间边权最小的边,直至所有的顶点都连通为止。

  3. 每次添加新边之前,都要检查该边是否会形成环路,如果会形成环路则丢弃该边,选择下一条边权最小的边。

这个算法的思路是:

  • 从一个顶点开始构建最小生成树
  • 每次都选择与当前最小生成树间边权最小的边来扩展树的顶点
  • 重复这个过程直到所有的顶点都被包含在树中

具体步骤实现较简单,只需要使用优先队列或堆来维护候选边的边权,每次取出队首的边就可以找到下一步要添加的边。PRIM算法是一个非常经典且高效的最小生成树算法。

朴素版Prim()算法

时间复杂度: O ( n 2 ) O(n^2) O(n2)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, M = 100010, INF = 0x3f3f3f3f;

int n, m;       // n 为点数,m 为边数
int g[N][N];    // 稠密图用邻接矩阵存
int dist[N];    // 连通块外每个点到连通块内的最短距离
bool st[N];     // 当前每一个点是否已经在连通块内

int prim() {
    memset(dist, 0x3f, sizeof dist);    // dist 初始化为正无穷
    dist[1] = 0;    // 将第一个点距离设为0,表示第一个点加入集合
    int res = 0;    // 答案表示当前边权和
    for (int i = 0 ; i < n ; i ++) {    // 一共要加 n 个点
        int t = -1;     // t 表示当前边权最小的点
        for (int j = 1 ; j <= n ; j ++) {   // 枚举所有点
            // 如果((当前这个点还没有在集合中)并且(t还没有选过其他点或者t选择的点没有当前点好))
            if (!st[j] && (t == -1 || dist[j] < dist[t]))     
                t = j;  // 更新成当前点
        }
        
        // 如果距离当前集合最小的点的长度都是正无穷的话,则不连通,无解,直接返回正无穷
        if (dist[t] == INF) return INF;  
        // 否则的话标记当前点,表示当前点已经在集合中了
        st[t] = true;
        res += dist[t];     // 每条边的长度需要加到res中
        // 更新其他所有点
        for (int j = 1 ; j <= n ; j ++) 
            dist[j] = min(dist[j], g[t][j]); 
    }
    return res;
}

int main() {
    memset(g, 0x3f, sizeof g);  // 将所有边初始化为正无穷
    cin >> n >> m;  
    while (m --) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);    // 存在自环和重边
    }
    
    int res = prim();
    if (res == INF) puts("impossible");
    else cout << res << endl;
    return 0;
}

Kruskal算法

克鲁斯卡尔算法步骤如下:

  1. 将所有边根据权值从小到大进行排序。如果权值相同,则以任意但一致的顺序进行排序。

  2. 初始化一棵森林F(即一组树),其中每个图中的节点都是一个单独的树。

  3. 取排序后的第一条边。如果该边连接森林F中的不同树,则将该边添加到森林中。如果形成环路,则丢弃该边。

  4. 继续取排序列表中的下一条边。只有当该边不形成环路时,才将其添加到F中。

  5. 算法在F成为一个包括所有节点的单一树时终止。这将发生在森林中的边数达到 |V|-1时,其中|V|是图中节点数。

  6. 结果森林F就是最小生成树。

克鲁斯卡尔算法的主要特点:

  • 只要边权满足三角不等式,它就能产生最小生成树。

  • 时间复杂度是O(ElogE),E是图中的边数。

  • 它是贪心算法,因为它总是选择不会形成环路的最小权值边。
    时间复杂度: O ( m l o g m ) O(mlogm) O(mlogm)

#include <iostream>
#include <algorithm>

using namespace std;

// N是一个较大的数,用来容纳各个节点
// M是边的数量上限
// INF是一个很大的数,用于表示无限
const int N = 510, M = 100010, INF = 0x3f3f3f3f;

// n是节点数量,m是边的数量
// p是并查集数组
int n, m, p[N];

// Edge结构体存储每条边的端点和权值
// 重载<运算符用于排序边
struct Edge {
    int a, b, c;
    bool operator< (const Edge& t) const {
        return c < t.c;
    }
} e[M];

// 并查集查找函数,路径压缩
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {

    // 读取节点和边的数量
    cin >> n >> m;

    // 读取每条边的信息
    for (int i = 0 ; i < m ; i ++)
        cin >> e[i].a >> e[i].b >> e[i].c;
    
    // 初始化并查集数组
    for (int i = 1 ; i <= n ; i ++)
        p[i] = i;
    
    // 对边进行升序排序
    sort(e, e + m);

    // res记录结果,cnt记录生成树的边数
    int res = 0, cnt = 0;

    for (int i = 0 ; i < m ; i ++) {

        // a,b是这条边的两个端点,c是权值
        int a = e[i].a, b = e[i].b, c = e[i].c;

        // 如果两个端点不在同一个集合,加入生成树
        if (find(a) != find(b)) {
            res += c;
            // 使用联通操作联通两个集合
            p[find(a)] = find(b);
            cnt ++;
        }
    }

    // 输出结果
    if (cnt == n - 1)
        cout << res << endl;
    else
        puts("impossible");

    return 0;
}

PRIM算法与Kruskal算法相比,主要优势有:

  1. PRIM算法可以保证生成的最小生成树是连通的。它是从一个顶点开始逐步扩张生成树,因此生成的最小生成树一定是连通的。而Kruskal算法可能生成非连通子图,需要额外判断。

  2. PRIM算法适用于任何加权无向图,而Kruskal算法需要图中所有边的边权进行排序,不适用于动态图或边权可能重复的情况。

  3. PRIM算法在多数实现中时间复杂度略优于Kruskal算法。PRIM算法常用的优先队列实现时间复杂度是O(ElogV),而Kruskal算法需要对所有边进行排序,时间复杂度一般是O(ElogE)。

  4. PRIM算法的实现相对简单,使用优先队列维护候选边即可。Kruskal需要实现排好序的边集合合并以判断是否形成环路,实现上较为复杂。

  5. PRIM算法空间复杂度通常比Kruskal算法低,因为不需要维护所有边的列表。

所以总体来说,PRIM算法生成的最小生成树一定是连通的,时间和空间复杂度优于Kruskal算法。但Kruskal算法在理解和证明上略更简单。两种算法都很经典,选择时需要结合具体情况。


Dijkstra算法是用于在加权有向图中求单源最短路径的一种算法。

Dijkstra算法的基本步骤:

  1. 初始化:将源节点s到自己的距离设为0,其他节点到s的距离都设为无限大。
  2. 选择距离源节点最近的节点u,将它从候选列表中删除。
  3. 修松u节点的邻接边:对u的每个邻节点v,计算u到v的距离之和,如果小于以前的距离,就更新v节点的距离值,同时在候选列表中移除旧的距离记录,加入新的记录。
  4. 重复步骤2和3,直到候选列表为空,算法终止。此时算法得到的就是单源最短路径树。

时间复杂度:

  • 朴素版Dijkstra: O ( N 2 ) O(N^2) O(N2) 通常用于稠密图
    • 堆优化版Dijkstra: O ( M l o g N ) O(MlogN) O(MlogN) 通常用于稀疏图
    • N为点数,M为边数

Dijkstra算法适用于边权非负的单源最短路径问题。它采用贪心策略,每次选择期望使总距离最小的节点入树,从而逐步求出最优解。是这一问题的典型算法之一。

输入样例

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例

3

屏幕截图 2023-10-22 040111.png

朴素版dijkstra:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, null = 0x3f3f3f3f;
int n, m;
int g[N][N];    // 存储每条边(用邻接矩阵存,因为是稠密图)
int dist[N];    // 存储1号点到每个点的最短距离
bool st[N];     // 存储每个点的最短路是否确定

// 求1号点到n号点的最短路,如果不存在则返回-1   
int dijkstra()
{
    memset(dist, null, sizeof dist);
    dist[1] = 0;

    for (int i = 1 ; i <= n ; i ++)
    {
        // 在还未确定最短路的点中,寻找距离最小的点
        int t = -1;
        for (int j = 1 ; j <= n ; j ++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        st[t] = true;
        
        // 用t更新其他点的距离
        for (int j = 1 ; j <= n ; j ++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    return dist[n] == null ? -1 : dist[n];
}

int main()
{
    memset(g, null, sizeof g);
    cin >> n >> m;
    for (int i = 0 ; i < m ; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    
    cout << dijkstra() << endl;
    
    return 0;
}

堆优化版:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

const int N = 150010, null = 0x3f3f3f3f;
int n, m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];
typedef pair<int, int> PII;

void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx ++;
}

int dijkstra()
{
    memset(dist, null, sizeof dist);
    dist[1] = 0;
    
    priority_queue<PII, vector<PII>, greater<PII>> heap;    // 小顶堆
    heap.push({0, 1});  // first 存储距离,second 存储节点编号
    
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, distance = t.first;
        
        if (st[ver]) continue;
        st[ver] = true;
        
        for (int i = h[ver] ; i != -1 ; i = ne[i])
        {
            int j = e[i];
            if (distance + w[i] < dist[j])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }
        
    return dist[n] == null ? -1 : dist[n];
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 1 ; i <= m ; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    cout << dijkstra() << endl;
    
    return 0;
}

屏幕截图 2023-03-05 143554.png
屏幕截图 2023-03-05 143656.png
屏幕截图 2023-10-22 035537.png

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510, M = 10010, INF = 0x3f3f3f3f; //定义常量

struct Edge{   //结构体定义边
    int a, b, c;
} edges[M];

int n, m, k;   //n个点,m条边,k次迭代
int dist[N], last[N]; //dist存储单源最短路径长度,last存储上一次迭代的值

void bellman_ford(){ //Bellman-Ford算法主函数

    memset(dist, 0x3f, sizeof dist); // dist初始化为无穷大

    dist[1] = 0; //源点路径长度设置为0

    for (int i = 0; i < k; i ++) { //k次迭代

        memcpy(last, dist, sizeof dist); //last记录本次迭代前的值

        for (int j = 0; j < m; j ++) {//枚举每条边

            Edge e = edges[j]; //取出一条边

            //松弛操作,更新dist值
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);
        }
    }
}

int main()
{
    cin >> n >> m >> k;
    
    for (int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b, c};
    }
    
    bellman_ford();
    
    if (dist[n] > INF / 2) puts("impossible");
    else cout << dist[n] << endl;
    
    return 0;
}

Floyd

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f; 

int n, m, k, d[N][N];

// 这段代码实现了Floyd算法来求解有向图或无向图所有点对之间的最短距离。
void floyd() {
    // 对中间顶点k从1遍历到n
    for (int k = 1; k <= n; k ++)  
        // 对起始顶点i从1遍历到n
        for (int i = 1; i <= n; i ++)
           // 对终点顶点j从1遍历到n  
           for (int j = 1; j <= n; j ++)
               // 对于每个点对<i,j>,计算经过每个中间顶点k的路径长度之和
               // 并与当前直接路径长度取最小值更新到d[i][j]中
               d[i][j] = min(d[i][j], d[i][k] + d[k][j]);  
}
//这段Floyd算法计算了所有的点对<i, j>之间通过中间顶点k的最短距离,时间复杂度为O(n^3)。

int main() {
    memset(d, 0x3f, sizeof d);
    cin >> n >> m >> k;
    while (m --) {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = min(d[a][b], c);
    }
    
    for (int i = 1; i <= n; i ++) d[i][i] = 0;
    
    floyd();
    
    while (k --) {
        int a, b;
        cin >> a >> b;
        if (d[a][b] > INF / 2) puts("impossible");
        else cout << d[a][b] << endl;
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值