草庐IT

最小生成树

minqiliang 2023-03-28 原文

如下图是个带权值的网结构图。要用最小的成本将所有元素连接起来,即n个顶点,用n-1条边把连通图连接起来,并且使得权值的和最小。定义:把构造连通网的最小代价生成树称为最小生成树。这里介绍两种经典算法。

1. 普里姆(Prim)算法

假设N=(V,E)是连通图,TE是N上的最小生成树中边的集合。

  1. U={u0}(u0∈V), TE={ }。

  2. 在所有u∈U,v∈V-U的边(u,v)∈E 中找一条代价(权值)最小的边(u0,v0) 并入集合TE,同时v0并入U。

  3. 重复2,直至U=V为止。
    此时TE中必有n-1条边,则T= (V,{TE}) 为N的最小生成树。

python代码实现:

class MGraph():
    def __init__(self):
        self.vertex = []
        self.matrix = []
        self.numNodes = 0
        self.numEdges = 0

    def createMGraph(self):
        """创建无向网图的邻接矩阵表示"""
        INFINITY = 65535
        self.numNodes = int(input("请输入顶点数:"))
        self.numEdges = int(input("请输入边数:"))
        for i in range(self.numNodes):
            self.vertex.append(input("请输入一个顶点:"))

        for i in range(self.numNodes):
            self.matrix.append([])
            for j in range(self.numNodes):
                if i == j:
                    self.matrix[i].append(0)  # 初始化邻接矩阵
                else:
                    self.matrix[i].append(INFINITY)  # 初始化邻接矩阵

        for k in range(self.numEdges):  # 读入numEdges条边,建立邻接矩阵
            i = int(input("请输入边(vi,vj)上的下标i:"))
            j = int(input("请输入边(vi,vj)上的下标j:"))
            w = int(input("请输入边(vi,vj)上的权w:"))
            self.matrix[i][j] = w
            self.matrix[j][i] = self.matrix[i][j]  # 因为是无向网图,矩阵对称

    def viewMGraphStruct(self):
        print(self.matrix)


def MiniSpanTree_Prim(G):
    INFINITY = 65535  # 用一个极大值代替∞
    adjvex = []  # 保存相关顶点间边的权值点下标
    lowcost = []  # 保存相关顶点间边的权值
    adjvex.append(0)  # 初始化第一个权值为0,即v0加入生成树
    lowcost.append(0)  # 初始化第一个顶点下标为0,表示从v0开始
    for i in range(1, G.numNodes):  # 循环除下标为0外的所有顶点
        lowcost.append(G.matrix[0][i])  # 将v0顶点与之有边的权值存入列表
        adjvex.append(0)  # 初始化都为v0的下标
    for i in range(1, G.numNodes):
        min_w = INFINITY  # 初始化最小权值为∞
        j, k = 1, 0
        while j < G.numNodes:  # 循环全部顶点
            if lowcost[j] != 0 and lowcost[j] < min_w:  # 如果权值不为0且权值小于min_w
                min_w = lowcost[j]  # 让当前值成为最小值
                k = j  # 记录最小值的下表,存入k
            j += 1
        print("({},{})".format(adjvex[k], k))  # 打印当前顶点边中权值最小的边
        lowcost[k] = 0  # 将当前顶点权值设为0,此顶点已完成任务
        for j in range(1,G.numNodes):  # 循环所有顶点
            if lowcost[j] != 0 and G.matrix[k][j] < lowcost[j]:  # 如果下标为k的顶点的各边权值小于此前这些顶点未被加入生成树的权值
                lowcost[j] = G.matrix[k][j]  # 将较小的权值存入lowcost相应位置
                adjvex[j] = k  # 将下标为k的顶点存入adjvex


if __name__ == '__main__':
    G = MGraph()
    G.createMGraph()
    G.viewMGraphStruct()
    MiniSpanTree_Prim(G)

以下图网图为例:

输入图的信息后生成的邻接矩阵和最小生成树如下所示:

[[0, 10, 65535, 65535, 65535, 11, 65535, 65535, 65535],
[10, 0, 18, 65535, 65535, 65535, 16, 65535, 12],
[65535, 18, 0, 22, 65535, 65535, 65535, 65535, 8], 
[65535, 65535, 22, 0, 20, 65535, 24, 16, 21],
[65535, 65535, 65535, 20, 0, 26, 65535, 7, 65535], 
[11, 65535, 65535, 65535, 26, 0, 17, 65535, 65535],
[65535, 16, 65535, 24, 65535, 17, 0, 19, 65535], 
[65535, 65535, 65535, 16, 7, 65535, 19, 0, 65535], 
[65535, 12, 8, 21, 65535, 65535, 65535, 65535, 0]]
(0,1)
(0,5)
(1,8)
(8,2)
(1,6)
(6,7)
(7,4)
(7,3)

算法时间复杂度分析:有代码中的循环嵌套可以看出对于有n个顶点的无向网图来说,算法的时间复杂度为O(n2)

2. 克鲁斯卡尔(Kruskal)算法

假设连通网N=(V,E),将N中的边按权值从小到大的顺序排列。

  1. 初始状态为只有n个顶点而无边的非连通图T={V,{ }},图中每个顶点自成一个连通分量。

  2. 在E 中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中(即不形成回路),否则舍去此边而选择下一条代价最小的边。

  3. 重复2,直至T中所有顶点都在同一个连通分量上为止。

    算法的实现要先将邻接矩阵的存储方式转换为边集数组的存储方式。

算法步骤:

  1. 将邻接矩阵的存储方式转换为边集数组的存储方式。
  2. 将边集数组Edge中的元素按权值从小到大排序。
  3. 依次查看数组Edge中的边,循环执行以下操作:
  • 依次从排序好的数组Edge中选出一条边(U1,U2)
  • 在Vexset中分别查找v1和v2 所在的连通分量vs1 和vs2,并进行判断:

*如果vs1 和vs2不等,表明所选的两个顶点分属不同的连通分量,输出此边,并合并vs1 和vs2两个连通分量。
*如果vs1 和vs2相等,表明所选的两个顶点属于同一个连通分量,舍去此边而选择下一条权值最小的边。

python实现:

 class MGraph():
    def __init__(self):
        self.vertex = []
        self.matrix = []
        self.numNodes = 0
        self.numEdges = 0

    def createMGraph(self):
        """创建无向网图的邻接矩阵表示"""
        INFINITY = 65535
        self.numNodes = int(input("请输入顶点数:"))
        self.numEdges = int(input("请输入边数:"))
        for i in range(self.numNodes):
            self.vertex.append(input("请输入一个顶点:"))

        for i in range(self.numNodes):
            self.matrix.append([])
            for j in range(self.numNodes):
                if i == j:
                    self.matrix[i].append(0)  # 初始化邻接矩阵
                else:
                    self.matrix[i].append(INFINITY)  # 初始化邻接矩阵

        for k in range(self.numEdges):  # 读入numEdges条边,建立邻接矩阵
            i = int(input("请输入边(vi,vj)上的下标i:"))
            j = int(input("请输入边(vi,vj)上的下标j:"))
            w = int(input("请输入边(vi,vj)上的权w:"))
            self.matrix[i][j] = w
            self.matrix[j][i] = self.matrix[i][j]  # 因为是无向网图,矩阵对称

    def viewMGraphStruct(self):
        print(self.matrix)


class EdgeNode():
    """边节点"""
    def __init__(self):
        self.begin = None
        self.end = None
        self.weight = None


def TransEdge(G):
    """将邻接矩阵转换为边集数组"""
    INFINITY = 65535  # 极大值
    k = 0
    edges = []  # 边集数组
    for i in range(G.numEdges):  # 注意这里千万不要用列表推导式,初始化边集数组,不然会出现列表越界错误
        e = EdgeNode()  # 实例化边节点
        edges.append(e)
    for i in range(G.numNodes - 1):
        for j in range(i + 1, G.numNodes):
            if 0 < G.matrix[i][j] < INFINITY:
                edges[k].begin = i
                edges[k].end = j
                edges[k].weight = G.matrix[i][j]
                k += 1
    # 按权由小到大排序(冒泡排序)
    for i in range(G.numEdges):
        for j in range(i + 1, G.numEdges):
            if edges[i].weight > edges[j].weight:
                edges[i], edges[j] = edges[j], edges[i]

    print("权排序后的为:")
    for i in range(G.numEdges):
        print("({},{}) {}".format(edges[i].begin, edges[i].end, edges[i].weight))

    return edges


def Find(parent, f):
    """查找连线顶点的尾部下标"""
    while parent[f] > 0:
        f = parent[f]
    return f


