它们其实都是“图”-最小生成树问题(Prim、Kruskal)
生成树的概念
给定一个无向图,如果它的某个子图中任意两个顶点都相互连通并且是一棵树,那么这棵树就叫做生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树。
最小生成树问题
Prim算法
Prim算法与Dijkstra算法类似,从某个顶点出发,不断添加边的算法。
思想:
- 维护一个当前顶点集合到其他顶点的最小权值数组。
- 找到最小权值的顶点
- 更新最小权值数组
package main
import "fmt"
func main() {
var V int // 顶点个数
cost := make([][]int, V)
mincost := make([]int, V)
used := make([]bool, V)
for i := range cost {
cost[i] = make([]int, V)
}
mincost[0] = 0
res := 0
for {
v := -1
for u := 0; u < V; u++ {
if !used[u] && (v == -1 || mincost[u] < mincost[v]) {
v = u
}
}
if v == -1 {
break
}
used[v] = true
res += mincost[v]
for u := 0; u < V; u++ {
if mincost[u] > cost[v][u] {
mincost[u] = cost[v][u]
}
}
}
fmt.Println(res)
}
Kruskal算法
每次取权值最小的边
package main
import (
"fmt"
"sort"
)
var par = []int{}
var rank = []int{}
// 初始化n个元素
func initUnionFind(n int) {
for i := 0; i < n; i++ {
par[i] = i
rank[i] = i
}
}
// 查询x的根节点
func find(x int) int {
if par[x] == x {
return x
} else {
par[x] = find(par[x])
return par[x]
}
}
// 合并x和y所属的集合
func unite(x, y int) {
x, y = find(x), find(y)
if x == y {
return
}
if rank[x] < rank[y] {
par[x] = y
} else {
par[y] = x
if rank[x] == rank[y] {
rank[x]++
}
}
}
// 判断x和y是否属于同一个集合
func same(x, y int) bool {
return find(x) == find(y)
}
func main() {
type edge struct{ u, v, cost int }
var V, E int // 顶点的个数和边的个数
es := make([]edge, E)
sort.Slice(es, func(i, j int) bool { return es[i].cost < es[j].cost })
initUnionFind(V)
res := 0
for i := 0; i < E; i++ {
e := es[i]
if !same(e.u, e.v) {
unite(e.u, e.v)
res += e.cost
}
}
fmt.Println(res)
}
应用问题

用Dijkstra算法,维护两个数组,一个是最短距离数组,一个是次短数组即可。
package main
import (
"container/heap"
"fmt"
"math"
)
func main() {
var N, R int
type edge struct{ from, to, cost int }
graph := make([][]edge, N)
dist := make([]int, N)
dist2 := make([]int, N)
for i := 0; i < N; i++ {
dist[i], dist2[i] = math.MaxInt32, math.MaxInt32
}
dist[0] = 0
h := hp{}
heap.Init(&h)
heap.Push(&h, pair{0, 0})
for len(h) > 0 {
node := heap.Pop(&h).(pair)
v, d := node.index, node.distance
if dist2[v] < d {
continue
}
for i := 0; i < len(graph); i++ {
e := graph[v][i]
d2 := d + e.cost
if dist[e.to] > d2 {
dist[e.to], d2 = d2, dist[e.to]
heap.Push(&h, pair{dist[e.to], e.to})
}
if dist2[e.to] > d2 && dist[e.to] < d2 {
dist2[e.to] = d2
heap.Push(&h, pair{dist2[e.to], e.to})
}
}
}
fmt.Println(dist2[N-1])
}
type pair struct{ distance, index int }
type hp []pair
func (h hp) Len() int { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].distance < h[j].distance }
func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(x interface{}) { *h = append(*h, x.(pair)) }
func (h *hp) Pop() interface{} { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }
题目:Conscription

