# 计算机23级数据结构上级实验(第8周)

A 快速排序

给定包含n个元素的整型数组a[1],a[2],...,a[n],利用快速排序算法对其进行递增排序,请输出排序过程,即每次Partition之后的数组。最后输出排序后的数组。每次选择所处理的子数组的第一个元素作为基准元素。

输入格式:

输入为两行,第一行为一个整数n(1<n≤1000),表示数组长度。第二行为n个空格间隔的整数,表示待排序的数组。

输出格式:

输出为若干行,每行依次输出Partition后的整个数组,每个元素后一个空格。最后一行输出排序后的数组。

输入样例:

5
4 5 3 2 1

输出样例:

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

提示:

就是在每次调用完Partition函数后输出R[1]….R[n]。其余与快速排序一致。

  • 模板题,经典快速排序霍尔版本,while里两个循环找到大数和小数交换
  • 不同于书本的是最右侧没有极大值保证避免边界判断所以需要在
  • while (a[i] <= key && i < j) { i++; }判断i < j
#include<iostream>
#include<vector>

using namespace std;

// 打印数组中的元素
void Print(const vector<int>& nums)
{
    // 遍历数组并打印每个元素,元素之间用空格隔开
    for (const auto& i : nums)
    {
        cout << i << " ";
    }
}

// 分区函数:对数组进行分区,返回基准元素的最终位置
int Partition(vector<int>& a, int left, int right)
{
    int i = left, j = right + 1;  // 初始化 i 和 j,i 从左边界开始,j 从右边界开始
    int key = a[left];  // 选择数组的第一个元素作为基准

    while (i < j)
    {
        // 向右移动 i,直到找到一个大于基准的元素
        i++;
        while (a[i] <= key && i < j) { i++; }//注意边界判断i<J

        // 向左移动 j,直到找到一个小于基准的元素
        j--;
        while (a[j] > key) { j--; }

        // 如果 i < j,交换 a[i] 和 a[j]
        if (i < j)
            swap(a[i], a[j]);
    }

    // 最后将基准元素放到正确的位置
    swap(a[j], a[left]);
    return j;  // 返回基准元素的位置
}

// 快速排序函数:递归地对数组进行排序
void QuickSort(vector<int>& nums, int left, int right)
{
    if (left < right)  // 如果左边界小于右边界,即区间里还有两个数未排序
    {
        int key = Partition(nums, left, right);  // 获取基准元素的位置

        // 打印每次分区后的数组状态
        Print(nums);
        cout << endl;

        // 递归排序基准元素左边的部分
        QuickSort(nums, left, key - 1);

        // 递归排序基准元素右边的部分
        QuickSort(nums, key + 1, right);
    }
}

int main()
{
    int n;
    cin >> n;  // 输入数组的大小
    vector<int> nums;  // 初始化一个空数组

    // 读取数组元素
    for (int i = 0; i < n; i++)
    {
        int v;
        cin >> v;  // 输入每个元素
        nums.push_back(v);  // 将元素添加到数组中
    }

    // 调用快速排序函数,对数组进行排序
    QuickSort(nums, 0, n - 1);

    // 打印排序后的数组
    Print(nums);
    cout << endl;  // 打印换行符
}

B 网络布线

亚洲杯赛期间需要保证运动员公寓网络畅通,以使运动员都能正常上网。

假定公寓楼内有n个房间,编号为0…n−1,每个房间都需要网络连接。房间 i 有网络,当且仅当满足如下2个条件之一:

(1)房间 i 安装了路由器(成本为 r**i>0)

(2)房间 i 和房间 j 有网线连接且房间 j 有网络(在房间 i 和房间 j 之间布置网线的成本为 f**ij>0)

假定你是赛事组委会的网络工程师,请编写程序设计一个网络布线方案(哪些房间安装路由器,哪些房间之间布置网线),使得所有房间都有网络,且总成本最小。

例如下图包含7个房间和10个可能的连接,安装路由器的成本为括号内数字,房间之间布置网线的成本为边的权值。其解决方案为右下图,即在房间1和4安装路由器,并进行图中的网线布置。总成本为120。

img.png

输入格式:

输入第一行为两个正整数nen为房间数,不超过600;e为可能的连接数,不超过2×105。接下来一行为n个空格间隔的正整数,第i个整数(i≥0)表示在房间i安装路由器的成本。接下来e行,每行为3个非负整数ijf,表示在房间i和房间j之间布置网线的成本为f

