1. 程式人生 > >數據結構 集合_集合實例(集合覆蓋)

數據結構 集合_集合實例(集合覆蓋)

不存在 人員 list 組成 elm != 文件 mem 組合數

集合覆蓋是一種優化求解問題,對很多組合數學和資源選擇問題給出了很好的抽象模型。 問題如下:給定一個集合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中移除。當P中剩余的成員沒有任何集合能夠覆蓋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},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}(見圖1)。

技術分享圖片

集合覆蓋問題的函數實現

我們使用函數cover,該函數在集合P的子集A1~An中挑選出能夠覆蓋集合S的近似最優解。該函數有3個參數:

1、members是待覆蓋的集合S;

2、subsets是集合P中的子集;

3、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(m3),這裏的m代表members集合中的初始成員個數。在最壞的情況下,對於members中的每一員,subsets中都只有唯一一個子集與之對應,此時的復雜度是O(m3)。在這種情況下,subsets有 m個子集,set_intesection以O(m)的復雜度執行,因為當計算和members求交集時,subsets的每個子集都只有唯一一個個成員需要遍歷。因此cover的內層循環是O(m2)的,而這個循環要執行m次。

示例1:集合覆蓋問題的頭文件

#ifndef COVER_H
#define COVER_H

#include "set.h"

typedef struct KSet_
{
    void *key;
    Set set;
}KSet;

int cover(Set *member, Set *subsets, Set *covering);

#endif 

示例2:集合覆蓋問題的函數實現

#include <stdlib.h>
#include "cover.h"
#include "list.h"
#include "set.h"

int cover(Set *members, Set *subsets, Set *covering)
{
    Set intersection;
    KSet *subset;
    ListElmt *member,*max_member;
    void *data;
    int max_size;
    
    /*初始化覆蓋集covering*/
    set_init(covering,subsets->match,NULL);
    
    while(set_size(members)>0 && set_size(subsets)>0)
    {
        /*找到能夠覆蓋最多members成員的子集*/
        max_size = 0;
        for(member = list_head(subsets);member!=NULL;member=list_next(member))
        {
            if(set_intersection(&intersection, &((KSet *)list_data(member))->set, members) != 0)
                return -1;
            
            if(set_size(&intersection)>max_size)
            {
                max_member = member;
                max_size = set_size(&intersection);
            }
            
            set_destroy(&intersection);
        }
        /*如果不存在交集,那麽就不可能有覆蓋集*/
        if(max_size==0)
            return 1;
        
        /*將被選到的子集插入覆蓋集covering中*/
        subset = (KSet *)list_data(max_member);
        
        if(set_insert(covering,subset) != 0)
            return -1;
        
        /*從members中移除已經被覆蓋的元素*/
        for(member=list_head(&((Kset *)list_data(max_member))->set);member != NULL;
            list_next(member))
        {
            data = list_data(member);
            if(set_remove(members,(void **)data)== 0 && members->destroy != NULL)
                members->destroy(data);
        }
        
        /*從子集集合中刪除已經被選用的子集*/
        if(set_remove(subsets,(void **)&subset) != 0)
            return -1;
    }
    
    /*如果members中仍然存在未被覆蓋的元素,那麽也不可能實現完全覆蓋*/
    if(set_size(members)>0)
        return -1;
    
    return 0;
}

數據結構 集合_集合實例(集合覆蓋)