Since the edit queue for user843453's post is full, I am going to create a new post with an explanation as to why this works. I have also made some changes to the variable names so that it's more clear what the algorithm is doing. Here is the code once more:
#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;
}
To understand how this works, we should understand what occurs in that sample call:
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 ---------------------
One nice way to understand this is by noting that in any possible subset of size 3, we know that any element of the original list A
is either part of this list or it's not part of the list.
This idea corresponds to the section of the code which pushes the element to the back, then does something else, and then removes that element.
The part which does "something else" is the most important part, and we can figure out what it does by looking at section A1
in the recursion, at this depth in the recursion we are given the list [a0, a1]
and are tasked with generating the last element for our subset, alternatively we can rephrase that as generating valid subsets of size one, which only use elements from the index
onward in the list.
The reason why we must go from index
onward is so that we do not generate duplicate situations. To fully understand what I mean you need to understand the left to right process of this algorithm.
On the first iteration of the whole algorithm it asks:
"Suppose this element is in the list, generate all possible subsets using the remaining elements, now suppose this element is not in the list, generate all possible subsets using the remaining elements"
And in general at it's core, it becomes "given this valid subset of size n - k, generate a valid subsets using elements I have not considered yet using k elements". This is the reason why we need to only consider elements from index
onward.
Concretely we can see that recursion level B
is saying "Assuming that a0
is going to be part of our subset, let's find subsets of size 2 and then adjoin them to the end of the list [a0]
.
The next call at that recursion level would say "Assuming that a0
is not part of the subset, but that a1
is part of the subset, generate subsets of size 2, and then adjoin them to the end of the list [a1]
.
Finally going one more time, it would say "Assuming that a0
and a1
are not part of the subset but that a2
is part of the subset ...
Remark
As the other solutions point out, you can use binary numbers to generate the sub sequences, simply because if we have the list A = [a0, a1, a2, a3, a4]
we can see that the binary number 1 0 1 1 1 could be a list which specified whether an element at that index is going to be part of that subset, so it would generate the subset [a0, a2, a3, a4]
.
Then to finish of the connection between the two different algorithms, you can see that this algorithm would be generating binary by doing this:
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
}