输出格式:

输出为一个整数,表示最优网络布线方案的成本。

输入样例:

7 10
60 10 35 55 40 70 70
0 1 20
0 4 75
0 3 45
1 3 50
1 2 15
2 6 5
5 6 45
4 5 5
3 5 25
3 6 65

输出样例:

120

提示:

可引入一个虚拟顶点,将该顶点与其他所有顶点用边相连,边权等于那些顶点的权值。进而形成一个新图,对新图求最小支撑树。注意本题顶点编号从0开始。 image.png image.png

  • 有提示后就成为Prim算法求稠密图的最小生成树的总代价
  • 需要注意的点是,因为输入多,并且时间限制得很巧,不优化输入输出就会超时
  • 所以需要优化输入输出,分离cin和scanf的缓冲区,或者直接使用scanf
#include <iostream>
#include <vector>
#include <climits>
#include <queue>
using namespace std;

// 图的邻接矩阵表示
vector<vector<int>> graph;

// 创建图,包括添加虚拟顶点和边
void CreateGraph() {
    int n, e;
    cin >> n >> e;
    // n号顶点作为虚拟顶点,初始化图的邻接矩阵,所有权值初始为INT_MAX(表示不连通)
    graph.resize(n + 1, vector<int>(n + 1, INT_MAX));

    // 输入虚拟顶点n与其他顶点之间的边权值
    for (int i = 0; i < n; i++) {
        int v;
        cin >> v;
        graph[n][i] = v; // 虚拟顶点到其他顶点
        graph[i][n] = v; // 其他顶点到虚拟顶点
    }

    // 输入图的边
    for (int i = 0; i < e; i++) {
        int a, b, w;
        cin >> a >> b >> w;
        graph[a][b] = w; // 无向边,双向赋值
        graph[b][a] = w;
    }
}

// 使用Prim算法计算最小生成树的总权值
int Prim() {
    int ans = 0; // 记录最小生成树的总权值

    // 用于存储与每个顶点的最小连接边权值和对应的顶点
    vector<pair<int, int>> close_edge;
    vector<bool> vis; // 标记顶点是否已被访问

    int num = graph.size(); // 图中顶点的数量
    int u = num - 1; // 虚拟顶点的编号
    vis.resize(num, false); // 初始化访问标记
    close_edge.resize(num);

    // 初始化close_edge数组,设置每个顶点与虚拟顶点的边权值
    for (int i = 0; i < num; i++) {
        if (i != u) {
            close_edge[i].first = graph[u][i]; // 初始化为虚拟顶点到其他顶点的边权值
            close_edge[i].second = u;         // 对应的顶点是虚拟顶点
        }
    }

    vis[u] = true; // 将虚拟顶点标记为已访问

    // 找到未访问顶点中与生成树连接权值最小的边
    auto Min = [&]() -> int {
        int minindex = -1; // 最小权值边的顶点编号
        int min = INT_MAX; // 最小权值
        for (int i = 0; i < close_edge.size(); i++) {
            const auto& vex = close_edge[i];
            // 未访问且有更小权值的边
            if (!vis[i] && vex.first < min) {
                minindex = i;
                min = vex.first;
            }
        }
        return minindex; // 返回最小权值顶点编号
    };

    // 循环选择剩余n个顶点中的n-1个,将其加入生成树
    for (int i = 1; i < num; i++) {
        int u0 = Min(); // 找到当前未访问顶点中连接权值最小的顶点

        ans += close_edge[u0].first; // 将最小权值累加到结果中
        vis[u0] = true;              // 标记该顶点为已访问

        // 更新close_edge数组,检查是否存在更小的连接权值
        for (int j = 0; j < close_edge.size(); j++) {
            if (graph[u0][j] < close_edge[j].first) {
                close_edge[j].first = graph[u0][j]; // 更新为更小的权值
                close_edge[j].second = u0;         // 更新对应顶点
            }
        }
    }

    return ans; // 返回最小生成树的总权值
}

int main() {
    // 优化输入输出
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    // 创建图并计算最小生成树的总权值
    CreateGraph();
    cout << Prim() << endl;

    return 0;
}

C 最大边

给定一个包含n个顶点的无向正权连通图,顶点编号为1到n。请编写程序计算其最小支撑树中任意两个顶点间路径中,权值最大的边的权值。

输入格式:

第一行为2个正整数n和m,n为图中顶点个数,m为边的条数。接下来m行,每行3个整数a、b、c,表示顶点a和顶点b之间有一条权值为c的边。随后一行为一个正整数T,表示查询数目。接下来T行,每行2个整数a和b,表示查询最小支撑树中顶点a和b间路径中的最大边。n≤2000,m≤30000,1 ≤a,b≤ n且a\=b,c ≤65535,T ≤ 1000 。

输出格式:

对于每个查询输出一行,为1个整数,表示最小支撑树两个顶点间的路径中的最大边的权值。

输入样例:

8 20
2 7 44181
1 2 36877
3 6 2506
2 8 46829
7 1 2843
4 5 40699
1 3 15911
7 6 15553
5 6 22541
8 6 62008
3 4 62009
5 7 53337
5 3 12157
4 6 10112
1 5 22574
3 7 28993
4 7 53536
6 1 951
4 2 31411
7 8 31020
10
7 5
5 4
4 2
7 2
3 4
1 5
1 5
7 3
6 1
4 1

输出样例:

12157
12157
31411
31411
10112
12157
12157
2843
951
10112
  • 首次思路利用kruskal算法再利用深度优先找到路径,但是超时
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

// 边的结构体,表示图中的一条边
struct edge
{
    int u;  // 边的起点
    int v;  // 边的终点
    int w;  // 边的权重
    edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) { }
};

vector<edge> graph; // 图的所有边
vector<vector<edge>> tree; // 存储生成树的邻接表
vector<int> fa; // 并查集父亲数组,用于判断是否在同一个连通块中

// 比较函数,用于按照边的权重排序
bool cmp(edge A, edge B) { return A.w < B.w; }

// 创建图的函数
void CreatGraph()
{
    int n, m;
    cin >> n >> m; // 输入节点数和边数
    tree.resize(n + 1); // 初始化树的邻接表,大小为n+1
    fa.resize(n + 1); // 初始化并查集,大小为n+1
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w; // 输入每条边的信息:起点、终点、权重
        graph.push_back(edge(a, b, w)); // 将每条边加入图中
    }

    sort(graph.begin(), graph.end(), cmp); // 按照权重对边进行排序
}

// 并查集的查找操作,带路径压缩
int findroot(int x)
{
    return fa[x] == x ? x : fa[x] = findroot(fa[x]);
}

// 并查集的合并操作
void Merge(int x, int y)
{
    x = findroot(x);
    y = findroot(y);
    fa[x] = y; // 将x的根节点指向y
}

// 创建生成树的函数,利用并查集进行kruskal算法
void CreatTree()
{
    // 初始化并查集
    for (int i = 0; i < fa.size(); i++)
    {
        fa[i] = i;
    }

    int nums = 0; // 记录已加入树中的边数

    // 按照权重从小到大遍历所有边
    for (int i = 0; i < graph.size(); i++)
    {
        int u = graph[i].u;
        int v = graph[i].v;
        int x = findroot(u); // 查找u的根
        int y = findroot(v); // 查找v的根
        if (x != y) // 如果u和v不在同一个连通块中
        {
            tree[u].push_back(edge(u, v, graph[i].w)); // 将这条边加入生成树
            tree[v].push_back(edge(v, u, graph[i].w)); // 生成树是无向图,所以要加反向边
            Merge(x, y); // 合并u和v的连通块
            nums++; // 增加已加入树中的边数
        }
        if (nums == fa.size() - 2) // 如果已经加入了n-1条边,生成树完成
        {
            return;
        }
    }
}

// 记录答案,存储最小路径的最大边权
int ans = INT_MIN;
vector<bool> vis; // 记录节点是否被访问
// 深度优先搜索,查找a到b的路径
void dfs(int a, int b, vector<int> path)
{
    vis[a] = true; // 标记当前节点已访问
    for (int i = 0; i < tree[a].size(); i++)
    {
        vector<int> temp = path; // 复制当前路径
        temp.push_back(tree[a][i].w); // 将当前边的权重加入路径
        if (tree[a][i].v == b) // 如果找到终点b
        {
            for (int j = 0; j < temp.size(); j++)
                ans = max(ans, temp[j]); // 更新路径中最大的边权
            break;
        }
        else if (!vis[tree[a][i].v]) // 如果终点还未访问
        {
            dfs(tree[a][i].v, b, temp); // 递归搜索
        }
    }
}

