• 重学数据结构系列之——森林之并查集(Disjoint set)


    1.森林


    森林就是有很多的树组成的

    2.森林(并查集)的初始化


    每个元素都是各自构成一个集合,比如说自然数1到3,初始化时就3个集合{1},{2},{3},(之后我们会实现合并操作,将两集合合并,最顶部的祖先就是该集合的代表元素,比如说下面的c和f,而且他们的父亲指针指向自己)

    3.大概长什么样



    4.简单实现

    #include <iostream>
    using namespace std;
    
    class DisjointSet {
    private:
        int *father;
    public:
        //构造函数
        //size为元素的个数
        //一开始时,每个元素自己组成一个集合(就集合中只有自己一个元素),
        //那么下面father[i] = i,就是让父亲为他们自己
        DisjointSet(int size) {
            father = new int[size];
            for (int i = 0; i < size; ++i) {
                father[i] = i;
            }
        }
        ~DisjointSet() {
            delete[] father;
        }
        //node:要寻找结点的编号
        //功能:查找node结点在哪个集合,就是node所在集合的最顶部的元素
        int find_set(int node) {
            if (father[node] != node) {
                return find_set(father[node]);
            }
            return node;
        }
        //合并node1和node2所在的集合
        bool merge(int node1, int node2){
            //先找到他们的最老的老祖宗(所在集合的最顶部的元素)
    		int ancestor1 = find_set(node1);
    		int ancestor2 = find_set(node2);
            //他们的祖宗不一样才让集合ancestor1成为集合ancestor2的儿子
            if (ancestor1 != ancestor2) {
    			father[ancestor1] = ancestor2; 
                return true;
    		}
            //来到这就是最老的老祖宗一样,他们在同一集合,就无需合并了
            return false;
    	}
    };
    
    int main() {
        DisjointSet dsu(100);
        int m, x, y;
        cin>>m;
        for (int i = 0; i < m; i++) {
    		cin>>x>>y;
            bool ans = dsu.merge(x,y);
            if (ans) {
    			cout << "success"<<endl;
    		}else{
    			cout << "failed" <<endl;
    		}
    	}
        return 0;
    }

    5.  按秩合并优化+路径压缩优化版


    因为合并的极端情况是让最终的集合成为一条链,当我们查找该集合的最后一个元素的代表元素(最老的那个祖宗),那么就要向上找很多次,上面的两种方法就是解决这种情况的,两种都用效果最佳
    按秩合并优化:在数据结构加个rank来表示当前结点下面有多少个孩子,当合并时,我们就将代表元素的rank小的集合作为代表元素的rank大的集合的儿子,这样就不会增加rank,如果将代表元素的rank大的集合作为代表元素的rank小的集合的儿子,最后合并后的结合的rank就+1了
    路径压缩优化:就是本来最底部的元素上有父亲,再上有爷爷,再上还有,最上就是代表元素,这样就使得这个整条链似的,那么我们将元素合并的时候将合并过来的元素全部都是代表元素的孩子,是直接孩子

    class DisjointSet{
    private:
    	//这个用来储存每个元素的父亲是第几个元素
    	int* father, *rank;
    public:
    	//构造函数
    	//size为元素的个数
    	//一开始时,每个元素自己组成一个集合(就集合中只有自己一个元素),
    	//那么下面father[i] = i,就是让父亲为他们自己
    	DisjointSet(int size){
    		father = new int[size];
    		rank = new int[size];
    		for (int i = 0; i < size; i++) {
    			father[i] = i;
    			rank[i] = 0;
    		}
    	}
    	~DisjointSet(){
    		delete[] father;
    		delete[] rank;
    	}
    	//路径压缩优化了的
    	int find_set(int node){
    		//就是使node的父亲就是代表元素
    		if (father[node] != node) {
    			father[node] = find_set(father[node]);
    		}
    		return father[node];
    	}
    	bool merge(int node1, int node2){
    		int ancestor1 = find_set(node1);
    		int ancestor2 = find_set(node2);
    		if (ancestor1 != ancestor2) {
    			//按秩合并优化
    			//如果ancestor1的孩子较多,就交换
    			if (rank[ancestor1] > rank[ancestor2]) {
    				swap(ancestor1, ancestor2);
    			}
    			father[ancestor1] = ancestor2; 
    			//更新ancestor2的孩子数
    			rank[ancestor2] = (rank[ancestor1]+1) > rank[ancestor2] ? (rank[ancestor1]+1):rank[ancestor2];
    			return true;
    		}
    		return false;
    	}
    };













  • 相关阅读:
    测试代码运行效率
    用宏实现的单例模式
    上传控件的按钮改为图片
    ODAC的使用笔记
    ASP.NET防止重复提交
    向SharePoint图片库添加Item
    获取页面上用户控件的子控件ID
    SharePoint的WebService的应用
    GridView中HyperLinkField的链接使用JavaScript问题
    C#日期验证的正则表达式
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286549.html
Copyright © 2020-2023  润新知