由于 user843453 的帖子的编辑队列已满,我将创建一个新帖子并解释其工作原理。我还对变量名称进行了一些更改,以便更清楚算法在做什么。这是代码:
#include<iostream>
#include<string>
#include<list>
using namespace std;
void print( list<int> l){
for(list<int>::iterator it=l.begin(); it!=l.end() ; ++it)
cout << " " << *it;
cout<<endl;
}
void subset(int arr[], int size, int choices_left, int index, list<int> &l){
if(choices_left==0){
print(l);
return;
}
for(int i=index; i<size;i++){
l.push_back(arr[i]);
subset(arr,size,choices_left-1,i+1,l);
l.pop_back();
}
}
int main(){
int array[5]={1,2,3,4,5};
list<int> lt;
subset(array,5,3,0,lt);
return 0;
}
要了解其工作原理,我们应该了解该示例调用中发生的情况:
A = [a0, a1, ..., a4]
ss(A, size = 5, choices_left = 3, index = 0, [])
|
| i = 0
|
| [] ~> [a0]
|
| ss(A, size = 5, choices_left = 2, index = 1, [a0])
| |
| | i = 1
| |
| | [a0] ~> [a0, a1]
| |
| | ss(A, size = 5, choices_left = 1, index = 2, [a0, a1])
| | |
| | | i = 2
| | |
| | | [a0, a1] ~> [a0, a1, a2]
| | |
| | | ss(A, size = 5, choices_left = 0, index = 3, [a0, a1, a2])
| | | | [a0, a1, a2] is printed
| | | -------------------- POPPED ---------------------
| | |
| | | [a0, a1, a2] ~> [a0, a1]
| | |
| | | i = 3
| | A1
| | | [a0, a1] ~> [a0, a1, a3]
| | |
| | | ss(A, size = 5, choices_left = 0, index = 4, [a0, a1, a3])
| | | | [a0, a1, a3] is printed
| | | -------------------- POPPED ---------------------
| | |
| | | [a0, a1, a3] ~> [a0, a1]
C | |
| | | i = 4
| | |
| | | [a0, a1] ~> [a0, a1, a4]
| | |
| | | ss(A, size = 5, choices_left = 0, index = 5, [a0, a1, a4])
| | | | [a0, a1, a4] is printed
| | | -------------------- POPPED ---------------------
| | |
| | | [a0, a1, a4] ~> [a0, a1]
| | |
| | -------------------- POPPED ---------------------
| |
| B [a0, a1] ~> [a0]
| |
| | i = 2
| |
| | [a0] ~> [a0, a2]
| |
| | ss(A, size = 5, choices_left = 1, index = 3, [a0, a2])
| | |
| | | ...
| | |
| | | [a0, a2, a3] is printed
| | |
| | A2 ...
| | |
| | | [a0, a2, a4] is printed
| | |
| | | ...
| | |
| | -------------------- POPPED ---------------------
| |
| | [a0, a2] ~> [a0]
| |
| | ...
| |
| -------------------- POPPED ---------------------
|
| [a0] ~> []
|
| i = 1
|
| [] ~> [a1]
|
| ...
-------------------- POPPED ---------------------
理解这一点的一个好方法是注意到在任何可能的大小为 3 的子集中,我们知道原始列表 A 的任何元素要么是此列表的一部分,要么不是列表的一部分。
这个想法对应于将元素推到后面的代码部分,然后执行其他操作,然后删除该元素。
做“其他事情”的部分是最重要的部分,我们可以通过查看递归中的A1部分来弄清楚它的作用,在递归的这个深度,我们得到列表[a0, a1]并负责为我们的子集生成最后一个元素,或者我们可以将其改写为生成大小为 1 的有效子集,它仅使用列表中 index 之后的元素。
我们必须从index 开始的原因是为了不产生重复的情况。要完全理解我的意思,你需要了解这个算法的从左到右的过程。
在它要求的整个算法的第一次迭代中:
“假设该元素在列表中,使用剩余元素生成所有可能的子集,现在假设该元素不在列表中,使用剩余元素生成所有可能的子集”
一般来说,它的核心是“给定这个大小为 n - k 的有效子集,使用我尚未考虑使用 k 个元素的元素生成一个有效子集”。这就是为什么我们只需要考虑从index 开始的元素。
具体来说,我们可以看到递归级别 B 表示“假设 a0 将成为我们子集的一部分,让我们找到大小为 2 的子集,然后将它们连接到列表的末尾 [a0]。
在该递归级别的下一个调用会说“假设 a0 不是子集的一部分,但 a1 是子集的一部分,生成大小为 2 的子集,然后将它们连接到列表[a1]。
最后再来一次,它会说“假设a0 和a1 不是子集的一部分,但a2 是子集的一部分......
备注
正如其他解决方案指出的那样,您可以使用二进制数来生成子序列,因为如果我们有列表A = [a0, a1, a2, a3, a4],我们可以看到二进制数 1 0 1 1 1 可以是一个指定是否该索引处的元素将成为该子集的一部分,因此它将生成子集[a0, a2, a3, a4]。
然后完成两个不同算法之间的连接,你可以看到这个算法将通过这样做来生成二进制:
for(int i=index; i<size;i++){
l.push_back(arr[i]); // THIS IS FLIPPING BIT AT INDEX I to a 1
// THE BELOW GENERATES ALL POSSIBLE BINARY NUMBERS SO THAT WHEN ANY ONE IS APPENDED TO OUR CURRENT BINARY NUMBER IT'S LENGTH IS N
subset(arr,size,choices_left-1,i+1,l);
l.pop_back(); // FLIPPING THE BIT AT INDEX I BACK TO 0
}