代码随想录Day17 图论-3

news2024/11/18 17:50:59

并查集理论基础

学习并查集 我们就要知道并查集可以解决什么问题

并查集主要有两个功能:

  • 将两个元素添加到一个集合中
  • 判断两个元素在不在同一个集合

 

以下是代码模板 

int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构

// 并查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}
// 并查集里寻根的过程
int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 将v->u 这条边加入并查集
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

在并查集中find是寻找根的操作 

如下图第二步 8的根是1 3的根就是3 所以加入之后由1指向3

107. 寻找存在的路径

 本题就是判断是否存在路径由source到destinaion 在无向图当中 我们只要判断这两个节点在一个集合里面就说明他们是可以相互到达的 所以我们就可以用到并查集的知识

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

int n;
vector<int> father = vector<int>(101,0);

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

int find(int u){
    return u==father[u] ? u : father[u]=find(father[u]);
}

bool isSame(int u,int v){
    u=find(u);
    v=find(v);
    return u==v;
}

void join(int u,int v){
    u=find(u);
    v=find(v);
    if(u==v) return ;
    father[v]=u;
}

int main(){
    int M,s,t,a,b;
    cin>>n>>M;
    init();
    while(M--){
        cin>>s>>t;
        join(s,t);
    }
    cin>>a>>b;
    if(isSame(a,b)) cout<<1<<endl;
    else cout<<0<<endl;
}

108. 冗余连接

这一题就是给你一棵树 然后让你删掉一条边 使其变成合法的树 要求如果有多个答案 需要我们输出最后出现的那条边

如果要加入树的两个节点 已经在一个集合里面了 然后再相连的话 就会产生环 所以我们可以从头遍历 然后只要这两个节点不在同一个集合 我们就让他们加入 否则就可以直接打印答案

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

int n;
vector<int> father = vector<int> (1005,0);

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

int find(int u){
    return u==father[u] ? u : father[u]=find(father[u]);
}

bool isSame(int u,int v){
    u=find(u);
    v=find(v);
    return u==v;
}

void join(int u,int v){
    u=find(u);
    v=find(v);
    if(u==v) return ;
    father[v]=u;
}

int main(){
    int s,t;
    cin>>n;
    init();
    for(int i=0;i<n;i++){
        cin>>s>>t;
        if(isSame(s,t)){
            cout<<s<<" "<<t<<endl;
            return 0;
        }else{
            join(s,t);
        }
    }
}

109. 冗余连接II

这一题变成了有向图 

我们要想到有向树的性质 有向树是只有根节点的入度为0 其他节点的入度都是1 然后我们删除的边 肯定是造成节点入度为2的 如果没有入度为2的节点 那肯定就是有环 我们只需要删除环的一个边可以了

处理入度为2的节点的情况是 分两个边来出来 主要逻辑是 遍历的时候如果遍历到了要删除的节点 就不做任何处理 然后如果最后没有形成环 说明删除该边是对的 反之就删除另一个边

处理有环的情况就是一旦加入树后 就形成了环 那么加入的一条边 就是我们要删除的那个边

