数据结构第八周笔记(1)——图(下)(慕课浙大版本--XiaoYu)

148 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

8.1最小 生成 树 问题(Minimum Spanning Tree)

  1. 是一棵树

    1. 无回路
    2. 有|V|个顶点的话一定刚好有|V|-1条边(不多不少)
  2. 是生成树

    1. 包含了全部的顶点(图全部的顶点都一定要在这棵树里)
    2. |V|-1条边都在原始的图里

image-20220803215420096向这个生成树中任加一条边都一定构成回路

  1. 边的权重和最小(指这个数里面这些边的权重和是最小的)
  2. 最小生成树存在的话图一定是连通的,图连通的话那最小生成树也一定是存在的

贪心算法

  1. 什么是"贪":解决问题是一步一步来解决的,每一步都要最好的(眼前最好的)

  2. 什么是"好":权重最小的边

  3. 需要约束:

    1. 只能用图里面有的边(原图里面没有的是不能用的)
    2. 只能正好用掉|V|-1条边
    3. 不能有回路

8.1.1 Prim算法——让一棵小树长大

树的初始样子:image-20220803222042856 收到一半:image-20220803222226809

因为为了不直接形成回路,所以接下来不能走v2到v4的那条边,不然就直接结束了

最后生成的样子为:image-20220803222434567

上图的收集算法就是Prim算法,收集的过程有点像Dijkstra算法

回顾Dijkstra算法

void Dijkstra(Vertex s)
{
    while(1){
        V = 未收录顶点中dist最小者;
        if( 这样的V不存在 )
            break;
        collected[V] = true;
        for( V 的每个邻接点 W )
            if(collected[W] == false)
                if( dist[V]+E<v,w> < dist[W] ){
                    dist[W] = dist[V] + E<v,w>;
                    path[W] = V;
                }
    }
}

Prim算法

void Prim()
{
    MST = {s};//跟上方不同的是这里需要先生成一个最小生成树,开始的时候随便选一个根结点s收录进来。这个树怎么存?未必需要真的定义树节点,把树构建出来。可以parent[s] = -1,就是他的父节点的编号,跟并查集里面的概念差不多。这样当我们发现他的parent的
    while(1){
        V = 未收录顶点中dist最小者;//dist在这个最小生成树的问题里被定义成一个顶点v到这棵生成树的最小距离(他跟这个生成树里面已经被收罗进去的顶点之间,所有的距离里面最小的那个,把它定义成是v到这棵树的距离,我们每一次是要从这个距离里面找一个最小的)
        //Prim算法中的dist[V]应该初始化为:E(s,V)或者正无穷
        //E(s,V):顶点到这个树的距离是这个边的权重
        //如果V跟s之间没有直接的边的话,那我们一定要把它定义成正无穷(跟Dijkstra类似)
        if( 这样的V不存在 )//v不存在就直接跳出来,这时候有两种情况:一种就是全部顶点收录了,如果是这种情况那程序顺利结束
            //第二种情况就是所有未收录的顶点的dist全部都是无穷大,意味着剩下那些顶点到这棵树之间都没有边,整个图是不连通的
            break;
        将V收录进MST:把V收进最小生成树意味着这个顶点到这棵树的距离就变成0了(因为他已经是这棵树的一部分了)
        dist[V] = 0;//设成0就相当于已经收进来了
        for( V的每个邻接点 W )
            if( W未被收录 )//dist不是0就意味着没被收录
                if(E(v,w) < dist[W] ){//v到w之间有一条直接的边,而这个边的距离是小于原始的dist
                    dist[W] = E(v,w);//更新一下这个w的距离。v可能是边直接指向w的
                    parent[W] = V;
                }
    }
    if( MST中收的顶点不到|V|个 )
        Error("生成树不存在 or 图不连通")
}//这一步取决于V= 未收录顶点中dist最小者的做法。粗暴的做法得出来的整个时间复杂度就是T = O(|V|²),稠密图这样处理好

总结:Prim算法对稠密图效果好

8.1.2 Kruskal算法(将森林合并成树)

直接了当的贪心(直接找权重最小的边把它收进来)

image-20220804170722722

什么叫做把森林合并成树呢?就是在初始的状况下认为每一个顶点都是一颗树,然后通过不断的把边收进来,就把两棵树合并成一棵树了,最后就是把所有的7个节点并成一棵树

image-20220804170944493边为1收完收2,2收完看3和4和5,3跟部分4和5会形成回路所以跳过

到6的时候形成了最小生成树:image-20220804171148363 以上就是Kruskal算法的基本思想

Krustal算法的伪代码

void Kruskal( Graph G )
{
    MST = { };//刚开始为空集
    while( MST 中不到|V|-1 条边 && E中还有边 ){
        从 E 中取一条权重最小的边E(v,w);//(v,w)是下标,上面用小括号括起来的v,w也都是。时间复杂度取决于这一步(是所有边都搜集一遍还是其他的方法,采用最小堆的方法是最好的)/*最小堆*/
        将 E(v,w)从 E 中删除;//E(v,w)是边集合
        if( E(v,w)不在 MST 中构成回路)//检查这条边加到最小生成树之后是否构成回路/*并查集*/
            将E(v,w)加入MST;//不构成的话边就被加进来了
        else
            彻底无视 E(v,w);
    }
    //while判定的时候不到|v|-1条边就跳出来的情况是:还没收满,原图里面的边就都已经被删光了,没有边了
    if( MST 中不到|V|-1条边 )
        Error("生成树不存在");
}
当图非常稀疏的时候,也就是E跟v差不多同一个数量级的时候
T = O(|E|log|E|)
​
并查集:一开始认为每个顶点都是独立的一棵树(集合),当我们把一条边收进去的时候就意味着把两棵树并成一棵

并查集

信息补充
原理:
初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为。
​
查找
查找元素所在的集合,即根节点。
​
合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
​