集合

来源:互联网 发布:sql中删除重复数据 编辑:程序博客网 时间:2024/05/22 01:42

0、引入

集合是不同对象(称为成员)的无序聚集。由于元素之间彼此相关联,因此可以理解为归聚在一起的成员组合。集合的两个重要特点是:第一,成员是无序的;第二,每个成员在集合中出现一次。集合是离散数学中的重要部分,离散数学与计算机科学之间有着很深的渊源。在计算机科学中,我们使用集合来归类数据,尤其是当我们计划以后将其与其他数据相关联时。一些编程语言比如Pascal原生支持集合,但C语言本身并没有这种特性。


1、集合介绍

集合是相关联成员的无序组合,且每个成员在集合中仅出现一次。集合的正式写法是在两边加上大括号。因此,如果S是一个包含成员1、2和3的集合,则集合S应写作S={1, 2, 3}。当然,由于集合是无序的,因此也可以写作S={3, 2, 1}。如果成员m在集合S中,则成员关系可以写作m∈S;否则写作m∉S。例如,在集合S中,S={1, 2, 3},则2∈S,但4∉S。要高效地使用集合,我们应该先熟悉一些定义、基本运算以及集合的属性。

集合的定义

1) 没有包含任何成员的集合称为空集。集合的所有可能成员称为全域。(当然,有时候全域比较难定义!)以集合的写法可表示为:

S=U表示全域;S=∅表示空集。

2) 如果两个集合中所包含的成员完全一样,则称这两个集合相等。例如,如果S1={1, 2, 3},S2={1, 2, 3},以及S3={1, 2, 4},则S1等于S2,但S1不等于S3。以集合的写法表示为:

S1=S2表示S1和S2相等;S1≠S3表示S1和S3不等。

3) 如果集合S2包含另一个集合S1所有的成员,则S1是S2的子集。例如:如果S1={1, 3},S2={1, 2, 3}以及S3={1, 2},则S1是S2的子集,但S1不是S3的子集。以集合的写法表示为:

S1⊂S2表示S1是S2的子集;S1⊄S3表示S1不是S3的子集。

集合的基本操作

1) 两个集合S1和S2的并集也是一个集合,记为Su,它包含了S1和S2中的所有成员。例如,如果集合S1={1, 2, 3},S2={3, 4},则Su={1, 2, 3, 4}。以集合的写法表示为:

Su=S1∪S2。

2) 两个集合S1和S2的交集也是一个集合,记为Si,它只包含同时存在于S1和S2中的成员。例如,如果S1={1, 2, 3},S2={1, 2}。以集合的写法表示为:

Si=S1∩S2。

3) 两个集合S1和S2的差集也是一个集合,记为Sd,它只包含在S1中出现过且不属于S2的成员。例如,如果S1={1, 2, 3},S2={3, 4},则Sd={1, 2}。以集合的写法表示为:

Sd=S1-S2。

集合的性质

1) 某个集合与空集的交集结果一定是空集。某个集合与空集的并集结果还是原来的集合本身。这些行为由集合的空集律描述为:

S∩∅=∅

S∪∅=S

2) 与集合本身求交集结果还是集合本身。同样的,与集合本身求并集结果还是集合本身。这种行为由集合的幂等律描述为:

S∩S=S

S∪S=S

3) 集合S1与集合S2的交集其结果等同于集合S2与集合S1的交集。同样的道理也适用于并集的情况。这种行为由集合的交换律描述为:

S1∩S2=S2∩S1

S1∪S2=S2∪S1

4) 一组集合的交集可以按照任意顺序来求解。同样的道理也适用于并集的情况。这种行为由集合的结合律描述为:

S1∩(S2∩S3)=(S1∩S2)∩S3

S1∪(S2∪S3)=(S1∪S2)∪S3

5) 某集合与其他两个集合的并集相交的结果可以按照一种分配的方式来解决。同样的道理也适用于求解其集合与其他两个集合的交集相合并的结果。这种行为由集合的分配率描述为:

S1∩(S2∪S3)=(S1∩S2)∪(S1∩S3)

S1∪(S2∩S3)=(S1∪S2)∩(S1∪S3)

6) 某集合与该集合和另外一个集合的并集相交的结果等于该集合本身。同样的道理也适用于求解某集合与该集合和另一个集合的交集相合并的结果。这种行为由集合的合并律描述为:

S1∩(S1∪S2)=S1

S1∪(S1∩S2)=S1

7) 当求解一个集合与其他两个集合的交集或并集之差时,可以得到一个有趣的结论。这种行为成为德摩根定律(DeMorgan's laws):

S1-(S2∪S3)=(S1-S2)∩(S1-S3)

S1-(S2∩S3)=(S1-S2)∪(S1-S3)


2、集合接口的定义

set_init

——————

void set_init(Set *set, int (*match)(const void *key1, const void *key2), void (*destroy)(void *data));

返回值:无

描述:初始化由参数set所指定的集合。该函数必须在集合做其他操作之前调用。参数match是一个函数指针,用来在各种集合操作中判断两个成员是否相匹配。如果key1等于key2,则match函数应返回1;否则返回0。当调用函数set_destroy时,destroy参数提供了一种释放动态分配空间的方法。例如,如果集合的成员包括使用malloc动态分配的数据空间,则当销毁集合时,destroy应该设置为free来释放数据占用的空间。对于结构化数据,其中包含了多个动态分配的成员,destroy应该设置为一个由用户自定义的析构函数来释放每个动态分配的成员以及结构体本身所占用的内存空间。如果集合包含的数据不应该释放的话,destroy就应该设置为NULL。


set_destroy

——————

void set_destroy(Set *set);

返回值:无

描述:销毁由参数set所指定的集合。调用set_destroy后集合的其他操作都不允许再执行,除非再次调用set_init。set_destroy操作将集合中的所有成员都移除,如果传递给set_init的参数destroy不为NULL的话,则调用destroy所指定的函数,对集合中每个移除的元素施行资源回收操作。

复杂度:O(n),这里n代表集合中的元素个数。


set_insert

——————

int set_insert(Set *set, const void *data);

返回值:如果插入操作成功则返回0;如果插入的成员在集合中已经存在返回1;否则返回-1。

描述:在由参数set所指定的集合中插入一个成员。新成员包含一个指向data的指针,因此只要该成员还在集合中,则data所引用的内存空间就应该保持合法。由调用者负责管理data所关联的存储空间。

复杂度:O(n),这里n代表集合中的元素个数。


set_remove

——————

int set_remove(Set *set, void **data);

返回值:如果移除操作成功则返回0;否则返回-1。

描述:在由参数set所指定的集合中移除数据域同data相吻合的成员。函数返回之后,data指向移除的成员的数据部分。由调用者负责管理data所关联的存储空间。

复杂度:O(n),这里n代表集合中的元素个数。


set_union

——————

int set_union(Set *setu, const Set *set1, const Set *set2);

返回值:如果计算并集成功则返回0;否则返回-1。

描述:建立一个集合,其结果为set1和set2所指定集合的并集。返回后,setu就代表这个并集。因为setu指向set1和set2中成员的数据域,所以set1和set2中的数据必须保持合法,直到setu被set_destroy销毁。

复杂度:O(mn),这里m和n分别代表集合set1和set2中的元素个数。


set_intersection

——————

int set_intersection(Set *seti, const Set *set1, const Set *set2);

返回值:如果交集计算成功返回0;否则返回-1。

描述:建立一个集合,其结果为set1和set2所指定集合的交集。函数返回后,seti就代表这个交集。由于seti指向set1中成员的数据域,因此set1中的数据必须保持合法,直到seti被set_destroy销毁。

复杂度:O(mn)。这里m和n分别代表着集合set1和set2中的元素个数。


set_difference

——————

int set_difference(Set *setd, const Set *set1, const Set *set2);

返回值:如果计算差集成功返回0;否则返回-1。

描述:建立一个集合,其结果为set1和set2所指定集合的差集。函数返回后,setd就代表这个差集。因为setd指向set1中的数据域,所以set1中的数据必须保持合法,直到setd被set_destroy销毁。

复杂度:O(mn),这里m和n分别代表着集合set1和set2中的元素个数。


set_is_member

——————

int set_is_member(const Set *set, const void *data);

返回值:如果找到成员返回1;否则返回0。

描述:判断由data所指定成员是否存在于set所指定的集合中。

复杂度:O(n),这里n代表集合中的元素个数。


set_is_subset

——————

int set_is_subset(const Set *set1, const Set *set2);

返回值:如果set1是set2的子集返回1;否则返回0。

描述:判断由参数set1所指定集合是否为参数set2所指定集合的子集。

复杂度:O(nm),这里n和m分别代表着集合set1和set2中的元素个数。


set_is_equal

——————

int set_is_equal(const Set *set1, const Set *set2);

返回值:如果set1和set2相等返回1;否则返回0。

描述:判断由参数set1所指定集合是否等于由参数set2所指定集合。

复杂度:O(mn),这里m和n分别代表集合set1和set2中的元素个数。


set_size

——————

int set_size(const Set *set);

返回值:返回集合中的元素个数。

描述:这是一个宏,用来返回由参数set所指定集合中的元素个数。

复杂度:O(1)


3、集合抽象数据类型的实现和分析

使用Set代表集合这种数据结构。实现集合的一种好的方式是采用链表。简单的方法,就是将List以typedef的形式重命名为Set。除了保持简洁性之外,使用typedef还使得Set具有一些多态的特性,如同在栈和队列中描述的那样。因此,由于Set实际上是一个链表,当需要将Set表现为一个链表时就可以对Set施行属于链表的操作。使用这种方法的最大的好处就是可以使用list_next来遍历一个集合,使用list_rem_next来移除一个成员而不用根据成员所存储的数据来标识它。回顾一下set_remove只能根据成员存储的数据来唯一标识,当我们不知道集合包含的成员时这就会成为一个麻烦。

一般来说,集合操作都或多或少需要一定的运行时开销,主要原因是很多操作都需要遍历集合中的每一个成员,以此来查询其中的成员。然而,可以通过采用一种更高效的查找技术来提高运行时的性能,比如哈希。尽管如此,我们这里还是讲解基于遍历的通用实现方式,在后面将会整理哈希相关的内容。通用的实现方式运行性能适合于小型到中等规模的集合数据。

// 集合抽象数据类型的头文件/* set.h */#ifndef SET_H#define SET_H#include <stdlib.h>#include "list.h"/* Implement sets as linked lists. */typedef List Set;/* Public Interface */void set_init(Set *set, int (*match)(const void *key1, const void *key2),void (*destroy)(void *data));#define set_destroy list_destroyint 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, const void *data);int set_is_subset(const Set *set1, const Set *set2);int set_is_equal(const Set *set1, const Set *set2);#define set_size(set) ((set)->size)#endif // SET_H

// Set抽象数据类型的实现/* set.c */#include <stdlib.h>#include <string.h>#include "list.h"#include "set.h"/* set_init */void set_init(Set *set, int (*match)(const void *key1, const void *key2),void (*destroy)(void *data)){/* Initialize the set. */list_init(set, destroy);set->match = match;return;}/* set_insert */int set_insert(Set *set, const void *data){/* Do not allow the insertion of duplicates. */if(set_is_member(set, data))return 1;/* Insert the data. */return list_ins_next(set, list_tail(set), data);}/* set_remove */int set_remove(Set *set, void **data){ListElmt *member, *prev;/* Find the member to remove. */prev = NULL;for(member = list_head(set); member != NULL; member = list_next(member)){if(set->match(*data, list_data(member)))break;prev = member;}/* Return if the member was not found. */if(member == NULL)return -1;/* Remove the member. */return list_rem_next(set, prev, data);}/* set_union */int set_union(Set *setu, const Set *set1, const Set *set2){ListElmt *member;void *data;/* Initialize the set for the union. */set_init(setu, set1->match, NULL);/* Insert the member of the first set. */for(member = list_head(set1); member != NULL; member = list_next(member)){data = list_data(member);if(list_ins_next(setu, list_tail(setu), data) != 0){set_destroy(setu);return -1;}}/* Insert the members of the second set. */for(member = list_head(set2); member != NULL; member = list_next(member)){if(set_is_member(set1, list_data(member))){/* Do not allow the insertion of duplicates. */continue;}else{data = list_data(member);if(list_ins_next(setu, list_tail(setu), data) != 0){set_destroy(setu);return -1;}}}return 0;}/* set_intersection */int set_intersection(Set *seti, const Set *set1, const Set *set2){ListElmt *member;void *data;/* Initialize the set for the intersection. */set_init(seti, set1->match, NULL);/* Insert the members present in both sets. */for(member = list_head(set1); member != NULL; member = list_next(member)){if(set_is_member(set2, list_data(member))){data = list_data(member);if(list_ins_next(seti, list_tail(seti), data) != 0){set_destroy(seti);return -1;}}}return 0;}/* set_difference */int set_difference(Set *setd, const Set *set1, const Set *set2){ListElmt *member;void *data;/* Initialize the set for the difference. */set_init(setd, set1->match, NULL);/* Insert the members from set1 not in set2. */for(member = list_head(set1); member != NULL; member = list_next(member)){if(!set_is_member(set2, list_data(member))){data = list_data(member);if(list_ins_next(setd, list_tail(setd), data) != 0){set_destroy(setd);return -1;}}}return 0;}/* set_is_member */int set_is_member(const Set *set, const void *data){ListElmt *member;/* Determine if the data is a member of the set. */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 */int set_is_subset(const Set *set1, const Set *set2){ListElmt *member;/* Do a quick test to rule out some cases. */if(set_size(set1) > set_size(set2))return 0;/* Determine if set1 is a subset of set2. */for(member = list_head(set1); member != NULL; member = list_next(member)){if(!set_is_member(set2, list_data(member)))return 0;}return 1;}/* set_is_equal */int set_is_equal(const Set *set1, const Set *set2){/* Do a quick test to rule out some cases. */if(set_size(set1) != set_size(set2))return 0;/* Sets of the same size are equal if they are subsets. */return set_is_subset(set1, set2);}


set_init

函数set_init用来初始化一个集合以便其他的操作能够执行。由于set实际上是一个链表,因此这里就直接调用list_init来初始化set。我们需要手动初始化match成员,因为这个成员在链表中并没有得到使用,所以list_init不能初始化这个成员。

set_init的运行时复杂度同list_init一样,都是O(1)级的。


set_destroy

函数set_destroy用来销毁一个集合。由于set实际上是一个链表,需要以相同的方式来销毁,因此将set_destroy用宏定义的方式定义为list_destroy即可。

set_destroy的时间复杂度同list_destroy一样,都是O(n)级的,这里n代表集合中的成员个数。


set_insert

函数set_insert将一个成员插入集合中。由于集合中的成员最多只能出现一次,因此将首先调用set_is_member以确保集合中不会已经包含了待插入的成员。只要集合中不存在待插入的成员,就调用list_ins_next将待插入的成员插入到集合中。

set_insert的时间复杂度为O(n)级的,因为set_is_member需要遍历整个集合,所以需要O(n)的时间复杂度,list_ins_next则以O(1)的时间复杂度将成员插入集合中。


set_remove

函数set_remove通过使用list_next遍历集合,根据match所指定的函数确定需要移除的成员然后将其从集合中移除。prev指针刚好指向需要移除成员的前一个元素,因为这是调用list_rem_next所需要的参数。list_rem_next将参数data指向已移除成员的数据域。

set_remove的时间复杂度为O(n)级的,这里n代表集合中的成员个数。这是因为在最坏的情况下,整个集合的成员都需要遍历一次,以此才能确定哪个成员需要移除。这导致n次的O(1)操作需要被执行,也就是在循环中总的开销成为O(n)。一旦找到了目标成员,list_rem_next将以O(1)的操作将其移除。


set_union

函数set_union操作将建立一个集合,即为setu。这是由参数set1和set2所指定的集合的并集结果。首先,调用set_init来初始化setu,然后通过不断调用list_ins_next将set1中的成员插入setu中。最后,set2中的成员以相同的方式将其成员插入setu中,除此之外,在插入之前还需要调用set_is_member来确保setu中的成员不会出现重复的现象。

set_union的时间复杂度为O(mn)级的,这里m和n分别代表集合set1和set2中的成员个数。在第一个循环中,遍历set1中的每个成员并把它们插入setu中,这个过程的时间复杂度为O(m)级的。在第二个循环中需要遍历set2的每个成员,这个循环中还包括时间复杂度为O(m)的set_is_member,因此,第二次循环的总体复杂度为O(mn)。由于这两次循环是顺序执行的,因此set_union的运行时复杂度比单独执行这两次循环还要大,记为O(mn)。


set_intersection

函数set_intersection操作将建立一个集合,记为seti。这是由参数set1和set2所指定集合的交集结果。首先,调用set_init来初始化seti,然后针对set1中的每一个成员调用set_is_member来确定set1中的成员是否在set2中出现过。如果是,将这个成员插入seti中。

set_intersection的时间复杂度为O(mn)级的,这里m和n分别代表集合set1和set2中的成员个数。这是因为对于set1中的每个成员,都需要调用O(n)级的set_is_member来确定该成员是否也在set2中出现过。


set_difference

函数set_difference操作将建立一个集合,记为setd。这是对参数set1和set2所指定集合求差集的结果。首先,调用set_init来初始化setd,其次针对set1中的每一个成员,调用set_is_member来确定该成员是否在集合set2中出现过。如果没有出现过,则将该成员插入setd中。

set_difference的时间复杂度为O(mn)级的,这里m和n分别代表集合set1和set2中的成员个数。这是因为,针对set1中的每一个成员都需要执行复杂度为O(n)的set_is_member操作以此来确定该成员是否也在集合set2中出现过。


set_is_member

函数set_is_member用来判断某个成员是否在集合中出现过。这是通过调用list_next来遍历集合中的成员直到找到与参数data相吻合的成员或者遍历完整个集合。

set_is_member的时间复杂度为O(n),这里n代表集合中的元素个数。这是因为在最坏的情况下,整个集合中的成员都需要遍历一次以此来确定要查找的成员。


set_is_subset

函数set_is_subset操作用来判断集合set1是否是集合set2的子集。由于要满足一个集合是另一个集合的子集,set1所包含的成员个数必须小于等于set2所包含的成员个数,因此首先比较它们的成员个数。如果这个测试失败,则set1不是set2的子集。否则,调用list_next来遍历set1中的成员,直到找到一个并不属于集合set2的成员,这说明set1不是set2的子集,或者遍历完整个set1,此时可确定set1是set2的子集。

set_is_subset的时间复杂度为O(mn),这里m和n分别代表集合set1和set2中的成员个数。这是因为对于集合set1中的每一个成员都需要调用复杂度为O(n)的set_is_member操作来判断该成员是否在集合set2中出现过。


set_is_equal

函数set_is_equal操作用来判断集合set1是否和集合set2相等。因为相等的两个集合必然有相同的成员个数,所以就从比较它们的成员个数开始。如果两个集合的成员个数不等,则它们必然不等。如果两个集合的成员个数相等,只需要确定set1是否为set2的子集就可以返回结果了,这可以通过调用set_is_subset来实现。

set_is_equal的运行时复杂度为O(mn),这里m和n分别代表集合set1和set2中的成员个数。这是因为set_is_subset的复杂度为O(mn)。


set_size

这是一个宏,用来计算集合中的成员个数。可以直接通过访问数据结构Set中的size成员得到。

因此,set_size的时间复杂度为O(1),因为访问结构体中的成员可以在恒定的时间内完成。


4、Set示例:集合覆盖

集合覆盖是一种优化求解问题,对很多组合数学和资源选择问题给出了漂亮的抽象模型。问题是这样的:给定一个集合S,集合P由集合S的子集A1到An组成,集合C由集合P中的一个或多个子集组成。如果S中的每个成员都包含在C的至少一个子集中则称集合C覆盖集合S。此外,C包含的P的子集应该越少越好。

举个例子,设想从一大群选手中挑选人员组建出一支队伍,每名选手都拥有特定的技能组合。目标是组建出一支最小的队伍,使得队伍整体拥有一组特定的技能组合。也就是说,对于队伍整体所需要的技能,队伍中至少有一名选手必须拥有这项技能。假定S为队伍所必须拥有的技能集合,P为所有待选选手的技能集合。从P中挑选出一些技能组合以构成集合C,C必须覆盖S中所要求的所有技能。记住,我们选择的选手数量必须尽可能少。

这里给出的针对集合覆盖的算法是一种近似算法。它并不总是获得最优解,但肯定能够达到对数级的复杂度。该算法的工作原理是:不断从P中选出一个集合,使其能够覆盖S中最多的成员数量。换句话说,该算法每次都尝试尽可能早覆盖S中更多的成员,因此该算法采用了贪心法的思想。由于每个集合都是从P中选出的,如果P被移除,则它的成员也将从S中移除。当S中的成员被完全覆盖时,此时覆盖集合C就完成了。

让我们看看对于12种技能的集合S={a, b, c, d, e, f, g, h, i, j, k, l}的最佳覆盖集。考虑现在有7名待选选手的集合P={A1,……,A7}。P中选手拥有的技能集合为:A1={a, b, c, d}, A2={e, f, g, h, i}, A3={j, k, l}, A4={a, e}, A5={b, f, g}, A6={c, d, g, h, k, l}, A7={l}。最佳覆盖集应该是C={A1, A2, A3}。这里给出的算法选择的集合是C={A6, A2, A1, A3}。

下面的代码实现了一个函数cover,该函数在集合P的子集A1~An中挑选出能够覆盖集合S的近似最优解。该函数有3个参数:members是待覆盖的集合S,subsets是集合P中的子集,covering作为返回的覆盖集C。该函数将修改所传入的3个参数,因此在调用该函数时,如果必要的话应该先保存一份参数的拷贝。

函数刚开始执行时,covering通过调用set_init先得到初始化。只要members中还有未覆盖的成员,且subsets中的子集还没有挑选完,最外层的循环就得继续迭代。在这个循环中,每次迭代时它都在subsets中找出能够覆盖到members的最大交集。然后它将这个集合加到覆盖集covering中并把它的成员从members中移除(因为这些成员已经被覆盖了,下一次迭代将判断剩余的成员能否被覆盖)。在循环的最后,将所选择的这个集合从subsets中移除(已经选中的要移除)。如果最外层的循环因为members不为空而终止迭代,则表示subsets中的集合不可能满足完全覆盖members的要求。同样,如果在迭代期间subsets中的成员无法与members的成员形成交集,则同样表示subsets中的成员无法满足完全覆盖members的要求。函数cover如果找到了一个完全覆盖解,该函数返回0,参数covering指向这个完全覆盖解;如果不可能实现完全覆盖,则返回1;其他情况返回-1。

cover的复杂度为O(m^3),这里m代表members集合中的初始成员个数。在最坏的情况下,对于members中的每一个成员,subsets中都只有唯一一个子集与之对应,此时的复杂度是O(m^3)。在这种情况下,subsets有m个子集,set_intersection以O(m)的复杂度执行,因为当计算和members求交集时,subsets的每个子集都只有唯一一个成员需要遍历。因此cover的内层循环是O(m^2),而这个循环需要执行m次。

// 集合覆盖问题的头文件/* cover.h */#ifndef COVER_H#define COVER_H#include "set.h"/* Define a structure for subsets idetified by a key. */typedef struct KSet_{void *key;Set set;} KSet;/* Public Interface */int cover(Set *members, Set *subsets, Set *covering);#endif // COVER_H

// 集合覆盖问题的函数实现/* cover.c */#include <stdlib.h>#include "cover.h"#include "list.h"#include "set.h"/* cover */int cover(Set *members, Set *subsets, Set *covering){Set intersection;KSet *subset;ListElmt *member, *max_member;void *data;int max_size;/* Initialize the covering. */set_init(covering, subsets->match, NULL);/* Continue while there are noncovered members and candidate subsets. */while(set_size(members) > 0 && set_size(subsets) > 0){/* Find the subset that covers the most members. */max_size = 0;for(member = list_head(subsets); member != NULL;member = list_next(member)){if(set_intersection(&intersection, &((KSet *)list_data(member))->set,member) != 0){return -1;}if(set_size(&intersection) > max_size){max_member = member;max_size = set_size(&intersection);}set_destroy(&intersection);}/* A covering is not possible if there was no intersection. */if(max_size == 0)return 1;/* Insert the selected subset into covering. */subset = (KSet *)list_data(member);if(set_insert(covering, subset) != 0)return -1;/* Remove each covered member from the set of noncovered members. */for(member = list_head(&((KSet *)list_data(max_member))->set);member != NULL; member = list_next(member)){data = list_data(member);if(set_remove(members, (void **)&data) == 0 && members->destroy != NULL)members->destroy(data);}/* Remove the subset from the set of candidate subsets. */if(set_remove(subsets, (void **)&subset) != 0)return -1;}/* No covering is possible if there are still noncovered members. */if(set_size(members) > 0)return -1;return 0;}


0 0
原创粉丝点击