文心一言 VS 讯飞星火 VS chatgpt (345)-- 算法导论23.2 4题

news2024/12/24 2:07:11

四、假定图中的边权重全部为整数,且在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1V内。在此种情况下,Kruskal算法最快能多快?如果边的权重取值范围在1到某个常数 W W W之间呢?如果要写代码,请用go语言。

文心一言:

首先,我们探讨Kruskal算法在不同条件下的性能表现,并给出基于Go语言的实现。

1. 当边权重全部在 1 ∼ ∣ V ∣ 1 \sim |V| 1V范围内时

在这种情况下,边的权重与图中顶点的数量成正比。这通常不会直接加速Kruskal算法,因为算法的主要复杂度来源于排序步骤( O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E| \log |E|) O(ElogE))和并查集的更新(通常为 O ( α ( ∣ V ∣ ) ⋅ ∣ E ∣ ) O(\alpha(|V|) \cdot |E|) O(α(V)E),其中 α \alpha α是Ackermann函数的反函数,可认为是非常小的常数)。但理论上,如果边数量接近顶点数量的平方(完全图),并且排序能够非常快(如利用边权重的范围进行某种形式的桶排序或基数排序),那么整体性能可能略优于一般情况。

然而,在实践中,通常的排序算法(如快速排序、归并排序)在这种特定范围内的权重排序上已经是相当高效的。

2. 当边权重在 1 1 1到某个常数 W W W之间时

当权重范围缩小到一个很小的常数 W W W时,可以考虑使用更高效的排序方法,如计数排序或桶排序,这些方法在此场景下的时间复杂度可以降到 O ( ∣ E ∣ ) O(|E|) O(E)。这能够显著提升Kruskal算法中排序步骤的效率,因为算法的整体复杂度将主要由并查集的更新操作决定。

Go语言实现(标准Kruskal算法)

以下是一个简单的Kruskal算法的Go语言实现,没有特别针对权重范围进行优化(因为这通常需要根据具体情况来设计):

package main

import (
    "fmt"
    "sort"
)

type UnionFind struct {
    parent []int
    rank   []int
}

func NewUnionFind(n int) *UnionFind {
    parent := make([]int, n)
    rank := make([]int, n)
    for i := 0; i < n; i++ {
        parent[i] = i
        rank[i] = 0
    }
    return &UnionFind{parent, rank}
}

func (uf *UnionFind) Find(x int) int {
    if uf.parent[x] != x {
        uf.parent[x] = uf.Find(uf.parent[x]) // 路径压缩
    }
    return uf.parent[x]
}

func (uf *UnionFind) Union(x, y int) {
    rootX := uf.Find(x)
    rootY := uf.Find(y)
    if rootX != rootY {
        if uf.rank[rootX] > uf.rank[rootY] {
            uf.parent[rootY] = rootX
        } else if uf.rank[rootX] < uf.rank[rootY] {
            uf.parent[rootX] = rootY
        } else {
            uf.parent[rootY] = rootX
            uf.rank[rootX]++
        }
    }
}

type Edge struct {
    u, v, weight int
}

type ByWeight []Edge

func (a ByWeight) Len() int           { return len(a) }
func (a ByWeight) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].weight < a[j].weight }

func kruskal(n int, edges []Edge) int {
    uf := NewUnionFind(n)
    mst := 0
    eCount := 0

    sort.Sort(ByWeight(edges))

    for _, e := range edges {
        if uf.Find(e.u) != uf.Find(e.v) {
            uf.Union(e.u, e.v)
            mst += e.weight
            eCount++
            if eCount == n-1 {
                break
            }
        }
    }

    if eCount < n-1 {
        return -1 // 表示不存在MST
    }
    return mst
}

func main() {
    edges := []Edge{{0, 1, 10}, {0, 2, 6}, {0, 3, 5}, {1, 3, 15}, {2, 3, 4}}
    n := 4
    fmt.Println("Cost of MST:", kruskal(n, edges))
}

在这个实现中,我们首先定义了一个并查集的数据结构来处理节点的连通性,然后通过Kruskal算法选择最小权重的边构建最小生成树。在排序边时,我们使用了标准的排序函数,而没有针对边权重范围进行优化。如果你想要针对特定的权重范围进行优化,你可能需要替换排序逻辑为更适合小范围整数的排序算法,如计数排序或桶排序。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

讯飞星火:

Kruskal算法的时间复杂度为O(ElogE),其中E是图中边的数量。这是因为Kruskal算法首先需要对所有的边按照权重进行排序,这通常需要O(ElogE)的时间复杂度。然后,它遍历排序后的边列表,并使用并查集数据结构来检查每条边是否会形成环。在最坏的情况下,每次添加一条边到并查集都需要O(logV)的时间复杂度,因此总的时间复杂度为O(ElogE + ElogV)。

当边的权重取值范围在1到某个常数W之间时,Kruskal算法的时间复杂度仍然是O(ElogE)。因为无论权重的范围如何,排序边的时间复杂度都是相同的。然而,如果W远大于V,那么并查集操作的时间复杂度可能会成为主导因素,此时时间复杂度可能接近于O(ElogW)。

以下是使用Go语言实现Kruskal算法的代码:

package main

import (
	"fmt"
	"sort"
)

type Edge struct {
	u, v, weight int
}

func find(parent []int, i int) int {
	if parent[i] == -1 {
		return i
	}
	return find(parent, parent[i])
}

func union(parent []int, x, y int) {
	xset := find(parent, x)
	yset := find(parent, y)
	if xset != yset {
		parent[xset] = yset
	}
}

func kruskal(edges []Edge, V int) []Edge {
	sort.Slice(edges, func(i, j int) bool {
		return edges[i].weight < edges[j].weight
	})

	parent := make([]int, V)
	for i := range parent {
		parent[i] = -1
	}

	var mst []Edge
	for _, edge := range edges {
		u := edge.u
		v := edge.v
		if find(parent, u) != find(parent, v) {
			mst = append(mst, edge)
			union(parent, u, v)
		}
	}

	return mst
}

func main() {
	edges := []Edge{
		{0, 1, 1},
		{0, 2, 2},
		{1, 2, 3},
		{1, 3, 4},
		{2, 3, 5},
	}
	V := 4
	mst := kruskal(edges, V)
	fmt.Println("Minimum Spanning Tree:")
	for _, edge := range mst {
		fmt.Printf("(%d, %d) -> %d
", edge.u, edge.v, edge.weight)
	}
}

这段代码首先定义了一个表示边的结构体Edge,然后实现了findunion函数用于并查集的操作。kruskal函数接受一个边的列表和顶点的数量,返回最小生成树的边列表。最后,main函数中给出了一个示例图,并调用kruskal函数计算其最小生成树。

360智脑:

首先,我们分析一下 Kruskal 算法的基本步骤和其时间复杂度。

Kruskal 算法的基本步骤如下:

  1. 将所有边按照权重从小到大排序。
  2. 遍历排序后的边,逐个检查是否形成环路,如果不形成环路,则加入结果集中。

在图的边数为 ∣ E ∣ |E| E,顶点数为 ∣ V ∣ |V| V 的情况下,Kruskal 算法的时间复杂度通常为 O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E| \log |E|) O(ElogE),主要的时间消耗在边的排序上。

特殊情况分析

边权重在 1 ∼ ∣ V ∣ 1 \sim |V| 1V 范围内

当边权重在 1 ∼ ∣ V ∣ 1 \sim |V| 1V 范围内时,我们可以使用计数排序(Counting Sort)来对边进行排序,因为计数排序的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是待排序元素的数量。在这种情况下, n = ∣ E ∣ n = |E| n=E

因此,在这种特殊情况下,Kruskal 算法的时间复杂度可以降低到 O ( ∣ E ∣ ) O(|E|) O(E)

边权重在 1 ∼ W 1 \sim W 1W 范围内,其中 W W W 是一个常数

当边权重在 1 ∼ W 1 \sim W 1W 范围内,且 W W W 是一个常数时,我们同样可以使用计数排序来对边进行排序。由于 W W W 是一个常数,计数排序的时间复杂度仍然是 O ( n ) O(n) O(n),其中 n = ∣ E ∣ n = |E| n=E

因此,在这种情况下,Kruskal 算法的时间复杂度也可以降低到 O ( ∣ E ∣ ) O(|E|) O(E)

Go 语言实现

以下是一个简单的 Kruskal 算法的 Go 语言实现,其中包括了计数排序的实现:

package main

import (
	"fmt"
)

type Edge struct {
	src int
	dst int
	wt  int
}

func countingSort(edges []Edge, maxWeight int) []Edge {
	count := make([]int, maxWeight+1)
	output := make([]Edge, len(edges))

	// 计算每个权重的出现次数
	for _, edge := range edges {
		count[edge.wt]++
	}

	// 累加次数
	for i := 1; i < len(count); i++ {
		count[i] += count[i-1]
	}

	// 构建输出数组
	for i := len(edges) - 1; i >= 0; i-- {
		output[count[edges[i].wt]-1] = edges[i]
		count[edges[i].wt]--
	}

	return output
}

