图形结构(三)--- 路径规划,最小生成树

连通图的生成树的定义:所谓一个连通图的生成树是一个极小的连通子图,它含有图中的全部n个顶点,但是只足以构成一棵树的n-1条边。

  • 通过上面的定义可得出连通图生成树的3个条件:
    1、图示连通图。
    2、图中包含N个顶点。
    3、图中边的数量等于N-1。

例题:

假设现在有N个顶点,每个顶点连接的路径不一样。请你设计一个算法,快速找出能覆盖所有顶点的路径。\color{blue}{[可以使用任何编程语言实现]}

image.png

注意:这个问题并不是求两点之间的最短路径问题;而是设计一个路线,能够覆盖所有顶点。

准备工作

首先拿到图之后,将图转化为邻接矩阵的形式。下面两个算法都会用到邻接矩阵
创建邻接矩阵

let INFINITYC = 65535
let MAXVEX = 20

/* 创建邻接矩阵 */
struct MGraph {
    var arc = [Array<Int>]()
    var numVertexes:Int = 0
    var numEdges:Int = 0
}

func createMGraph(_ G: inout MGraph) -> Void {
    
    //输入边数和顶点数
    G.numEdges = 15   //边数
    G.numVertexes = 9 //顶点
    G.arc = [Array<Int>](repeating: [Int](repeating: 0, count: G.numVertexes), count: G.numVertexes)
    //初始化图
    for i in 0..<G.numVertexes {
        for j in 0..<G.numVertexes {
            if i == j {
                G.arc[i][j] = 0
            } else {
                G.arc[i][j] = INFINITYC
            }
        }
    }
    
    G.arc[0][1]=10;
    G.arc[0][5]=11;
    G.arc[1][2]=18;
    G.arc[1][8]=12;
    G.arc[1][6]=16;
    G.arc[2][8]=8;
    G.arc[2][3]=22;
    G.arc[3][8]=21;
    G.arc[3][6]=24;
    G.arc[3][7]=16;
    G.arc[3][4]=20;
    G.arc[4][7]=7;
    G.arc[4][5]=26;
    G.arc[5][6]=17;
    G.arc[6][7]=19;
    
    for i in 0..<G.numVertexes {
        for j in i..<G.numVertexes {
            G.arc[j][i] = G.arc[i][j]
        }
    }
}

算法一:普⾥姆(Prim)算法

该算法的关键点是俩个数组,adjvex[]用于记录相关顶点下标(eg: adjvex[5] == 0,表示此时V5与V0相连);lowcost[]用于记录相关顶点之间边的权值(eg: lowcost[1] == 10, 表示此时V0与V1之间边的权值为10);注意此时为初始状态,假定V0 已经存入树里面。

image.png


/**
 Prim算法生成最小生成树
 */
func MiniSpanTree_Prim(_ G: MGraph) -> Void {
    //计数
    var sum = 0
    
    //保存相关顶点下标, 初始化,假定所有顶点都与V0相连接
    var adjvex = [Int](repeating: 0, count: G.numVertexes)
    //保存相关顶点间的权值
    var lowcost = [Int]()
    
    //初始化第一个权值为0,即V0加入生成树
    lowcost.append(0)
    
    //1、初始化
    for i in 1..<G.numVertexes { //循环除下标为0以为的全部顶点
        lowcost.append(G.arc[0][i]) //将于V0相关的所有权值存入到数组中
    }
    
    //2、循环除了下标为0以外的全部顶点,找到lowcost数组中最小的顶点K
    for _ in 1..<G.numVertexes {
        /*初始化最小权值为 ∞*/
        var min = INFINITYC
        var k = 0
        
        ///获取当前能拿到的所有 边 中,权值最小的边的尾顶点
        var j = 0
        while j < G.numVertexes { //循环所有顶点
            /*如果当前权值不为0,且权值小于min*/
            if lowcost[j] != 0 && lowcost[j] < min {
                /*当前权值为最小值,并更新min*/
                min = lowcost[j]
                k = j
            }
            j += 1
        }
        
        /*打印当前顶点边中权值最小的边*/
        print("(V\(adjvex[k]), V\(k)) = \(G.arc[adjvex[k]][k])")
        
        sum += G.arc[adjvex[k]][k]
        
        //3、将当前顶点的权值设置为0,表示此顶点已经完成任务
        lowcost[k] = 0
        
        /**
         循环所有顶点,找到与顶点K相连接的顶点
         1.与顶点 k 相连接
         2.该节点没有被加入生成树
         3.顶点 k 与 顶点 j 之间的权值 小于 顶点 j 与其他顶点的权值,则更新lowcost数组
         */
        ///每次要遍历的顶点是随机的,所以要每次要从头开始遍历。但是不需要跟 0 进行比较
        for i in 1..<G.numVertexes { //遍历顶点(0之外,此时联想一下二位数组,假设在第二行,则从[1,1]开始,如果在第三行,则从[2,1]开始)
            if lowcost[i] != 0 && G.arc[k][i] < lowcost[i] {
                lowcost[i] = G.arc[k][i]
                adjvex[i] = k
            }
        }    }
    print("sum = \(sum))")
}

var G = MGraph.init()
createMGraph(&G)
MiniSpanTree_Prim(G)

输出结果为:

(V0, V1) = 10
(V0, V5) = 11
(V1, V8) = 12
(V8, V2) = 8
(V1, V6) = 16
(V6, V7) = 19
(V7, V4) = 7
(V7, V3) = 16
sum = 99

算法二:克鲁斯卡尔(Kruskal)算法

此算法的关键是,先规划路线,将每一条边的起始位置,结束位置以及权重都事先统计出来,然后在所有的路径中规划最小生成树
思路:
1、将 邻接矩阵 转化为 边表数组
2、对 边表数组 根据权值按照从小到大的顺序排序
3、遍历所有的边,通过 parent 数组找到边的连接信息,避免闭环问题
4、如果不存在闭环问题,则加入到 最小生成树种 种,并且修改 parent 数组

image.png

第一次执行最小生成树

image(1).png

第二次执行最小生成树

image(2).png

第三次执行最小生成树

image(3).png

以此类推,第八次执行最小生成树的时候

第八次执行最小生成树

注意:此时begin == 5,在parent数组中 parent[5] == 8;则找到8,parent[8] == 6,所以n = 6。因为end == 6,并且parent[6] == 0 所以m = 6。

image(8).png

就这样一直执行下去,直到执行完毕

Kruskal 算法代码如下:

1、定义边表数组的结构:

/* 对象数组Edge结构的定义 */
struct Edge {
    var begin:Int = 0
    var end:Int = 0
    var weight:Int = 0
}

2、创建排序函数查找函数,用于对边表数组进行排序和查找尾结点

func sort(_ G: MGraph, _ edges: inout [Edge]) -> Void {
    //根据权值大小进行排序(从小到大)
    for i in 0..<G.numEdges {
        for j in i+1..<G.numEdges {
            if edges[i].weight > edges[j].weight {
                edges.swapAt(i, j)
            }
        }
    }
    
    print("边集数组根据裙纸排序之后:")
    for k in 0..<G.numEdges {
        print("(\(edges[k].begin), \(edges[k].end)) \(edges[k].weight)")
    }
}

func Find(_ parent: [Int], _ f: Int) -> Int {
    var num = f
    while parent[num] > 0 {
        num = parent[num]
    }
    return num
}

3、规划最小生成树

/**
 生成最小生成树
 */
func MiniSpanTree_Kruskal(_ G: MGraph) -> Void {
    
    //定义一个数组用来判断边与边是否形成环路,用来记录顶点见的连接关系,通过它来防止最小生成树产生闭环
    var parent = [Int](repeating: 0, count: MAXVEX)
    
    //定义边集数组,edge的结构为begin,end,weight,均为整型
    var edges = [Edge](repeating: Edge.init(), count: MAXVEX)
    
    //1、构建边集数组
    var k = 0
    
    for i in 0..<G.numVertexes-1 {
        for j in i+1..<G.numVertexes {
            //如果当前路径的权值 != ∞
            if G.arc[i][j]<INFINITYC {
                //将路径对应的begin,end,weight 存储到edges 边集数组中
                edges[k].begin = i
                edges[k].end = j
                edges[k].weight = G.arc[i][j]
                
                //边集数组计算器 k++
                k += 1
            }
        }
    }
    
    //2、对边集数组进行排序
    sort(G, &edges)
    
    //3、计算最小生成树
    print("打印最小生成树:")
    var sum = 0
    
    /* 循环每一条边 G.numEdges 有15条边 */
    for i in 0..<G.numEdges {
        //获取begin,end 在parent数组中的信息
        //如果 n = m,将 begin 和 end 连接,就会产生闭环
        let n = Find(parent, edges[i].begin)
        let m = Find(parent, edges[i].end)
        
        /* 如果 n != m 说明此边没有与现有的生成树形成环路 */
        if n != m {
            /*将此边的结尾顶点放入下标为起点的parent中*/
            /*表示此顶点已经在生成树集合中*/
            parent[n] = m
            
            /*打印最小生成树路径*/
            print("(\(edges[i].begin), \(edges[i].end)) \(edges[i].weight)")
            
            sum += edges[i].weight
        }
    }
    
    print("sum = \(sum)")
}

var G = MGraph.init()
createMGraph(&G)
MiniSpanTree_Kruskal(G)

4、输出结果为:

边集数组根据裙纸排序之后:
(4, 7) 7
(2, 8) 8
(0, 1) 10
(0, 5) 11
(1, 8) 12
(3, 7) 16
(1, 6) 16
(5, 6) 17
(1, 2) 18
(6, 7) 19
(3, 4) 20
(3, 8) 21
(2, 3) 22
(3, 6) 24
(4, 5) 26
打印最小生成树:
(4, 7) 7
(2, 8) 8
(0, 1) 10
(0, 5) 11
(1, 8) 12
(3, 7) 16
(1, 6) 16
(6, 7) 19
sum = 99
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容