首页 > 编程语言 >图及相关算法

图及相关算法

时间:2023-10-19 23:15:36浏览次数:34  
标签:return int 算法 图及 parents func 相关 uf size

准备找实习了,把忘了的东西从头捡一捡

基本实现

大一时候有个特别蠢的问题,一直老想为什么不内置图的实现,现在想想真是蠢到家了……

Go语言实现无向无环图

import "fmt"

//Implment by adjacency matrix
type graphadjMat struct {
	vertices []int
	adjMat   [][]int
}

func newGraphAdjMat(vertices []int, edges [][]int) *graphadjMat {
	n := len(vertices)
	adjMat := make([][]int, n)
	for i := range adjMat {
		adjMat[i] = make([]int, n)
	}
	g := &graphadjMat{
		vertices: vertices,
		adjMat:   adjMat,
	}
	for i := range edges {
		g.addEdge(edges[i][0], edges[i][1])
	}
	return g
}

func (g *graphadjMat) addVertex(val int) {
	n := g.size()
	g.vertices = append(g.vertices, val)
	newRow := make([]int, n)
	g.adjMat = append(g.adjMat, newRow)
	for i := range g.adjMat {
		//Looking at the matirx horizontally,the action is euqivalent to inserting 0 at the end of the row
		g.adjMat[i] = append(g.adjMat[i], 0)
	}
}

func (g *graphadjMat) removeVertex(index int) {
	if index >= g.size() {
		fmt.Errorf("%s", "Index out of Bounds")
		return
	}
	g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)
	g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)
	for i := range g.adjMat {
		g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)
	}
}

// acyclic graph [ˌeɪˈsaɪklɪk ɡræf]
func (g *graphadjMat) addEdge(i, j int) {
	if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {
		fmt.Errorf("%s", "Index out of bounds")
	}
	g.adjMat[i][j] = 1
	g.adjMat[j][i] = 1
}

func (g *graphadjMat) removeEdge(i, j int) {
	if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {
		fmt.Errorf("%s", "Index out of bounds")
	}
	g.adjMat[i][j] = 0
	g.adjMat[j][i] = 0
}

func (g *graphadjMat) size() int {
	return len(g.vertices)
}

func sum[T int | int16 | int32 | int64](nums []T) T {
	var sum T
	for _, v := range nums {
		sum += v
	}
	return sum
}

func max[T int | int16 | int32 | int64](a, b T) T {
	if a > b {
		return a
	} else {
		return b
	}
}

func minWithT[T int | int16 | int32 | int64](numList ...T) T {
	var minValue T = numList[0]
	for _, v := range numList {
		if minValue > v {
			minValue = v
		}
	}
	return minValue
}
type vertex struct {
	val int
}

type graphadjMat struct {
	vertices []int
	adjMat   [][]int
}

type graphAdjList struct {
	adjList map[vertex][]vertex
}

//Implement by adjacency list
func newGraphAdjList(edges [][]vertex) *graphAdjList {
	g := &graphAdjList{
		adjList: make(map[vertex][]vertex),
	}
	for _, edge := range edges {
		g.addVertex(edge[0])
		g.addVertex(edge[1])
		g.addEdge(edge[0], edge[1])
	}
	return g
}

func (g *graphAdjList) size() int {
	return len(g.adjList)
}

func (g *graphAdjList) addVertex(ver vertex) {
	_, ok := g.adjList[ver]
	if ok {
		fmt.Errorf("%s", "The vertex has been found")
		return
	}
	g.adjList[ver] = make([]vertex, 0)
}

func (g *graphAdjList) deleteVertex(ver vertex) {
	_, ok := g.adjList[ver]
	if !ok {
		panic("error,the vertex cant be found")
	}
	delete(g.adjList, ver)
	for i, list := range g.adjList {
		g.adjList[i] = deleteSliceElems(list, ver)
	}
}

func (g *graphAdjList) addEdge(ver1, ver2 vertex) {
	_, ok1 := g.adjList[ver1]
	_, ok2 := g.adjList[ver2]
	if !ok1 || !ok2 || ver1 == ver2 {
		panic("Error")
	}
	g.adjList[ver1] = append(g.adjList[ver1], ver2)
	g.adjList[ver2] = append(g.adjList[ver2], ver1)
}

func (g *graphAdjList) removeEdge(ver1, ver2 vertex) {
	_, ok1 := g.adjList[ver1]
	_, ok2 := g.adjList[ver2]
	if !ok1 || !ok2 || ver1 == ver2 {
		panic("Error")
	}
	g.adjList[ver1] = deleteSliceElems(g.adjList[ver1], ver1)
	g.adjList[ver2] = deleteSliceElems(g.adjList[ver2], ver2)
}

func deleteSliceElems(slice []vertex, ver vertex) []vertex {
	for i, v := range slice {
		if v == ver {
			slice = append(slice[:i], slice[i+1:]...)
			break
		}
	}
	return slice
}

Union-Find algorithm(并查集算法)

Overview

作为最小生成树算法的前置内容,以及解决部分问题的有效解法,并查集算法有必要学习并记录下来

API we need to implement(需要实现的API)

type UF struct{
	func union(p,q int)
	func connected(p,q int) bool
	func count() int
}
  • union函数表示将p与q两节点连通
  • connected函数表示判断p与q节点是否联通
  • count函数将返回图中的连通分量数量
  • 请注意,上述内容不符合Go语言语法要求,仅是说明举例用途
    结合一下离散数学的内容,连通是一种等价关系。满足自反性、对称性、传递性

How to implement(实现思路)

设定每个节点有一个指针指向其父节点,如果父节点是自身的节点就是根节点。所以一开始所有节点应该都算是根节点。

type UF struct{
	count int
	parents []int
}

func newUF(n int) *UF{
	uf:=&UF{count:n,parents:make([]int,n)}
	for i:=0;i<n:i++{
		//Every node's parents is itself when initilize
		uf.parents[i]=i
	}
	return uf
}

如果A节点需要与B节点联通,只需要将A节点的根节点连接到B节点的根节点即可。

func (uf *UF) unionWithPro(p, q int) {
	rootP := uf.findWithPro(p)
	rootQ := uf.findWithPro(q)
	if rootP == rootQ {
		return
	}
	uf.parents[rootP] = rootQ
	uf.count--
}

但这样会出现问题,在极端情况下,可能会经过连接形成一条链表。也就是说此时高度为N,二叉树不平衡,这样会导致如下问题:
如果我们需要判断两节点是否联通,需要判断二者的根节点是否为同一节点,所以就需要一个API可以找到某节点的根节点:

func (uf *UF) findWithPro(x int) int {
	for uf.parents[x] != x {
		x = uf.parents[x]
	}
	return x
}

不难分析,这个函数会从某个节点向上遍历直至树根,时间复杂度为高度O(logN),但可惜正如上面所言,当树极度不平衡时,会退化为O(N),当数据量极大时,会造成很大的性能损失。所以需要优化。

优化

问题的根源在于union函数只是粗暴的把一个根节点接到另外一个,并没有考虑两种里哪一种可以更好的维护平衡。如果我们每次都将高度较小的树接到高度较大的树下面,就可以避免这一问题了。遂修改代码如下:

type UF struct {
	//the amount of connected components in the graph
	count   int
	//a node's parents node
	parents []int
	//every root node's tree's height
	size    []int
}

func newUF(n int) *UF {
	uf := &UF{count: n, parents: make([]int, n), size: make([]int, n)}
	for i := 0; i < n; i++ {
		uf.parents[i] = i
		uf.size[i] = 1
	}
	return uf
}

func (uf *UF) union(p, q int) {
	rootP := uf.find(p)
	rootQ := uf.find(q)
	if rootP == rootQ {
		return
	}
	sizeP := uf.size[rootP]
	sizeQ := uf.size[rootQ]
	if sizeP > sizeQ {
		uf.parents[rootQ] = rootP
		uf.size[rootP] += sizeQ
	} else {
		uf.parents[rootP] = rootQ
		uf.size[rootQ] += sizeP
	}
	uf.count--
}

为了判断树对应的高度,我们还需要修改UF数据结构的定义,加入size数组,存储每个根节点对应树的高度。
至此,时间复杂度下降为O(logN)。

路径压缩

image.png
此时按照上面的代码,我们也许可以得到一颗这样的树。但其实我们还可以进一步优化,因为我们实际上只关心某两个节点的根节点是不是同一个,以便来判断连通与否。所以我们可以尝试压缩路径到同一层,这样就可以将时间复杂度降为O(1)
image.png
可以通过修改find函数的逻辑实现这一操作,有迭代法和递归法两种,此处只记录效果更好的递归法:

func (uf *UF) find(x int) int {
	if uf.parents[x] != x {
		//we move the current node to previous layer if it doesn't fit the condition
		uf.parents[x] = uf.find(uf.parents[x])
	}
	return uf.parents[x]
}

如果采用迭代写法,会压缩成如下的效果
image.png
如果采用递归写法,会压缩成如下:
image.png
借助路径压缩,size数组就可以去掉,保留与否均可。

Kruskal

Overview

克鲁斯卡尔是一种贪心算法,用来求图中的最小生成树。每次都选择最小的边加入(前提是保证不会形成回路),以便得到全局权重和最小。所以我们需要以下手段:

  • 能够选择权值最小的边(排序解决)
  • 能够判断加入后是否形成回路(并查集解决)

实现

1584. 连接所有点的最小费用 - 力扣(LeetCode)
以上题为例,将并查集也用上。
首先解决第一点,将图中权值排序,遍历每个点,计算到其他点的距离,即为此边的权值。最后排序即可。
第二点,是否形成回路,每次我们都从排序好了的边集合中选取,选取前判断此边的两点是否连通,如果已连通就跳过,未连通便加入,并在并查集中连接两点。
有一个结论是,最终形成的最小生成树一定有n(节点数量)-1条边,当我们判断已经加入这么多条边时,就可以返回最后的结果

type UF struct {

    count   int

    parents []int

    size    []int

}

  
  

func newUF(n int) *UF {

    uf := &UF{count: n, parents: make([]int, n), size: make([]int, n)}

    for i := 0; i < n; i++ {

        uf.parents[i] = i

        uf.size[i] = 1

    }

    return uf

}

  

func (uf *UF) counts() int {

    return uf.count

}

  

func (uf *UF) find(x int) int {

    if uf.parents[x] != x {

        uf.parents[x] = uf.find(uf.parents[x])

    }

    return uf.parents[x]

}

  

func (uf *UF) union(p, q int) {

    rootP := uf.find(p)

    rootQ := uf.find(q)

    if rootP == rootQ {

        return

    }

    if uf.size[rootP] > uf.size[rootQ] {

        uf.parents[rootQ] = rootP

        uf.size[rootP] += uf.size[rootQ]

    } else {

        uf.parents[rootP] = rootQ

        uf.size[rootQ] += uf.size[rootP]

    }

    uf.count--

}

  

func (uf *UF) connected(p, q int) bool {

    rootP := uf.find(p)

    rootQ := uf.find(q)

    return rootP == rootQ

}

  

func minCostConnectPoints(points [][]int) int {

    var ans = 0

    type edge struct{ v, w, dis int }

    edges := []edge{}

    for i := 0; i < len(points); i++ {

        for j := i + 1; j < len(points); j++ {

            edges = append(edges, edge{i, j, int(cal_distance(points[i][0], points[i][1],

                points[j][0], points[j][1]))})

        }

    }

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

    graphUnion := newUF(len(points))

    edgeCount := len(points) - 1

    for _, v := range edges {

        if graphUnion.connected(v.v, v.w) {

            continue

        } else {

            ans += v.dis

            edgeCount--

            if edgeCount == 0 {

                break

            }

            graphUnion.union(v.v, v.w)

        }

    }

    return ans

}

  

func cal_distance(x1, y1, x2, y2 int) float64 {

    x := math.Abs(float64(x1 - x2))

    y := math.Abs(float64(y1 - y2))

    return x + y

}

标签:return,int,算法,图及,parents,func,相关,uf,size
From: https://www.cnblogs.com/appletree24/p/17775934.html

相关文章

  • 算法训练day37 LeetCode860.406.452.
    算法训练day37LeetCode860.406.452.860.柠檬水找零题目860.柠檬水找零-力扣(LeetCode)题解代码随想录(programmercarl.com)5:收五元10:收十元,返五元20:优先还十元+五元;否则还五元*3classSolution{public:boollemonadeChange(vector<int>&bills)......
  • 文心一言 VS 讯飞星火 VS chatgpt (116)-- 算法导论10.3 1题
    一、用go语言,画图表示序列(13,4,8,19,5,11),其存储形式为多数组表示的双向链表。同样画出单数组表示的形式。文心一言:在Go语言中,我们无法直接画图,但我可以帮助你描述如何使用Go语言来表示和操作多数组表示的双向链表和单数组表示。首先,多数组表示的双向链表可以表示为以下的数据结构:......
  • C#基数排序算法
    前言基数排序是一种非比较性排序算法,它通过将待排序的数据拆分成多个数字位进行排序。实现原理首先找出待排序数组中的最大值,并确定排序的位数。从最低位(个位)开始,按照个位数的大小进行桶排序,将元素放入对应的桶中。将各个桶中的元素按照存放顺序依次取出,组成新的数组。接着......
  • 希尔排序:优化插入排序的精妙算法
    排序算法在计算机科学中扮演着重要的角色,其中希尔排序(ShellSort)是一种经典的排序算法。本文将带您深入了解希尔排序,包括其工作原理、性能分析以及如何使用Java进行实现。什么是希尔排序?希尔排序,又称“缩小增量排序”,是插入排序的一种改进版本。它的核心思想是通过逐步缩小增量值......
  • 有关操作系统部分相关知识点的总结
    1、进程是程序的一次运行2、死锁的相关问题当有K个进程,每个进程都需要n个资源才可以运行,则系统不发生死锁的资源数至少为k*(n-1)+1;例题如下:3、银行家算法例子对于这种题目,我是根据选项将答案代入验证得到的:......
  • 第K短路相关证明
    Dijkstra正确性证明采用反证法+数学归纳法设目前已经出对的点得到的都是最短路,那么对于现在刚好出队这个点t来说:因为它是优先队列的对头,而且图中的边权都是非负数,所以它不可能被队列中的点再更新因为每个点只会出队一次,所以它也不会被已经出队的点再次更新还没有入队的点距......
  • celery包结构、celery延迟任务和定时任务、django中使用celery、接口缓存、双写一致性
    celery包结构project├──celery_task #celery包  这个包可以放在任意位置│├──__init__.py#包文件│├──celery.py#celery连接和配置相关文件,且名字必须叫celery.py│└──tasks.py#所有任务函数│├──add_task.p......
  • TSINGSEE烟火识别算法的技术原理是什么?如何应用在视频监控中?
    AI烟火识别算法是基于深度学习技术的一种视觉识别算法,主要用于在视频监控场景中自动检测和识别烟雾、火焰的行为。该技术基于深度学习神经网络技术,可以动态识别烟雾和火焰从有到无、从小到大、从大到小、从小烟到浓烟的状态转换过程。1、技术原理1)数据采集与准备:首先需要采集大量带......
  • TSINGSEE烟火识别算法的技术原理是什么?如何应用在视频监控中?
    AI烟火识别算法是基于深度学习技术的一种视觉识别算法,主要用于在视频监控场景中自动检测和识别烟雾、火焰的行为。该技术基于深度学习神经网络技术,可以动态识别烟雾和火焰从有到无、从小到大、从大到小、从小烟到浓烟的状态转换过程。1、技术原理1)数据采集与准备:首先需要采集大......
  • 数据库相关概念
    数据库系统相关概念数据库优点数据持久性(DataPersistence):数据库系统可以将数据永久存储在磁盘上,即使系统关闭或断电,数据也不会丢失。数据共享和多用户访问(DataSharingandMulti-UserAccess):多个用户可以同时访问数据库,而不会发生冲突,这有助于团队协作和数据共享。......