它们其实都是“图”-最小生成树问题(Prim、Kruskal)

本文介绍了图的生成树概念,重点探讨了最小生成树问题,包括Prim算法和Kruskal算法的工作原理,并通过实际应用问题及题目解析加深理解,如Conscription和Layout (POJ No.3169)问题,其中涉及如何处理包含负权边的情况。

它们其实都是“图”-最小生成树问题(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])
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值