int main()
{
    CreatGraph(); // 创建图
    CreatTree(); // 创建生成树
    int t;
    cin >> t; // 输入查询次数
    for (int i = 0; i < t; i++)
    {
        int a, b;
        cin >> a >> b; // 输入每次查询的起点a和终点b
        ans = INT_MIN; // 初始化答案
        vis.assign(fa.size(), false); // 初始化访问标记数组
        vector<int> path; // 存储当前路径
        dfs(a, b, path); // 深度优先搜索找到a到b的路径
        cout << ans << endl; // 输出查询结果
    }
    system("Pause"); // 暂停程序,等待用户输入
}
  • 紧接着认为是找路径时耗时太多,所以采用每次dfs为尽量多的一对点找到最大边,类似贪心
  • 却发现依旧超时
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

// 边的结构体,表示图中的一条边
struct edge
{
    int u;  // 边的起点
    int v;  // 边的终点
    int w;  // 边的权重
    edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) { }
};

vector<edge> graph; // 存储图的所有边
vector<vector<edge>> tree; // 存储生成树的邻接表
vector<int> fa; // 并查集的父节点数组,用于判断连通性

// 比较函数,用于按照边的权重从小到大排序
bool cmp(edge A, edge B) { return A.w < B.w; }

// 创建图的函数
void CreatGraph()
{
    int n, m;
    cin >> n >> m; // 输入节点数和边数
    tree.resize(n + 1); // 初始化树的邻接表,大小为n+1
    fa.resize(n + 1); // 初始化并查集父节点数组
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w; // 输入每条边的起点、终点和权重
        graph.push_back(edge(a, b, w)); // 将每条边加入图中
    }

    sort(graph.begin(), graph.end(), cmp); // 按照权重对边进行排序
}

// 并查集查找操作,带路径压缩
int findroot(int x)
{
    return fa[x] == x ? x : fa[x] = findroot(fa[x]);
}

// 并查集合并操作,将两个连通块合并
void Merge(int x, int y)
{
    x = findroot(x);
    y = findroot(y);
    fa[x] = y; // 将x的根节点指向y
}

// 使用Kruskal算法构建最小生成树
void CreatTree()
{
    // 初始化并查集
    for (int i = 0; i < fa.size(); i++)
    {
        fa[i] = i;
    }

    int nums = 0; // 记录已加入树中的边数

    // 按照权重从小到大遍历所有边
    for (int i = 0; i < graph.size(); i++)
    {
        int u = graph[i].u;
        int v = graph[i].v;
        int x = findroot(u); // 查找u的根
        int y = findroot(v); // 查找v的根
        if (x != y) // 如果u和v不在同一个连通块中
        {
            tree[u].push_back(edge(u, v, graph[i].w)); // 将这条边加入生成树
            tree[v].push_back(edge(v, u, graph[i].w)); // 生成树是无向图,要加反向边
            Merge(x, y); // 合并u和v的连通块
            nums++; // 增加已加入树中的边数
        }
        if (nums == fa.size() - 2) // 如果已加入n-1条边,生成树完成
        {
            return;
        }
    }
}

// DFS搜索函数,查找从a到b的路径
int ans = INT_MIN; 
vector<bool> vis; // 记录节点是否已访问

// dfs遍历寻找路径上的最大边
void dfs(int a, int b, vector<int> path)
{
    vis[a] = true; // 标记当前节点已访问
    for (int i = 0; i < tree[a].size(); i++)
    {
        vector<int> temp = path; // 复制当前路径
        temp.push_back(tree[a][i].w); // 将当前边的权重加入路径
        if (tree[a][i].v == b) // 如果找到终点b
        {
            for (int j = 0; j < temp.size(); j++)
                ans = max(ans, temp[j]); // 更新路径中最大的边权
        }
        else if (!vis[tree[a][i].v]) // 如果终点还未访问
        {
            dfs(tree[a][i].v, b, temp); // 递归搜索
        }
    }
}

// 存储路径信息的二维数组,用来存储每一对节点之间的最大边权
vector<vector<int>> maxedge;

// 更新路径上经过的每一条边的最大权重
void updata(int v, const vector<pair<int, int>>& path)
{
    int temp = INT_MIN;
    // 从路径末尾开始,更新路径上每条边的最大边权
    for (int i = path.size() - 1; i >= 0; i--)
    {
        temp = max(temp, path[i].second); // 更新最大边权
        maxedge[path[i].first][v] = max(maxedge[path[i].first][v], temp); // 更新最大边权
    }
}

