zoukankan      html  css  js  c++  java
  • 路径压缩

    -------------------siwuxie095

       

       

       

       

       

       

       

       

    路径压缩

       

       

    基于 size 的优化和基于 rank 的优化,主要都是针对 Union 操作

    的优化,实际上,Find 操作其实也是可以优化的

       

       

    Find 操作就是从一个节点开始,通过它的指向父亲的指针,不断

    向上追溯,直至根节点,也就是寻找根的过程

       

       

    在这个过程中,其实已经将从这个节点开始,一直到根节点的每

    个节点,都遍历了一遍

       

       

    之前所实现的 Find 操作只是直接的遍历,没有做任何操作,但在

    这个过程中,其实可以对节点做一些变化,让树的层数更少

       

       

    具体方法:尝试着在执行 Find 操作(自底向上,不断溯源)时,

    如果没有找到根节点,就想办法把这些节点向上挪一挪,称这个

    过程为 路径压缩(Path Compression)

       

       

    「即 判断当前节点是否为根,如果不是(即 没有找到),就把

    以当前节点为根的子树整体向上挪,挪到当前节点的父亲的父亲

    处,下次再判断当前节点的父亲(即 原来的祖先)是否为根

    相当于跳了一步,所以叫 路径压缩」

       

       

       

       

       

    假设存在并查集,如下:

       

       

       

       

    如果要 find( 4 ),发现 parent[4] = 3,而 4,判断 4 不是根节点

       

    1)在原来的 Find 操作中,要继续向上找,判断 3 是不是根节点 …

       

    2)而现在的路径压缩则是压缩一步,让 4 去连接它父亲的父亲,也

    就是 2。此时,整棵树的层数就变少了,即 被压缩了

       

    注意:这里是先判断是否为根,如果 4 已经是根了,自然无须去连接

    它父亲的父亲,直接返回即可。如果 3 已经是根了,因为 3 的父亲是

    3,所以让 4 去连接它父亲的父亲,依然成立

       

       

       

       

    下面要考察的就是 4 的父亲 2,即 find( 2 ),这里没有考察 3

    相当于跳了一步

       

    发现 parent[2] = 1,而 2,即 2 也不是根节点,继续路径压

    缩,让 2 去连接它父亲的父亲,也就是 0

       

       

       

       

    下面要考察的就是 2 的父亲 0,即 find( 0 ),这里没有考察 1,

    相当于又跳了一步

       

    发现 parent[0] = 0,即 0 就是根节点,返回 0 即可

       

       

       

       

       

    find( 4 ) 的过程中,成功把这颗树的根节点 0 返了回去,

    同时,整棵树的层数也大大地减少了,这便是 路径压缩

       

       

       

       

       

       

    路径压缩还可以继续优化,最优的情况(理论上),能将整个路径

    压缩成 2 层,所有的节点都指向一个根,如下:

       

       

       

    「注意:这种理论上的优化,实际用时更长(主因:递归的开销)」

       

       

       

       

       

       

       

    程序 1:路径压缩的实现

       

    UnionFind.h:

       

    #ifndef UNIONFIND_H

    #define UNIONFIND_H

       

    #include <iostream>

    #include <cassert>

    using namespace std;

       

       

       

    //并查集:Quick Union + rank + path compression

    namespace UF

    {

       

    class UnionFind

    {

       

    private:

    int* parent;

    int* rank; // rank[i]表示以i为根的集合所表示的树的层数

    int count;

       

    public:

    UnionFind(int count)

    {

    this->count = count;

    parent = new int[count];

    rank = new int[count];

    //在初始情况下,并查集里的元素,两两之间互不连接

    for (int i = 0; i < count; i++)

    {

    parent[i] = i;

    rank[i] = 1;

    }

    }

       

       

    ~UnionFind()

    {

    delete []parent;

    delete []rank;

    }

       

       

    int size()

    {

    return count;

    }

       

       

    int find(int p)

    {

       

    assert(p >= 0 && p < count);

       

    // path compression 1

    while (p != parent[p])

    {

    //路径压缩

    parent[p] = parent[parent[p]];

    p = parent[p];

    }

       

    return p;

    }

       

       

    bool isConnected(int p, int q)

    {

    return find(p) == find(q);

    }

       

       

    void unionElements(int p, int q)

    {

       

    int pRoot = find(p);

    int qRoot = find(q);

       

    if (pRoot == qRoot)

    {

    return;

    }

       

    //rank小的那棵树的根节点指向rank大的那棵树的根节点

    if (rank[pRoot] < rank[qRoot])

    {

    parent[pRoot] = qRoot;

    }

    else if (rank[qRoot] < rank[pRoot])

    {

    parent[qRoot] = pRoot;

    }

    // rank[pRoot] == rank[qRoot]

    else

    {

    //可互换

    parent[pRoot] = qRoot;

    rank[qRoot] ++;

    }

       

    }

       

       

    void show()

    {

    for (int i = 0; i < count; i++)

    {

    cout << i << " : " << parent[i] << endl;

    }

    }

    };

    }

       

    //路径压缩:在寻找根的时候,两步一跳,比原来的 Find 操作要快,

    //与此同时,如果下一次要寻找这棵树上某个元素的根节点,由于层

    //数变低,相应的速度也会快很多

       

    #endif

       

       

       

    UnionFindTestHelper.h:

       

    #ifndef UNIONFINDTESTHELPER_H

    #define UNIONFINDTESTHELPER_H

       

    #include "UnionFind.h"

    #include <iostream>

    #include <ctime>

    using namespace std;

       

       

       

    namespace UnionFindTestHelper

    {

       

    void testUF(int n)

    {

    //设置随机种子

    srand(time(NULL));

    UF::UnionFind uf = UF::UnionFind(n);

       

    time_t startTime = clock();

       

    //先进行n次的并,即 Union 操作

    for (int i = 0; i < n; i++)

    {

    int a = rand() % n;

    int b = rand() % n;

    uf.unionElements(a, b);

    }

       

    //再进行n次的查,即 Find 操作

    for (int i = 0; i < n; i++)

    {

    int a = rand() % n;

    int b = rand() % n;

    uf.isConnected(a, b);

    }

       

    time_t endTime = clock();

       

    //打印2*n个操作耗费的时间

    cout << "UF, " << 2 * n << " ops, " << double(endTime - startTime) / CLOCKS_PER_SEC

    << " s" << endl;

    }

    }

       

       

    #endif

       

       

       

    main.cpp:

       

    #include "UnionFindTestHelper.h"

    #include <iostream>

    using namespace std;

       

       

       

    int main()

    {

    //规模是一百万

    int n = 1000000;

       

    UnionFindTestHelper::testUF(n);

       

    system("pause");

    return 0;

    }

       

       

       

    运行一览:

       

       

       

       

       

       

       

       

    程序 2:路径压缩的优化(在程序 1 的基础上,修改 UnionFind.h 即可)

       

    UnionFind.h:

       

    #ifndef UNIONFIND_H

    #define UNIONFIND_H

       

    #include <iostream>

    #include <cassert>

    using namespace std;

       

       

       

    //并查集:Quick Union + rank + path compression

    namespace UF

    {

       

    class UnionFind

    {

       

    private:

    int* parent;

    int* rank; // rank[i]表示以i为根的集合所表示的树的层数

    int count;

       

    public:

    UnionFind(int count)

    {

    this->count = count;

    parent = new int[count];

    rank = new int[count];

    //在初始情况下,并查集里的元素,两两之间互不连接

    for (int i = 0; i < count; i++)

    {

    parent[i] = i;

    rank[i] = 1;

    }

    }

       

       

    ~UnionFind()

    {

    delete []parent;

    delete []rank;

    }

       

       

    int size()

    {

    return count;

    }

       

       

    int find(int p)

    {

       

    assert(p >= 0 && p < count);

       

    //path compression 2

    if (p != parent[p])

    {

    //这个版本的路径压缩实际上用时更长

    //虽然理论上更优(采用递归)

    parent[p] = find(parent[p]);

    }

    //此时,parent[p]是整棵树的根节点

    return parent[p];

    }

       

       

    bool isConnected(int p, int q)

    {

    return find(p) == find(q);

    }

       

       

    void unionElements(int p, int q)

    {

       

    int pRoot = find(p);

    int qRoot = find(q);

       

    if (pRoot == qRoot)

    {

    return;

    }

       

    //rank小的那棵树的根节点指向rank大的那棵树的根节点

    if (rank[pRoot] < rank[qRoot])

    {

    parent[pRoot] = qRoot;

    }

    else if (rank[qRoot] < rank[pRoot])

    {

    parent[qRoot] = pRoot;

    }

    // rank[pRoot] == rank[qRoot]

    else

    {

    //可互换

    parent[pRoot] = qRoot;

    rank[qRoot] ++;

    }

       

    }

       

       

    void show()

    {

    for (int i = 0; i < count; i++)

    {

    cout << i << " : " << parent[i] << endl;

    }

    }

    };

    }

       

    //路径压缩的最优的情况,能将整个路径压缩成 2 层,所有的节点

    //都指向一个根

    //

    //在这种情况下,搜索任何节点最多只要一步,就找到了根节点

    //

    //但实际上用时更长一些,这个时间消耗主要是递归过程所产生的开销

    //

    //不过,虽然递归过程产生了一些额外开销,但从逻辑上来讲,这样做

    //是更优的,只是实践效果并不是特别的好

    //

    //理论和实践通常会稍微有点出入,所以在实际的工作过程中也应该根据

    //实际情况,灵活选择最合适的方式,而不一定是理论上最优的那个方式

    //

    //

    //

    //此时,并查集的操作(UnionFind),时间复杂度近乎是O(1)

    //

    //注意:是近乎,不是完全。不难想象,经过路径压缩以后,在每次查找

    //以后,每一位元素就离根节点非常近了,甚至在的这个优化版本的路径

    //压缩,经过一次查询,查询路径上的所有节点,离根节点的距离,都变

    // 1 了。尽管如此,在这个过程中,依然有时间的消耗,并非每次操作

    //都是 O(1) 级别的

    //

    //对于这个时间的消耗,在数学上,数学家们发明了一个专门的函数来表达,

    //这个函数叫做 alpha

       

    #endif

       

       

       

       

       

    运行一览:

       

       

       

       

       

       

       

       

       

       

    【made by siwuxie095】

  • 相关阅读:
    H264 RTP封包原理(转载)
    FFmpeg的H264编码有内存泄漏吗??!!!
    最近在研究FFmpeg编解码
    罗一迦
    健康导报
    Vivado生成edf文件
    VIVADO生成MCS
    网络变压器
    Zynq 在Ubuntu上搭建编译环境
    [转]TimeQuest之delay_fall clock_fall傻傻分不清楚
  • 原文地址:https://www.cnblogs.com/siwuxie095/p/7002491.html
Copyright © 2011-2022 走看看