def MiniSpanTree_Kruskal(G):
    parent = [0 for _ in range(G.numNodes)]  # 定义并初始化一个列表用来判断边与边是否形成环路
    edges = TransEdge(G)  # 由邻接矩阵转换来的边集数组
    print("打印最小生成树:")
    for i in range(G.numEdges):  # 循环每一条边
        n = Find(parent, edges[i].begin)
        m = Find(parent, edges[i].end)
        if n != m:  # 如果n与m不相等,说明此边没有与现有的生成树形成环路
            parent[n] = m  # 将此边的结尾顶点放入下标为起点的的parent中,表示此顶点已经在生成树集合中
            print("({},{}) {}".format(edges[i].begin, edges[i].end, edges[i].weight))


if __name__ == '__main__':
    G = MGraph()
    G.createMGraph()
    G.viewMGraphStruct()
    MiniSpanTree_Kruskal(G)

以下图网图为例:

输入图的信息后生成的邻接矩阵和最小生成树如下所示:

[[0, 10, 65535, 65535, 65535, 11, 65535, 65535, 65535], 
[10, 0, 18, 65535, 65535, 65535, 16, 65535, 12], 
[65535, 18, 0, 22, 65535, 65535, 65535, 65535, 8],
[65535, 65535, 22, 0, 20, 65535, 24, 16, 21], 
[65535, 65535, 65535, 20, 0, 26, 65535, 7, 65535], 
[11, 65535, 65535, 65535, 26, 0, 17, 65535, 65535],
[65535, 16, 65535, 24, 65535, 17, 0, 19, 65535], 
[65535, 65535, 65535, 16, 7, 65535, 19, 0, 65535],
[65535, 12, 8, 21, 65535, 65535, 65535, 65535, 0]]
打印最小生成树:
(4,7) 7
(2,8) 8
(0,1) 10
(0,5) 11
(1,8) 12
(3,7) 16
(1,6) 16
(6,7) 19

算法时间复杂度分析:此算法的Find函数由边数e决定,时间复杂度为O(loge),而外面有一个for循环e次,所以克鲁斯卡尔算法的时间复杂度为O(eloge)。对比两个算法,克鲁斯卡尔算法主要针对边来展开,变数少时效率会非常高,所以对于稀疏图有很大优势,而普里姆算法对于稠密图,即边数非常多的情况会更好一些。

本文部分概念和图片来自:https://www.jianshu.com/p/d9ca383e2bd8

