数据结构---集合
前言
集合是不同的对象的(或者称成员)的无序集合,由于成员直接存在关联,可以理解为归聚在一起的成员组合。集合有两种重要的特点:
- 成员是无序
- 每个集合的中的成员不重复
这是集合中成员的重要特征。
集合的介绍和基本知识
集合的介绍的定义
集合是相关有关联的无序组合,每个成员在一个集合中只出现一次,在数学中我们可以使用集合论有关的定义在定义集合的概念。
- 没有包含任何成员的集合我们称之为空集。集合的所有可能成员构成的集合称之为全集(全域),使用集合的表示可以写成\(S=U\)表示全集,使用\(S = \emptyset\) 表示空集。
- 如果两个集合中所包含的集合是一样的,则可以称这两个集合相等。例如\(S1=\{1,2,3\}\) ,\(S2=\{1,2,3\}\)这两个集合中成员是相同所\(S1 = S2\)。
- 如果两个集合中\(S2\)包含另一个集合\(S1\)集合的所有成员,称\(S1\)是\(S2\)的子集。记为则\(S1\in S2\)
集合的性质
集合的基本操作我们常用的有三种,分别是集合的交集,并集和差集操作当然还存在其他的集合操作,但是这三种是最为主要的集合操作了。
- 两个集合的\(S1\)和\(S2\)的并集也是一个集合,记为\(S{_m}\),它包含了S1和S2中的全部成员,这种运算称之为集合的并集,数学表达为\(S1\cup S2 = S{_m}\)。
- 两个集合的\(S1\)和\(S2\)的交集也是一个集合,记为\(S{_i}\),它包含了S1和S2中的共有成员,这种运算称之为集合的并集,数学表达为\(S1\cap S2 = S{_i}\)。
- 两个集合的\(S1\)和\(S2\)的差集也是一个集合,记为\(S{_d}\),它包含了只存在\(S1\)的元素而不在\(S2\)中的元素,这种运算称之为集合的差集,数学表达为\(S1 \setminus S2 = S{_d}\),有的教材也写为 \(S1 - S2 = S{_d}\)。
以上是集合一些基础概念,如果关心编程的话,了解一下即可。
集合的抽象数据结构实现的方法
在这篇文章中,我们使用Set来表示集合这个数据结构。实现集合的方式中是采用链表。简单的一种思路就是将链表中List
重定义为Set
但是Set
和链表又有不同的地方,链表可以存储相同的成员,但是集合不同,集合中的每一个元素都是独一无二的。所以在使用链表的方式插入元素时候必须先对链表进行遍历,判断是否已经存在相同的成员。这部分的对于程序的性能是一个很大的影响,尤其当集合的长度很大时候,这样对于每个新插入的成员的遍历时间也变长,后面在讲到Hash的时候,可以使用Hash表对集合进行优化大大的提高集合的操作速度。
集合的接口定义
在实现集合前先对集合对一些公共的接口定义:
-
void set_init(Set* set, int(*match)(const void* key1, const void* key2), void (*destory)(void* data));
- 描述:由于这里实现set的本质是一个链表,所以直接使用链表对
set
进行初始化,但是这个成员的重复的函数这个是list
所没有的,所以要自己这个判断函数,这样提高了C语言的灵活性。 - 时间复杂度;和链表的初始化函数一致,这个也是\(O(1)\)的时间复杂度。
- 描述:由于这里实现set的本质是一个链表,所以直接使用链表对
-
int set_insert(Set *set, const void *data)
- 和链表的实现方式是一致
- 时间复杂度:和链表的时间复杂度一致,和成员的数量有关\(O(n)\)。
-
int set_remove(Set* set, void** data);
- 描述:
int set_remove(Set* set, void** data);
利用list_next
来遍历集合,根据match
函数来检查数据是否匹配。prev
是记录要移除的前一个元素的信息,这是因为使用的函数的list_rem_next
()是移除后一个元素。 - 时间复杂度:\(O(n)\) 在最糟糕的情况下 必须对集合的每一个元素进行遍历 ,这就导致必须执行n个\(O(1)\)的操作。
- 描述:
-
int set_union(Set* setu, const Set *set1, const Set *set2);
- 描述:
set_union
是建立一个并集,记为setu
,这个是由参数set1
和参数set2
组成的并集,思路是先把集合set1
的中成员加入setu
中然后,再加集合set2
,不过在加入set2
的时候要对set2
中的每一成员进行判断。判断函数程序中为set_is_member
。 - 时间复杂度:\(O(mn)\) m为集合set1中的成员数量,n是集合set2中的成员数量,在将集合set1加入集合setu中的时间复杂度是\(O(m)\) 这个时候setu中的成员为m个,在执行加入第二个set2时,在加入集合还需要对集合进行遍历判断n所以第二次操作的时间复杂度为\(O(mn)\)两次操作是顺序,所以最大的时间复杂度为\(O(mn)\)。
- 描述:
-
int set_intersection(Set *seti, const Set *set1, const Set* Set2);
- 描述:
set_intersection
就求解两个集合的交集 - 时间复杂:和交集的分析一样也是\(O(mn)\)
- 描述:
-
``int set_difference(Set* setd, const Set* set1, const Set* set2);`
- 描述:
set_intersection
就求解两个集合的差集 - 时间复杂:和交集的分析一样也是\(O(mn)\)
- 描述:
-
int set_is_member(const Set *set,void *data);
- 描述:判断成员是否在集合中出现过,出现过返回1,没出现过返回0
- 时间复杂:在最糟糕的情况下,必须对集合的全部成员遍历所以时间复杂度为\(O(n)\)
int set_is_subset(const Set* set1, const Set* set2);
-
描述:判断集合
set1
是否是set2
的子集 -
时间复杂度:\(O(mn)\) 假设集合
set1
中的成员数为m 集合set2
的成员数为n 要对于集合set1
中每一个成员都要在set2
中进行遍历匹配,所以最糟糕的情况下要遍历\(mn\)次。 -
int set_is_equel(const Set* set1, const Set* set2);
- 描述:判断集合
set1
是否是相等于集合set2
- 时间复杂度:\(O(mn)\)
- 描述:判断集合
代码实现
头文件
#pragma once
#ifndef SET_H
#define SET_H
#include <stdio.h>
#include "list.h"
// 使用链表的方式实现集合
typedef List Set;
// 公共接口
void set_init(Set* set, int(*match)(const void* key1, const void* key2), void (*destory)(void* data));
#define set_destory list_destory
int set_insert(Set *set, const void *data);
int set_remove(Set* set, void** data);
int set_union(Set* setu, const Set *set1, const Set *set2);
int set_intersection(Set *seti, const Set *set1, const Set* Set2);
int set_difference(Set* setd, const Set* set1, const Set* set2);
int set_is_member(const Set *set,void *data);
int set_is_subset(const Set* set1, const Set* set2);
int set_is_equel(const Set* set1, const Set* set2);
#define set_size(set) ((set)->size)
#endif // !SET_H
实现文件
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
#include "set.h"
/*set_init 集合初始化函数*/
void set_init(Set* set, int(*match)(const void* key1, const void* key2), void (*destory)(void* data))
{
list_init(set, destory);
set->match = match;
return;
}
/*set_insert 集合插入成员*/
int set_insert(Set* set, const void* data)
{
// 检查是否已存在成员在集合中
if (set_is_member(set,data))
{
return 1;
}
// 不存在就把成员插入链表尾部
return list_ins_next(set, list_tail(set), data);
}
/*set_remove 集合删除成员*/
int set_remove(Set* set, void** data)
{
ListElement* member, *prev;
// 寻找成员的位置
prev = NULL;
for (member = list_head(set);member != NULL; member = list_next(member))
{
if (set->match(*data, list_data(member)))
break;
prev = member;
}
// 如果成员不在集合中
if (member == NULL)
{
return -1;
}
return list_rem_next(set, prev, data);
}
/*set_union 集合的交集*/
int set_union(Set* setu, const Set* set1, const Set* set2)
{
ListElement* member;
// 初始化这个交集的集合
set_init(setu, set1->match, NULL);
// 插入这个第一个集合
for (member = list_head(set1); member != NULL; member = list_next(member))
{
// 失败要回滚
if (list_ins_next(setu, list_tail(setu), list_data(member)) != 0)
{
set_destory(setu);
return -1;
}
}
// 插入第二个集合
for (member = list_head(set2); member != NULL; member = list_next(member))
{
// 防止同样的成员插入
if (set_is_member(setu, list_data(member)))
{
continue;
}
else
{
if (list_ins_next(setu, list_tail(setu), list_data(member)) != 0)
{
set_destory(setu);
return -1;
}
}
}
return 0;
}
/*set_intersection 集合交集*/
int set_intersection(Set* seti, const Set* set1, const Set* set2)
{
ListElement* member;
// 初始化交集的集合
set_init(seti, set1->match, NULL);
// 插入这个第一个集合
for (member = list_head(set1); member != NULL; member = list_next(member))
{
// 这个数据两个集合都存在
if (set_is_member(set2, list_data(member)))
{
if (list_ins_next(seti, list_tail(seti), list_data(member)) != 0)
{
set_destory(seti);
return -1;
}
}
}
return 0;
}
/*set_difference 集合差集*/
int set_difference(Set* setd, const Set* set1, const Set* set2)
{
ListElement* member;
// 差集初始化
set_init(setd,set1->match,NULL);
// 将集合1插入差集
for (member = list_head(set1); member != NULL; member = list_next(member))
{
if (!set_is_member(set2, list_data(member)))
{
set_destory(setd);
return -1;
}
}
return 0;
}
/*set_is_member 判断成员是否在集合里*/
int set_is_member(const Set* set, void* data)
{
ListElement* member;
for (member = list_head(set); member != NULL; member = list_next(member))
{
if (set->match(data,list_data(member)))
{
return 1;
}
}
return 0;
}
/*set_is_subset 判断set1是否set2的子集*/
int set_is_subset(const Set* set1, const Set* set2)
{
ListElement* member;
if (set_size(set1)>set_size(set2))
{
return 0;
}
for (member = list_head(set1); member != NULL; member = list_next(member))
{
if (!set_is_member(set1, set2))
{
return 0;
}
}
return 1;
}
/*set_is_equel 判断set1相等set2*/
int set_is_equel(const Set* set1, const Set* set2)
{
ListElement* member;
if (set_size(set1) != set_size(set2))
{
return 0;
}
for (member = list_head(set1); member != NULL; member = list_next(member))
{
if (!set_is_member(set1, set2))
{
return 0;
}
}
return 1;
}
集合覆盖问题
有空补充