字符串的全排列和全组合

来源:互联网 发布:水地暖 电地暖 知乎 编辑:程序博客网 时间:2024/04/29 18:38

字符串的全排列。。。

打印字符串的所有排列。例如,若输入为abc,则打印出acb、bac、cba、cab、bca、abc。。。。

void AllPermutate(char a[], int num,int first){         if(first == (num-1) )        {         copy(a, a + num,ostream_iterator<char>(cout, " "));         cout << endl;        }        else     {         char temp;               for(inti = first; i < num; i++)         {                        if(a[i] == a[first] && first !=i)  //avoid genearate duplicate..                               continue;              temp=a[i];             a[i]=a[first];             a[first]=temp;              AllPermutate(a, num, first + 1);              temp=a[i];             a[i]=a[first];              a[first]=temp;     //backtrace        }   }}


把升序的排列(当然,也可以实现为降序)作为当前排列开始,然后依次计算当前排列的下一个字典序排列。
对当前排列从后向前扫描,找到一对为升序的相邻元素,记为i和j(i < j)。如果不存在这样一对为升序的相邻元素,则所有排列均已找到,算法结束;否则,重新对当前排列从后向前扫描,找到第一个大于i的元素k,交换i和k,然后对从j开始到结束的子序列反转,则此时得到的新排列就为下一个字典序排列。这种方式实现得到的所有排列是按字典序有序的,这也是C++ STL算法next_permutation的思想。参考JULY的博客,算法实现如下:

template <typename T> void CalcAllPermutation(T perm[],int num) {    if (num < 1)         return;             while (true) {         int i;         for (i = num - 2; i >= 0; --i){             if (perm[i] < perm[i + 1])                 break;         }                  if (i < 0)             break;  // 已经找到所有排列              int k;         for (k = num - 1; k > i; --k) {             if (perm[k] > perm[i])                 break;         }                  swap(perm[i], perm[k]);         reverse(perm + i + 1, perm + num);            } } 

分析:这个方法是康托展开的应用。

何为康托展开?

举例:找出45231在12345所有字典序排列中的顺序?

比4小的数有3个
比5小的数有4个但4已经在之前出现过了所以是3个
比2小的数有1个
比3小的数有两个但2已经在之前出现过了所以是1个
比1小的数有0个

那么45231在这个排列中的顺序是3*4!+3*3!+1*2!+1*1!+0*0!+1=94

明白了其原理,就可以得到康托展开的表达式:

X=an*(n-1)!+a(n-1)*(n-2)!+…+ai*(i-1)!+…+a2*1!+a1*0!其中,a[m]代表比在第m位的数字小并且没有在第m位之前出现过的数字的个数, x代表比这个数小的数的个数,所以这个数的顺序就是x+1

我们就可以通过这一性质解释上述的算法:

· 从后往前找,直到找到序列第一次下降为止,找到k,在第i位。

· 从该位置向后找,找到最小一个比改为数字大的数字,找到m,在第j位。

· 交换k,m这两个数字。

· 将排列的第i+1位到末尾倒转,即得到了下一个排列。

拿14253做例子:

· 找到第一个下降的数字:2,在第3位。

· 找到第4位及以后的数字中最小的,但比2大的数字:3,在第5位。

· 交换2和3,重新得到排列 14352。

· 将第4位到第5位的数字倒转 得到下一个排列 14325。

· 算法结束。

算法的正确性:

考虑当前位的康托表达式X=an*(n-1)!+a(n-1)*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

我们证明算法生成的排列的表达式为X'-X=1:

首先,算法并未改变i位以前的任何数,故x与x'的an~a[i+1]的值一样;

对于第i位之后的数,由于其单调下降,把i和j位的数调转后ai+=1,a(j)不变

(k<m,但原本k在m前,但现在k在m后,故ai=(aj)原+1,又由于区间[i-1,j+1]中的数均>J>I,故(ai)原=(aj)原)

此时[i-1,1]中的数也是单调下降的,将其掉转后变为单调上升,a'[i-1]=a'[i-2]=...=a'[1]=0;

故可以计算出X'-X=(i-1)!-(a[i-1]*(i-2)!+a[i-2]*(i-1)!+...+a[1]*(1-1)!);由于在原排列中[i-1,1]单调下降,故a[1],...,a[i-1],组成一个首项为0,公差为1,项数为i-1的等差数列,得a[i]=i-1;

又有:(i-1)!=(i-2)!+(i-2)*(i-2)!

           =(i-3)!+(i-3)*(i-3)!+(i-2)*(i-2)!

           =...

           =0!+∑(i=0..i-2):i*i!=1+(a[i-1]*(i-2)!+a[i-2]*(i-1)!+...+a[1]*(1-1)!)

所以,X'-X=1;

字符串的全组合。。。

打印字符串(无重复字符)的所有组合。例如,若输入为abc,则打印出a、b、c、ab、ac、bc、abc。仅字符顺序不同而字符组成相同者,视为无差别,如ab与ba被认为无区别。一般地,若字符串的长度为N,则输出项数为N取1、N取2、... N取N一系列组合数之和。各输出字符串的相对顺序不限。

 //方法一。/*********************************************************************************** *从字符串的后面开始扫描,把结果保存到result中。当往result中加入新的字符时, *它与result中已存在的每一个字符组合成新的字符加入到result中。***********************************************************************************/typedef vector<string>strVector; void doCombine(const string&original, strVector &result_container, unsigned offset) {      if (offset >= original.size())          return;      doCombine(original, result_container, offset+1);      const unsigned nCount = result_container.size();      result_container.push_back(string(1, original[offset]));      for (unsigned index = 0; index < nCount; ++index) {          string temp(1, original[offset]);         temp.append(result_container[index]);          result_container.push_back(temp);     } } void combine(const char *szStr){     const string original(szStr);     vector<string> container;      if (!original.size())          return;      doCombine(original, container, 0);      for (unsigned index = 0; index < container.size(); ++index) {                cout<< container[index].c_str() <<endl;     }}   //方法二。/************************************************************************************  *从字符串的前面开始扫描,取出第一个字符,与剩余的字符相组合。 *然后回溯之。如'abc',-->'a', 'ab', 'abc', 'abcd', 'abd', 'ac'.....************************************************************************************/void str_doCombine(const string&original, string &result, unsigned offset) {           const unsigned nLength = original.size();            if (offset >= nLength)                return;           for (unsigned index = offset; index <nLength; ++index) {                 result.append(1, original[index]);                 cout << result.c_str() <<endl;                 str_doCombine(original, result, index +1);                 result.resize(result.size() - 1);     //backtrace         } } void str_combine(const char *szStr){          string original(szStr);          string result;           if (!original.size())                return;           str_doCombine(original, result, 0); } 

参考文献:

http://www.cnblogs.com/sujz/archive/2011/06/16/2082831.html

http://blog.csdn.net/ly92are1999/article/details/6590802 

http://blog.csdn.net/v_july_v/article/details/6879101

http://www.yuxingzhou.com/?p=99