数据结构与算法——不相交集类的C++实现

简介:

不相交集类是将一些元素合并为不相交的各个集合。在同一个集合中的元素两两等价,不同集合中的元素不等价。

1.等价关系

等价关系必须满足下面三个性质:

(1):自反性,对于集合S中的任意元素a,a R a;(R为定义的关系,比如R为<=, >=等等)

(2);对称性,a R b当且仅当b R a

(3):传递性,若a R b且b R c,则a R c

2.动态等价性问题

集合S中元素a的等价类是集合S的一个子集,该等价类中包含所有与a有等价关系的元素。所以为确定a是否等价b,只需要验证a和b是否属于同一个等价类中。

find操作,它返回包含给定元素的等价类集合的名字。比如:find(a) == find(b),那么a和b处于同一个集合中。

添加操作,如果想添加关系a ~ b,那么首先要判断a和b是否有关系(可以通过find操作验证它们是否在同一个等价类中)。如果a和b不在同一个等价类中,那么要使用求并操作union,这种操作将含有a和b的两个等价类合并为一个等价类。把这种算法称为不相交集合的求并/查找算法。该算法是动态的,因为在算法的执行过程中,集合可以通过union操作而发生改变。

解决动态等价性问题的方案有两种,第一种方案可以保证指令find能够以常数最坏情形运行时间执行,而另一种方案可以保证操作union能够以常数最坏情形运行时间执行。但是两个操作不能同时以常数最坏情形时间执行。

第一种方案:为使find操作快,可以在一个是数组中保存每个元素的等价类的名字。此时find就是简单的O(1)查找。设执union(a,b),并设a在等价类i中,b在等价类j中,此时我们扫描该数组,将所有的i都改变为j。union的时间复杂度为O(N);

第二种方案:将所有在同一个等价类中的元素放到一个链表中,更新的时候不用搜索整个数组。如果我们还要跟踪每个等价类的大小,并在执行union时将较小的等价类的名字改为较大的等价类的名字。

3.基本数据结构

可以将每个元素都看作是一棵独立的树,每个集合的名字用树根表示,可以用一个数组就可以实现该思路。将所有的元素用一个数组表示,数组每个成员s[i]表示元素i的父亲,如果i是根,那么s[i]=-1;

vector<int> s;//存放每个元素的根节点或父节点

对元素x的一次find(x)操作通过返回包含x的树的根而完成。

合并操作union(root1, root2),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

下面是具体的例子:

初始化元素集合:0,1,2,3,4,5,6,7

合并操作union(4,5),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

合并操作union(6,7),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

4.灵巧求并算法

上面不相交集类中的合并函数是想当随意的,它通过使第二棵树成为第一棵树的子树而完成合并。

第一种改进方法:

对其进行简单的改进是借助任意的方法打破现在的随意性,使得总是较小的树成为较大的树的子树。将这种方法称为按大小求并。

如果不是按大小求并,那么随着合并的进行,某些集合树的深度会增加太多(大于logN),这意味这find操作的执行时间为O(logN)。

为了实现按大小合并的方法,需要记住每棵树的大小,可以让每个根元素包含它的树的大小的负值。合并的时候首先检查树的大小,将较小的树成为较大树的子树,新的树的大小为两棵树大小的和。

按大小合并的例子步骤:

第二种改进方法:

按高度求并,它同样保证所有的树的深度最多为O(logN)。我们跟踪每棵树的高度而不是大小并执行合并使得浅的树成为深的树的子树。只有两棵深度相等的树求并的时候树的高度才增加(树的深度加1)。

为了实现按高度求并,需要记住每棵树的高度,可以让每个根元素包含它的树的高度的负值。只有合并的两棵树高度相等的时候才需要更新树的高度(根元素的值减去1)。

按树高度合并的步骤:

5.主要成员函数

        explicit DisjSets(int numElements);
        ~DisjSets(){}

        int find(int x) const;//查找
        void unionSets(int root1, int root2);//合并
        void unionSetsBySize(int root1, int root2);//按树大小合并
        void unionSetsByHeight(int root1, int root2);//按树高度合并
        void print();//输出各个不相交集合类中元素

6.主要成员函数介绍

(1):查找操作find(int x) const,返回该元素所在集合的树的根。

/****************************************************************
*   函数名称:find(int x) const
*   功能描述: 查找元素x处于集合的名字
*   参数列表: x是要查找的元素
*   返回结果:返回元素x的集合名字
*****************************************************************/
int DisjSets::find(int x) const
{
    if(s[x] < 0)
        return x;
    else
        return find(s[x]);
}

(2):任意合并unionSets(int root1, int root2),将root2树作为root1的子树完成合并。

/****************************************************************
*   函数名称:unionSets(int root1, int root2)
*   功能描述: 合并两个集合
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSets(int root1, int root2)
{
    s[root2] = root1;
}

(3):按树的大小合并unionSetsBySize(int root1, int root2),使得较小的树成为较大树的子树

/****************************************************************
*   函数名称:unionSetsBySize(int root1, int root2)
*   功能描述: 按集合大小合并两个集合,使得较小的树成为较大树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSetsBySize(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较大
        s[root2] += s[root1];//更新树的大小
        s[root1] = root2;//root1的父节点变为root2
    }
    else{
        s[root1] += s[root2];
        s[root2] = root1;
    }
}

(4):按树高度合并两个集合unionSetsByHeight(int root1, int root2),使较浅的树成为较深的树的子树

/****************************************************************
*   函数名称:unionSetsByHeight(int root1, int root2)
*   功能描述: 按集合高度合并两个集合,使较浅的树成为较深的树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSetsByHeight(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较高
        s[root1] = root2;//直接合并, root1成为root2树的子树
    }
    else{//root1树比较高,或相等。

         //如果相等则更新树的高度
        if(s[root1] == s[root2])
            s[root1]--;
         s[root2] = root1;
    }
}

7.下面是测试主函数main()

//测试主函数
int main()
{
    cout << "任意合并: " << endl;
    DisjSets disjSets(8);
    disjSets.unionSets(4, 5);
    disjSets.unionSets(6, 7);
    disjSets.unionSets(4, 6);

    disjSets.print();

    cout << "按大小合并: " << endl;
    DisjSets disjSets2(8);
    disjSets2.unionSetsBySize(4, 5);
    disjSets2.unionSetsBySize(6, 7);
    disjSets2.unionSetsBySize(4, 6);
    disjSets2.unionSetsBySize(3, 4);
    disjSets2.print();

    cout << "按高度合并: " << endl;
    DisjSets disjSets3(8);
    disjSets3.unionSetsByHeight(4, 5);
    disjSets3.unionSetsByHeight(6, 7);
    disjSets3.unionSetsByHeight(4, 6);
    disjSets3.unionSetsByHeight(3, 4);
    disjSets3.print();

    return 0;
}

8.下面是不相交集类的源代码

/*************************************************************************
	> File Name: DisjointSets.cpp
	> Author:
	> Mail:
	> Created Time: 2016年04月25日 星期一 11时22分48秒
 ************************************************************************/

#include <iostream>
#include <vector>
using namespace std;

/********************************************
*    类名称:不相交集合类DisjSets
********************************************/
class DisjSets{
    public:
        explicit DisjSets(int numElements);
        ~DisjSets(){}

        int find(int x) const;//查找
        void unionSets(int root1, int root2);//合并
        void unionSetsBySize(int root1, int root2);//按树大小合并
        void unionSetsByHeight(int root1, int root2);//按树高度合并
        void print();//输出各个不相交集合类中元素
    private:
        void print(int x);
    private:
        vector<int> s;//存放每个元素的根节点或父节点
};

/****************************************************************
*   函数名称:DisjSets(int numElements)
*   功能描述: 构造函数,同时对每个元素进行集合初始化
*   参数列表: numElements是集合中元素的个数
*   返回结果:无
*****************************************************************/
DisjSets::DisjSets(int numElements):s(numElements)
{
    for(unsigned i = 0; i < s.size(); ++i)
        s[i] = -1;
}

/****************************************************************
*   函数名称:print(int x)
*   功能描述: 打印元素x
*   参数列表: x是元素的
*   返回结果:void
*****************************************************************/
void DisjSets::print(int x)
{
    cout << x << " ";
    for(unsigned i = 0; i < s.size(); ++i){
        if(s[i] == x)
            print(i);
    }
}
/****************************************************************
*   函数名称:print
*   功能描述: 打印集合中的元素
*   参数列表: 无
*   返回结果:void
*****************************************************************/
void DisjSets::print()
{
    cout << "输出不相交集合类(每行表示一个相交集合): " << endl;
    cout << "s: ";
    for(unsigned i = 0; i < s.size(); ++i)
        cout << s[i] << " ";
    cout << endl;

    for(unsigned i = 0; i < s.size(); ++i){
        if(s[i] < 0){
            print(i);
            cout << endl;
        }
    }
}

/****************************************************************
*   函数名称:find(int x) const
*   功能描述: 查找元素x处于集合的名字
*   参数列表: x是要查找的元素
*   返回结果:返回元素x的集合名字
*****************************************************************/
int DisjSets::find(int x) const
{
    if(s[x] < 0)
        return x;
    else
        return find(s[x]);
}

/****************************************************************
*   函数名称:unionSets(int root1, int root2)
*   功能描述: 合并两个集合
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSets(int root1, int root2)
{
    s[root2] = root1;
}

/****************************************************************
*   函数名称:unionSetsBySize(int root1, int root2)
*   功能描述: 按集合大小合并两个集合,使得较小的树成为较大树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSetsBySize(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较大
        s[root2] += s[root1];//更新树的大小
        s[root1] = root2;//root1的父节点变为root2
    }
    else{
        s[root1] += s[root2];
        s[root2] = root1;
    }
}

/****************************************************************
*   函数名称:unionSetsByHeight(int root1, int root2)
*   功能描述: 按集合高度合并两个集合,使较浅的树成为较深的树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void
*****************************************************************/
void DisjSets::unionSetsByHeight(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较高
        s[root1] = root2;//直接合并, root1成为root2树的子树
    }
    else{//root1树比较高,或相等。

         //如果相等则更新树的高度
        if(s[root1] == s[root2])
            s[root1]--;
         s[root2] = root1;
    }
}

//测试主函数
int main()
{
    cout << "任意合并: " << endl;
    DisjSets disjSets(8);
    disjSets.unionSets(4, 5);
    disjSets.unionSets(6, 7);
    disjSets.unionSets(4, 6);

    disjSets.print();

    cout << "按大小合并: " << endl;
    DisjSets disjSets2(8);
    disjSets2.unionSetsBySize(4, 5);
    disjSets2.unionSetsBySize(6, 7);
    disjSets2.unionSetsBySize(4, 6);
    disjSets2.unionSetsBySize(3, 4);
    disjSets2.print();

    cout << "按高度合并: " << endl;
    DisjSets disjSets3(8);
    disjSets3.unionSetsByHeight(4, 5);
    disjSets3.unionSetsByHeight(6, 7);
    disjSets3.unionSetsByHeight(4, 6);
    disjSets3.unionSetsByHeight(3, 4);
    disjSets3.print();

    return 0;
}

运行结果为:

任意合并:
输出不相交集合类(每行表示一个相交集合):
s: -1 -1 -1 -1 -1 4 4 6
0
1
2
3
4 5 6 7
按大小合并:
输出不相交集合类(每行表示一个相交集合):
s: -1 -1 -1 4 -5 4 4 6
0
1
2
4 3 5 6 7
按高度合并:
输出不相交集合类(每行表示一个相交集合):
s: -1 -1 -1 4 -3 4 4 6
0
1
2
4 3 5 6 7 
时间: 2024-10-18 15:46:11

数据结构与算法——不相交集类的C++实现的相关文章

数据结构与算法——插入类排序(直接插入排序,希尔排序)

一.直接插入排序 对于一个有序的序列,不断将后面的元素插入前面的有序序列,保持序列继续有序. 对于直接插入排序的思路:将要排序的元素保存,然后逐个和其前面的元素进行比较,如果前面的元素比其大,则将前面的元素后移一个. 时间复杂度为n*n void insert_sort(int a[],int n) { int i,j; int temp; for(i=1;i<n;i++) { temp = a[i]; j=i-1; while((j>=0)&& (temp<a[j]))

利用不相交集类制作迷宫游戏(数据结构课程设计——迷宫老鼠)

之前大一的时候有几天闲来无事,为了学习做了一个可以自动生成迷宫,可以寻找最短路径的小游戏,现在整理分享一下 简单介绍: 利用不相交集类考虑一个迷宫的生成,一个简单算法就是从各处的墙壁开始(除入口和出口之外).此时,不断地随机选择一面墙,如果被该墙分割的单元彼此不联通,那么就把这面墙拆掉.重复这个过程直到开始单元和终止单元联通,那么就得到一个迷宫.实际上不断的拆掉墙壁直到每个单元都可以从其他单元到达更好(这会使迷宫产生更多误导的路径). 整理一下迷宫的生成算法就是: (1)将迷宫初始时看成一个一个

数据结构与算法-排序算法-partial

前言 都什么时代了,还写排序算法的总结? 原因有二.一是别人的精彩永远是别人的,你只有鼓掌的份儿:有些事情实际动手去做了才会有所体会. 二是排序算法是一类基础类的算法,不光是IT从业者真正入门的门槛,也是一些高级算法的关键部分或算法评估的benchmark. 计划说明的算法内容有哪些?  算法的思想.Java代码实现和平均算法复杂度.算法运行完整示例. 参考文献有哪些? wiki[EB/OL] Shaffer C. A. Data Structure and Algorithm Analysis

数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树

[本文谢绝转载,原文来自http://990487026.blog.51cto.com] 树 数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树 二叉树的创建,关系建立 二叉树的创建,关系建立2 三叉链表法 双亲链表: 二叉树的遍历 遍历的分析PPT 计算二叉树中叶子节点的数目:使用全局变量计数器 计算二叉树中叶子节点的数目:不使用全局变量计数器 无论是先序遍历,中序遍历,后序遍历,求叶子的数字都不变;因为本质都是一样的,任何一个节点都会遍历3趟 求二叉树的高度 二叉树的拷

数据结构与算法之线性表

前言 上一篇<数据结构和算法之时间复杂度和空间复杂度>中介绍了时间复杂度的概念和常见的时间复杂度,并分别举例子进行了一一说明.这一篇主要介绍线性表. 线性表属于数据结构中逻辑结构中的线性结构.回忆一下,数据结构分为物理结构和逻辑结构,逻辑结构分为线性结构.几何结构.树形结构和图形结构四大结构.其中,线性表就属于线性结构.剩余的三大逻辑结构今后会一一介绍. 线性表 基本概念 线性表(List):由零个或多个数据元素组成的有限序列. 注意: 1.线性表是一个序列. 2.0个元素构成的线性表是空表.

数据结构与算法1

数据结构与算法(一),概述 转载请注明出处:http://www.cnblogs.com/wangyingli/p/5919297.html 数据结构学了有一年的时间了,但是一直没有好好的总结一下,现在回想起来,感觉好像都不怎么记得了.所以接下来一段时间我将重新学习一下,算是温故而知新了.本着「分享是一种美德」的精神,我将把我的学习总结记录下来,并与大家分享. 本节的主要内容有: 一.数据结构 1.定义 2.关于数据结构的几个术语 3.逻辑结构与物理结构 二.抽象数据类型 三.算法 四.算法的复

数据结构与算法

1. 解决一个复杂的问题的时候,肯能先想到的是如何建模,建模之后,怎么去求解这个问题.求解一个问题的时候,需要用到算法的时候,我们应该想到的是该算法需要什么样的数据结构.可能涉及一个算法最原始的数据结构,可能就比较复杂. 怎么样对一个算法来进行优化,我想从学完数据结构的角度来谈谈,总共有3个方面: 第一:可能就是算法本身,比如在排序过程中,我们知道快速排序在相对的情况下,要比冒泡排序更加高效,这就是为什么都是排序,我们为什么不选择好的算法,从算法本身来下手呢? 第二:可能就是合适的数据结构,能够

python 下的数据结构与算法---1:让一切从无关开始

我也忘了大概多久了,好像是三周多一点,终于把Data Structure and Algorithms with python以及 Problem Solving with  Algorithms and DataStructures看完了(图那部分没仔细看,太难太费时间,毕业设计开始了,有点忙).[github地址,包含了那两本书带笔记版以及下面零的代码] 所以啦,这作为第一篇总结笔记就从点无关的开始吧(也就是这两本书中提到的python相关但是与数据结构和算法无关的东东) 目录: 零:有些什

面试常考数据结构与算法

数据结构部分: 1.数组和链表的区别.(很简单,但是很常考,记得要回答全面) C++语言中可以用数组处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小.而在实际应用中,用户使用数组之前无法确定数组的大小,只能够将数组定义成足够大小,这样数组的空间可能不被使用,从而造成内存空间的浪费.链表是一种常见的数据组织形式,他采用动态分配内存的形式实现.需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费. 从逻辑结构上来看