generate all subsets of size k from a set
I want to generate all the subsets of size k from a set.
eg:-say I have a set of 6 elements, I have to list all the subsets in which the cardinality of elements is 3.
I tried looking for solution,but those are code snippets. Its been long since I have done coding,so I find it hard to understand the code and construct a executable program around it.
A complete executable program in C 开发者_开发问答or C++ will be quite helpful. Hoping of an optimal solution using recursion.
Find a working code below
#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 left, int index, list<int> &l){
if(left==0){
print(l);
return;
}
for(int i=index; i<size;i++){
l.push_back(arr[i]);
subset(arr,size,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;
}
Initialize a bit array with (1<<nbits)-1
and then use this algorithm:
http://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
For sets larger than the maximum integer size, you can still apply the same algorithm to your own type.
#include <cstdio>
void g(int s[],int p,int k,int t[],int q=0,int r=0)
{
if(q==k)
{
for(int i=0;i<k;i++)
printf("%d ",t[i]);
printf("\n");
}
else
{
for(int i=r;i<p;i++)
{
t[q]=s[i];
g(s,p,k,t,q+1,i+1);
}
}
}
main()
{
int s[]={1,2,3,4,5},t[5];
g(s,5,3,t);
}
The Problem can be solved using recursion. We need to consider the following cases for recursion.
- The current element is chosen . Now we recursively choose the remaining k-1 elements from the remaining set .(inclusion)
- The current element is not chosen . Now we recursively choose k elements from the remaining set.(exclusion)
Following is a C++ program that demonstrates the above Algorithm.
#include<iostream>
#include<vector>
using namespace std;
void computeSubsets(vector<vector<int> > & results, vector<int> const& nums, vector<int> & curSet, int idx, int size) {
if (idx == size) {
results.push_back(curSet);
return ;
}
// Include
curSet.push_back(nums[idx]);
computeSubsets(results, nums, curSet, idx+1, size);
curSet.pop_back();
// Exclude
computeSubsets(results, nums, curSet, idx+1, size);
}
vector<vector<int>> getSubsets(vector<int> const& nums) {
vector<vector<int> > results;
vector<int> temp;
int size = nums.size();
temp.reserve(size);
results.reserve(1 << size);
computeSubsets(results, nums, temp, 0, size);
return results;
}
int main(){
vector<int> nums = {1, 2, 3};
auto subsets = getSubsets(nums);
for (auto const& subset : subsets) {
for (auto const& x : subset) {
cout << x << " ";
}
cout << "\n";
}
return 0;
}
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
}
The most intuitive algorithm would indeed use recursion. When you have a set, we will assume you can iterate over all its elements.
If I call tail(e) a set of all the elements after element e.
Thus I want right now combinations(s,k)
loop over each element in s and get e :: combinations(tail(e), k-1) where :: means "concatenated to each of"
Of course sometimes there will be no such combinations (you are off the end of the list).
You just need a master collection (set of sets) to add your combinations to and a way to create
So assuming we have no globals anywhere we can have something like:
getCombinations( headset [in], tailset [in], count [in], output [append] )
headset or tailset could be empty. count could be 0, in which case we write "headset" to output (the base condition) otherwise we iterate through each element in tailset, adding it (locally) to headset, make tailset (locally) the tail of our element, subtract 1 from count and "recurse" by calling the function.
Here's some pseudocode. You can cut same recursive calls by storing the values for each call as you go and before recursive call checking if the call value is already present.
The following algorithm will have all the subsets excluding the empty set.
list * subsets(string s, list * v){
if(s.length() == 1){
list.add(s);
return v;
}
else
{
list * temp = subsets(s[1 to length-1], v);
int length = temp->size();
for(int i=0;i<length;i++){
temp.add(s[0]+temp[i]);
}
list.add(s[0]);
return temp;
}
}
#include <stdio.h>
#define FIN "subsets.in"
#define FOUT "subsets.out"
#define MAXSIZE 100
void performSubsets(int n, int k){
int i, j, s, v[ MAXSIZE ];
freopen(FOUT, "w", stdout);
memset(v, 0, sizeof( v ));
do {
v[ n - 1 ]++;
for(i = n - 1; i >= 1; i--) {
if(v[ i ] > 1) {
v[ i ] -= 2;
v[ i - 1 ] += 1;
}
}
s = 0;
for(j = 0; j < n; j++) s += v[j];
for(j = 0; j < n; j++)
if( v[ j ] && s == k) printf("%d ", (j + 1));
if(s == k) printf("\n");
} while(s < n);
fclose( stdout );
}
int main() {
int n, k;
freopen(FIN, "r", stdin);
//read n and size k
scanf("%d %d", &n, &k);
fclose( stdin );
performSubsets(n,k);
}
This problem can be solved using an algorithm non-recursive.
My old code gives the following result:
111000
110100
110010
110001
101100
101010
101001
100110
100101
100011
011100
011010
011001
010110
010101
010011
001110
001101
001011
000111
Enough optimized:
#include <iostream>
int firstPermutation(int n, int k) {
return ((1 << k) - 1) << (n - k);
}
int shiftLast1(int a) {
return (a - 1) ^ ((a^(a - 1)) >> 2);
}
int add1AfterLast1(int a) {
return a | (((a^(a - 1)) + 1) >> 2);
}
int nextPermutation(int a) {
if ((a & (a + 1)) == 0) {
return 0;
}
if (a & 1) {
return add1AfterLast1(nextPermutation(a >> 1) << 1);
}
else {
return shiftLast1(a);
}
}
int main() {
int n = 6;
int k = 3;
int a = firstPermutation(n, k);
do {
for (int i = 0; i < n; i++) {
std::cout << ((a >> (n - 1 - i)) & 1);
}
std::cout << std::endl;
} while ((a = nextPermutation(a)));
}
Here is an Iterative solution :
#include <stdio.h>
#include <stdlib.h>
void printer(int locations[],int a[],int r)
{
int i;
for(i=0; i<r; i++)
{
int x=locations[i];
printf("%d ",a[x]);
}
printf("\n");
}
int main()
{
int a[100000];
int locations[1000];
int i,n,r;
printf("Enter N: ");
scanf("%d",&n);
printf("Enter K: ");
scanf("%d",&r);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0; i<r; i++)
locations[i]=i;
printer(locations,a,r);
while(locations[0]<n-r)
{
for(i=r-1; i>0; i--)
{
if(locations[i-1]<n-r+i-1)
{
if(locations[i]<n-r+i)
{
locations[i]++;
printer(locations,a,r);
break;
}
else
{
locations[i-1]++;
int j;
for(j=i; j<r; j++)
{
locations[j]=locations[j-1]+1;
}
printer(locations,a,r);
break;
}
}
}
}
return 0;
}
精彩评论