K:图相关的最小生成树(MST)

by admin on 2018年12月24日

有关介绍:

 按照树的特性可知,连通图的生成树是图的极小连通子图,它富含图中的全体终端,但只有结合一棵树的边;生成树又是图的翻天覆地无回路子图,它的边集是关系图中的所有终端而又没有形成回路的边。

 一个有n个顶点的连通图的生成树唯有n-1条边。若有n个顶点而个别n-1条边,则是非连通图(将其想成有n个顶点的一条链,则其为连通图的原则是起码有n-1条边);若多于n-1条边,则必定形成回路。值得注意的是,有n-1条边的生成子图,并不一定是生成树。此处,介绍一个定义。:指的是边带有权值的图。

 在一个网的拥有生成树中,权值总和最小的生成树,称之为最小代价生成树,也称为最小生成树。

”我只是一个过路人,从你的世界经过。我不敢太多不舍,怕您看出自己难过。”

最小生成树:

 依据生成树的概念,具有n个顶点的连通图的生成树,有n个顶点和n-1条边。因而,构造最小生成树的准则有以下3条:

  1. 唯其如此采纳图中的边构造最小生成树
  2. 当且仅当使用n-1条边来连续图中的n个顶点
  3. 无法运用发生回路的边

需要专注的某些是,虽然最小生成树一定存在,但其并不一定是绝无仅有的。以下介绍求图的最小生成树的六个优秀的算法,分别为克鲁斯卡尔(Carl)算法(kruskal)和普里姆算法(prim)

先是次会见是在列席初赛的时候。她相比较骄傲一直如此,不把其余此外选手放在眼里。她的口齿伶俐,讲话有心绪,当然,她当选了。他诙谐好玩,到她的时候,先是让我们讲讲大笑,然后就最先了他略显笨拙,但又可爱的上演。结果,他也相中了。她很如沐春风,因为一切都在她的掌控之中,她还是那么的骄傲自大,第一步已经完结,接下去是准备阶段,参预决赛,然后拿奖,没错,好像一切都是这样的稳操胜算。在他所擅长的世界,她就该这么。可是,这三回他错了。她和她分到了一组。这天,说好要在体育场馆一起谈谈问题。她先来了,坐在这里悠闲地看着课外书,过了片刻,他也来了。这是首先次他抬初步认真的看着眼前的这一个男孩,五官精致,小麦色皮肤,笑起来很难堪,给人一种沐浴阳光的清新感。他也感觉到了女孩的目光,略显羞涩地躲开了她的视线,和他打着照顾。他和他坐在这里有一句没一句的聊着,直到所有的partner都来了。在商讨的进程中,她日常看着她思考,没悟出他了解挺多的。她偶尔傻笑,因为她就是这样,时而幼稚的像个小孩儿,时而成熟的像个大人。听着partner的探究,她很少发言,怎么初赛的时候,就没发现有如此多厉害的人物呢?她想。他们谈论了很久,我们很心花怒放,但逐渐的都跑题了。

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

 克鲁斯Carl算法是基于边的权值递增的主意,依次找出权值最小的边建立的最小生成树,并且确定每一遍新增的边,无法促成生成树有回路,直到找到n-1条边停止。

主干思想:设图G=(V,E)是一个富有n个顶点的过渡无向网,T=(V,TE)是图的最小生成树,其中V是T的顶点集,TE是T的边集,则构造最小生成树的具体步骤如下:

  1. T的始发状态为T=(V,空集),即起先时,最小生成树T是图G的生成零图

  2. 将图G中的边遵照权值从小到大的各样依次选拔,若采纳的边未使生成树T形成回路,则进入TE中,否则摈弃,直至TE中带有了n-1条边停止

下图演示克鲁斯卡尔(Carl)算法的布局最小生成树的长河:

必发365乐趣网投手机版 1

其示意代码如下:

有关代码

package all_in_tree;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

import algorithm.PathCompressWeightQuick_Union;
import algorithm.UF;

/**
 * 该类用于演示克鲁斯卡尔算法的过程
 * @author 学徒
 *
 *由于每次添加一条边时,需要判断所添加的边是否会产生回路,而回路的产生,当且仅当边上的两个节点处在同一个连通
 *分支上,为此,可以使用Union-Find算法来判断边上的两个点是否处在同一个连通分支上
 *
 */