func kruskalMST(graph [][]int, V int, E int) []Edge {
	edges := make([]Edge, E)
	for i := 0; i < E; i++ {
		edges[i] = Edge{graph[i][0], graph[i][1], graph[i][2]}
	}

	// 对边进行计数排序
	sortedEdges := countingSort(edges, V)

	// 并查集初始化
	parent := make([]int, V)
	for i := 0; i < V; i++ {
		parent[i] = i
	}

	mst := make([]Edge, 0)
	e := 0 // 当前已经选择的边数
	i := 0 // 当前正在处理的边的索引

	for e < V-1 {
		u, v, w := sortedEdges[i].src, sortedEdges[i].dst, sortedEdges[i].wt
		i++

		x := find(parent, u)
		y := find(parent, v)

		if x != y {
			mst = append(mst, Edge{u, v, w})
			e++
			union(parent, x, y)
		}
	}

	return mst
}

func find(parent []int, i int) int {
	if parent[i] == i {
		return i
	}
	return find(parent, parent[i])
}

func union(parent []int, x int, y int) {
	parent[x] = y
}

func main() {
	V := 5 // 顶点数
	E := 8 // 边数

	// 图的边集合
	graph := [][]int{
		{0, 1, 2},
		{0, 3, 6},
		{1, 2, 3},
		{1, 3, 8},
		{1, 4, 5},
		{2, 4, 7},
		{3, 4, 9},
	}

	// Kruskal 算法求最小生成树
	mst := kruskalMST(graph, V, E)

	fmt.Println("Minimum Spanning Tree:")
	for _, edge := range mst {
		fmt.Printf("%d -- %d : %d\n", edge.src, edge.dst, edge.wt)
	}
}

这个实现中,countingSort 函数用于对边进行计数排序,kruskalMST 函数是 Kruskal 算法的主要实现,findunion 函数用于并查集的操作。

请注意,这个实现假设输入的图是一个无向图,并且边的权重在 1 ∼ ∣ V ∣ 1 \sim |V| 1V 范围内。如果你需要处理更一般的权重范围,你可能需要使用其他排序算法,如快速排序或归并排序。

智谱清言:

Kruskal算法的时间复杂度主要由排序边的操作决定。在最坏的情况下,如果使用比较排序,其时间复杂度通常是(O(E \log E)),其中(E)是边的数量。但由于每条边的权重在(1 \sim |V|)范围内,我们可以使用计数排序(Counting Sort),这是一种非比较排序算法,其时间复杂度为(O(E + V))。
当边的权重取值范围在1到某个常数(W)之间时,如果(W)是一个较小的常数,我们同样可以使用计数排序,此时算法的时间复杂度为(O(E + W))。
下面是使用Go语言实现的Kruskal算法,假设边的权重范围在1到|V|之间:

package main
import (
	"sort"
)
// Edge represents an edge in the graph.
type Edge struct {
	From   int
	To     int
	Weight int
}
// UnionFind is a data structure that keeps track of a partition of a set into disjoint subsets.
type UnionFind struct {
	parent []int
	rank   []int
}
// NewUnionFind creates a new UnionFind data structure.
func NewUnionFind(size int) *UnionFind {
	uf := &UnionFind{
		parent: make([]int, size),
		rank:   make([]int, size),
	}
	for i := 0; i < size; i++ {
		uf.parent[i] = i
		uf.rank[i] = 0
	}
	return uf
}
// Find finds the representative element of the set containing 'p'.
func (uf *UnionFind) Find(p int) int {
	if uf.parent[p] != p {
		uf.parent[p] = uf.Find(uf.parent[p]) // path compression
	}
	return uf.parent[p]
}
// Union merges the sets containing 'p' and 'q'.
func (uf *UnionFind) Union(p, q int) {
	rootP := uf.Find(p)
	rootQ := uf.Find(q)
	if rootP != rootQ {
		// rank is used to keep the tree flat
		if uf.rank[rootP] > uf.rank[rootQ] {
			uf.parent[rootQ] = rootP
		} else if uf.rank[rootP] < uf.rank[rootQ] {
			uf.parent[rootP] = rootQ
		} else {
			uf.parent[rootQ] = rootP
			uf.rank[rootP]++
		}
	}
}
// Kruskal finds the Minimum Spanning Tree (MST) of a graph using Kruskal's algorithm.
func Kruskal(edges []Edge, numVertices int) []Edge {
	sort.Slice(edges, func(i, j int) bool {
		return edges[i].Weight < edges[j].Weight
	})
	mst := make([]Edge, 0)
	uf := NewUnionFind(numVertices)
	for _, edge := range edges {
		if uf.Find(edge.From) != uf.Find(edge.To) {
			uf.Union(edge.From, edge.To)
			mst = append(mst, edge)
		}
	}
	return mst
}
func main() {
	// Example usage:
	// edges := []Edge{{0, 1, 10}, {0, 2, 6}, {0, 3, 5}, {1, 3, 15}, {2, 3, 4}}
	// numVertices := 4
	// mst := Kruskal(edges, numVertices)
	// fmt.Println("MST edges:", mst)
}

在这个代码示例中,我们定义了一个Edge结构体来表示图中的边,以及一个UnionFind结构体来实现并查集,它用于检测添加边是否会形成环。Kruskal函数实现了Kruskal算法,它首先对边按权重进行排序,然后使用并查集来构建最小生成树。
请注意,这个实现假设顶点编号从0开始,并且边列表edges和顶点数量numVertices应由用户提供。如果要处理权重范围在1到常数(W)之间的情况,排序部分的逻辑不需要改变,因为计数排序在这里同样适用。

天工:

对于Kruskal算法的性能优化,关键在于两个方面:排序和并查集操作。

  1. 排序时间复杂度:在图的边权重全部为整数且在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1V内时,可以使用计数排序(Counting Sort),其时间复杂度为 O ( ∣ E ∣ + ∣ V ∣ ) O(|E| + |V|) O(E+V),比一般的比较排序 O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E| \log |E|) O(ElogE)要快。这里 ∣ E ∣ |E| E是边的数量, ∣ V ∣ |V| V是顶点的数量。

  2. 并查集操作时间复杂度:使用路径压缩和按秩合并的优化,可以达到接近常数时间的查找和合并操作,即 O ( α ( ∣ V ∣ ) ) O(\alpha(|V|)) O(α(V)),其中 α ( n ) \alpha(n) α(n)是阿克曼函数的反函数,增长极其缓慢。

当边权重在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1V

此时排序可以使用计数排序,时间复杂度为 O ( ∣ E ∣ + ∣ V ∣ ) O(|E| + |V|) O(E+V),并查集操作总时间复杂度为 O ( ∣ E ∣ α ( ∣ V ∣ ) ) O(|E|\alpha(|V|)) O(Eα(V))。因此,Kruskal算法的总时间复杂度为 O ( ∣ E ∣ + ∣ V ∣ + ∣ E ∣ α ( ∣ V ∣ ) ) O(|E| + |V| + |E|\alpha(|V|)) O(E+V+Eα(V)),可以简化为 O ( ∣ E ∣ + ∣ V ∣ ) O(|E| + |V|) O(E+V),因为 ∣ E ∣ |E| E通常远大于 ∣ V ∣ |V| V,且 α ( ∣ V ∣ ) \alpha(|V|) α(V)接近常数。

当边的权重取值范围在1到某个常数 W W W

如果 W W W是一个常数,那么排序可以使用桶排序(每个桶代表一个权重值),时间复杂度为 O ( ∣ E ∣ ) O(|E|) O(E)(假设 W W W为常数)。并查集操作的时间复杂度保持不变。因此,Kruskal算法的总时间复杂度为 O ( ∣ E ∣ + ∣ E ∣ α ( ∣ V ∣ ) ) O(|E| + |E|\alpha(|V|)) O(E+Eα(V)),简化为 O ( ∣ E ∣ ) O(|E|) O(E)

Go语言示例代码

下面是一个使用计数排序优化Kruskal算法的Go语言示例,假设边权重在 1 ∼ ∣ V ∣ 1 \sim |V| 1V范围内:

package main

import (
	"fmt"
)

type Edge struct {
	u, v, w int
}

type UnionFind struct {
	parents []int
}

func (uf *UnionFind) New(V int) {
	uf.parents = make([]int, V)
	for i := range uf.parents {
		uf.parents[i] = i
	}
}

func (uf *UnionFind) Find(x int) int {
	if uf.parents[x] != x {
		uf.parents[x] = uf.Find(uf.parents[x])
	}
	return uf.parents[x]
}

func (uf *UnionFind) Union(x, y int) {
	px, py := uf.Find(x), uf.Find(y)
	if px != py {
		uf.parents[px] = py
	}
}

func Kruskal(edges []Edge, V int) int {
	// Counting Sort
	count := make([]int, V+1)
	for _, e := range edges {
		count[e.w]++
	}
	sortedEdges := make([]Edge, 0, len(edges))
	for i, cnt := range count {
		for j := 0; j < cnt; j++ {
			sortedEdges = append(sortedEdges, Edge{w: i})
		}
	}

	uf := UnionFind{}
	uf.New(V)
	mstWeight := 0
	for _, e := range sortedEdges {
		if uf.Find(e.u) != uf.Find(e.v) {
			uf.Union(e.u, e.v)
			mstWeight += e.w
		}
	}
	return mstWeight
}

func main() {
	edges := []Edge{{0, 1, 2}, {0, 3, 6}, {1, 2, 3}, {2, 3, 1}, {1, 3, 4}}
	V := 4
	fmt.Println("MST Weight:", Kruskal(edges, V))
}

注意,上述代码中Kruskal函数实现了Kruskal算法的逻辑,使用了计数排序对边进行排序,然后使用并查集找到最小生成树的权重总和。

chatgpt:

在图中的边权重全部为整数且在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1V内时,Kruskal算法的最快时间复杂度为 O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E| \log |E|) O(ElogE),其中 ∣ E ∣ |E| E表示图中的边数。

当边的权重取值范围在1到某个常数 W W W之间时,Kruskal算法的时间复杂度为 O ( ∣ E ∣ log ⁡ ∣ E ∣ + ∣ E ∣ log ⁡ W ) O(|E| \log |E| + |E| \log W) O(ElogE+ElogW)

下面是一个用Go语言实现Kruskal算法的简单代码示例:

package main

import (
	"fmt"
	"sort"
)

type Edge struct {
	Start, End, Weight int
}

type UnionFind struct {
	parent []int
	size   []int
}

func (uf *UnionFind) Init(n int) {
	uf.parent = make([]int, n)
	uf.size = make([]int, n)
	for i := range uf.parent {
		uf.parent[i] = i
		uf.size[i] = 1
	}
}

func (uf *UnionFind) Find(x int) int {
	if x != uf.parent[x] {
		uf.parent[x] = uf.Find(uf.parent[x])
	}
	return uf.parent[x]
}

func (uf *UnionFind) Union(x, y int) {
	rootX := uf.Find(x)
	rootY := uf.Find(y)
	if rootX != rootY {
		uf.parent[rootX] = rootY
		uf.size[rootY] += uf.size[rootX]
	}
}

func Kruskal(edges []Edge, n int) []Edge {
	sort.Slice(edges, func(i, j int) bool {
		return edges[i].Weight < edges[j].Weight
	})

	uf := UnionFind{}
	uf.Init(n)

	var result []Edge

	for _, edge := range edges {
		if uf.Find(edge.Start) != uf.Find(edge.End) {
			result = append(result, edge)
			uf.Union(edge.Start, edge.End)
		}
	}

	return result
}

func main() {
	edges := []Edge{
		{0, 1, 4},
		{0, 2, 3},
		{1, 2, 2},
		{1, 3, 1},
		{2, 3, 5},
	}

	n := 4 // 4个节点

	mst := Kruskal(edges, n)

	for _, edge := range mst {
		fmt.Printf("Edge (%d, %d) with weight %d\n", edge.Start, edge.End, edge.Weight)
	}
}

这段代码实现了Kruskal算法,通过创建UnionFind结构用于并查集操作,并根据边权重对边进行排序和最小生成树构建。

kimi:

Kruskal算法是一种用于寻找最小生成树的算法,它适用于边权重为正整数的情况。算法的效率取决于如何管理边的集合以及如何选择最小权重的边。在最好的情况下,算法的效率可以非常高。

  1. 当边权重在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1V 内时:

    • 在这种情况下,边的权重是均匀分布的,且最小生成树的边权重不会超过 ∣ V ∣ − 1 |V|-1 V1(因为最小生成树包含 ∣ V ∣ − 1 |V|-1 V1 条边)。使用最小堆(或者优先队列)来管理边,每次从堆中取出最小权重的边,然后检查是否形成环。如果边的数量是 ∣ V ∣ − 1 |V|-1 V1,那么最小堆中的边数量最多是 ∣ V ∣ − 1 |V|-1 V1,每次取出边的操作是 O ( log ⁡ ( ∣ V ∣ − 1 ) ) O(\log (|V|-1)) O(log(V1))。因此,Kruskal算法在这种情况下的时间复杂度是 O ( ( ∣ V ∣ − 1 ) log ⁡ ( ∣ V ∣ − 1 ) ) O((|V|-1) \log (|V|-1)) O((V1)log(V1))
  2. 当边权重取值范围在1到某个常数 W W W 之间时:

    • 如果边的权重是一个常数 W W W,那么最小堆中的所有边权重都相等,这将导致算法退化到 O ( ∣ E ∣ ) O(|E|) O(E) 的时间复杂度,其中 ∣ E ∣ |E| E 是边的数量。这是因为在这种情况下,算法无法通过边的权重来区分它们,而必须检查所有边来确定它们是否形成环。

下面是使用Go语言实现Kruskal算法的示例代码:

package main

import (
	"container/heap"
	"fmt"
)

// 边结构体
type Edge struct {
	V, W int // 顶点V和W
	Weight int // 权重
	Index int // 索引,用于比较
}

// 边的比较器
type Edges []Edge

func (e Edges) Len() int            { return len(e) }
func (e Edges) Less(i, j int) bool  { return e[i].Weight < e[j].Weight }
func (e Edges) Swap(i, j int)       { e[i], e[j] = e[j], e[i] }
func (e *Edges) Push(x interface{}) { *e = append(*e, x.(Edge)) }
func (e *Edges) Pop() interface{} {
	old := *e
	n := len(old)
	x := old[n-1]
	*e = old[0 : n-1]
	return x
}

// 并查集
type UnionFind struct {
	parent []int
	rank   []int
}

func (u *UnionFind) Find(x int) int {
	if u.parent[x] != x {
		u.parent[x] = u.Find(u.parent[x])
	}
	return u.parent[x]
}

func (u *UnionFind) Union(x, y int) {
	xRoot, yRoot := u.Find(x), u.Find(y)
	if xRoot == yRoot {
		return
	}
	if u.rank[xRoot] < u.rank[yRoot] {
		u.parent[xRoot] = yRoot
	} else if u.rank[xRoot] > u.rank[yRoot] {
		u.parent[yRoot] = xRoot
	} else {
		u.parent[yRoot] = xRoot
		u.rank[xRoot]++
	}
}

func Kruskal(n int, edges []Edge) []Edge {
	uf := &UnionFind{parent: make([]int, n), rank: make([]int, n)}
	for i := range uf.parent {
		uf.parent[i] = i
	}
	edges = append(edges[:0:0], edges...)
	heap.Init(&edges)
	var result []Edge
	for edges.Len() > 0 {
		edge := heap.Pop(&edges).(Edge)
		if uf.Find(edge.V) != uf.Find(edge.W) {
			result = append(result, edge)
			uf.Union(edge.V, edge.W)
			if len(result) == n-1 {
				break
			}
		}
	}
	return result
}

func main() {
	n := 4
	edges := []Edge{
		{0, 1, 10},
		{0, 2, 6},
		{0, 3, 5},
		{1, 3, 15},
		{2, 3, 4},
	}
	mst := Kruskal(n, edges)
	fmt.Println("Minimum Spanning Tree:")
	for _, e := range mst {
		fmt.Printf("%d - %d: %d\n", e.V, e.W, e.Weight)
	}
}

这段代码定义了一个边结构体 Edge 和一个并查集结构体 UnionFind,用于实现Kruskal算法。Edges 类型实现了 heap.Interface,用于管理边的集合。Kruskal 函数实现了算法的核心逻辑。在 main 函数中,我们创建了一个示例图并调用 Kruskal 函数来找到最小生成树。

混元:

Kruskal算法是一种贪心算法,用于求解最小生成树问题。它的工作原理是按照边的权重顺序(从小到大)处理每条边,并且在不形成环的前提下将边加入到最小生成树中。

分析

  1. 边的权重范围是 1 ∼ ∣ V ∣ 1 \sim |V| 1V

    • 在这种情况下,边的数量最多是 ∣ V ∣ 2 |V|^2 V2(完全图),但由于我们只考虑权重在 1 ∼ ∣ V ∣ 1 \sim |V| 1V 范围内的边,所以边的数量最多是 ∣ V ∣ ( ∣ V ∣ − 1 ) / 2 |V|(|V|-1)/2 V(V1)/2
    • 如果我们使用一个优先队列(最小堆)来存储所有边,并且每次都能在 O ( log ⁡ ∣ E ∣ ) O(\log |E|) O(logE) 时间内取出最小权重的边,那么总的时间复杂度将是 O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E|\log |E|) O(ElogE)
    • 但是,由于边的权重范围是 1 ∼ ∣ V ∣ 1 \sim |V| 1V,我们可以使用计数排序的思想,将时间复杂度优化到 O ( ∣ E ∣ + ∣ V ∣ log ⁡ ∣ V ∣ ) O(|E| + |V|\log |V|) O(E+VlogV)。这是因为我们可以先对所有边按权重进行排序,然后使用并查集来检查环的存在,每次合并的时间复杂度是 O ( α ( ∣ V ∣ ) ) O(\alpha(|V|)) O(α(V)),其中 α \alpha α 是 Ackermann 函数的反函数,它增长得非常慢,可以视为常数。
  2. 边的权重范围是 1 ∼ W 1 \sim W 1W

    • 在这种情况下,边的数量可能远大于 ∣ V ∣ 2 |V|^2 V2,但是权重只有 W W W 种可能的值。
    • 我们可以使用桶排序的思想,将所有边按权重分到 W W W 个桶中,然后依次处理每个桶中的边。
    • 这样,总的时间复杂度将是 O ( ∣ E ∣ + W log ⁡ ∣ V ∣ ) O(|E| + W\log |V|) O(E+WlogV)

Go语言实现

下面是Kruskal算法的Go语言实现,假设边的权重范围是 1 ∼ ∣ V ∣ 1 \sim |V| 1V

package main

import (
	"fmt"
	"sort"
)

type Edge struct {
	Src, Dst, Weight int
}

type Subset struct {
	Parent, Rank int
}

func Find(subsets []Subset, i int) int {
	if subsets[i].Parent != i {
		subsets[i].Parent = Find(subsets, subsets[i].Parent)
	}
	return subsets[i].Parent
}

func Union(subsets []Subset, x, y int) {
	rootX := Find(subsets, x)
	rootY := Find(subsets, y)

	if subsets[rootX].Rank < subsets[rootY].Rank {
		subsets[rootX].Parent = rootY
	} else if subsets[rootX].Rank > subsets[rootY].Rank {
		subsets[rootY].Parent = root->X
	} else {
		subsets[rootY].Parent = rootX
		subsets[rootX].Rank++
	}
}

func KruskalMST(edges []Edge, V int) []Edge {
	result := make([]Edge, 0)
	subsets := make([]Subset, V)

	for i := 0; i < V; i++ {
		subsets[i].Parent = i
		subsets[i].Rank = 0
	}

	sort.Slice(edges, func(i, j int) bool {
		return edges[i].Weight < edges[j].Version
	})

	e := 0
	for i := 0; e < V-1 && i < len(edges); i++ {
		x := Find(subsets, edges[i].Src)
		y := Find(subsets, edges[i].Dst)

		if x != y {
			result = append(result, edges[i])
			Union(subsets, x, y)
			e++
		}
	}

	return result
}

func main() {
	V := 4
	edges := []Edge{
		{0, 1, 10},
		{0, 2, 6},
		{0, 3, 5},
		{1, 3, 15},
		{2, 3, 4},
	}

	mst := KruskalMST(edges, V)
	for _, edge := range mst {
		fmt.Printf("Edge from %d to %d with weight %d\n", edge.Src, edge.Dst, edge.Weight)
	}
}

这段代码实现了Kruskal算法,并且使用了并查集来检测环的存在。它首先对所有边按权重进行排序,然后依次尝试加入每条边到最小生成树中,如果加入后不会形成环,则将其加入结果集。

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

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

相关文章

RPY角的具体描述

目录 一、 RPY角度 二、左乘与右乘 三、xyz固定角和zyx欧拉角旋转矩阵等价 四、参考文献 一、 RPY角度 1.1、X-Y-Z固定角[1] 首先将坐标系{B}和一个已知参考坐标系{A}重合。先将{B}绕旋转γ角&#xff0c;在绕旋转β角&#xff0c;在绕旋转α角&#xff0c;每次旋…

做统计(蓝桥杯初级)

系列文章目录 e&#xff0c;新系列没有目录&#xff09; 文章目录 系列文章目录前言一、个人名片二、描述三、输入输出以及代码示例1.输入输入样例&#xff1a; 2.输出输出样例&#xff1a; 3.代码示例 四、思路总结 前言 今天我们来做《做统计》 一、个人名片 个人主页&…

Flutter-底部选择弹窗(showModalBottomSheet)

前言 现在有个需求&#xff0c;需要用底部弹窗来添加定时的重复。在这里使用原生的showModalBottomSheet来实现 showModalBottomSheet的Props 名称 描述 isScrollControlled全屏还是半屏isDismissible外部是否可以点击&#xff0c;false不可以点击&#xff0c;true可以点击&a…

剪花布条(KPM模板题)

思路&#xff1a;套用KMP模板即可。 #include<bits/stdc.h> using namespace std; #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define endl \n int ne[200005]; int main() {IOSstring a,b;while(cin >> a){if(a"#") break;cin …

LEAN 类型系统属性 之 算法式相等的非传递性(Algorithm equality is not transitive)注解

由于 subsingleton 使用函数&#xff08;eliminator&#xff09; 的存在&#xff0c;导致算法式相等&#xff08;Algorithm defintional equality&#xff09;的非传递性。 在《定义上相等的非确定性&#xff08;Undecidability of Definitional Equality&#xff09;》 中有&…

[基于 Vue CLI 5 + Vue 3 + Ant Design Vue 4 搭建项目] 10 Ant Design Vue 的注册

1.全局全部注册 这样就可以将 ant design vue 全部组件注册进来 2.全局部分注册 这样就是按需注册了 本次&#xff0c; 我们选择第1种方式&#xff0c;全部注册进来 3.注册全局 css 4.测试一下 在 AboutView.vue 中添加一个 Test 按钮 使用 npm run serve 启动服务 访问 A…

如何通过subprocess在数据采集中执行外部命令 —以微博为例

介绍 在现代网络爬虫开发中&#xff0c;爬虫程序常常需要与外部工具或命令交互&#xff0c;以完成一些特定任务。subprocess 是 Python 提供的强大模块&#xff0c;用于启动和管理外部进程&#xff0c;广泛应用于爬虫技术中。本文将探讨如何通过 subprocess 在爬虫中执行外部命…

k8s 常见问题梳理

1、“cni0” already has an IP address different from 10.244.2.1/24 删除网卡 ifconfig cni0 down ip link delete cni0ip link add cni0 type bridge ip link set dev cni0 up ifconfig cni0 10.244.2.1/24 ifconfig cni0 mtu 1450 up

二.Unity中使用虚拟摇杆来控制角色移动

上一篇中我们完成了不借助第三方插件实现手游的虚拟摇杆&#xff0c;现在借助这个虚拟摇杆来实现控制角色的移动。 虚拟摇杆实际上就给角色输出方向&#xff0c;类似于键盘的WSAD&#xff0c;也是一个二维坐标&#xff0c;也就是(-1,1)的范围&#xff0c;将摇杆的方向进行归一化…

Windows与Linux下 SDL2的第一个窗口程序

Windows效果和Linux效果如下&#xff1a; 下面是代码&#xff1a; #include <stdio.h> #include "SDL.h"int main(int argc, char* argv[]) { // 初始化SDL视频子系统if (SDL_Init(SDL_INIT_VIDEO) ! 0){// 如果初始化失败&#xff0c;打印错误信息printf(&…

HPA自动扩缩容和命名空间资源限制

目录 HPA概念 安装HPA的依赖环境 安装metrics-server 手动扩缩容 自动扩缩容 yaml文件 创建HPA 自动扩容 自动缩容 命名空间资源限制 HPA概念 HPA是针对pod的数量进行自动扩缩容。&#xff08;是针对控制器deployment、replicaset、StatefulSet创建的pod&#xff0…

TS接口、泛型、自定义类型

这里记录下typescript中接口、泛型和自定义类型的使用 接口定义 // 定义一个接口,用来限制Teacher的属性 export interface Teacher {name: string;age: number;gender: string; }export type teacherList Teacher[];// 一个自定义类型 export type Teachers Array<Teach…

【UE5 C++课程系列笔记】02——创建C++类的三种方式

目录 一、从UE编辑器中创建 引用头文件报错的两种解决方式 &#xff08;1&#xff09;方式1 &#xff08;2&#xff09;方式2 二、在文件夹中直接创建 三、在Visual Studio中创建 一、从UE编辑器中创建 在UE编辑器中选择“Tools-》New C Class” 这里新建的类的父类选择…

Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)

前言 公司主要使用 Go 语言做项目&#xff0c;有一些 Gitlab 私有仓库需要引用&#xff0c;在做 CI 时&#xff0c;要自行配置权限以获取代码。 最近发现各个项目组在做 CI 遇到仓库权限问题时的解决方式不尽相同&#xff0c;有用 Project Token 的&#xff0c;有用 Deploy K…

【python】OpenCV—Augmented Reality Using Aruco Markers

文章目录 1、任务描述2、Aruco Markers3、代码实现4、更多例子展示5、涉及到的库cv2.findHomography 6、参考 1、任务描述 借助 Aruco Markers&#xff0c;替换墙面上画面中的内容 2、Aruco Markers OpenCV 中的 aruco 模块共有 25 个预定义的标记字典。字典中的所有标记包含…

新代机床采集数据

新代集團1995年成立於台灣新竹,事業版圖遍布全球,以台灣為中心向外發展,據點橫跨歐洲、美洲、亞洲三大洲。新代長期深耕於機床控制器的軟體及硬體技術研發,專注於運動控制領域,目前已成為亞太市場中深具影響力的控制器領導品牌之一。主營產品包括:機床數控系統、伺服驅動…

Java虚拟机 - 高级篇

一、GraalVM 1. 什么是GraalVM 2. GraalVM的两种运行模式 &#xff08;1&#xff09;JIT即时编译模式 &#xff08;2&#xff09;AOT提前编译模式 3. 应用场景 4. 参数优化和故障诊断 二、新一代的GC 1. 垃圾回收器的技术演进 2. Shenandoah GC 测试代码&#xff1a; /** C…

Ubuntu 20.04/22.04无法连接网络(网络图标丢失、找不到网卡)的解决方案

问题复述&#xff1a; Ubuntu 20.04无法连接到网络&#xff0c;网络连接图标丢失&#xff0c;网络设置中无网络设置选项。 解决方案 对于Ubuntu 20.04而言&#xff1a;逐条执行 sudo service network-manager stopsudo rm /var/lib/NetworkManager/NetworkManager.statesudo…

《深度学习》OpenCV轮廓检测 模版匹配 解析及实现

目录 一、模型匹配 1、什么是模型匹配 2、步骤 1&#xff09;提取模型的特征 2&#xff09;在图像中查找特征点 3&#xff09;进行特征匹配 4&#xff09;模型匹配 3、参数及用法 1、用法 2、参数 1&#xff09;image&#xff1a;待搜索对象 2&#xff09;templ&am…

利用AI驱动智能BI数据可视化-深度评测Amazon Quicksight(四)

简介 随着生成式人工智能的兴起&#xff0c;传统的 BI 报表功能已经无法满足用户对于自动化和智能化的需求&#xff0c;今天我们将介绍亚马逊云科技平台上的AI驱动数据可视化神器 – Quicksight&#xff0c;利用生成式AI的能力来加速业务决策&#xff0c;从而提高业务生产力。…