字符串的全排列和全组合

字符串的全排列。。。

打印字符串的所有排列。例如,若输入为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.youkuaiyun.com/ly92are1999/article/details/6590802 

http://blog.youkuaiyun.com/v_july_v/article/details/6879101

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








































                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值