ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

04.图的最小生成普利姆和克努斯卡尔

2022-01-12 15:58:00  阅读:135  来源: 互联网

标签:普利 return string 04 Edges 斯卡尔 Indexs Nodes AddEdge


一、克努斯卡尔的代码实现

1. 图的邻接矩阵实现

mgraph.go

package graph

import "errors"

const MaxSize = 20

type MGraph struct {
	Edges   [MaxSize][MaxSize]int
	EdgeNum int
	Nodes   []string
	Indexs  map[string]int
}

type Edge struct {
	NodeStart, NodeEnd string
	Val                int
}

func NewMGraph() MGraph {
	var g MGraph
	for k, v := range g.Edges {
		for kk, _ := range v {
			g.Edges[k][kk] = -1
		}
	}
	g.Indexs = make(map[string]int)
	return g

}

func (g *MGraph) AddNode(nodeName string) error {
	if g.Indexs == nil {
		return errors.New("不是有效的图")
	}
	if _, ok := g.Indexs[nodeName]; ok {
		return errors.New("已经添加过此结点")
	}
	g.Indexs[nodeName] = len(g.Nodes)
	g.Nodes = append(g.Nodes, nodeName)
	return nil
}

func (g *MGraph) AddEdge(nodeName1, nodeName2 string, val int) error {

	if _, ok := g.Indexs[nodeName1]; !ok {
		return errors.New("结点不存在:" + nodeName1)
	}
	if _, ok := g.Indexs[nodeName2]; !ok {
		return errors.New("结点不存在:" + nodeName2)
	}
	if g.Edges[g.Indexs[nodeName1]][g.Indexs[nodeName2]] != -1 {
		return errors.New("边已经存在")
	}

	g.Edges[g.Indexs[nodeName1]][g.Indexs[nodeName2]] = val
	g.Edges[g.Indexs[nodeName2]][g.Indexs[nodeName1]] = val
	g.EdgeNum++
	return nil
}

func (g *MGraph) GetEdgeList() []Edge {
	var edgeList []Edge

	for i := 0; i < len(g.Nodes); i++ {
		for j := 0; j < i; j++ {
			if g.Edges[i][j] >= 0 {
				edgeList = append(
					edgeList,
					Edge{NodeStart: g.Nodes[i], NodeEnd: g.Nodes[j], Val: g.Edges[i][j]},
				)
			}
		}
	}

	return edgeList
}

2.克努斯卡尔实现代码

Kruskal.go

package minimum_spanning_tree

import (
	"fmt"
	"gitee.com/gudongkun/datestruct/dataStructures/graph"
	"gitee.com/gudongkun/datestruct/dataStructures/unionfind"
	"sort"
)

func GetExGraph() *graph.MGraph {
	g := graph.NewMGraph()

	g.AddNode("A")
	g.AddNode("B")
	g.AddNode("C")
	g.AddNode("D")
	g.AddNode("E")
	g.AddNode("F")
	g.AddNode("G")
	g.AddNode("H")
	g.AddNode("I")
	g.AddNode("J")

	g.AddEdge("A", "B", 5)
	g.AddEdge("A", "D", 9)
	g.AddEdge("A", "E", 1)
	g.AddEdge("B", "D", 2)
	g.AddEdge("B", "C", 4)
	g.AddEdge("C", "H", 4)
	g.AddEdge("C", "I", 1)
	g.AddEdge("C", "J", 8)
	g.AddEdge("D", "H", 2)
	g.AddEdge("D", "G", 11)
	g.AddEdge("D", "F", 5)
	g.AddEdge("D", "E", 2)
	g.AddEdge("E", "F", 1)
	g.AddEdge("F", "G", 7)
	g.AddEdge("G", "H", 1)
	g.AddEdge("G", "I", 4)
	g.AddEdge("H", "I", 6)
	g.AddEdge("I", "J", 0)

	return &g
}

func Kruskal() []graph.Edge {
	var treeEdge []graph.Edge
	g := GetExGraph()
	originEdge := g.GetEdgeList()

	//1.给边排序
	sort.SliceStable(originEdge, func(i, j int) bool {
		return originEdge[i].Val < originEdge[j].Val
	})
	//2.构建并查集
	union := unionfind.NewUnionFind(g.Nodes)
	for _, v := range originEdge {
		if union.IsConnected(v.NodeStart, v.NodeEnd) {
			continue
		}
		treeEdge = append(treeEdge, v)
		union.Unify(v.NodeStart, v.NodeEnd)
		if union.Groups() == 1 {
			break
		}
	}

	if union.Groups()>1 {
		fmt.Println("非联通图",union)
	} else  {
		fmt.Println("生成树为:",treeEdge)
	}

	return treeEdge
}

3.测试代码

Kruskal_test.go

package minimum_spanning_tree

import "testing"

func TestGetExGraph(t *testing.T) {
	g := GetExGraph()
	if g.EdgeNum != 18 {
		t.Error("GetExGraph Error")
	}

	if len(g.GetEdgeList()) != 18 {
		t.Error("GetEdgeList Error")
	}
}

func TestKruskal(t *testing.T) {
	Kruskal()
}


二、图的普利姆算法

算法思想:在图中任意选出一个顶点作为一颗树,选出和这个顶点相连的最短路径,把路径加入树中,边加入选择的边中,此时得到了一颗有两个边的树。然后再从与这颗树相连的边中中选出最短的边,把他的另外一个顶点加入树,边加入选择边,依次类推,直到所有顶点加入到树中。此时得到的树就是最小生成树。

1、算法描述

  1. 初始化 三个数组
    seleted:记录节点是否再已经再生成树中,下标代表节点,初始都为false;
    minDist: 记录某一时刻,这个点到 已选顶点的集合的 最小权边的权值大小,初始为无穷大
    prev :记录 顶点连再树中的父元素的下标。初始都为-1,代表不连接任何顶点。

  2. 初始化任意顶点s操作:
    任意选择一个顶点s,添加到生成树中(seleted[s] = true);
    点s已经进入到生成树中,到生成树的距离已经没有意义,设置为-1(minDist[s] = -1);
    初始顶点没有,父元素(prev[s] = -1)

  3. 更新第一个顶点s操作:
    扫描所有与顶点s 连接的边(cp另外一个顶点,v边的权重值),如果顶点到这个店的距离,比minDist中第小,minDist[cp] = v ,prev[cp] = s

  4. 扫描:
    扫描所有未选中结点(seleted中值为false的点),找到minDist 值最小的点p

  5. 添加:
    添加点p到生成树中(seleted[p] = true)
    p点到树的距离,失去意义设置为-1(minDist[p] = -1)

  6. 更新:
    扫描所有与顶点p 连接的边(cp边的另外一个顶点,v边的权重值),如果顶点到这个店的距离,比minDist中第小,minDist[cp] = v ,prev[cp] = p

  7. 循环执行 4.扫描、5.添加、6.更新 ,一直到seleted中

  8. 所有的点都变成 true,
    通过prev数组,就能推倒出最小生成树的边。

2、图形描述

图形演示请参考:https://www.bilibili.com/video/BV1Eb41177d1

3、算法代码实现

(1)图增加获取相邻结点接口

func (g *MGraph) GetConnectedEdges(ele string) []Edge {
	var edgeList []Edge
	k := g.Indexs[ele]
	for i := 0; i < len(g.Nodes); i++ {
		if g.Edges[k][i] >= 0 {
			edgeList = append(
				edgeList,
				Edge{NodeStart: g.Nodes[k], NodeEnd: g.Nodes[i], Val: g.Edges[k][i]},
			)
		}
	}
	return edgeList
}

(2)普利姆算法

prim.go

package minimum_spanning_tree

import "fmt"

func Prim() map[string]string {
	g := GetExGraph()
	//初始化
	selected := make(map[string]bool)
	minDist := make(map[string]int)
	prev := make(map[string]string)

	for _, node := range g.Nodes {
		selected[node] = false
		minDist[node] = 9999999
	}
	// 添加第一个顶点
	selected[g.Nodes[0]] = true
	minDist[g.Nodes[0]] = -1
	prev[g.Nodes[0]] = "root"
	// 更新第一个顶点
	for _, edge := range g.GetConnectedEdges(g.Nodes[0]) {
		if minDist[edge.NodeEnd] > edge.Val {
			minDist[edge.NodeEnd] = edge.Val
			prev[edge.NodeEnd] = g.Nodes[0]
		}
	}
	for {
		// scan操作
		minK := ""
		minV := 9999999
		for k, v := range selected {
			if v {
				continue
			}
			if minDist[k] < minV { //选出未选中的点中,距离最小的点
				minK = k
				minV = minDist[k]
			}
		}
		if minK == "" {
			break
		}
		//add 操作
		selected[minK] = true // minK 设置为已选择
		minDist[minK] = -1    // minK 的距离失效
		//更新操作,更新未选中点到 selected 的距离
		for _, edge := range g.GetConnectedEdges(minK) {
			if minDist[edge.NodeEnd] > edge.Val {
				minDist[edge.NodeEnd] = edge.Val
				prev[edge.NodeEnd] = minK
			}
		}
	}

	fmt.Println(prev, len(prev))
	return prev
}

(3) 普利姆算法单元测试

package minimum_spanning_tree

import "testing"

func TestPrim(t *testing.T) {
	prev := Prim()
	if len(prev) != 10 {
		t.Error(prev)
	}
}

(4)修改后图的完整代码

package graph

import "errors"

const MaxSize = 20

type MGraph struct {
	Edges   [MaxSize][MaxSize]int
	EdgeNum int
	Nodes   []string
	Indexs  map[string]int
}

type Edge struct {
	NodeStart, NodeEnd string
	Val                int
}

func NewMGraph() MGraph {
	var g MGraph
	for k, v := range g.Edges {
		for kk, _ := range v {
			g.Edges[k][kk] = -1
		}
	}
	g.Indexs = make(map[string]int)
	return g

}

func (g *MGraph) AddNode(nodeName string) error {
	if g.Indexs == nil {
		return errors.New("不是有效的图")
	}
	if _, ok := g.Indexs[nodeName]; ok {
		return errors.New("已经添加过此结点")
	}
	g.Indexs[nodeName] = len(g.Nodes)
	g.Nodes = append(g.Nodes, nodeName)
	return nil
}

func (g *MGraph) AddEdge(nodeName1, nodeName2 string, val int) error {

	if _, ok := g.Indexs[nodeName1]; !ok {
		return errors.New("结点不存在:" + nodeName1)
	}
	if _, ok := g.Indexs[nodeName2]; !ok {
		return errors.New("结点不存在:" + nodeName2)
	}
	if g.Edges[g.Indexs[nodeName1]][g.Indexs[nodeName2]] != -1 {
		return errors.New("边已经存在")
	}

	g.Edges[g.Indexs[nodeName1]][g.Indexs[nodeName2]] = val
	g.Edges[g.Indexs[nodeName2]][g.Indexs[nodeName1]] = val
	g.EdgeNum++
	return nil
}

func (g *MGraph) GetEdgeList() []Edge {
	var edgeList []Edge

	for i := 0; i < len(g.Nodes); i++ {
		for j := 0; j < i; j++ {
			if g.Edges[i][j] >= 0 {
				edgeList = append(
					edgeList,
					Edge{NodeStart: g.Nodes[i], NodeEnd: g.Nodes[j], Val: g.Edges[i][j]},
				)
			}
		}
	}

	return edgeList
}

func (g *MGraph) GetConnectedEdges(ele string) []Edge {
	var edgeList []Edge
	k := g.Indexs[ele]
	for i := 0; i < len(g.Nodes); i++ {
		if g.Edges[k][i] >= 0 {
			edgeList = append(
				edgeList,
				Edge{NodeStart: g.Nodes[k], NodeEnd: g.Nodes[i], Val: g.Edges[k][i]},
			)
		}
	}
	return edgeList
}

相关代码的git 连接:

https://gitee.com/gudongkun/datestruct

标签:普利,return,string,04,Edges,斯卡尔,Indexs,Nodes,AddEdge
来源: https://blog.csdn.net/gudongkun1121/article/details/122455469

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有