在不求助于蛮力技术或任何需要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-bitVal,向左位移一位,如果bitVal不为0,则加1。
对于第一次迭代,结果应与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
如果bitVal大于0且小于testNum,则以当前迭代的原始startNum作为第一个参数进行递归。第二个参数是bitVal右移1(与整数除以2相同)。
现在我们递归,将新的bitVal设置为
当前bitVal右边的下一位的值。下一位是
在下一次迭代中减去的位。
继续递归直到bitVal等于零。
因为在第二个递归调用中bitVal向右移了一位,所以我们
最终将在bitVal等于0时到达一个点。此算法扩展为
树,并且当bitVal等于0且最左边的位为1时,我们返回
距我们当前位置一层。最终,这将一直级联
到根。
在此示例中,树具有3个子树和6个叶节点。现在,我将逐步
浏览第一个子树,该子树由1个根节点和3个叶节点组成。
我们将从第一次迭代的最后一行开始,即
if (bitVal)
r_nCr(startNum, bitVal >> 1, testNum);
So we now enter the second iteration with startNum=00111(7), bitVal =
00010(2), and testNum = 10000(16) (this number never changes).
Second Iteration
Step 1:
n = 00111 - 00010 = 00101 // Subtract bitVal
n = 00101 << 1 = 01010 // Shift left
n = 01010 + 1 = 01011 // bitVal is not 0, so add 1
Step 2: Print result.
Step 3:bitVal不为0,因此将bitVal右移1进行递归。现在,我们
进入第四次迭代,其中startNum = 01011(11),bitVal = 00001(1)和
testNum = 10000(16)。
Third Iteration
Step 1:
n = 01011 - 00010 = 01001 // Subtract bitVal
n = 01001 << 1 = 10010 // Shift left
n = 10010 + 1 = 10011 // bitVal is not 0, so add 1
Step 2: Print result.
Step 3: The left-most bit is 1, so do not recurse.
Step 4: bitVal is not 0, so recurse with bitVal shifted right by 1. We now
enter the fourth iteration with startNum=01011(11), bitVal = 00001(1), and
testNum = 10000(16).
Fourth Iteration
Step 1:
n = 01011 - 00001 = 01010 // Subtract bitVal
n = 01010 << 1 = 10100 // Shift left
n = 10100 + 1 = 10101 // bitVal is not 0, so add 1
Step 2: Print result.
Step 3: The left-most bit is 1, so do not recurse.
Step 4: bitVal is not 0, so recurse with bitVal shifted right by 1. We now
enter the fifth iteration with startNum=01011(11), bitVal = 00000(0), and
testNum = 10000(16).
Fifth Iteration
Step 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为止,然后
返回到第一次迭代,这时我们将
完全从函数中返回。
这是一个简单的图,显示了函数的树状扩展: 该图显示了递归扩展
这是一个更复杂的图,显示了函数的
执行线程:Diagrom显示执行线程
这是一个使用逐位或替代加法和
逐位异或替代减法的替代版本:
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);
}
我想从数组创建所有可能的数组可能大于或小于。输出数组中的元素不必是唯一的。 例如: 根据这个数组 给定所需大小的函数,应返回: 例2 根据这个数组 给定所需大小的函数,应返回: 用Swift怎么做?
我想在java中创建一个方法,该方法接收两个字符串列表:
问题内容: 有一个包含15个数字的列表,我需要编写一些代码来生成这些数字的所有32,768个组合。 我已经找到了一些代码(通过Googling),这些代码显然可以满足我的需求,但是我发现代码相当不透明并且对使用它很谨慎。另外我觉得必须有一个更优雅的解决方案。 对我而言,唯一发生的就是循环遍历十进制整数1–32768,并将其转换为二进制,然后使用二进制表示形式作为筛选器来选择适当的数字。 有人知道更
问题内容: 我想以所有可能的组合将列表分为n个组(允许可变的组长)。 说,我有以下列表: 如果我指定n = 2,则列表可以分为1个element-3元素或2个element-2元素的组。在拆分列表的两种方式中,每个列表中都有哪些元素的独特组合。 在n = 2的情况下,它们将是: 当n = 1时,这些将是: 在n = 3的情况下,它们将是: 我对长度为0的组不感兴趣,并且组内的顺序无关紧要。 我发现
我正在尝试构造一个程序,该程序将获取一个int({1,2,3})数组和一个长度值,并计算该数组的所有可能组合。 例如: 这将输出: 但是当我尝试在 for 循环中调用可能的梳子时,我不断收到堆栈溢出错误 }
问题内容: 我需要迭代计算排列。方法签名如下所示: 为了n = 3例如,返回值将是: 您将如何以最有效的方式迭代进行此操作?我可以递归执行此操作,但是我有兴趣看到许多其他迭代执行方法。 问题答案: 请参阅QuickPerm算法,它是迭代的:http ://www.quickperm.org/ 编辑: 为了清楚起见,在Ruby中进行了重写: