2024-02-24:用go语言,给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1, 同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti
2024-02-24:用go语言,给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1,
同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti],
表示在 fromi 和 toi 节点之间有一条带权无向边,
最小生成树 (MST) 是给定图中边的一个子集,
它连接了所有节点且没有环,而且这些边的权值和最小。
请你找到给定图中最小生成树的所有关键边和伪关键边。
如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边,
伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。
请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。
输入:n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]。
输出:[[0,1],[2,3,4,5]]。
答案2024-02-24:
来自左程云。
大体过程如下:
1.定义并查集和辅助数组:首先定义并查集的数据结构,包括父节点数组 father、节点大小数组 size、辅助数组 help,以及集合数量 sets。还需要定义边的状态记录数组 status,其中 status[ei] 记录第 ei 条边的状态。
2.初始化并查集:使用 buildUnionSet(n) 函数初始化并查集,将每个节点自成一个集合。
3.构建边数组:使用 buildEdges(e) 函数将输入的边数组 e 转换成包含边信息的二维数组 edges,并按照权值从小到大进行排序。
4.建立图:根据集合编号建立图的相关数据结构,包括链式前向星建图。定义头指针数组 head、边信息数组 info、下一条边指针数组 next,以及边数量 edgeSize。使用 buildGraph(k) 函数初始化这些数组。
5.添加边:使用 addEdge(a, b, ei) 函数向图中添加边,其中 a 和 b 是集合编号,ei 是边的索引。
6.找桥:使用 Tarjan 算法,利用深度优先搜索找到所有的桥。具体实现在函数 tarjan(init, cur, father, ei) 中,其中 init 是起始节点,cur 是当前节点,father 是当前节点的父节点,ei 是当前边的索引。
7.寻找关键边和伪关键边:通过遍历边数组 edges,逐个加入到最小生成树中,并利用并查集判断是否形成环。在每次加入边的过程中,记录是否是关键边或伪关键边。具体过程如下:
初始化起始索引
start = 0。当并查集的集合数量不为 1 时,继续循环。
确定结束索引
end,使得edges[start][3] != edges[end][3]或者end == m。调用
connect(start, end)连接边,构建大团子的图并找到桥。遍历
start到end的边,根据边的状态记录到关键边或伪关键边的数组中。合并集合,更新并查集。
更新
start = end,继续下一轮循环。
8.返回结果:将关键边和伪关键边的数组返回作为结果。
综上所述,总的时间复杂度为 O(m^2 * α(n)),其中 m 是边的数量,n 是节点的数量,α 是阿克曼函数的反函数。总的额外空间复杂度为 O(m + n)。
go完整代码如下:
package main
import (
"fmt"
"sort"
)
const MAXN = 101
const MAXM = 201
// 边状态的记录
// status[ei] = 0,代表ei这个边既不是关键边也不是伪关键边
// status[ei] = 1,代表ei这个边是伪关键边
// status[ei] = 2,代表ei这个边是关键边
var status [MAXM]int
// 并查集相关
var father [MAXN]int
var size [MAXN]int
var help [MAXN]int
var sets int
// 并查集初始化
func buildUnionSet(n int) {
for i := 0; i < n; i++ {
father[i] = i
size[i] = 1
}
sets = n
}
// 并查集向上找代表节点
func find(i int) int {
r := 0
for i != father[i] {
help[r] = i
i = father[i]
r++
}
for r > 0 {
r--
father[help[r]] = i
}
return i
}
// 并查集合并集合
func union(i, j int) {
fi := find(i)
fj := find(j)
if fi != fj {
if size[fi] >= size[fj] {
father[fj] = fi
size[fi] += size[fj]
} else {
father[fi] = fj
size[fj] += size[fi]
}
sets--
}
}
// 边相关
var edges [MAXM][4]int
var m int
func buildEdges(e [][]int) {
for i := 0; i < m; i++ {
edges[i][0] = i
edges[i][1] = e[i][0]
edges[i][2] = e[i][1]
edges[i][3] = e[i][2]
}
sort.Slice(edges[:m], func(i, j int) bool {
return edges[i][3] < edges[j][3]
})
}
// 通过集合编号建图相关
// 链式前向星建图
// 为啥用这玩意儿建图?没啥,就是想秀
var head [MAXN]int
var info [MAXM][3]int
var next [MAXM]int
var edgeSize int
func buildGraph(k int) {
for i := 0; i < k; i++ {
head[i] = -1
edgeSize = 0
}
}
func addEdge(a, b, ei int) {
next[edgeSize] = head[a]
info[edgeSize][0] = ei
info[edgeSize][1] = a
info[edgeSize][2] = b
head[a] = edgeSize
edgeSize++
}
// 哈希表相关
// 一个集合,给一个编号
var id [MAXN]int
// 找桥相关
var dfn [MAXN]int
var low [MAXN]int
var cnt int
func findBridge(k int) {
for i := 0; i < k; i++ {
dfn[i] = 0
low[i] = 0
}
cnt = 0
for init := 0; init < k; init++ {
if dfn[init] == 0 {
tarjan(init, init, -1, -1)
}
}
}
func tarjan(init, cur, father, ei int) {
cnt++
dfn[cur] = cnt
low[cur] = cnt
for i := head[cur]; i != -1; i = next[i] {
edgei := info[i][0]
nodei := info[i][2]
if nodei != father {
if dfn[nodei] == 0 {
tarjan(init, nodei, cur, edgei)
low[cur] = min(low[cur], low[nodei])
} else {
low[cur] = min(low[cur], dfn[nodei])
}
}
}
if low[cur] == dfn[cur] && cur != init {
status[ei] = 2
}
}
func findCriticalAndPseudoCriticalEdges(n int, e [][]int) [][]int {
m = len(e)
buildUnionSet(n)
buildEdges(e)
var bridge []int
var pseudo []int
start := 0
for sets != 1 {
end := start + 1
for end < m && edges[start][3] == edges[end][3] {
end++
}
connect(start, end)
for i := start; i < end; i++ {
ei := edges[i][0]
if status[ei] == 2 {
bridge = append(bridge, ei)
} else if status[ei] == 1 {
pseudo = append(pseudo, ei)
}
union(edges[i][1], edges[i][2])
}
start = end
}
return [][]int{bridge, pseudo}
}
// 大团子,一个集合,缩成一个点
// 当前的边,[start...end)
// 做图!大团子的图,找桥!
func connect(start, end int) {
for i := start; i < end; i++ {
id[find(edges[i][1])] = -1
id[find(edges[i][2])] = -1
}
k := 0
for i := start; i < end; i++ {
if id[find(edges[i][1])] == -1 {
id[find(edges[i][1])] = k
k++
}
if id[find(edges[i][2])] == -1 {
id[find(edges[i][2])] = k
k++
}
}
buildGraph(k)
for i := start; i < end; i++ {
index := edges[i][0]
a := id[find(edges[i][1])]
b := id[find(edges[i][2])]
if a != b {
status[index] = 1
addEdge(a, b, index)
addEdge(b, a, index)
}
}
findBridge(k)
sort.Slice(info[:edgeSize], func(i, j int) bool {
if info[i][1] != info[j][1] {
return info[i][1] < info[j][1]
}
return info[i][2] < info[j][2]
})
right, left := 0, 0
for left < edgeSize {
right = left + 1
for right < edgeSize && info[left][1] == info[right][1] {
right++
}
for i := left + 1; i < right; i++ {
if info[i][2] == info[i-1][2] {
status[info[i][0]] = 1
status[info[i-1][0]] = 1
}
}
left = right
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
n := 5
edges := [][]int{{0, 1, 1}, {1, 2, 1}, {2, 3, 2}, {0, 3, 2}, {0, 4, 3}, {3, 4, 3}, {1, 4, 6}}
result := findCriticalAndPseudoCriticalEdges(n, edges)
fmt.Println(result)
}

2024-02-24:用go语言,给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1, 同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti的更多相关文章
- ROS的安装-> rosdep init /update报错2022.02.24实测有效
ROS的安装-> rosdep init /update报错2022.02.24实测有效 一. 解决rosdep_init问题 正常执行sudo rosdep init会报错,如下: ERR ...
- c语言main函数返回值、参数详解(返回值是必须的,0表示正常退出)
C语言Main函数返回值 main函数的返回值,用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出. 很多人甚至市面上的一些书籍,都 ...
- 动态语言切换(续)-designer中的retranslateUi(带源码)
本站所有文章由本站和原作者保留一切权力,仅在保留本版权信息.原文链接.原文作者的情况下允许转载,转载请勿删改原文内容, 并不得用于商业用途. 谢谢合作.原文链接:动态语言切换(续)-designer中 ...
- 并查集例题02.带权并查集(poj1182)
Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A.现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底 ...
- C 语言学习的第 02 课:C 语言的开发环境
工欲善其事,必先利其器.不知道还是不是记得上一篇文章中说到的,计算机本身是一个数据输入及输出的设备.所以,为了将你大脑中的各种 idea 输入到电脑,且最终生成能够执行的程序,总是要预备点什么的. 通 ...
- Objective-C的对象模型 http://foredoomed.org/blog/2014/02/24/object-modeling-of-objective-c/
Objective-C是一门面向对象,并且在C的基础上加入了Smalltalk式的消息机制而形成的编程语言,它主要被苹果公司用于开发Mac OS X和iOS操作系统.既然Objective-C是面向对 ...
- 让你提前知道软件开发(24):C语言和主要特征的发展史
文章1部分 再了解C语言 C语言的发展历史和主要特点 作为一门众所周知的计算机编程语言,C语言是谁发明的呢?它是怎样演进的?它有何特点?究竟有多少人在使用它? 1. C语言之父 C语言是1972年由美 ...
- 让你提前知道软件开发(24):C语言和主要特征的历史
文章1部分 再次了解C语言 C语言的发展历史和主要特点 作为一门众所周知的计算机编程语言,C语言是谁发明的呢?它是怎样演进的?它有何特点?究竟有多少人在使用它? 1. C语言之父 C语言是1972年由 ...
- 2015/12/24:嵌入式C语言的位操作随笔
今晚是平安夜,首先祝大家平安夜快乐,明天是圣诞,祝大家圣诞快乐!! 好了,这周都特别有空,上班也非常轻松,基本就是看看内核驱动,学学安卓,没什么正事的开发活干.今晚,我们来总结一例在现实 ...
- 02树莓派4B—C语言编程——PWM
01树莓派直接输出PWM波 —— 硬件PWM程序 (推荐使用) #include <stdio.h> #include <wiringPi.h> #include <s ...
随机推荐
- Numa以及其他内存参数等对Oracle的影响
Numa以及其他内存参数等对Oracle的影响 背景知识: Numa的理解 Numa 分一致性内存访问结构 主要是对应UMA 一致性内存访问而言的. 在最初一个服务器只有一个CPU的场景下, 都是UM ...
- [转帖]金仓数据库KingbaseES 数据库参数优化
目录 一.数据库应用类型 二.主要参数 max_connections shared_buffers effective_cache_size maintenance_work_mem checkpo ...
- [转]流程自动化机器人(RPA)概念、原理与实践
[转]流程自动化机器人(RPA)概念.原理与实践 http://blog.sina.com.cn/s/blog_be0833d00102yho9.html 大多数人每天都会使用到一些机器人流程自动化工 ...
- 使用 Taro 开发鸿蒙原生应用 —— 探秘适配鸿蒙 ArkTS 的工作原理
背景 在上一篇文章中,我们已经了解到华为即将发布的鸿蒙操作系统纯血版本--鸿蒙 Next,以及各个互联网厂商开展鸿蒙应用开发的消息.其中,Taro作为一个重要的前端开发框架,也积极适配鸿蒙的新一代语言 ...
- K3S +Helm+NFS最小化测试安装部署只需十分钟
作者:郝建伟 k3s 简介 官方文档:k3s 什么是k3s k3s 是一个轻量级的 Kubernetes 发行版 它针对边缘计算.物联网等场景进行了高度优化. k3s 有以下增强功能: 打包为单个二进 ...
- Ant Design Vue数字输入框InputNumber 有值但是验证却不能够通过
InputNumber 有值但是验证却不能够通过 今天遇见这样一个问题,InputNumber 输入框中有值 但是却却提示验证不能够通过 后来经过分析,怀疑是数据类型不正确, 后面经过验证,果然是数据 ...
- Redis启用认证
要在Redis中启用认证,您需要在Redis配置文件中设置requirepass指令.以下是步骤: 找到Redis配置文件.这通常是redis.conf,可能位于/etc/redis/或/etc/目录 ...
- 在Unity中使用SQLite保存配置表数据(For Lua)
在Lua中使用sqlite Lua版本Sqlite文档:http://lua.sqlite.org/index.cgi/doc/tip/doc/lsqlite3.wiki sqlite官网:https ...
- 2.11 PE结构:添加新的节区
在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据.每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中 ...
- c#树结构转npoi复杂表头
Vue 前端框架框架中采用树结构打印表头,为了前后端适配NPOI导出. 这里重点做树结构转换 NPOI 复杂表头的结构数据( 跨行.跨列),其它具体导出功能请参考 https://www.cnblo ...