#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> father (1001, 0);
// 并查集初始化
void init() {
    for (int i = 1; i <= n; ++i) {
        father[i] = i;
    }
}
// 并查集里寻根的过程
int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]);
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
    u = find(u);
    v = find(v);
    if (u == v) return ;
    father[v] = u;
}
// 判断 u 和 v是否找到同一个根
bool same(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 在有向图里找到删除的那条边,使其变成树
void getRemoveEdge(const vector<vector<int>>& edges) {
    init(); // 初始化并查集
    for (int i = 0; i < n; i++) { // 遍历所有的边
        if (same(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
            cout << edges[i][0] << " " << edges[i][1];
            return;
        } else {
            join(edges[i][0], edges[i][1]);
        }
    }
}

// 删一条边之后判断是不是树
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
    init(); // 初始化并查集
    for (int i = 0; i < n; i++) {
        if (i == deleteEdge) continue;
        if (same(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树
            return false;
        }
        join(edges[i][0], edges[i][1]);
    }
    return true;
}

int main() {
    int s, t;
    vector<vector<int>> edges;
    cin >> n;
    vector<int> inDegree(n + 1, 0); // 记录节点入度
    for (int i = 0; i < n; i++) {
        cin >> s >> t;
        inDegree[t]++;
        edges.push_back({s, t});
    }

    vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
    // 找入度为2的节点所对应的边,注意要倒序,因为优先删除最后出现的一条边
    for (int i = n - 1; i >= 0; i--) {
        if (inDegree[edges[i][1]] == 2) {
            vec.push_back(i);
        }
    }
    // 情况一、情况二
    if (vec.size() > 0) {
        // 放在vec里的边已经按照倒叙放的,所以这里就优先删vec[0]这条边
        if (isTreeAfterRemoveEdge(edges, vec[0])) {
            cout << edges[vec[0]][0] << " " << edges[vec[0]][1];
        } else {
            cout << edges[vec[1]][0] << " " << edges[vec[1]][1];
        }
        return 0;
    }

    // 处理情况三
    // 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
    getRemoveEdge(edges);
}

prim算法

题意很简单 就是要我们求一个最小路径 联通所有节点

prim算法有三部曲 

1.我们要找到距离最小生成树最近的那个节点

2.将该节点加入到最小生成树种

3.更新minDest数组

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

int main(){
    int v,e;
    int s,t,k;
    cin>>v>>e;
    vector<vector<int>> grid(v+1,vector<int>(v+1,10001));
    while(e--){
        cin>>s>>t>>k;
        grid[s][t]=k;
        grid[t][s]=k;
    }
    //所有节点距离最小生成树的距离
    vector<int> minDist(v+1,10001);
    vector<bool> isIntree(v+1,false);
    for(int i=1;i<v;i++){
        //prim三部曲 第一步 选取距离最小生成树最近的节点
        int cur=-1;
        int minVal=INT_MAX; //记录最小的距离
        for(int j=1;j<=v;j++){
            if(!isIntree[j] && minVal>minDist[j]){
                minVal=minDist[j];
                cur=j;
            }
        }
        
        //prim三部曲 第二步 将最近的节点加入生成树
        isIntree[cur]=true;
        
        //prim三部曲 第三步 更新minDist数组
        for(int j=1;j<=v;j++){
            if(!isIntree[j] && minDist[j]>grid[cur][j]){
                minDist[j]=grid[cur][j];
            }
        }
    }
    int ans=0;
    for(int i=2;i<=v;i++){
        ans+=minDist[i];
    }
    cout<<ans<<endl;
}

kruskal算法

kruskal算法也可以解决上一道题 不过该算法是处理边的 prim算法是处理点的

kruskal算法的思路是 我们先把所有边的权值从小到大排序 然后从头开始遍历边 利用并查集 我们要判断 边的两个节点是不是在一个集合 找出两个节点的祖先 如果不在同一个集合 就可以作为最小生成树的边 然后加入到同一个集合

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// l,r为 边两边的节点,val为边的数值
struct Edge {
    int l, r, val;
};

// 节点数量
int n = 10001;
// 并查集标记节点关系的数组
vector<int> father(n, -1); // 节点编号是从1开始的,n要大一些

// 并查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}

// 并查集的查找操作
int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}

// 并查集的加入集合
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

int main() {

    int v, e;
    int v1, v2, val;
    vector<Edge> edges;
    int result_val = 0;
    cin >> v >> e;
    while (e--) {
        cin >> v1 >> v2 >> val;
        edges.push_back({v1, v2, val});
    }

    // 执行Kruskal算法
    // 按边的权值对边进行从小到大排序
    sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
            return a.val < b.val;
    });

    // 并查集初始化
    init();

    // 从头开始遍历边
    for (Edge edge : edges) {
        // 并查集,搜出两个节点的祖先
        int x = find(edge.l);
        int y = find(edge.r);

        // 如果祖先不同,则不在同一个集合
        if (x != y) {
            result_val += edge.val; // 这条边可以作为生成树的边
            join(x, y); // 两个节点加入到同一个集合
        }
    }
    cout << result_val << endl;
    return 0;
}

拓扑排序 

拓扑排序就是打个比方 大学排课,例如 先上A课,才能上B课,上了B课才能上C课,上了A课才能上D课,等等一系列这样的依赖顺序。 问给规划出一条 完整的上课顺序。

概括来说 给出一个有向图 然后让我们把这个有向图转成线性排序 就是拓扑排序 它也可以用来判断一个有向图是否有环

如下图 我们自己模拟 肯定是把0放入结果集 然后1和2都可以放入结果集 我们可以发现 放入结果集的都是没有依赖的 也就是说它的入度是为0的

我们就可以利用邻接表 将0放入结果集之后 遍历邻接表0 把后面所连接的元素的入度要减1 然后减1过后 入度为0 就让它入队

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;