public class Kruskal
{
    //用于记录节点的数目
    private int nodeCount;
    //用于判断是否会形成回路
    private UF unionFind;
    //用优先级队列,每次最先出队的是其权值最小的边
    private Queue<Edge> q;
    //用于存储图的生成树
    private Edge[] tree;
    /**
     * 初始化一个图的最小生成树所需的数据结构
     * @param n 图的节点的数目
     */
    public Kruskal(int n)
    {
        this.nodeCount=n;
        tree=new Edge[n-1];
        unionFind=new PathCompressWeightQuick_Union(n);
        Comparator<Edge> cmp=new Comparator<Edge>()
        {
            @Override
            public int compare(Edge obj1,Edge obj2)
            {
                int obj1W=obj1.weight;
                int obj2W=obj2.weight;
                if(obj1W<obj2W)
                    return -1;
                else if(obj1W>obj2W)
                    return 1;
                else
                    return 0;
            }
        };
        q=new PriorityQueue<Edge>(11,cmp);
    }
    /**
     * 用于添加一条边
     * @param edge 所要进行添加的边
     */
    public void addEdge(Edge edge)
    {
        q.add(edge);
    }

    /**
     * 用于生成最小生成树
     * @return 最小生成树的边集合
     */
    public Edge[] getTree()
    {
        //用于记录加入图的最小生成树的边的数目
        int edgeCount=0;
        //用于得到最小生成树
        while(!q.isEmpty()&&edgeCount<this.nodeCount-1)
        {
            //每次取出权值最小的一条边
            Edge e=q.poll();
            //判断是否产生回路,当其不产生回路时,将其加入到最小生成树中
            int index1=unionFind.find(e.node1);
            int index2=unionFind.find(e.node2);
            if(index1!=index2)
            {
                tree[edgeCount++]=e;
                unionFind.union(e.node1, e.node2);
            }
        }
        return tree;
    }
}

/**
 * 测试用例所使用的类,该类的测试用例即为上图中中所示的Kruskal算法最小生成树的构造
 * 过程的示例图,且其节点编号从0开始,而不从1开始
 * @author 学徒
 *
 */
class Test
{
    public static void main(String[] args)
    {
        Kruskal k=new Kruskal(6);
        k.addEdge(new Edge(0,3,5));
        k.addEdge(new Edge(0,1,6));
        k.addEdge(new Edge(1,4,3));
        k.addEdge(new Edge(4,5,6));
        k.addEdge(new Edge(3,5,2));
        k.addEdge(new Edge(0,2,1));
        k.addEdge(new Edge(1,2,5));
        k.addEdge(new Edge(2,4,6));
        k.addEdge(new Edge(2,5,4));
        k.addEdge(new Edge(2,3,6));
        Edge[] tree=k.getTree();
        for(Edge e:tree)
        {
            System.out.println(e.node1+" --> "+e.node2+"  : "+e.weight);
        }
    }
}

/**
 * 图的边的数据结构
 * @author 学徒
 *
 */
class Edge
{
    //节点的编号
    int node1;
    int node2;
    //边上的权值
    int weight;

    public Edge()
    {
    }
    public Edge(int node1,int node2,int weight)
    {
        this.node1=node1;
        this.node2=node2;
        this.weight=weight;
    }
}


运行结果:
0 --> 2  : 1
3 --> 5  : 2
1 --> 4  : 3
2 --> 5  : 4
1 --> 2  : 5

ps:上述代码中所用到的Union-Find算法的连带代码及分析,请点击
K:Union-Find(并查集)算法
举办查看

分析
:该算法的时日复杂度为O(elge),即克鲁斯卡尔(Carl)算法的推行时间首要在于图的边数e,为此,该算法适用于针对稀疏图的操作

必发365乐趣网投手机版,在这之后,女孩儿的影象中就有了男孩。由于竞赛的案由,他们又要一并钻探了,她略显兴奋,又足以看到他了。那种既盼望又惶恐的痛感笼罩着女孩的心。这四次,他坐在她的边际,装作看课外书的他,略显不安总是动来动去,后来她意识可能男娃娃不爱好这样好动的女孩子吧。

