JHHK

欢迎来到我的个人网站
行者常至 为者常成

【进阶】10、图(四)

目录

生成树(Spanning Tree)

一、生成树(Spanning Tree),也称为支撑树

连通图的极小连通子图,它含有图中全部的 n 个顶点,恰好只有 n – 1 条边

img

二、最小生成树(Minimum Spanning Tree)

最小生成树(Minimum Spanning Tree,简称MST)
也称为最小权重生成树(Minimum Weight Spanning Tree)、最小支撑树
是所有生成树中,总权值最小的那棵
适用于有权的连通图(无向)

最小生成树在许多领域都有重要的作用,例如
要在 n 个城市之间铺设光缆,使它们都可以通信,铺设光缆的费用很高,且各个城市之间因为距离不同等因素,铺设光缆的费用也不同如何使铺设光缆的总费用最低?

如果图的每一条边的权值都互不相同,那么最小生成树将只有一个,否则可能会有多个最小生成树

求最小生成树的2个经典算法
Prim(普里姆算法)
Kruskal(克鲁斯克尔算法)

切分定理

切分(Cut):把图中的节点分为两部分,称为一个切分

下图有个切分 C = (S, T),S = {A, B, D},T = {C, E}

img

横切边(Crossing Edge):如果一个边的两个顶点,分别属于切分的两部分,这个边称为横切边

比如上图的边 BC、BE、DE 就是横切边

切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树

Prim算法

一、原理

假设 G = (V,E) 是有权的连通图(无向),A 是 G 中最小生成树的边集

算法从 S = { u0 }(u0 ∈ V),A = { } 开始,重复执行下述操作,直到 S = V 为止

找到切分 C = (S,V – S) 的最小横切边 (u0,v0) 并入集合 A,同时将 v0 并入集合 S

二、执行流程

img

img

img

三、代码实现

//prim 算法
-(NSSet<LCEdgeInfo*>*)__prim{
    //从一个vertex开始
    NSEnumerator * itrator = self.vertics.allValues.objectEnumerator;
    LCVertex* vertex = itrator.nextObject;
    if (vertex == nil) return nil;
    
    //最终返回的边集合
    NSMutableSet * edgeInfos = [NSMutableSet set];
    
    //已经分离出的vertex
    NSMutableSet * addedVertices = [NSMutableSet set];
    [addedVertices addObject:vertex];
    
    //outEdges 创建小顶堆 按边的权重进行比较
    LCBinaryHeap * heap = [LCBinaryHeap binaryHeapWithSet:vertex.outEdges
                                               comparator:self.edgeComparator];
    
    //所有顶点的数量
    int verticesSize = (int)self.vertics.allKeys.count;
    
    while (!heap.isEmpty && addedVertices.count < verticesSize) {
        
        LCEdge* edge = heap.remove;
        
        //如果edge.to包含在addedVertices 证明这条路已经被选过了
        if ([addedVertices containsObject:edge.to]) continue;
        
        //添加最小权重边
        [edgeInfos addObject:edge.info];
        //添加分离出的顶点
        [addedVertices addObject:edge.to];
        //添加分离出顶点的所有outedge,为下次切分做准备
        [heap addAllWithSet:edge.to.outEdges];
    }
    
    return [edgeInfos copy];
}

Kruskal算法

一、原理

按照边的权重顺序(从小到大)将边加入生成树中,直到生成树中含有 V – 1 条边为止( V 是顶点数量)
若加入该边会与生成树形成环,则不加入该边

二、执行流程

从第3条边开始,可能会与生成树形成环

img

img

img

三、代码实现

//kruskal算法
-(NSSet<LCEdgeInfo*>*)__kruskal{
    
    int edgeSize = (int)self.vertics.count-1;
    if (edgeSize == -1) return nil;

    //最终返回的边集合
    NSMutableSet * edgeInfos = [NSMutableSet set];
    
    //Edges 创建小顶堆 按边的权重进行比较
    LCBinaryHeap * heap = [LCBinaryHeap binaryHeapWithSet:self.edges
                                               comparator:self.edgeComparator];

    //并查集,用于判断加入边是否会形成环
    LCGenericUnionFind * unionFind = [[LCGenericUnionFind alloc] init];
    [self.vertics enumerateKeysAndObjectsUsingBlock:^(id key, LCVertex* vertex, BOOL* stop) {
        [unionFind makeSet:vertex];
    }];
    
    while (!heap.isEmpty && edgeInfos.count < edgeSize) {
        LCEdge * edge = [heap remove];
        
        //如果from 跟 to 在 同一个集合 那么合并后就会形成环
        if([unionFind isSameWithV1:edge.from v2:edge.to]) continue;
        
        [edgeInfos addObject:edge];
        [unionFind unionWithV1:edge.from v2:edge.to];
    }
      
    return [edgeInfos copy];
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.