[英]All possible combinations of r elements in a given array of size n with conditions
[英]How to (cheaply) calculate all possible length-r combinations of n possible elements
在不求助於蠻力技術或任何需要STL的情況下,計算n個可能元素的所有可能的length-r組合的最快方法是什么?
在為數據結構課程中的最終項目開發Apriori算法時,我開發了一個有趣的解決方案,該解決方案使用了移位和遞歸,下面將向有興趣的人分享一個答案。 但是,這是實現此目標的最快方法(不使用任何公共庫)嗎?
我出於好奇心要求的更多,因為我目前擁有的算法可以很好地滿足我的目的。
這是我為解決此問題而開發的算法。 它當前僅將每個組合輸出為一系列的一和零,但是可以很容易地調整為根據可能的元素數組創建數據集。
void r_nCr(const unsigned int &startNum, const unsigned int &bitVal, const unsigned int &testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1)
{
unsigned int n = (startNum - bitVal) << 1;
n += bitVal ? 1 : 0;
for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s
cout << (n >> (i - 1) & 1);
cout << endl;
if (!(n & testNum) && n != startNum)
r_nCr(n, bitVal, testNum);
if (bitVal && bitVal < testNum)
r_nCr(startNum, bitVal >> 1, testNum);
}
此函數將元素的每種組合視為一串零序列,然后可以相對於一組可能的元素表示(但在此特定示例中不是)。
例如,3C2的結果(來自3個可能元素的長度2的所有組合)可以表示為011、110和101。如果所有可能元素的集合為{A,B,C},則結果可以針對此集合表示為{B,C},{A,B}和{A,C}。
為了便於說明,我將計算5C3(由5個可能的元素組成的所有長度為3的組合)。
此函數接受3個參數,所有參數均為無符號整數:
第一個參數是可能的最小整數,其二進制表示形式的1等於我們正在創建的組合的長度。 這是用於生成組合的起始值。 對於5C3,這將是00111b或十進制的7。
第二個參數是在起始編號中設置為1的最高位的值。 這是創建組合時要減去的第一位。 對於5C3,這是從右邊開始的第三位,其值為4。
第三個參數是從右起第n位的值,其中n是我們正在組合的可能元素的數量。 此數字將按位與我們創建的組合進行校驗,以檢查組合的最左邊的位是1還是0。對於5C3,我們將使用右邊的第5位,即10000b或16十進制。
對於第一次迭代,結果應與startNum相同。 這樣,我們就可以打印出函數中的第一個組合(等於startNum),因此我們不必提前手動進行操作。 此操作的數學運算如下:
00111 - 00100 = 00011
00011 << 1 = 00110
00110 + 1 = 00111
我們將把結果打印到控制台。 這是使用for循環完成的,該循環的變量開頭等於我們正在使用的位數(通過將testNum的log2加上1; log2(16)+ 1 = 4 + 1 = 5計算得出),並結束於0.每次迭代,我們將i-1右移,並通過將結果與1相加來打印最右邊的位。這是數學公式:
i=5:
00111 >> 4 = 00000
00000 & 00001 = 0
i=4:
00111 >> 3 = 00000
00000 & 00001 = 0
i=3:
00111 >> 2 = 00001
00001 & 00001 = 1
i=2:
00111 >> 1 = 00011
00011 & 00001 = 1
i=1:
00111 >> 0 = 00111
00111 & 00001 = 1
output: 00111
顯然,這將在第一次迭代時跳過,因為我們已經表明n等於startNum。 這在隨后的迭代中變得很重要,我們將在后面看到。
現在,我們將新的bitVal設置為當前bitVal右邊的下一位的值來遞歸。 下一位是在下一次迭代中減去的位。
因為在第二次遞歸調用中bitVal向右移了一個位,所以我們最終將在bitVal等於0時到達一個點。該算法擴展為一棵樹,並且當bitVal等於0且最左邊的位為1時,我們返回距我們當前位置一層。 最終,這將一直級聯到根。
在此示例中,樹具有3個子樹和6個葉節點。 現在,我將逐步瀏覽第一個子樹,該子樹包含1個根節點和3個葉節點。
我們將從第一次迭代的最后一行開始,即
if (bitVal)
r_nCr(startNum, bitVal >> 1, testNum);
因此,我們現在輸入第二個迭代,其中startNum = 00111(7),bitVal = 00010(2)和testNum = 10000(16)(此數字永不變)。
第二次迭代
第1步:
n = 00111 - 00010 = 00101 // Subtract bitVal
n = 00101 << 1 = 01010 // Shift left
n = 01010 + 1 = 01011 // bitVal is not 0, so add 1
步驟2:列印結果。
步驟3:最左邊的位是0,並且n不等於startNum,因此我們以n作為新的startNum遞歸。 現在,我們以startNum = 01011(11),bitVal = 00010(2)和testNum = 10000(16)輸入第三次迭代。
第三次迭代
第1步:
n = 01011 - 00010 = 01001 // Subtract bitVal
n = 01001 << 1 = 10010 // Shift left
n = 10010 + 1 = 10011 // bitVal is not 0, so add 1
步驟2:列印結果。
步驟3:最左邊的位是1,所以不要遞歸。
步驟4:bitVal不為0,因此使用bitVal右移1遞歸。現在,我們進入第四次迭代,其中startNum = 01011(11),bitVal = 00001(1)和testNum = 10000(16)。
第四次迭代
第1步:
n = 01011 - 00001 = 01010 // Subtract bitVal
n = 01010 << 1 = 10100 // Shift left
n = 10100 + 1 = 10101 // bitVal is not 0, so add 1
步驟2:列印結果。
步驟3:最左邊的位是1,所以不要遞歸。
步驟4:bitVal不為0,因此將bitVal右移1以進行遞歸。現在,我們進入第五次迭代,其中startNum = 01011(11),bitVal = 00000(0)和testNum = 10000(16)。
第五次迭代
第1步:
n = 01011 - 00000 = 01011 // Subtract bitVal
n = 01011 << 1 = 10110 // Shift left
n = 10110 + 0 = 10110 // bitVal is 0, so add 0
// Because bitVal = 0, nothing is subtracted or added; this step becomes just a straight bit-shift left by 1.
步驟2:列印結果。
步驟3:最左邊的位是1,所以不要遞歸。
步驟4:bitVal為0,因此請勿遞歸。
返回第二個迭代
步驟4:bitVal不為0,因此使用bitVal右移1進行遞歸。
這將一直持續到樹的第一級的bitVal = 0為止,然后返回到第一次迭代,這時我們將完全從函數中返回。
這是顯示函數樹狀擴展的簡單圖:
這是一個更復雜的圖,顯示了函數的執行線程:
這是一個使用逐位或替代加法和逐位異或替代減法的替代版本:
void r_nCr(const unsigned int &startNum, const unsigned int &bitVal, const unsigned int &testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1)
{
unsigned int n = (startNum ^ bitVal) << 1;
n |= (bitVal != 0);
for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s
cout << (n >> (i - 1) & 1);
cout << endl;
if (!(n & testNum) && n != startNum)
r_nCr(n, bitVal, testNum);
if (bitVal && bitVal < testNum)
r_nCr(startNum, bitVal >> 1, testNum);
}
那這個呢?
#include <stdio.h>
#define SETSIZE 3
#define NELEMS 7
#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d"
#define BYTETOBINARY(byte) \
(byte & 0x80 ? 1 : 0), \
(byte & 0x40 ? 1 : 0), \
(byte & 0x20 ? 1 : 0), \
(byte & 0x10 ? 1 : 0), \
(byte & 0x08 ? 1 : 0), \
(byte & 0x04 ? 1 : 0), \
(byte & 0x02 ? 1 : 0), \
(byte & 0x01 ? 1 : 0)
int main()
{
unsigned long long x = (1 << SETSIZE) -1;
unsigned long long N = (1 << NELEMS) -1;
while(x < N)
{
printf ("x: "BYTETOBINARYPATTERN"\n", BYTETOBINARY(x));
unsigned long long a = x & -x;
unsigned long long y = x + a;
x = ((y & -y) / a >> 1) + y - 1;
}
};
它應該打印7C3。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.