// 新的DFS搜索函数,更新最大边权
void new_dfs(int a, vector<pair<int, int>> path)
{
    vis[a] = true; // 标记当前节点已访问
    updata(a, path); // 更新当前路径上的边的最大边权
    for (int i = 0; i < tree[a].size(); i++)
    {
        int v = tree[a][i].v;
        auto temp = path;
        temp.push_back({a, tree[a][i].w}); // 将当前边加入路径
        if (!vis[v]) new_dfs(v, temp); // 如果未访问过,继续DFS搜索
    }
}

// 初始化函数
void Init()
{
    // 初始化maxedge为最小值
    maxedge.resize(fa.size(), vector<int>(fa.size(), INT_MIN));
    vis.resize(fa.size(), false);
    // 初始化相邻节点之间的最大边权,即相连边的边权
    for (int i = 0; i < tree.size(); i++)
    {
        for (int j = 0; j < tree[i].size(); j++)
        {
            int u = tree[i][j].u;
            int v = tree[i][j].v;
            int w = tree[i][j].w;
            maxedge[u][v] = w; // 记录相邻点的边权
        }
    }
}

int main()
{
    CreatGraph(); // 创建图
    CreatTree(); // 创建最小生成树
    Init(); // 初始化maxedge数组
    int t;
    cin >> t; // 输入查询次数
    for (int i = 0; i < t; i++)
    {
        int a, b;
        cin >> a >> b; // 输入查询的起点a和终点b

        // 比较a到b和b到a的最大边权
        maxedge[a][b] = max(maxedge[a][b], maxedge[b][a]);

        // 如果a到b已经有路径,直接输出最大边权
        if (maxedge[a][b] != INT_MIN)
        {
            cout << maxedge[a][b] << endl;
        }
        else
        {
            // 如果没有直接路径,执行DFS查找路径并更新最大边权
            vis.assign(fa.size(), false); // 重置访问标记
            vector<pair<int, int>> path; // 存储路径
            new_dfs(a, path); // 执行DFS搜索
            maxedge[a][b] = max(maxedge[a][b], maxedge[b][a]); // 比较两种路径的最大边权
            cout << maxedge[a][b] << endl; // 输出最大边权
        }
    }
    system("Pause"); // 暂停程序,等待用户输入
}
  • 然后认为是稠密图应该用邻接矩阵存储图,并且用Prim求出最小生成树
  • 但依旧失败
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

// 边的结构体,表示图中的一条边
struct edge
{
    int v;  // 边的目标节点
    int w;  // 边的权重
    edge(int _v, int _w) : v(_v), w(_w) { }
};

vector<vector<int>> graph; // 存储图的邻接矩阵,表示节点之间的权重
vector<vector<edge>> tree; // 存储最小生成树的邻接表
vector<bool> vis; // 记录节点是否已访问

// 创建图的邻接矩阵
void CreatGraph()
{
    int n, m;
    cin >> n >> m; // 输入图的节点数n和边数m
    graph.resize(n + 1, vector<int>(n + 1, INT_MAX)); // 初始化邻接矩阵为最大整数值,表示没有边
    tree.resize(n + 1); // 初始化树的邻接表

    // 输入每条边的起点、终点和权重,填充邻接矩阵
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        graph[a][b] = w; // 更新邻接矩阵
        graph[b][a] = w; // 因为是无向图,需要更新反向边
    }
}

// 使用Prim算法生成最小生成树
void CreatTree()
{
    int u = 1; // 从节点1开始
    int vexnum = tree.size(); // 获取节点数
    // 存储每个节点的最小边的权重,以及该边的另一端节点
    std::vector<std::pair<int, int>> close_edge(vexnum, { INT_MAX, u });
    std::vector<bool> vis(vexnum, false); // 标记节点是否已访问

    vis[u] = true; // 标记起始节点1为已访问
    // 初始化与节点u相邻的边
    for (int i = 1; i < graph[u].size(); i++)
    {
        close_edge[i].first = graph[u][i];
        close_edge[i].second = u;
    }

    // 获取最小权值边的索引
    auto Min = [&]()->int {
        int minindex = -1;
        int min = INT_MAX;
        for (int i = 1; i < close_edge.size(); i++)
        {
            const auto& vex = close_edge[i];
            // 找到未访问且权重最小的边
            if (!vis[i] && vex.first < min)
            {
                minindex = i;
                min = vex.first;
            }
        }
        return minindex;
    };

    // 生成n-1条边的最小生成树(n=节点数)
    for (int j = 1; j < vexnum - 1; j++)
    {
        int u0 = Min(); // 找到权重最小的边的起点
        int v0 = close_edge[u0].second; // 获取该边的终点
        int w0 = close_edge[u0].first; // 获取该边的权重

        // 将该边加入生成树
        tree[u0].push_back(edge(v0, w0));
        tree[v0].push_back(edge(u0, w0)); // 添加无向边

        vis[u0] = true; // 标记该节点为已访问

        // 更新与u0相邻的节点的最小边
        for (int i = 1; i < graph[u0].size(); i++)
        {
            int temp = graph[u0][i];
            if (temp < close_edge[i].first)
            {
                close_edge[i].first = temp;
                close_edge[i].second = u0;
            }
        }
    }
}

// 存储每对节点间的最大边权
vector<vector<int>> maxedge;

// 更新路径上的每一条边的最大权重
void updata(int v, const vector<pair<int, int>>& path)
{
    int temp = INT_MIN;
    // 从路径的末尾开始更新每个边的最大边权
    for (int i = path.size() - 1; i >= 0; i--)
    {
        temp = max(temp, path[i].second); // 更新路径上的最大边权
        maxedge[path[i].first][v] = max(maxedge[path[i].first][v], temp); // 更新最大边权
    }
}

// 新的DFS搜索函数,用于更新路径上每条边的最大边权
void new_dfs(int a, vector<pair<int, int>> path)
{
    vis[a] = true; // 标记当前节点已访问
    updata(a, path); // 更新当前路径上的最大边权
    // 遍历与当前节点相邻的未访问节点
    for (int i = 0; i < tree[a].size(); i++)
    {
        int v = tree[a][i].v;
        auto temp = path;
        temp.push_back({a, tree[a][i].w}); // 将当前边加入路径
        if (!vis[v]) new_dfs(v, temp); // 如果未访问,继续DFS搜索
    }
}

// 初始化maxedge数组
void Init()
{
    // 初始化maxedge为最小值
    maxedge.resize(tree.size(), vector<int>(tree.size(), INT_MIN));
    vis.resize(tree.size(), false);

    // 初始化与每个节点相邻的最大边权
    for (int i = 1; i < tree.size(); i++)
    {
        for (int j = 0; j < tree[i].size(); j++)
        {
            int u = i;
            int v = tree[i][j].v;
            maxedge[u][v] = tree[i][j].w; // 记录每条边的权重
        }
    }
}

int main()
{
    // 优化输入输出
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    CreatGraph(); // 创建图
    CreatTree();  // 创建最小生成树
    Init();       // 初始化maxedge数组

    int t;
    cin >> t; // 输入查询次数

    // 对每个查询进行处理
    for (int i = 0; i < t; i++)
    {
        int a, b;
        cin >> a >> b; // 输入查询的起点a和终点b

        // 比较a到b和b到a的最大边权
        maxedge[a][b] = max(maxedge[a][b], maxedge[b][a]);

        // 如果a到b已经有路径,直接输出最大边权
        if (maxedge[a][b] != INT_MIN)
        {
            cout << maxedge[a][b] << endl;
        }
        else
        {
            // 如果没有直接路径,执行DFS查找路径并更新最大边权
            vis.assign(tree.size(), false); // 重置访问标记
            vector<pair<int, int>> path;    // 存储路径
            new_dfs(a, path);               // 执行DFS搜索
            maxedge[a][b] = max(maxedge[a][b], maxedge[b][a]); // 比较两条路径的最大边权
            cout << maxedge[a][b] << endl;  // 输出最大边权
        }
    }

    return 0;
}
  • 最后发现查询次数过少,不需要dfs时为其他顶点对找到最大边,只需要朴素的方法找到最大边
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

// 边的结构体,表示图中的一条边
struct edge
{
    int v;  // 目标节点
    int w;  // 边的权重
    edge(int _v, int _w) : v(_v), w(_w) { }
};

vector<vector<int>> graph; // 图的邻接矩阵,存储节点之间的边权
vector<vector<edge>> tree; // 存储最小生成树的邻接表
vector<bool> vis; // 访问标记,记录节点是否被访问过

// 创建图的邻接矩阵
void CreatGraph()
{
    int n, m;
    cin >> n >> m; // 输入图的节点数n和边数m
    graph.resize(n + 1, vector<int>(n + 1, INT_MAX)); // 初始化邻接矩阵为最大整数值,表示没有边
    tree.resize(n + 1); // 初始化树的邻接表

    // 输入每条边的起点、终点和权重,并填充邻接矩阵
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        graph[a][b] = w; // 更新邻接矩阵的正向边
        graph[b][a] = w; // 更新邻接矩阵的反向边(因为是无向图)
    }
}

// 使用Prim算法生成最小生成树
void CreatTree()
{
    int u = 1; // 从节点1开始
    int vexnum = tree.size(); // 获取节点数
    // first存储最小边的权值,second存储该边的起始节点
    std::vector<std::pair<int, int>> close_edge(vexnum, { INT_MAX, u });
    std::vector<bool> vis(vexnum, false); // 标记节点是否已访问

    vis[u] = true; // 标记起始节点1为已访问
    // 初始化与节点u相邻的边
    for (int i = 1; i < graph[u].size(); i++)
    {
        close_edge[i].first = graph[u][i];
        close_edge[i].second = u;
    }

    // 获取最小权值边的索引
    auto Min = [&]()->int {
        int minindex = -1;
        int min = INT_MAX;
        for (int i = 1; i < close_edge.size(); i++)
        {
            const auto& vex = close_edge[i];
            // 找到未访问且权重最小的边
            if (!vis[i] && vex.first < min)
            {
                minindex = i;
                min = vex.first;
            }
        }
        return minindex;
    };

    // 生成最小生成树,选择n-1条边(n=节点数)
    for (int j = 1; j < vexnum - 1; j++)
    {
        int u0 = Min(); // 找到权重最小的边的起点
        int v0 = close_edge[u0].second; // 获取该边的终点
        int w0 = close_edge[u0].first; // 获取该边的权重

        // 将该边加入生成树
        tree[u0].push_back(edge(v0, w0));
        tree[v0].push_back(edge(u0, w0)); // 因为是无向图,加入反向边

        vis[u0] = true; // 标记该节点为已访问

        // 更新与u0相邻的节点的最小边
        for (int i = 1; i < graph[u0].size(); i++)
        {
            int temp = graph[u0][i];
            if (temp < close_edge[i].first)
            {
                close_edge[i].first = temp;
                close_edge[i].second = u0;
            }
        }
    }
}

// 用于存储最大边权的变量
int ans = INT_MIN;

// 深度优先搜索(DFS)查找从节点a到节点b的路径上的最大边权
void dfs(int a, int b, int nowmax)
{
    vis[a] = true; // 标记当前节点已访问
    // 遍历当前节点a的邻接节点
    for (int i = 0; i < tree[a].size(); i++)
    {
        int temp = max(nowmax, tree[a][i].w); // 当前路径的最大边权
        if (tree[a][i].v == b)
        {
            // 找到目标节点b,更新最大边权
            ans = max(ans, temp);
        }
        else if (!vis[tree[a][i].v])
        {
            // 如果目标节点没有访问过,继续深度搜索
            dfs(tree[a][i].v, b, temp);
        }
    }
}

int main()
{
    // 优化输入输出,提高程序效率
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    CreatGraph(); // 创建图的邻接矩阵
    CreatTree();  // 使用Prim算法创建最小生成树
    vis.resize(graph.size(), false); // 初始化访问标记

    int t;
    cin >> t; // 输入查询次数

    // 对每个查询进行处理
    for (int i = 0; i < t; i++)
    {
        int a, b;
        cin >> a >> b; // 输入起点a和终点b

        vis.assign(vis.size(), false); // 重置访问标记
        ans = INT_MIN; // 重置答案
        dfs(a, b, INT_MIN); // 执行DFS查找最大边权

        cout << ans << endl; // 输出结果
    }

    return 0;
}
  • 题解办法:
  • 在建立最小支撑树时找到最大边

img

因为加入树的那条边即其他点到新点的必经之路,每次根据新找到的边更新其他最大边即可

#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

// 边结构体,表示图中的边:v 表示目标顶点,w 表示边的权重
struct edge {
    int v, w;
    edge(int _v, int _w) : v(_v), w(_w) { }
};

// 邻接矩阵表示图
vector<vector<int>> graph;
// 记录顶点是否被访问过
vector<bool> vis;

// 最小生成树中任意两点之间路径的最大边权值
vector<vector<int>> maxedge;

// 创建图:读取顶点数、边数,并构建邻接矩阵
void CreatGraph() {
    int n, m;
    cin >> n >> m;  // 输入顶点数 n 和边数 m
    graph.resize(n + 1, vector<int>(n + 1, INT_MAX));  // 初始化邻接矩阵,所有边的初始权重设为 INT_MAX (无穷大)
    maxedge.resize(n + 1, vector<int>(n + 1, INT_MIN));  // 初始化 maxedge 矩阵,表示最小生成树中任意两点之间的最大边权重

    // 读取所有边的信息,并更新邻接矩阵
    for (int i = 0; i < m; i++) {
        int a, b, w;
        cin >> a >> b >> w;  // 输入边的两个端点和边的权重
        graph[a][b] = w;
        graph[b][a] = w;  // 无向图,边是双向的
    }
}

// 使用 Prim 算法生成最小生成树,并计算最小生成树中任意两点间的最大边
void CreatTree() {
    int u = 1;  // 从顶点 1 开始生成最小生成树
    int vexnum = graph.size();  // 获取顶点数
    // close_edge[i] 用来存储当前最小边的信息,first 存储最小边的权重,second 存储最小边的另一端点
    vector<pair<int, int>> close_edge(vexnum, { INT_MAX, u });

    // 标记各顶点是否被访问过
    vis.resize(vexnum, false);
    close_edge[u].first = 0;  // 从 u 顶点开始,初始化 close_edge[u] 的边权为 0
    vis[u] = true;  // 标记 u 顶点已访问

    // 初始化 close_edge 数组,填充与 u 相邻的边的信息
    for (int i = 1; i < vexnum; i++) {
        if (graph[u][i] != INT_MAX) {  // 只更新与 u 相连的边
            close_edge[i].first = graph[u][i];
            close_edge[i].second = u;
        }
    }

    // 用 lambda 表达式来找到当前权值最小的未访问的顶点
    auto Min = [&]() -> int {
        int minindex = -1;
        int min = INT_MAX;
        for (int i = 1; i < vexnum; i++) {
            // 找到未访问的顶点,并且该顶点的边权最小
            if (!vis[i] && close_edge[i].first < min) {
                minindex = i;
                min = close_edge[i].first;
            }
        }
        return minindex;  // 返回最小边权对应的顶点
    };

    // 构造最小生成树,选择剩余的 n-1 个顶点,生成 n-1 条边
    for (int j = 1; j < vexnum - 1; j++) {
        // 获取当前最小权值的边对应的顶点
        int u0 = Min();
        int v0 = close_edge[u0].second;
        int w0 = close_edge[u0].first;

        // 将边加入最小生成树,并更新 maxedge
        maxedge[v0][u0] = maxedge[u0][v0] = w0;

        // 更新与 u0 相邻的顶点的信息,记录路径中的最大边权
        for (int i = 1; i < vexnum; i++) {
            if (vis[i]) {  // 如果顶点 i 已访问,更新 maxedge
                maxedge[i][u0] = maxedge[u0][i] = max(w0, maxedge[i][v0]);
            }
        }

        vis[u0] = true;  // 标记 u0 顶点为已访问

        // 更新与 u0 相邻的所有未访问顶点的 close_edge 信息
        for (int i = 1; i < vexnum; i++) {
            int temp = graph[u0][i];
            if (temp < close_edge[i].first && temp != INT_MAX) {  // 更新最小边信息
                close_edge[i].first = temp;
                close_edge[i].second = u0;
            }
        }
    }
}

// 主函数,处理输入和输出
int main() {
    ios::sync_with_stdio(false);  // 提高输入输出效率
    cin.tie(nullptr);  // 解除 cin 和 cout 的同步

    CreatGraph();  // 创建图
    CreatTree();   // 创建最小生成树

    int t;
    cin >> t;  // 输入查询次数
    for (int i = 0; i < t; i++) {
        int a, b;
        cin >> a >> b;  // 输入查询的两个顶点
        // 输出最小生成树中 a 到 b 路径中的最大边权
        cout << maxedge[a][b] << endl;
    }
}

©OZY all right reserved该文件修订时间: 2026-06-25 14:57:48

评论区 - 计算机23级数据结构上级实验(第8周)

results matching ""

    No results matching ""