普里姆算法(Prim):

 为描述的有利,在介绍普里姆算法前,给出如下有关距离的概念:

  1. 六个顶峰之间的相距:是指将顶点u邻接到v的涉嫌边的权值,即为|u,v|。若五个顶峰之间无边相连,则那五个极点之间的距离为无穷大

  2. 极限到顶点集合之间的偏离:顶点u到终极集合V之间的离开是指顶点u到终极集合V中颇具终端之间的偏离中的最小值,即为|u,V|=\(min|u,v| , v\in V\)

  3. 五个极点集合之间的离开:顶点集合U到极点集合V的相距是指顶点集合U到终极集合V中具有终端之间的离开中的最小值,记为|U,V|=\(min|u,V| , u\in U\)

基本考虑:假如G=(V,E)是一个持有n个顶点的连通网,T=(V,TE)是网G的最小生成树。其中,V是R的顶点集,TE是T的边集,则最小生成树的协会过程如下:从U={u0},TE=\(\varnothing\)开端,必存在一条边(u,v),u\(\in U\),v\(\in
V-U\),使得|u,v|=|U,V-U|,将(u,v)参加集合TE中,同时将顶点v*插手顶点集U中,直到U=V停止,此时,TE中必有n-1条边(最小生成树存在的状态),最小生成树T构造完毕。下图演示了使用Prim算法构造最小生成树的长河

必发365乐趣网投手机版 2

其示意代码如下:

连带代码

package all_in_tree;
/**
 * 该类用于演示Prim算法构造最小生成树的过程
 * @author 学徒
 *
 */
public class Prim
{
    //用于记录图中节点的数目
    private int nodeCount;
    //用于记录图的领接矩阵,其存储对应边之间的权值
    private int[][] graph;
    //用于记录其对应节点是否已加入集合U中,若加入了集合U中,则其值为true
    private boolean[] inU;
    //用于记录其生成的最小生成树的边的情况
    private Edge[] tree;
    //用于记录其下标所对的节点的编号相对于集合U的最小权值边的权值的情况
    private int[] min;
    //用于记录其下标所对的节点的最小权值边所对应的集合U中的节点的情况
    private int[] close;
    /**
     * 用于初始化
     * @param n 节点的数目
     */
    public Prim(int n)
    {
        this.nodeCount=n;
        this.graph=new int[n][n];
        //初始化的时候,将各点的权值初始化为最大值
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                graph[i][j]=Integer.MAX_VALUE;
            }
        }
        this.inU=new boolean[n];
        this.tree=new Edge[n-1];
        this.min=new int[n];
        this.close=new int[n];
    }

    /**
     *用于为图添加一条边 
     * @param edge 边的封装类
     */
    public void addEdge(Edge edge)
    {
        int node1=edge.node1;
        int node2=edge.node2;
        int weight=edge.weight;
        graph[node1][node2]=weight;
        graph[node2][node1]=weight;
    }

    /**
     * 用于获取其图对应的最小生成树的结果
     * @return 由最小生成树组成的边的集合
     */
    public Edge[] getTree()
    {
        //用于将第一个节点加入到集合U中
        for(int i=1;i<nodeCount;i++)
        {
            min[i]=graph[0][i];
            close[i]=0;
        }
        inU[0]=true;
        //用于循环n-1次,每次循环添加一条边进最小生成树中
        for(int i=0;i<nodeCount-1;i++)
        {
            //用于记录找到的相对于集合U中的节点的最小权值的节点编号
            int node=0;
            //用于记录其相对于集合U的节点的最小的权值
            int mins=Integer.MAX_VALUE;
            //用于寻找其相对于集合U中最小权值的边
            for(int j=1;j<nodeCount;j++)
            {
                if(min[j]<mins&&!inU[j])
                {
                    mins=min[j];
                    node=j;
                }
            }
            //用于记录其边的情况
            tree[i]=new Edge(node,close[node],mins);
            //修改相关的状态
            inU[node]=true;
            //修改其相对于集合U的情况
            for(int j=1;j<nodeCount;j++)
            {
                if(!inU[j]&&graph[node][j]<min[j])
                {
                    min[j]=graph[node][j];
                    close[j]=node;
                }
            }
        }
        return tree;
    }
}

class Edge
{
    //节点的编号
    int node1;
    int node2;
    //边上的权值
    int weight;

    public Edge()
    {
    }
    public Edge(int node1,int node2,int weight)
    {
        this.node1=node1;
        this.node2=node2;
        this.weight=weight;
    }
}

/**
 * 测试用例所使用的类,该类的测试用例即为上图中中所示的Prim算法最小生成树的构造
 * 过程的示例图,且其节点编号从0开始,而不从1开始
 * @author 学徒
 *
 */
class Test
{
    public static void main(String[] args)
    {
        Prim k=new Prim(6);
        k.addEdge(new Edge(0,3,5));
        k.addEdge(new Edge(0,1,6));
        k.addEdge(new Edge(1,4,3));
        k.addEdge(new Edge(4,5,6));
        k.addEdge(new Edge(3,5,2));
        k.addEdge(new Edge(0,2,1));
        k.addEdge(new Edge(1,2,5));
        k.addEdge(new Edge(2,4,6));
        k.addEdge(new Edge(2,5,4));
        k.addEdge(new Edge(2,3,5));
        Edge[] tree=k.getTree();
        for(Edge e:tree)
        {
            System.out.println(e.node1+" --> "+e.node2+"  : "+e.weight);
        }
    }
}


运行结果如下:
2 --> 0  : 1
5 --> 2  : 4
3 --> 5  : 2
1 --> 2  : 5
4 --> 1  : 3

总结:kruskal算法的岁月复杂度与求解最小生成树的图中的边数有关,而prim算法的刻钟复杂度与求解最小生成树的图中的节点的数目有关。为此,Kruskal算法更加适用于稀疏图,而prim算法适用于稠密图。当e>=n^2时,kruskal算法比prim算法差,但当e=O(n^2)时,kruskal算法却比prim算法好得多。

归来目录|·(工)·)

直到竞赛这天,她发布有失常态了,输得一败涂地,她心底很难受。绝不只是输掉了比赛,仿佛还输掉了别样什么事物。由于投机的预备不足贪玩,比赛这天总是傻笑,不能专心,一切的失误,就像影子一样笼罩着她。为何会这么,结局不是她想看看的。她善于总括失误,一方面可能是出于轻敌,另外,还可能是因为她的产出呢,自己开班变得小心,所有他出现的地点,女孩儿都不可能很好的表达,她以为温馨也许喜欢上她了。

比赛截至之后,他再也从没出现过,尽管在QQ上,有时跟他出言他都略带鄙视。她很哀伤,她不停地告知要好,他对自己又不佳,为啥要欣赏她?于是,在很长日子的大好下,她的生存又过来了事先他没现身的容貌。

莫不老天喜欢开玩笑。一回偶然,她又赶上了她。说了几句话之后,他就略显不耐烦的距离了,只留下她一个人在原地,看着她渐渐走远了。她爱好他的背影,喜欢她的走动模式,喜欢他的说话模式,喜欢她的处理,喜欢她的孤寂,喜欢他的整套,尤其是欣赏首次见她时的微笑,就像寒冷夏天里的一缕阳光,很暖很暖。后来,他再也未曾关联过他了。她的心底其实已经知道他不爱好她,可能她不希罕他这类的女人吧。没错,在本场竞赛中,她又输了,输得彻底。她天天都握先河机生怕漏掉他的每一条信息,把他设为特别关爱,为了能首先个看到他的动态。她每日出去,害怕看见他牵着另一个儿童的手从她后边经过。而他啊?或许在她的世界里,早就把他忘得一干二净,她也许只是一个过客。

前几日看到一篇著作,是说贾乃亮和李小璐的,标题是那样的”爱一个人爱到卑微,有错吗?”爱一个人是未曾错,但因为爱旁人弄丢了自己,或许从一先河就是错的。她很理性因为她精通可以放纵自己很喜爱他,但也不可以弄丢自己。虽然有时候她会从睡梦中惊醒,以为是他发的信息,结果令自己失望,明知这是不可能了。当期望攒得充足多而从不兑现的时候,就会化为失望。她想哭,却哭不出来,她也许爱过她,干脆利落。

“我爱你不后悔,也推崇故事结尾。”尽管日后不会再见,也冀望她平素能过得幸福。其实我们生命中的每一个人都不会无故的产出,他一定会教给你点什么,然后又匆匆离去,就像没有出现过一样。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图