数据结构:以Kruskal为例判断最小支撑树是否唯一

文章目录

  • 一、证明思路
      • 1. 权重的唯一性
      • 2. 权重不唯一时的多样性
      • 3. 形式化证明
      • 4. 图的特性和边的可替换性
  • 二、简单算法实现

数据结构:最小支撑树
在图论中,使用 Kruskal 算法构建最小生成树 (MST) 时,是否存在多个不同的最小生成树取决于图中边的权重。如果所有边的权重都是唯一的,那么最小生成树也将是唯一的。然而,如果存在具有相同权重的边,并且这些边可以互换而不改变整体的最小权重,那么可能存在多个最小生成树。

一、证明思路

以下是证明图中可能存在多个最小生成树的思路:

1. 权重的唯一性

如果图中所有边的权重都是唯一的,那么任何时候选择下一条边添加到树中时都只有一个最优选择。这意味着构建过程中没有任何决策分支,从而保证了最小生成树的唯一性。

2. 权重不唯一时的多样性

如果存在多条具有相同最小权重的边,且它们连接的顶点不会形成环,那么选择不同的边将可能导致构建不同的最小生成树。在这种情况下,每个选择都可以导致不同的树结构,而总权重仍然相同。

3. 形式化证明

要形式化证明可能存在多个最小生成树,可以采用以下步骤:

  • 定义:假设一个连通图 G = ( V , E ) G = (V, E) G=(V,E),其边 E E E 中存在权重相等的边。假设 e 1 e_1 e1 e 2 e_2 e2 是权重相等的边,且它们连接不同的顶点对。

  • 构建MST:使用 Kruskal 算法开始构建 MST。当到达选择 e 1 e_1 e1 e 2 e_2 e2的步骤时,两条边都可选,因为选择任意一条都不会与已选择的边形成环,且权重相同。

  • 分叉:选择 e 1 e_1 e1 后继续构建 MST,完成后得到 T 1 T_1 T1。重置,再选择 e 2 e_2 e2,继续构建另一个 MST,得到 T 2 T_2 T2

  • 比较:如果 T 1 T_1 T1 T 2 T_2 T2 在结构上不同,这证明了存在多个最小生成树。

4. 图的特性和边的可替换性

在实际操作中,可以通过检查图中边权重的分布来预判是否可能存在多个 MST。如果能够在不违反最小权重总和的前提下替换边,则表明存在多个 MST。

总结来说,Kruskal 算法在存在权重相同且可交换的边时,可能会构建出结构不同但权重相等的多个最小生成树。这种情况下,具体的 MST 可能依赖于算法处理边的顺序。

二、简单算法实现

要判断Kruskal算法下,最小支撑树是否相同:

  • 我们在最小支撑树中加入一条边e,使得最小支撑树形成一个环,如果环中存在一条其他的边和e的权值相同,则最小支撑树不唯一。因为删掉原来最小支撑树环中和e权值相同的一条边,将e添加进去,则仍然构成一个最小支撑树,并且和原来的最小支撑树不同。
    • 我们可以发现,只有具有相同权值的非最小支撑树中的边才可能形成和原最小支撑树不同的最小支撑树。我们可以在最小支撑树中加入它来判断形成的环中是否和它有相同权值的边,来判断是否有多种最小支撑树。但这样实现非常复杂,我们可以考虑删除原来最小支撑树中与 非最小支撑树边集中的某条边 权值相同的 边(标记即可),来判断原边集删除该边后能否生成相同权值的最小支撑树。
  • 为了方便,我们直接在最小支撑树边集合里面标记!然后下一次生成最小支撑树时再忽略该边。如果能生成同样权重的最小生成树则表示最小生成树不唯一。

为何使用tuple类型而不使用结构体,在C++STL——tuple中有讲到。

#include<bits/stdc++.h>
using namespace std;
class UnionFind {
public:
    UnionFind(int c): n(c) {
        parent.resize(c);
        rank.resize(c, 0); // 初始化秩数组
        for (int i = 0; i < c; ++i)
            parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x)
            parent[x] = find(parent[x]); // 路径压缩
        return parent[x];
    }
    void Union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            // 按秩合并
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX] += 1; // 如果秩相同,则任选一个作为根,并增加其秩
            }
            minus();
        }
    }

    int n;
