排列组合总结
组合
1.位运算实现求组合:
在此介绍二进制转化法,即,将每一个组合与一个二进制数相应起来,枚举二进制的同一时候,枚举每一个组合。如字符串:abcde,则有
00000---------null
00001---------a
00010 --------b
00011---------ab
00100---------c
… …
11111--------abcde
#include <stdio.h> #include <string.h> void printCombination(char* str, int i); void combination(char* str) { int len = strlen(str); //共同拥有(1<<len)个组合,当中有一次什么都不打印外 for(int i=0; i< (1<<len); ++i) { printCombination(str, i); printf("\n"); } } void printCombination(char* str, int i) { int len = strlen(str); for(int j=0; j<len; ++j) { //看s中哪些位为 int s = i&(1<<j); if(s) printf("%c", str[j]); } } int main() { char str[] = "abc"; combination(str); }
2.递归的思路解决
#include <stdio.h> #include <string.h> void printCombination(char* str, int len, int m, int* arr, const int M); void combination(char* str) { int len = strlen(str); int* arr = new int[len]; for(int i=1; i<=len; ++i) { printCombination(str, len, i, arr, i); } delete[] arr; } //从n中选m个数进行组合 /************************************** a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数, 直到从n-(m-1)个数中选取个数为止。 b. 从n个数中选取编号次小的一个数,继续运行步。直到当前可选编号最大的数为m。 ******************************************/ void printCombination(char* str, int len, int m, int* arr, const int M) { for(int i=len; i>=m;--i) { //依次选择编号最大的数,次大的数.... arr[m-1] = i-1; if(m>1) { //选择的数目大于,从剩余的i-1个数中选取m-1个数的组合 printCombination(str,i-1,m-1,arr,M); } else { //打印M个数字 for(int j=M-1; j>=0; --j) { printf("%c ", str[arr[j]]); } printf("\n"); } } } int main() { char str[] = "abcd"; combination(str); return 0; }
3.非递归实现
首先,初始化一个n个元素的数组(所有由0。1组成)。将前m个初始化为1。后面的为0。这时候就能够输出第一个组合了。为1的元素的下标所相应的数。算法開始:从前往后找。找到第一个10组合,将其反转成01,然后将其前面的所有1,所有往左边推,即保证其前面的1都在最左边。
然后就能够依照这个01序列来输出一个组合结果了。
而假设找不到10组合,就表示说全部情况都输出了。为什么?你想,(以n=5,m=3为例)一開始是11100,最后就是00111。已经没有10组合了。
这样的将问题转换为01序列(也就是真假序列)的想法值得我们考虑和借鉴。
比如求5中选3的组合:
1 1 1 0 0 //1,2,3
1 1 0 1 0 //1,2,4
1 0 1 1 0 //1,3,4
0 1 1 1 0 //2,3,4
1 1 0 0 1 //1,2,5
1 0 1 0 1 //1,3,5
0 1 1 0 1 //2,3,5
1 0 0 1 1 //1,4,5
0 1 0 1 1 //2,4,5
0 0 1 1 1 //3,4,5
实现代码例如以下:
#include <stdio.h> #include <stdlib.h> #include <string.h> int l=0; //function definition void composition(const char [],int,int); void printCompostion(const char[],const bool[],int); //function implementation void printCompostion(const char source[],const bool comp[],int n){ int i; for (i=0;i<n;i++) if (comp[i]==true) printf ("%c-",source[i]); printf ("\n"); } void compostion(const char* source,int n,int m){ bool * comp = (bool*)malloc(n*sizeof(bool)); int i; for (i=0;i<m;i++) comp[i]=true; for (i=m;i<n;i++) comp[i]=false; printCompostion(source,comp,n); l++; while(true){ for (i=0;i<n-1;i++) if (comp[i]==true&&comp[i+1]==false) break; if (i==n-1) return; //all the compostion is found out comp[i]=false; comp[i+1]=true; int p=0; while (p<i){ while (comp[p]==true) p++; while (comp[i]==false) i--; if (p<i) { comp[p]=true; comp[i]=false; } } printCompostion(source,comp,n); l++; } } //test function void testCompostion(){ char* testcase = "abcdefghijklmno"; int n=strlen(testcase); int m=7; compostion(testcase,n,m); } //main function void main(){ testCompostion(); printf ("total=%d\n",l); }
全排列:
1. 递归
分治算法:这个算法利用了分而治之的思想。我们先从2个数開始,比方说4,5,他们的全排列仅仅有两个45和54。假设在前面加个3,那么全排列就是345,354,也就是3(54),括号表示里面的数的全排列。
当然还有4(35),5(34)...写到这里,各位看官应该已经看出点门道了吧。三个数的全排列,能够分为三次计算,第一次计算3和(45)的全排列。第二次计算4和(35)的全排列.....也就是说,将序列第一个元素分别与它以及其后的每个元素代换,得到三个序列,然后对这些序列的除首元素外的子序列进行全排列。思想事实上还是挺简单的:
代码实现例如以下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #define LENGTH 27 int n=0; void permute(int[],int,int); void swapint(int &a,int &b); void printIntArray (int[],int); //Function Implementation void swapint(int &a,int &b){ int temp; temp = a; a = b; b = temp; } void printIntArray(int target[],int length){ int i; for (i=0;i<length;i++) printf ("%d",target[i]); printf ("\n"); } void permute(int target[],int begin,int end){ if (begin==end) { printIntArray(target,end+1); n++; return; } int i; for (i=begin;i<=end;i++){ swapint(target[begin],target[i]); permute(target,begin+1,end); swapint(target[begin],target[i]); } } //test Functions void testPermute(){ int len; scanf ("%d",&len); int *testcase =(int *)malloc(len*sizeof(int)); int i; for (i=0;i<len;i++) testcase[i]=i+1; permute(testcase,0,len-1); } //Main Function void main(){ testPermute(); printf ("n=%d",n); }
2. 字典序
有时候递归的效率使得我们不得不考虑除此之外的其它实现。非常多把递归算法转换到非递归形式的算法是比較难的,这个时候我们不要忘记了标准模板库已经实现的那些算法,这让我们非常轻松。STL有一个函数next_permutation(),它的作用是假设对于一个序列。存在依照字典排序后这个排列的下一个排列,那么就返回true且产生这个排列,否则返回false。注意。为了产生全排列,这个序列要是有序的,也就是说要调用一次sort。
直接用STL上的
#include "iostream" #include "algorithm" using namespace std; void permutation(char* str,int length) { sort(str,str+length); //必须得先排序 do { for(int i=0;i<length;i++) cout<<str[i]; cout<<endl; }while(next_permutation(str,str+length)); } int main(void) { char str[] = "acb"; cout<<str<<"全部全排列的结果为:"<<endl; permutation(str,3); system("pause"); return 0; }
当中next_permutation()的实现例如以下:
template<class BidirectionlIterator> bool next_permutation(BidirectionalIterator firt, BidirectionalIterator last) { if(first == last) return false; //空区间 BidirectionalIterator i = first; ++i; if(i == last) return false; //仅仅有一个元素 i = last; //i指向尾端 --i; for(;;){ BidrectionalIterator ii = i; --i; //以上,锁定一组(两个)相邻元素 if(*i < *ii) //假设前一个元素小于后一个元素 { BidrectionalIterator j = last; //令j指向尾端 while(!(*i < *--j)); //由尾端往前找,直到遇上比*i大的元素 iter_swap(i,j); //交换i,j reverse(ii, last); //将ii之后的元素所有逆向重排 return true; } if(i == first) //进行到最前面了 { reverse(first, last); //所有逆向重置 return false; } } }
自己设计函数next_permutation():
#include <stdio.h> #include <algorithm> #include <string.h> using namespace std; //反转数组 void reverse(char* str, int first, int last) { if(str == NULL || *str == '\0') return; while(first < last) { char ch = str[first]; str[first] = str[last]; str[last] = ch; first++; last--; } } //打印数组 void printfString(char* str) { for(int i=0; i<strlen(str); ++i) { printf("%c", str[i]); } printf("\n"); } //找到下一个数组 bool next_permutation(char* str) { if(str == NULL || *str=='\0') return false; int len = strlen(str); int i = len - 2; int ii = i+1; int j = len - 1; //从后端開始找到第一对str[i]<str[j]的数字 while(str[i] > str[ii]) { --i; --ii; //假设i<0,则表示已经所有排列 if(i<0) { //所有翻转 reverse(str, i+1, len-1); return false; } } //从后面找到第一个大于str[i]的数字 while(str[j] < str[i]) { --j; } //交换 char ch = str[i]; str[i] = str[j]; str[j] = ch; //翻转j之后的数组 reverse(str, ii, len-1); return true; } int main() { char str[] = "cab"; int len = strlen(str); //必须先排序 sort(str, str + len); printfString(str); //打印全排列 while(next_permutation(str)) { printfString(str); } return 0; }
參见:
http://blog.csdn.net/hackbuteer1/article/details/7462447