输出:69022
所有人费用最小,意味着亲密度总和最大。
因此,答案应该为10000∗(N+M)−Max(亲亲)10000*(N+M)-Max(亲亲)10000∗(N+M)−Max(亲亲)
package main
import (
"fmt"
"sort"
)
func main() {
var N, M, R int
fmt.Scanf("%d %d %d", &N, &M, &R)
type edge struct{ from, to, cost int }
edges := make([]edge, R)
for i := 0; i < R; i++ {
e := edge{}
fmt.Scanf("%d %d %d", &e.from, &e.to, &e.cost)
e.to = N + e.to
e.cost = -e.cost
edges[i] = e
}
fmt.Println(edges)
var kruskal func() int
kruskal = func() int {
res := 0
sort.Slice(edges, func(i, j int) bool { return edges[i].cost < edges[j].cost })
initUnionFind(N + M)
for i := 0; i < R; i++ {
e := edges[i]
if !same(e.from, e.to) {
unite(e.from, e.to)
fmt.Printf("e.cost: %v\n", e.cost)
res += e.cost
}
}
return res
}
fmt.Println(10000*(N+M) + kruskal())
}
var par = []int{}
var rank = []int{}
// 初始化n个元素
func initUnionFind(n int) {
par = make([]int, n)
rank = make([]int, n)
for i := 0; i < n; i++ {
par[i] = i
rank[i] = 0
}
}
// 查询x的根节点
func find(x int) int {
if par[x] == x {
return x
} else {
par[x] = find(par[x])
return par[x]
}
}
// 合并x和y所属的集合
func unite(x, y int) {
x, y = find(x), find(y)
if x == y {
return
}
if rank[x] < rank[y] {
par[x] = y
} else {
par[y] = x
if rank[x] == rank[y] {
rank[x]++
}
}
}
// 判断x和y是否属于同一个集合
func same(x, y int) bool {
return find(x) == find(y)
}
题目:Layout(POJ No.3169)


根据题意列出约束条件:
$d[i+1] \ge d[i] \
d[AD] + DL \ge d[BL] \
d[BD] - dd \ge d[AD] \$由于图中存在负权边,因此不使用Dijkstra算法而是使用Bellman-Ford算法求解。
package main
import (
"fmt"
"math"
)
func main() {
var N, ML, MD int
fmt.Scanf("%d %d %d", &N, &ML, &MD)
d := make([]int, N)
AL := make([]int, ML)
BL := make([]int, ML)
DL := make([]int, ML)
AD := make([]int, MD)
BD := make([]int, MD)
DD := make([]int, MD)
for i := 0; i < ML; i++ {
fmt.Scanf("%d %d %d", &AL[i], &BL[i], &DL[i])
}
for i := 0; i < MD; i++ {
fmt.Scanf("%d %d %d", &AD[i], &BD[i], &DD[i])
}
updated := false
var update func(x *int, y int)
update = func(x *int, y int) {
if *x > y {
*x = y
updated = true
}
}
var bellmanFord func()
bellmanFord = func() {
for k := 0; k <= N; k++ {
updated = false
for i := 0; i+1 < N; i++ {
if d[i+1] < math.MaxInt32 {
update(&d[i], d[i+1])
}
}
for i := 0; i < ML; i++ {
if d[AL[i]]-1 < math.MaxInt32 {
update(&d[BL[i]-1], d[AL[i]-1]+DL[i])
}
}
for i := 0; i < MD; i++ {
if d[BD[i]-1] < math.MaxInt32 {
update(&d[AD[i]-1], d[BD[i]-1]-DD[i])
}
}
}
}
bellmanFord()
if updated {
fmt.Println("-1")
return
}
for i := 0; i < N; i++ {
d[i] = math.MaxInt32
}
d[0] = 0
bellmanFord()
if d[N-1] == math.MaxInt32 {
fmt.Println("-2")
return
}
fmt.Println(d[N-1])
}
本文介绍了图的生成树概念,重点探讨了最小生成树问题,包括Prim算法和Kruskal算法的工作原理,并通过实际应用问题及题目解析加深理解,如Conscription和Layout (POJ No.3169)问题,其中涉及如何处理包含负权边的情况。
&spm=1001.2101.3001.5002&articleId=124259449&d=1&t=3&u=0cf4e88bc11d43bb96ae9deade77e077)
1730

被折叠的 条评论
为什么被折叠?