有关最小生成树的更多相关文章

  1. ruby - 获取数组中的值并最小化某个类属性的最优雅的方法是什么? - 2

    假设我有以下类(class):classPersondefinitialize(name,age)@name=name@age=ageenddefget_agereturn@ageendend我有一组Person对象。是否有一种简洁的、类似于Ruby的方法来获取最小(或最大)年龄的人?如何根据它对它们进行排序? 最佳答案 这样做会:people_array.min_by(&:get_age)people_array.max_by(&:get_age)people_array.sort_by(&:get_age)

  2. ruby - 返回空白页的最小 Capybara/Poltergeist 测试 - 2

    看来我正在回顾SO帖子中采取的步骤:Capybara,PoltergeistandPhantomjsandgivinganemptyresponseinbody.(如果你愿意,可以将其标记为重复,但我包含了一个最小的独立测试用例和版本号。)问题我做错了什么吗?我可以运行另一个可能有助于隔离问题的最小测试吗?文件:pgtest.rbrequire'rubygems'require'capybara'require'capybara/dsl'require'capybara/poltergeist'modulePGTestincludeCapybara::DSLextendselfdeft

  3. ruby - 寻找产品和商店的最佳组合以最小化成本的算法 - 2

    你好,Stackoverflow的人们,我经营一个网站,为用户寻找最便宜的书籍购买地点。这对于单本书来说很容易,但对于多本书来说,有时在一家商店购买一本书而在另一家商店购买另一本书会更便宜。目前我找到了销售用户列表中所有书籍的最便宜的商店,但我想要一个更智能的系统。这里有更多信息:一本书的价格对于一家商店来说是不变的。运费可能会有所不同,具体取决于书籍的数量或书籍的总值(value)。每个商店对象都可以获取一组书籍并返回运费。通常,并非每家书店都出售每一本书。不确定在这里链接到我的站点是否很酷,但它列在我的用户配置文件中。我希望能够找到最便宜的商店和书籍组合。我担心这需要一种蛮力方法-

  4. 用于进行线性或非线性最小二乘近似的 Ruby 库? - 2

    是否有Ruby库允许我对一组数据进行线性或非线性最小二乘法逼近。我想做的是:给定一系列[x,y]数据点针对该数据生成线性或非线性最小二乘法近似值库不必弄清楚它是否需要进行线性或非线性近似。库的调用者应该知道他们需要什么类型的回归我不想尝试移植某些C/C++/Java库来获得此功能,因此我希望有一些现有的Ruby库可供我使用。 最佳答案 尝试使用“statsample”gem。您可以使用下面提供的示例执行对数、指数、幂或任何其他转换。我希望这有帮助。require'statsample'#IndependentVariablex_da

  5. ruby-on-rails - 如何在 Rails 3 的表格列中找到最小值 - 2

    嗨,假设我有一个table(ads),其中有一个column(views)观看次数21463如何找到该列中的最小值?有什么简单的方法可以做到这一点?这就是我的@ads=Ad.all@show_this_ad=@ads.min(:views)这给了我一个“参数数量错误(1代表0)错误”@ads=Ad.all@show_this_ad=@ads.minimum(:views)这给了我一个“未定义的方法错误” 最佳答案 Ad.minimum(:views)应该可以您仍然可以添加更多限制,例如:Ad.where(:user_id=>1234

  6. ruby - 在 Ruby 中获取整数数组的最小公倍数 - 2

    我知道,在Ruby中,您可以使用Integer#lcm求两个数的最小公倍数的方法。例如:10.lcm(15)#=>30是否有一种有效的(或内置于核心或标准库中)方法来获取给定数组中所有整数的最小公倍数?例如:[5,3,10,2,20].lcm#=>60 最佳答案 任何需要两个操作数的操作都可以通过folding迭代地应用于一个集合它:Enumerable#inject/reduce.为了覆盖空情况,将identityelement作为第一个参数传递操作的最小公分母为1。[5,3,10,2,20].reduce(1){|acc,n|a

  7. Ruby:如何找到最小数组元素的索引? - 2

    有什么办法可以更优雅地重写这个吗?我认为,这是一段糟糕的代码,应该重构。>>a=[2,4,10,1,13]=>[2,4,10,1,13]>>index_of_minimal_value_in_array=a.index(a.min)=>3 最佳答案 我相信这只会遍历数组一次并且仍然很容易阅读:numbers=[20,30,40,50,10]#=>[20,30,40,50,10]elem,idx=numbers.each_with_index.min#=>[10,4] 关于Ruby:如何找

  8. ruby - 如何使用 Ruby 找到最小值/最大值 - 2

    我想使用min(5,10)或Math.max(4,7)。Ruby中有实现这种效果的函数吗? 最佳答案 你可以做到[5,10].min或[4,7].max它们来自Enumerablemodule,因此任何包含Enumerable的内容都将具有可用的方法。v2.4引入了自己的Array#min和Array#max,它们比Enumerable的方法快得多,因为它们跳过调用#each.@nicholasklick提到了另一个选项,Enumerable#minmax,但这次返回一个[min,max]数组。[4,5,7,10].minmax=>

  9. javascript - 与用户操作交互的模态弹出窗口,如用户设置的最大化、最小化、关闭、调整大小和可拖动 - 2

    我需要与用户操作交互的模态弹出窗口,如下图所示。但是这个模态弹出窗口应该是纯java脚本。严禁使用JQuery或JQuery插件。期待您的来信。提前致谢。 最佳答案 这里我分享一些插件,基本上都是用Jquery和Javascript创建的。无论您在纯JavaScript中寻找什么,都可以使用http://alpha.jspanel.de/media/demos/nojquery/index.php另一个是使用Jquery创建的。是https://lobianijs.com/site/lobipanel#examples使用第一个选项

  10. 使用 Genie 效果最小化 <div> 的 Javascript? - 2

    这个问题不太可能帮助任何future的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visitthehelpcenter.关闭10年前。我想最小化一个框,就像Mac上Sprite效果中的弹出窗口一样,我找到了jQueryTransfereffect很接近,但还不够,它只画了一个轮廓,并没有真正涂抹物体,有没有办法模仿Mac的Sprite最小化效果?谢谢。

随机推荐