枚舉子集的3種方式 -- C++描述


要求:

  給定一個集合,枚舉所有可能的子集。此處的集合是不包含重復元素的。

 

Method0: 增量構造法

  思路:每次選取一個元素至集合中,為了避免枚舉重復的集合,此處要采用定序技巧 -- 除了第一個元素,每次選取必須要比集合中的前一個元素要大!

  

// A 為原集合;
// B 為子集,每次調用函數即會打印一次
// cur 為子集元素個數
void print_subset0(int *A, int *B, int N, int cur) {
    for(int i=0; i<cur; i++) {
        printf("%5d", B[i]);
    }
    printf("\n");
    if( cur < N ) {
        for( int i=0; i<N; i++ ) {
            if( !cur || A[i] > B[cur-1] ) {
                B[cur] = A[i];
                print_subset0(A, B, N, cur+1);
            }
        }
    }
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};
    printf("Method0:\n");
    int B[Length] = {0};
    print_subset0(A, B, Length, 0);
    printf("\n");
    return 0;
}

 

 

   采用的是遞歸調用,但此處不需要return語句,因為當沒有元素可用於枚舉時,就不會調用函數,也就是不會繼續遞歸。

   此函數輸出的子集中是包含空集的,如果不想用空集,則需判斷 cur 是否為0,不為0才打印子集

   測試樣例的輸出結果(包含空集):

    

 

Method1: 位向量法

  思路:1個容量為N的集合,每個位置0~N-1,對於每個子集,要么被選中,要么沒被選中。枚舉每一個位置的狀態,可得到各種子集。

  

// A 為原集合;
// A 為原集合
// used為當前A中每個位置的元素的狀態(選中或未被選中)
// cur代表現在枚舉A[cur]的狀態
void print_subset1(int *A, int *used, int N, int cur) {
    if( cur == N ) {
        for(int i=0; i<N; i++) {
            if( used[i] ) {
                printf("%5d", A[i]);
            }
        }
        printf("\n");
        return ;
    }
    used[cur] = 0;
    print_subset1(A, used, N, cur+1);
    used[cur] = 1;
    print_subset1(A, used, N, cur+1);
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};

    printf("Method1:\n");
    int B[Length] = {0};
    print_subset1(A, B, Length, 0);
    printf("\n");

    return 0;
}

 

  同樣是遞歸枚舉,這里需要用return終止遞歸,終止條件就是cur == N即枚舉了一種子集,然后輸出  

  此函數的輸出是包含空集的,如果不想要空集,則需要判斷used函數是否全為0,如果全為0,則不輸出

  樣例輸出(包含空集):

    

 

 

Method10: 二進制法

  類似於位向量法,同樣也是枚舉各個位置的狀態,但這次用二進制表示,二進制長度為N,與原集合大小相同。二進制的第 i 位代表原集合中的第 i 位是否被選中,枚舉各種情況。集合大小為N,就是2的N次種方式。

  

void print_subset10(int *A, int N, int seq) {
    for(int i=0; i<N; i++) {
        if( seq & (1<<i) ) {
            printf("%5d", A[i]);
        }
    }
    printf("\n");
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};
    printf("Method10:\n");
    for(int i=0; i<(1<<Length); i++) {
        print_subset10(A, Length, i);
    }
    printf("\n");
    return 0;
}

 

   這種方式很好寫,也很好記,但問題是,因為函數中的形參seq是int型的,所以N最大也就只能32,如果long long,那N也只能最大64,再超過64,就需要用大數或其它表示方式表示了。

   如果不想要空集,可以將main函數中的 i 從1枚舉起。

  樣例輸出結果(包含空集):

    

 

參考資料: 《算法競賽入門經典(第2版)》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM