1. 程式人生 > >給力!簡單!易懂!位運算之求集合的所有子集

給力!簡單!易懂!位運算之求集合的所有子集

  

摘要

剛剛完成一篇利用位運算高效地巧妙地來解決求組合的博文:《非常給力:位運算求組合》巧合的是,我在《資料結構演算法與應用》一書中看到一道課後題是:用遞迴實現求一個集合的所有子集。受到題目的要求,我開始想遞迴,想著想著,我就發現此題不用遞迴而用位運算來求解,仍然非常巧妙!本篇,我將講解如何利用位運算來求解集合的所有子集,個人認為這種方法簡單、易懂,而且高效!欲知詳情,請看下文:

前奏

最近一直在關注位運算及其應用,並完成了多篇博文,其中是一篇非常值得閱讀的一篇,它是我將POJ上的一道水題跟求組合的問題結合而成的。在此力薦各位朋友看看那篇博文。一方面,我正是在完成那篇博文的基礎上想到本篇即將要介紹的求子集的方法的,可以說要是沒有撰寫那篇博文的經歷,我根本無法想到本篇的方法;另一方面,本篇應用了那篇博文的很多知識點,這些東西由於在那篇博文中已經介紹過,因此本篇直接利用之,如果您閱讀時有不解之處,那麼請先閱讀那篇博文。

入題

編寫演算法求一個集合的所有子集,比如集合set={a,b,c,d}的所有子集是:

{},

{a}, {b}, {c}, {d},

{a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d},

{a, b, c}, {a, b, d}, {a, c, d}, {b, c, d},

{a, b, c, d}

一共16個,事實上n個元素的集合的子集共有2n個(包含空集)。

攀親

事實上“求集合的子集”跟“求組合”是親緣關係。為什麼呢?首先,集合set={s1,s2,……sn}的所有子集可以看做是由多組子集組成的,這些集合組是:

包含0個元素的子集(空集)

包含1個元素的子集

……

包含n個元素的子集。

其次,“包含1個元素的子集”與“從set中選擇1個元素的組合”等價,“包含2個元素的子集”與“從set中選擇2個元素的組合”等價……“包含n個元素的子集”與“從set中選擇n個元素的組合”等價!

因此,求set的所有子集的問題就化成了分別求從set中選擇1,2……n個元素的組合的問題!注意這裡我們並沒考慮空子集,因為任何集合都包含且僅包含一個空子集,因此可以不用考慮它。

還記得上一篇嗎,它的主題就是求組合,其核心是位運算!因此,本篇也是上一篇的姊妹篇。這裡再次提醒,如果你沒有看上一篇,那麼你可能會在閱讀本篇中遇到麻煩,由於上一篇我講解得比較細緻(個人認為),特別是那個至關重要的引例(利用位操作實現求最小的比N大的,二進位制表示中1的個數跟N相同的數

),因此,本篇對這些重複的內容不再講解,而是直接拿來應用。下面是上一篇的核心程式,同時它也是本篇的核心:

int NextN(int N)
{
    return (N+(N&(-N))) | ((N^(N+(N&(-N))))/(N&(-N)))>>2;
}

該程式碼實現的功能是:求得最小的比N大的,二進位制表示中1的個數跟N相同的數!如果你對該程式碼有任何疑問,那麼請您回過頭去看一文,那裡有對該程式碼的詳細剖析,我個人強烈推薦它,如果您在閱讀它遇到任何問題,或者發覺任何不對之處,請與我聯絡,聯絡方式是:[email protected]

思路&實現

現在開始介紹利用位操作來求一個集合的所有子集。用語言描述這個過程就是:用一個整數的二進位制位來標識集合中的某個元素是否包含在某個子集內。我們的任務是要求得set的包含i個元素的子集(組合),其中i取值1,2,……n。這裡假設set={a, b, c, d};那麼下面四個數對應著set的四個只包含1個元素的子集:

00000001 :  {a}

00000010 :  {b}           //0000 0010 = NextN(00000001)

00000100 :  {c}           //0000 0100 = NextN(00000010)

00001000 :  {d}           //0000 1000 = NextN(00000100)

同理可得,下面的6個數,分別與set的6個只包含2個元素的子集對應

00000011: {a, b}

00000101: {a, c}

00000110: {b, c}

00001001: {a, d}

00001010: {b, d}

00001100: {c, d}

由上可知,我們只需要根據每組的第一個數(上面標粗加紅的數字)就能利用前面那個NextN函式求得該組後面的所有的數,這樣就能求得其對應的子集。通過分析,我們得知每一組的第一個數字(上面標粗加紅的數字)必然是(1<<i) -1;比如說與set的只包含1個元素的子集對應的第一個數字是0000 0001 = (1<<1) -1,與set的只包含2個元素的子集對應的第一個數字是0000 0011 = (1<<2) -1。有了每一組的第一個數字,我們利用NextN函式就能求得該組的其他數字,當求出一個數字之後,打印出該數字所對應的組合。只要打印出每一組的所有數字對應的組合,我們就能求得集合set的子集,下面是求集合set的子集的步驟(其中假設集合set包含n個元素):

1、  定義i= 1; //表示當前輸出包含i個元素的所有子集

2、  判斷i是否小於n,如果是,則轉3,否則轉7。

3、  定義c = (1<<i)-1; //c是所有包含i個元素的子集中的第一個子集

4、  判斷c是否小於等於(1<<n)-1,如果是則轉5,否則轉6。//最後一個子集包含所有元素,其對應的數字是(1<<n)-1

5、  呼叫print(set,c)列印set的與當前c對應的子集,並用c=NextN(c)計算下一個子集對應的數字,然後轉4。

6、  執行i++;然後轉2

7、  結束

從上面的步驟可以看出,該程式是個兩重迴圈結構,其流程圖如下:

在上面的流程中用於列印set的與數字c對應的子集的函式也是我在博文中實現過的,其程式碼如下:

void print(char* set,int C)
{
    int i = 0;
    int k;
    while((k=1<<i)<=C)
    {//迴圈測試每個bit是否為1 
        if((C&k)!=0)
        {
            cout<<set[i];
        }
        i++;
    }
}

然後根據上述流程圖,寫出求集合set的所有子集(不包含空集)的函式如下:

//求set的所有非空子集,n是set中包含的元素個數            
void SubSet(char* set,int n)
{
	int i =1;
	while (i<=n)
	{
		int c = (1<<i) -1;	//c是每一組的第一個數
		while (c<=(1<<n)-1)
		{
			print(set,c);
			cout<<endl;
			c = NextN(c);
		}
		i++;
	}
}

最後寫一個用於測試的程式:

char set[4] = {'a','b','c','d'};     
void main()
{
	SubSet(set,4);
}

該程式在VC6.0上測試通過,其執行結果如下圖:

分析

如前面的圖所示,實現了set的所有子集的輸出(當然不包含空子集,如果需要你可以隨時加上,同時輸出格式並不是{a, b, c}的形式,這些問題都很好解決,這裡不贅述了)。

通過分析,該方法跟非常給力:位運算求組合一文中的求組合一樣有其使用限制:1、當集合中元素個數較多時,無法使用該方法,因為你無法找到足夠大的整數來標識每一個元素。

2、順序問題:原題要求輸出{a, c}之後輸出{a, d},然而我的程式輸出的卻是{b, c},原因跟非常給力:位運算求組合一文一樣。

結束語

到此本篇即將結束,如果您有不清楚的地方或者發現有不妥之處,請與本人聯絡,最後感謝您的閱讀!最後預告我的下一篇即將完成的關於位運算博文《位操作應用之位排序》,我計劃面向對位排序瞭解不多的讀者來寫,由淺入深。並且對用標準庫的bitset類來完成位排序進行講解,提出其中的問題,最後自己實現一個簡化版的bitset類,用於位排序。

申明

本人的所有原著博文的版權均歸本人和CSDN所有。除侵權行為外,歡迎各位朋友評論指正轉載分享,分享時請標明作者和出處,本人暱稱:語過添情(w57w57w57)。其中語過添情”是我一直用的QQ暱稱,從2001年至今,它跟我已經10個年頭了。後面那一串古怪的字元的來歷是:字母w和數字5分別是本人姓(伍)的拼音首字母和諧音數字,7是因為我的啟蒙女友(初一時的,非初戀女友,因為那時我完全不懂戀愛)的姓(柒)的諧音數字。以前天真的以為以後生個小孩可以取名伍陸柒(567)的,可惜一學期後她就輟學了……

                                                        作者:    語過添情 (w57w57w57)

                                                        QQ:       11335457

                                                             2011-08-03

相關推薦

簡單易懂運算集合所有子集

   摘要 剛剛完成一篇利用位運算高效地、巧妙地來解決求組合的博文:《非常給力:位運算求組合》。巧合的是,我在《資料結構演算法與應用》一書中看到一道課後題是:用遞迴實現求一個集合的所有子集。受到題目的要求,我開始想遞迴,想著想著,我就發現此題不用遞迴而用位運算來求解,仍然非

強烈推薦一個線上學習C++的網站,非常贊簡單+高效+實用

網址:http://en.cppreference.com/w/cpp/language/reference 為什麼點贊: 1、在Search欄裡輸入你需要參考的內容,如lvalue 很快就能得到參考內容: 2、同時還能看到例子程式碼: 如果只能是看就用不著點讚了,

OpenCV_Python官方文件7+——按運算影象加logo

OpenCV-Python Tutorials 按位運算 包括按位與(AND)、按位或(OR)、按位非(NOT)、按位異或(XOR)等運算。 按位運算的用途:比如要得到一個加logo的影象。如果將兩幅圖片直接相加會改變圖片的顏色,如果用影象混合,則會改變圖片的

[Java]實現簡單的a+b(運算篇)

實現簡單的a+b 說明 a和b都是 32位 整數麼? 是的 我可以使用位運算子麼? 當然可以 樣例 如果 a=1 並且 b=2,返回3。 挑戰 顯然你可以直接 return a + b,但是你是否可以挑戰一下不這樣做?(不使用 "+" 等算數運算子) 當然

運算 C 與或非異或

代碼 cout expr namespace 整數 建議 div 不同 har View Code 位運算比較易混: 位運算之 C 與或非異或 與運算:& 兩者都為1為1,否則為0 1&1=1, 1&0=0, 0&1=0, 0

運算巧解

c代碼 所有 題目 sca bsp return put 忘記 十進制數 上班打卡 Time Limit: 2000/1000ms (Java/Others) Problem Description: 某公司上班使用打卡制度,員工需要在打卡機器

python運算計算中位數

() 否則 進制 software war c語言 Coding 語言 arm # -*- coding: utf-8 -*- # @Time : 2018/11/23 10:49 PM # @Author : cxa # @File : 1.py # @Software:

利用運算組合問題:

題目如下: 連結:https://ac.nowcoder.com/acm/contest/303/D 來源:牛客網   星際爭霸(StarCraft)單人戰役模式中有很多供人遊玩的任務關卡。   tokitsukaze新開始了一關單人戰役模式下的任務。在這場戰役中

運算實現絕對值-有效避開if-else判斷

一般情況下,如果要我們寫一個求絕對值的函式,我們的實現很有可能會是這樣: template<class T>T abs_Normal(T tNum){    if(tNum >0.0)        return tNum;    elsereturn-tNum;} 也就

C:運算 左移運算和右移運算

C:位運算之 左移運算(<<)和右移運算(>>) 在C中,位運算包含兩種移位運算: 左移運算:<< 右移運算:>> 左右位移運算,在數值為無符號和有符號情況下具有不同行為。 有符號左右位移運算

運算不使用任何比較判斷比較兩個數大小問題

【題目】 對於兩個32位整數a和b,請設計一個演算法返回a和b中較大的。但是不能用任何比較判斷。若兩數相同,返回任意一個。 給定兩個整數a和b,請返回較大的數。 測試樣例: 1,2 返回:2 【分析】 不用分析了,直接給程式碼,我第一次沒有仔細讀題,實際上用了判斷的,解

運算左移右移運算詳解

先看如下一段左移右移的程式碼及其結果: #include "stdio.h" char leftshift(char i, int n){if(n <0)return-1;return i<<n;}char rightshift(char i, in

運算判斷奇偶性&1

一個整數 n,n&1 這個表示式 可以用來 判斷 a的奇偶性。 二進位制的末位為 0表示偶數,末位為 1表是奇數。 使用 n%2 來判斷奇偶性 和 n&1 是一樣的作用,但是 n&

運算指定操作

# 題目: 實現對一個8Bit資料(unsigned char型別)的指定位(例如第n位)的置0或者置1操作,並保持其他位變。 @ 函式原型:unsigned char _operator_bit

運算十進位制轉二進位制 十六進制轉二進位制

利用位運算進行進位制間的轉換#include <stdio.h> #include <string.h> int inttoBin(unsigned int num) {

運算只出現一次的的數字

******位運算系列之陣列中只出現一次的數字****** //題目(1):在一個數組中只有一個數字出現一次,其他數字都是成對出現的!讓你找出這個只出現一次的數字, //其實,這也叫缺失的數字,用

運算——按與(&)操作——(快速取模演算法)

位運算之——按位與(&)操作——(快速取模演算法)   由於位運算直接對記憶體資料進行操作,不需要轉成十進

Java成員變量與屬性的區別,簡單易懂的解釋

col 最好 name color poj student oid style 簡單 例一: 一個Student pojo類: public class Student{ private String name; private int age; public S

美工沒時間圖,簡單的圖讓我們自己寫,哭啊 所以具體研究了一下shape的使用,保存下

其它 結束 get alt 屬性 width drawable 樣式 ref 在drawable文件夾中創建一個shape的資源文件,其中shape有四個屬性(rectangle、oval、line、ring) 這四個屬性是用來定義圖形的形狀對應(矩形、橢圓、線、圓環) 除

用心剖析,詳解如何搭建百萬PV網站架構,簡單易懂

socket cache sad nco sla tom 百萬 redis主從 debug 簡介: 本項目案例結合SVN、LNMP和MySQL三種環境,部署一個社交網站,本社交網站采用PHP語言開發,搭建SVN服務器進行版本控制和集中管理PHP程序員開發的代碼,以Nginx