private:
    vector<int> parent;
    vector<int> rank; // 秩
    void minus() { --n; } // 减少连通分量的数量
};
int ans = INT_MAX;
int N,M;
tuple<int,int,int> flag_Node;
bool flag = false;
bool Kruskal(vector<tuple<int,int,int>> & edge,set<tuple<int,int,int>> & st){
    UnionFind uf(N);
    int weight = 0;
    for(auto & i : edge){
        if(!flag || i != flag_Node)
            if(uf.find(get<1>(i)) != uf.find(get<2>(i))){
                uf.Union(get<1>(i),get<2>(i));
                weight += get<0>(i);
                if(!flag)//还没有标记则说明是第一次生成时期
                    st.insert(i);
            }
    }
    if(uf.n == 1 && weight == ans) return false;//表示存在多种最小支撑树

    if(uf.n == 1 && !flag) ans = min(ans,weight);//求得最小支撑树的权重

    if(uf.n != 1 && !flag) {//没有连通,并且是第一次生成。
        cout << "No MST" << endl;
        cout << uf.n;
        return false;
    }
    return true;
}
int main(void){
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cin >> N >> M;

    vector<tuple<int,int,int>> edge;
    for(int i = 0;i < M;++i){
        int v1,v2,weight;
        cin >> v1 >> v2 >> weight;
        edge.push_back({weight,v1-1,v2-1});
    }

    //排序边权
    sort(edge.begin(),edge.end());

    set<tuple<int,int,int>> st;//保存最小生成树的边
    if(!Kruskal(edge, st)) {//生成最小支撑树
        return 0;
    }
    cout << ans <<'\n';
    flag = true;//开始判断是否存在最小支撑树
    for(auto & i : st){
        flag_Node = i;//标记该边,之后生成最小支撑树不考虑该边。
        if(!Kruskal(edge,st)){//复用一下
            cout << "No";
            return 0;
        }
    }
    cout << "Yes";
    return 0;
}

清晰版:

#include<bits/stdc++.h>
using namespace std;
class UnionFind {
public:
    UnionFind(int c): n(c) {
        parent.resize(c);
        rank.resize(c, 0); // 初始化秩数组
        for (int i = 0; i < c; ++i)
            parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x)
            parent[x] = find(parent[x]); // 路径压缩
        return parent[x];
    }
    void Union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            // 按秩合并
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX] += 1; // 如果秩相同,则任选一个作为根,并增加其秩
            }
            minus();
        }
    }
    int n;
private:
    vector<int> parent;
    vector<int> rank; // 秩
    void minus() { --n; } // 减少连通分量的数量
};
int ans = INT_MAX;
int N,M;
tuple<int,int,int> flag_Node;
bool Kruskal(vector<tuple<int,int,int>> & edge,set<tuple<int,int,int>> & st){
    UnionFind uf(N);
    int weight = 0;
    for(auto & i : edge){
        if(uf.find(get<1>(i)) != uf.find(get<2>(i))){
            uf.Union(get<1>(i),get<2>(i));
            weight += get<0>(i);
            st.insert(i);
        }
    }
    ans = weight;
    if(uf.n != 1) {//没有连通,并且是第一次生成。
        cout << "No MST" << endl;
        cout << uf.n;
        return false;
    }
    return true;
}
bool unique(vector<tuple<int,int,int>> & edge,set<tuple<int,int,int>> & st){
    UnionFind uf(N);
    int weight = 0;
    uf.n = N;
    for(auto & i : edge){
        if(i != flag_Node)
            if(uf.find(get<1>(i)) != uf.find(get<2>(i))){
                uf.Union(get<1>(i),get<2>(i));
                weight += get<0>(i);
            }
        cout << uf.n<<' ';
    }
    if(uf.n == 1 && weight == ans) return false;//表示存在多种最小支撑树
    return true;
}
int main(void){
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cin >> N >> M;

    vector<tuple<int,int,int>> edge;
    for(int i = 0;i < M;++i){
        int v1,v2,weight;
        cin >> v1 >> v2 >> weight;
        edge.push_back({weight,v1-1,v2-1});
    }

    //排序边权
    sort(edge.begin(),edge.end());

    set<tuple<int,int,int>> st;//保存最小生成树的边
    if(!Kruskal(edge, st)) {//生成最小支撑树
        return 0;
    }
    cout << ans <<'\n';
    for(auto & i : st){
        flag_Node = i;//标记该边,之后生成最小支撑树不考虑该边。
        if(!unique(edge,st)){
            cout << "Yes";
            return 0;
        }
    }
    cout << "No";
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

09_电子设计教程基础篇(电阻)

文章目录 前言一、电阻原理二、电阻种类1.固定电阻1、材料工艺1、线绕电阻2、非线绕电阻1、实心电阻1、有机实心电阻2、无机实心电阻 2、薄膜电阻&#xff08;常用&#xff09;1、碳膜电阻2、合成碳膜电阻3、金属膜电阻4、金属氧化膜电阻5、玻璃釉膜电阻 3、厚膜电阻&#xff0…

segformer部分错误

亲测有用 1、TypeError: FormatCode() got an unexpected keyword argument ‘verify‘ mmcv中出现TypeError: FormatCode() got an unexpected keyword argument ‘verify‘-CSDN博客 pip install yapf0.40.0 2、“EncoderDecoder: ‘mit_b1 is not in the backbone regist…

达梦数据库导入数据问题

进行数据导入的时候遇到了导入数据问题 第一个问题&#xff1a; 该工具不能解析此文件&#xff0c;请使用更高版本的工具 这个是因为版本有点低&#xff0c;需要下载最新的达梦数据库 第二个问题&#xff1a; &#xff08;1&#xff09;本地编码&#xff1a;PG_GBK, 导入文…

美特CRM upload.jsp 文件上传致RCE漏洞复现(CNVD-2023-06971)

0x01 产品简介 MetaCRM是一款智能平台化CRM软件,通过提升企业管理和协同办公,全面提高企业管理水平和运营效率,帮助企业实现卓越管理。美特软件开创性地在CRM领域中引入用户级产品平台MetaCRM V5/V6,多年来一直在持续地为客户创造价值,大幅提升了用户需求满足度与使用的满意…

21 内核开发-临界区及临界区代码段判断

内核开发-临界区判断 目录 内核开发-临界区判断 1.定义 2.临界区实现机制 3.使用互斥锁实现临界区的示例 4.怎么识别是临界区代码 5.总结 1.定义 临界区是计算机系统中的一段代码&#xff0c;在任何时刻只能被一个线程执行。临界区的目的是防止多个线程同时访问共享资源…

Make3D数据集相关介绍

一、参考资料 Make3d数据集使用方法 二、相关介绍 1. 简介 Make3D 数据集的每帧图像的深度值均由激光雷达进行采集&#xff0c;相较于 Kinect 相机采集的深度信息&#xff0c;该测距仪可以得到室外图像更加精确的深度信息&#xff0c;而且测距范围更大&#xff0c;与普通的…

【stm32笔记】DSP库调用

参考&#xff1a;DSP库调用 , __CC_ARM,__TARGET_FPU_VFP, __FPU_PRESENT1U, ARM_MATH_CM4把需要的库复制出来单独用&#xff0c;方便移植

websevere服务器从零搭建到上线(三)|IO多路复用小总结和服务器的基础框架

文章目录 epollselect和poll的优缺点epoll的原理以及优势epoll 好的网络服务器设计Reactor模型图解Reactor muduo库的Multiple Reactors模型 epoll select和poll的优缺点 1、单个进程能够监视的文件描述符的数量存在最大限制&#xff0c;通常是1024&#xff0c;当然可以更改数…

什么是X电容和Y电容?

先补充个知识&#xff1a; 一、什么是差模信号和共模信号 差模信号&#xff1a;大小相等&#xff0c;方向相反的交流信号&#xff1b;双端输入时&#xff0c;两个信号的相位相差180度 共模信号&#xff1a;大小相等。方向相同。双端输入时&#xff0c;两个信号相同。 二、安规…

小程序如何重启

用户在使用小程序的过程中&#xff0c;有时候会碰到一些问题。比如小程序数据不加载、卡顿、崩溃或者出现其他异常情况。这时候&#xff0c;最简单的办法就是重启小程序。但是很多客户不知道如何重启小程序&#xff0c;下面就具体介绍小程序重新启动的几种方法。 1. 强制关闭&…

CWDM、DWDM、MWDM、LWDM:快速了解光波复用技术

在现代光纤通信领域&#xff0c;波分复用&#xff08;WDM&#xff09;技术作为一项先进的创新脱颖而出。它通过将多个不同波长和速率的光信号汇聚到一根光纤中来有效地传输数据。本文将深入探讨几种关键的 WDM 技术&#xff08;CWDM、DWDM、MWDM 和 LWDM&#xff09;&#xff0…

流量分析。

流量分析 在Wireshak抓包可以看到正常的执行流程如下&#xff1a; ● Client向Server发起Load data local infile请求 ● Server返回需要读取的文件路径 ● Client读取文件内容并发送给Server ● PS&#xff1a;在本机上启动服务端与客户端&#xff0c;启动wireshark 抓包&…

根据相同的key 取出数组中最后一个值

数组中有很多对象 , 需根据当前页面的值current 和 数组中的key对比 拿到返回值 数据结构如下 之前写法 const clickedItem routeList.find(item > item.key current) // current是当前页 用reduce遍历数组返回最后一个值 const clickedItem routeList.reduce((lastIte…

41.乐理基础-拍号-小节、小节线、终止线

小节线&#xff1a;下图红框中的竖线就是小节线 小节、终止线&#xff1a;最后的终止线就是文字意思表示乐谱结束了&#xff0c;后面没有了 下图中 0.5表示0.5拍&#xff08;八分音符&#xff09;、1表示1拍&#xff08;四分音符&#xff09;、0.25表示0.25拍&#xff08;十六分…

学习Rust的第29天: cat in Rust

今天即将是这个系列的最后一次内容&#xff0c;我们正在catRust 中从 GNU 核心实用程序进行重建。cat用于将文件内容打印到STDOUT.听起来很容易构建&#xff0c;所以让我们开始吧。 GitHub 存储库&#xff1a;GitHub - shafinmurani/gnu-core-utils-rust 伪代码 function read(…

Transformer详解:从放弃到入门(一)

Transformer由论文《Attention is All You Need》提出&#xff0c;是一种用于自然语言处理&#xff08;NLP&#xff09;和其他序列到序列&#xff08;sequence-to-sequence&#xff09;任务的深度学习模型架构&#xff0c;在自然语言处理领域获得了巨大的成功&#xff0c;在这个…

免费开源线上线下交友社交圈子系统 小程序+APP+H5 可支持二开!

为什么要玩社交软件&#xff1a;互联网社交软件的独特优势 首先&#xff0c;社交软件为我们提供了一个便捷的沟通方式。在传统的交往方式中&#xff0c;人们需要面对面交流&#xff0c;这种方式在时间和空间上都受到限制。而社交软件打破了这些限制&#xff0c;无论我们身处何地…

如何复制本地docker镜像到其他主机

&#xff08;1&#xff09;打包镜像 比如我要复制的镜像是grafana的镜像 docker images 这里我把打包的镜像放在了根~目录下&#xff0c;如截图所示&#xff1a; docker save grafana/grafana:latest -o ~/grafana.jar &#xff08;2&#xff09;移动镜像 scp命令拷贝镜像到目标…

linux学习:线程池

目录 原理 初始线程池 运行中的线程池 相关结构体 api 线程池初始化 投送任务 增加活跃线程 删除活跃线程 销毁线程池 例子 thread_pool.h thread_pool.c test.c 测试程序 原理 一个进程中的线程就好比是一家公司里的员工&#xff0c;员工的数目应该根据公司的…

AI神助攻!小白也能制作自动重命名工具~

我们平时从网上下载一些文件&#xff0c;文件名很多都是一大串字母和数字&#xff0c;不打开看看&#xff0c;根本不知道里面是什么内容。 我想能不能做个工具&#xff0c;把我们一个文件夹下面的所有word、excel、ppt、pdf文件重命名为文件内容的第一行。 我们有些朋友可能不会…
最新文章