• 图之最小生成树问题


    星空
    这篇文章用来复习最小生成树问题.
    此问题要解决的是在一个无向图中找出一颗最小生成树(minimum spanning tree),这个问题对有向图也有意义,不过找起来更困难.
    引用wiki对Prim的描述:

    In computer science, Prim's algorithm is a greedy algorithm that finds a minimum spanning tree for a weighted undirected graph.

    大体而言,一个无向图的最小生成树就是图中那些连接所有顶点的边构成的树,且其总价值最低.
    解决这个问题至少有两个算法,但我只实现了第一种:

    • Prim
    • Kruskal

    Prim算法:

    Prim算法和Dijkstra基本类似(参见Dijkstra博文),对每个顶点都有dv,pv,known,不同之处在于dv的定义不同,所以更新法则也不同,在每个顶点V被选取后,对每个与V邻接的未知顶点W,会触发的更新dw=min(dw,Cv-w),Cv-w指的是V-W的权值.即它并没有像Dijkstra那样判断累加值的大小,Prim只是在比较当前的大小,选取最小的那个权值.
    由于仅是更新法则的不同,所以在实现的时候只需对其稍作改动即可:

    // 使用邻接表存储图
    //...
    			// 若此相邻点未被访问
    			if (getVertexState(node->verIndex) == NOVISIT)
    			{
    				// 获取更新的权值
    				int updateDis = node->weight;
    				// 判断更新后的权值是否小于现有的权值
    				if (updateDis < distance[node->verIndex])
    				{
    					// 更新现有权值
    					distance[node->verIndex] = updateDis;
    					// 记录当前顶点的上一个顶点编号
    					path[node->verIndex] = minDistanceVertex;
    				}
    			}
    

    关于路径的记录:

    其实这也谈不上路径的记录,path只是记录了一对一对的顶点.
    例如 由< path的索引(顶点编号),索引值>构成

    < V1,-1>,< V2,V1>,

    < V3,V4>,< V4,V1>,

    < V5,V7>,< V6,V7>,

    < V7,V4>

    这些顶点集合构成了一颗最小生成树.

    关于Prim:

    Prim算法是在无向图上运行的,不用堆时的运行时间为O(V2),对于一个稠密图来说还是不错的,使用二叉堆的运行时间是O(E · log(V)),但似乎最好是使用斐波那契堆与邻接表的结合,其运行时间为O(E + V · log(V)),但这仅仅是从时间复杂度上取得了优势.

    实现中可能会用到的变量:

    我使用的是邻接表存储图

    	// 记录距离的数组
    	int * distance;
    	// 记录路径的数组
    	int * path;
    	// 记录顶点访问状态
    	VISITEDSTATE * visited;
    

    详细代码见我的github


    Kruskal 算法:

    引用wiki对其的描述:
    Kruskal's algorithm is a minimum-spanning-tree algorithm which finds an edge of the least possible weight that connects any two trees in the forest. It is a greedy algorithm in graph theory as it finds a minimum spanning tree for a connected weighted graph adding increasing cost arcs at each step

    • 连续的按照最小的权值选择边,并且当所选的边不产生圈时,就把它作为取定的边.

    • 形式上,Kruskal算法像是在处理一个树的集合(森林),开始的时候,存在V颗单节点的树,而添加一条边就会使两棵树合并成一棵树,当算法结束的时候,就只有一棵树了,这棵树就是最小生成树:

    • 对于一条边(u,v),若u和v在同一个集合中,那么连接他们的边就要放弃,因为他们已经连通了.因此若再添加此边就会增加一个圈.若这两个顶点不再同一个集合中,则将该边加入,并对含有(u,v)的这两个集合实施一次合并,因此可以通过并查集来实现.

    • 代码描述如下:

    // 最小生成树的边数 = 顶点树 - 1
    while (EdgesAccepted < NumVertex - 1)
    {
    	// 从由边建立的最小堆H中取出堆首元素 E = (u,v)
        E = DeleteMin(H);
        
        // 查找u顶点所在的集合
        Uset = Find(u,S);
        // 查找v顶点所在的集合
        Vset = Fins (v,S);
        
        if(Uset != Vset)
        {
        	// 找到一条生成树的边
            EdgesAccepted ++ ;
            // 将Uset和Vset合并
            setUnion(S,Uset,Vset);
        }
    }
    

    关于Kruskal:

    该算法的最坏情况情形运行时间为O(E · log(E)),获得了堆的特性,由于E=O(V2),因此运行时间实际上是O(E · log(V)),但是由于实际情况中会有孤立的点,故实践中该算法要比这个时间界要快一些.

  • 相关阅读:
    常用CDN
    SQL语句小结
    jQuery源码解析----domManip
    服务治理 SpringCloud Eureka
    docker容器操作
    docker镜像操作常用命令
    Maven
    Centos6解决网络不可达
    MyBatis
    SpringMVC
  • 原文地址:https://www.cnblogs.com/leihui/p/6038185.html
Copyright © 2020-2023  润新知