int main(){
    int n,m;
    cin>>n>>m;
    int s,t;
    vector<int> inDegree(n,0);
    unordered_map<int, vector<int>> umap; 
    vector<int> ans;
    while(m--){
        cin>>s>>t;
        umap[s].push_back(t);
        inDegree[t]++;
    }
    queue<int> que;
    for(int i=0;i<n;i++){
        if(inDegree[i]==0){
            que.push(i);
        }
    }
    
    while(que.size()){
        int cur=que.front();
        que.pop();
        ans.push_back(cur);
        vector<int> file=umap[cur];
        if(file.size()){
            for(int i=0;i<file.size();i++){
                inDegree[file[i]]--;
                if(inDegree[file[i]]==0){
                    que.push(file[i]);
                }
            }
        }
    }
    if (ans.size() == n) {
        for (int i = 0; i < n - 1; i++) cout << ans[i] << " ";
        cout << ans[n - 1];
    } else cout << -1 << endl;
}

dijkstra(朴素版) 

该题求小明从起点到终点所花费的最小时间 dijksta算法和prim算法思路很像 也有三部曲

1.找到距离源点最近的节点(注意不是最小生成树)

2.把该节点标记为访问过

3.更新minDest数组

#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main() {
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<vector<int>> grid(n + 1, vector<int>(n + 1, INT_MAX));
    for(int i = 0; i < m; i++){
        cin >> p1 >> p2 >> val;
        grid[p1][p2] = val;
    }

    int start = 1;
    int end = n;

    // 存储从源点到每个节点的最短距离
    std::vector<int> minDist(n + 1, INT_MAX);

    // 记录顶点是否被访问过
    std::vector<bool> visited(n + 1, false);

    minDist[start] = 0;  // 起始点到自身的距离为0

    for (int i = 1; i <= n; i++) { // 遍历所有节点

        int minVal = INT_MAX;
        int cur = 1;

        // 1、选距离源点最近且未访问过的节点
        for (int v = 1; v <= n; ++v) {
            if (!visited[v] && minDist[v] < minVal) {
                minVal = minDist[v];
                cur = v;
            }
        }

        visited[cur] = true;  // 2、标记该节点已被访问

        // 3、第三步,更新非访问节点到源点的距离(即更新minDist数组)
        for (int v = 1; v <= n; v++) {
            if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) {
                minDist[v] = minDist[cur] + grid[cur][v];
            }
        }

    }

    if (minDist[end] == INT_MAX) cout << -1 << endl; // 不能到达终点
    else cout << minDist[end] << endl; // 到达终点最短路径

}

类比一下prim算法

 prim 更新 minDist数组的写法:

for (int j = 1; j <= v; j++) {
    if (!isInTree[j] && grid[cur][j] < minDist[j]) {
        minDist[j] = grid[cur][j];
    }
}

dijkstra 更新 minDist数组的写法:

dijkstra是前一个节点的minDest加上到该节点的权值要小于该节点原本的minDest数组

for (int v = 1; v <= n; v++) {
    if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) {
        minDist[v] = minDist[cur] + grid[cur][v];
    }
}

 以上代码都是我们从节点的角度去考虑的 所以需要我们有两层for循环

dijkstra堆优化版

我们可以用小顶堆来让边权值最小的永远在堆顶 

dijkstra三部曲依然不变 我们取距离源点最近的点就是堆顶权值最小的边 然后标记该点访问过

我们只需要在第三步用到一个for循环来更新一下minDest数组 然后因为cur节点的加入 要让新链接上的边加入队列        

#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <climits>
using namespace std;

class mycomparsion{
public:
    bool operator()(const pair<int,int>& lrs, const pair<int,int>& rhs){
        return lrs.second>rhs.second;
    }
};

struct Edge{
  int to;
  int val;
  Edge(int t,int w):to(t),val(w){}
};

int main(){
    int n,m;
    cin>>n>>m;
    int s,e,v;
    vector<list<Edge>> grid(n+1);
    for(int i=0;i<m;i++){
        cin>>s>>e>>v;
        grid[s].push_back(Edge(e,v));
    }
    int start=1,end=n;
    vector<int> minDest(n+1,INT_MAX);
    vector<bool> visited(n+1,false);
    priority_queue<pair<int,int>, vector<pair<int,int>>, mycomparsion> pq;
    pq.push(pair<int, int>(start,0));
    minDest[start]=0;
    
    while(!pq.empty()){
        pair<int,int> cur=pq.top();
        pq.pop();
        if(visited[cur.first]) continue;
        visited[cur.first]=true;
        for(Edge edge:grid[cur.first]){
            if(!visited[edge.to] && minDest[cur.first]+edge.val<minDest[edge.to]){
                minDest[edge.to]=minDest[cur.first]+edge.val;
                pq.push(pair<int,int>(edge.to,minDest[edge.to]));
            }
        }
    }
    if (minDest[end] == INT_MAX) cout << -1 << endl; // 不能到达终点
    else cout << minDest[end] << endl; // 到达终点最短路径
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2175341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jetlinks物联网平台学习4:http协议设备接入

http协议设备接入 1、创建产品2、配置设备接入方式3、配置网络组件4、上传消息协议5、填写网关信息6、配置http认证信息7、配置物模型8、创建设备9、使用apiPost模拟设备接入9.1、设备上线9.2、设备上报属性9.3、设备事件上报 1、创建产品 创建产品 2、配置设备接入方式 点击…

【Echarts】折线图和柱状图如何从后端动态获取数据?

&#x1f680;个人主页&#xff1a;一颗小谷粒 &#x1f680;所属专栏&#xff1a;Web前端开发 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1.1 前端数据分析 1.2 数据库表分析 1.3 后端数据处理 1.4 前端接收数据 继上一篇文章&…

开源图像降噪算法与项目介绍【持续更新】

Intel Open Image Denoise 介绍&#xff1a;Intel Open Image Denoise&#xff08;OIDN&#xff09;是一个开源库&#xff0c;它提供了一系列高性能、高质量的去噪滤镜&#xff0c;专门用于光线追踪渲染的图像。这个库是Intel Rendering Toolkit的一部分&#xff0c;并且是在宽…

基因功能分析:DAP-seq与H3K4me3 ChIP-seq的协同效应

什么是DAP-Seq&#xff1f; DAP-Seq&#xff0c;即DNA亲和纯化测序技术&#xff0c;是一种创新的基因组分析方法。它通过体外表达转录因子&#xff0c;精确地鉴定转录因子与基因组的结合位点。与传统的ChIP-seq相比&#xff0c;DAP-Seq不受抗体和物种的限制&#xff0c;使得研…

springboot+vue+java校园共享厨房菜谱系统

目录 功能介绍使用说明系统实现截图开发核心技术介绍&#xff1a;开发步骤编译运行核心代码部分展示开发环境需求分析详细视频演示源码获取 功能介绍 用户 首页&#xff1a;展示系统基本信息&#xff0c;包括厨房介绍、使用指南、最新公告等。 登录注册&#xff1a;用户注册账…

【Qt笔记】QFrame控件详解

目录 引言 一、QFrame的基本特性 二、QFrame的常用方法 2.1 边框形状&#xff08;Frame Shape&#xff09; 2.2 阴影样式&#xff08;Frame Shadow&#xff09; 2.3 线条宽度&#xff08;Line Width&#xff09; 2.4 样式表(styleSheet) 三、QFrame的应用场景 四、应用…

906. 超级回文数

1. 题目 906. 超级回文数 2. 解题思路 题目意思很简单&#xff0c;在给定范围中找到所有满足&#xff0c;它本身是回文&#xff0c;且它的平方也是回文的数字个数。 这题需要注意题目给定的范围&#xff0c;后面很有用&#xff1a; 因为回文范围是有限的&#xff0c;那么我…

C++(9.26)

1.什么是虚函数&#xff0c;什么是纯虚函数 虚函数是在基类中声明为virtual的函数&#xff0c;可以被重新定义来实现多态&#xff0c;纯虚函数则是以0结尾的虚函数&#xff0c;是没有实现的虚函数。 2.基类为什么需要虚构析函数 为了防止在后面delete释放空间时导致溢出 数据…

【IPV6从入门到起飞】5-6 IPV6+Home Assistant(ESPHome+ESP-cam)实时监控

5-6 IPV6Home Assistant[ESPHomeESP-cam]实时监控 1、背景2、ESPHome 安装2-1 ESPHome 简述2-2 安装 3、创建ESP32-CAM设备4、编辑yaml配置4-1 找到合适的配置4-2 修改配置4-3 验证配置4-4 编译项目 5、烧录固件6、绑定设备7、效果实现 1、背景 在前面我们已经实现了数据采集与…

【Python游戏开发】扫雷游戏demo

准备步骤 项目开发使用【Mu 编辑器】 1.使用Mu编辑器创建新项目 2.点击【保存】&#xff0c;将项目保存到指定路径中 3.点击【图片】&#xff0c;会打开项目图片存储位置&#xff0c;将所需图片复制粘贴至该文件夹中 游戏编写 1.设置游戏场景 ROWS 15 # 设置行数 COLS …

【面试题】软件测试实习(含答案)

软件测试实习常见面试题&#xff0c;主要是功能测试相关的基础问题 目录 一、软件测试基础 1、介绍一下你最近的项目&#xff0c;以及工作职责 2、软件项目的测试流程? 3、黑盒测试与白盒测试的区别? 4、黑盒测试常见的设计方法?怎么理解等价类方法和边界值方法 1&…

GO语言环境搭建和字符说明

Go的三种安装方式 Go有多种安装方式&#xff0c;你可以选择自己喜欢的。这里我们介绍三种最常见的安装方式&#xff1a; 1.Go源码安装&#xff1a;这是一种标准的软件安装方式。对于经常使用Unix类系统的用户&#xff0c;尤其对于开发者来说&#xff0c;从源码安装是最方便而…

传奇架设教程:传奇登录器公告窗口如何设置?link.htm网页文件制作教程

今天给大家分享传奇登录器公告窗口的设置教程&#xff1a; 我们玩服时可以看到很多服的登录器公告看上去都很高大上&#xff0c;并且窗口比例也非常的合适&#xff0c;但是自己架设起来并没有看到窗口公告&#xff0c;这是为什么呢&#xff1f; 这是因为你的列表没有设置公告…

计算机视觉中的2D变换:用Python让图像‘动’起来

嘿&#xff0c;小伙伴们&#xff01;今天我们要聊的是计算机视觉中的2D变换——那些能让图像‘动’起来的技术。无论你是初学者还是资深开发者&#xff0c;这篇文章都将带你深入了解2D变换&#xff0c;并通过Python代码实战演示。准备好了吗&#xff1f;让我们开始吧&#xff0…

Humans or LLMs as the Judge? A Study on Judgement Bias

文章目录 题目摘要引言相关作品论法官的偏见实验方案结果与讨论欺骗LLM法官结论 题目 人类还是LLMs作为裁判&#xff1f;判断偏差研究 论文地址&#xff1a;https://arxiv.org/pdf/2402.10669 摘要 采用人类和大型语言模型(LLM)作为评估LLM性能的评判者(也称为人类和LLM-as-a…

多车合作自动驾驶框架CoDrivingLLM:基于大语言模型驱动的决策框架

Abstract 目前&#xff0c;全球范围内已开始对联网自动驾驶汽车&#xff08;CAVs&#xff09;进行道路测试&#xff0c;但它们在复杂场景中的安全性和效率表现仍不令人满意。合作驾驶利用CAVs的连接能力&#xff0c;通过协同作用超越个体表现&#xff0c;使其成为在复杂场景中…

MySQL篇(主从复制/读写分离/分库分表)(持续更新迭代)

目录 讲解一&#xff1a;主从复制 一、何为主从复制&#xff1f; 二、主从复制的优点 三、主从复制原理 四、主从复制环境搭建 1. 准备 2. 主库配置 2.1. 修改配置文件 /etc/my.cnf 2.2. 重启MySQL服务器 2.3. 授予主从复制权限 2.4. 查看二进制日志坐标 3. 从库配置…

ubuntu 安装harbor

#安装包 wget https://github.com/goharbor/harbor/releases/download/v2.10.3/harbor-offline-installer-v2.10.3.tgz wget https://github.com/goharbor/harbor/releases/download/v2.10.3/harbor-offline-installer-v2.10.3.tgz.asc#导入签名公钥 gpg --keyserver hkps://ke…

EDM平台大比拼 用户体验与营销效果双重测评

本文评测了ZohoCampaigns、Mailchimp、Sendinblue、AWeber四款EDM平台&#xff0c;分别适合中小企业、多平台集成、多功能集成、初学者等需求。建议企业根据自身规模、技术水平和功能需求选择最适合的平台。 一、Zoho Campaigns 功能概述 Zoho Campaigns是Zoho旗下的一款专注…

每日OJ题_牛客_拼三角_枚举/DFS_C++_Java

目录 牛客_拼三角_枚举/DFS 题目解析 C代码1 C代码2 Java代码 牛客_拼三角_枚举/DFS 拼三角_枚举/DFS 题目解析 简单枚举&#xff0c;不过有很多种枚举方法&#xff0c;这里直接用简单粗暴的枚举方式。 C代码1 #include <iostream> #include <algorithm> …