关于求排列组合数

1、求1~N的全排列 

    (1)我们先从最简单的例子看起:1,2 两个数的情况,显然有1--2, 2--1,两种排列,即1和2交换一下。

    1,2,3 三个数的情况:

    1--2--3

    1--3--2   //固定第一个数,递归的求后两个数的组合,2和3交换一下 

    2--1--3   //1,2 交换

    2--3--1   //固定2, 递归求两个数的组合,交换1,3 

    3--2--1   //1和3交换

    3--1--2   //固定3, 递归求两个数的组合,交换2,1

    将上述过程转换为代码:

 

ExpandedBlockStart.gif View Code 
template< class T>
void permutation(T* array,  int n)
{
    permutation(array, n, array, n);
}

template< class T>
void permutation(T* array,  int size, T* start,  int n)
{
     if(n ==  1)
    {
        cout<<array[ 0];  // output current array;
         for( int i =  1; i < size; ++i)
            cout<< "   "<<array[i];
        cout<<endl;
         return;
    }
    permutation(array, size, start +  1, n -  1); // first permutation, 1--2--3
    
    T tmp = start[ 0];
     for( int i =  1; i < n; ++i)
    {
         // interchange a[0] and a[i]
        start[ 0] = start[i];  // 1-2-3 ==> 2-1-3
        start[i] = tmp;
        permutation(array, size, start+ 1, n -  1);
        start[i] = start[ 0];  // note to copy the original order back, 2-1-3 ==> 1-2-3, next round interchange 1 and 3
        start[ 0] = tmp;
    }

 

    其实想清楚了很简单, 就是递归的思想,2个数很好想:做一下交换,延伸到3个数、到N个数。

    3个数1-2-3,可以1和2、3分别做一次交换,后边又成了两个数的情况。 Aha, world is simple!

    (2) 在c++的标准库里,有next_permutation(array, array+N)这种东西,可以拿过来直接用:

 View Code

 
#include<iostream>
#include<algorithm>
using  namespace std;

template< class T>
void permutation(T* array,  int n); 
template< class T>
void combination(T* array,  int N,  int m);
int main()
{
     int array[] = { 1, 2, 3};
    permutation(array,  3);
   
    cout<< " ----------- "<<endl;
     do
    {
         for( int i =  0; i <  3; ++i)
            cout<<array[i]<<ends;
        cout<<endl;
    } while( next_permutation(array, array+ 3) );
    system( " pause ");
     return  0;

     运行结果如下图所示。 

  

 

   在写这个simple的算法过程中, 看到了两篇不错的博客,一并转载过来:

   http://apps.hi.baidu.com/share/detail/19561533 

   http://www.cppblog.com/yindf/archive/2010/02/24/108312.html 

   第二篇写的非常好,

了解C++的童鞋都知道algorithm里面有个next_permutation可以求下一个排列数,通过《STL 源码剖析》(或者自己读代码)可以知道其实现,比如:

abcd  next_permutation ->  abdc

那么,为什么abcd的下一个是abdc而不是acbd呢?
说简单一点,用 1,2,3,4 代替 a,b,c,d,可以得到:

//注释一下:这里的所谓的中间转换,其实就是逆序数... 

原排列                  中间转换                值
1,2,3,4        3,2,1            ((3 * (3) + 2) * (2) + 1) * (1) = 23
1,2,4,3        3,2,0            ((3 * (3) + 2) * (2) + 0) * (1) = 22
1,3,2,4        3,1,1            ((3 * (3) + 1) * (2) + 1) * (1) = 21
1,3,4,2        3,1,0            ((3 * (3) + 1) * (2) + 0) * (1) = 20
1,4,3,2        3,0,1            ((3 * (3) + 0) * (2) + 1) * (1) = 19
.                  .                     .
.                  .                     .
.                  .                     .
4,3,2,1        0,0,0            ((0 * (3) + 0) * (2) + 0) * (1) = 0
                               |      |      |                       |                    |                   |
                               |      |                              |                    |
                               |                                     |


 上面的中间转换指的是:每一个数字后面比当前位数字大的数字的个数。比如:

1,3,4,2  中,1 后面有(3, 4, 2) 他们都大于1,所以第一位是 3
                              3 后面有(4, 2), 但只有4大于3,所以第二位是 1
                              4 后面有(2), 没有比4 大的,所以第三位是 0
                              最后一位后面肯定没有更大的,所以省略了一个0。

经过这种转换以后,就得到了一种表示方式(中间转换),这种表达方式和原排列一一对应,可以相互转化。

仔细观察这种中间表达方式,发现它的第一位只能是(0,1,2,3),第二位只能是(0,1,2),第三位只能是(0,1)。

通常,数字是用十进制表示的,计算机中用二进制,但是现在,我用一种特殊的进制来表示数:

第一位用1进制,第二位用2进制。。。

于是就得到了这种中间表示方式的十进制值。如:

                                                              阶                  
                                            |                  |                    |
1,1,0    ---->   ((1 * (3) + 1) * (2) + 0) * (1) = 8

3,1,0    ---->   ((3 * (3) + 1) * (2) + 0) * (1) = 20

这样,就可以得到一个十进制数和一个排列之间的一一对应的关系。

现在排列数和有序的十进制数有了一一对应的关系(通过改变对应关系,可以使十进制数升序)。 

-------------------------------------------------------

1,2,3,4        3,2,1            ((3 * (3) + 2) * (2) + 1) * (1) = 23
1,2,4,3        3,2,0            ((3 * (3) + 2) * (2) + 0) * (1) = 22

1,3,2,4        3,1,1            ((3 * (3) + 1) * (2) + 1) * (1) = 21 

看完了这儿我就在想,为什么最后一位表示1进制,依次是2进制、3进制,为什么这样表示出来之后正好就是 [0,4!),why?

很容易理解,因为最高位*(3)表示,其可能出现的最大的逆序数=3。

其实就是排列的选择过程,C(4,1)*C(3,1)*C(2,1)*C(1,1)

 

2、求1~N个数选m个所有的组合

    我们同样从最简单的例子开始入手,1-2-3,选2个数

    先考虑1-2中选1个数的情况C(2,1):  1, 2, 第一次选1, 选定了1之后,下一次从1之后开始选,选2,即保证一个选择的顺序

    1-2-3,从中选2个数 C(3,2):

    1 -- ?  选定第一个数,下面成了C(2,1)

    1--2, 1--3 两种。

    我们保证按顺序去选, 因为是C(3,2),所以第1个数可以取到:1, 2, 1已经选过,下一轮选2

    2 -- ?, 选定第一个数=2,下面又成了C(2,1),但是要按顺序选:从3一个数里 C(2,1)

    2--3,只有这一种情况。

    trick就在于为了避免重复,我们以“有序”的方式去选择数 1--2, 1--3, 2--3

    我们将上边的过程先转换为伪码:

    //C(N, m)

    if(m < 1)

         //输出结果 a[1~m]

    else 

   { 

         for(i = m ~ N) // 为了方便,代码逆着选,第m个数 可以依次取[m, N]

        { 

             a[m] = i; //填第m个数

             C( i - 1, m - 1); // i 已经被 a[m]选过了,下一轮只能从i - 1开始选。

         } 

    } 

    源代码:

    View Code

 
#include<iostream>
#include<algorithm>
using  namespace std;

template< class T>
void combination(T* array,  int N,  int m);
int main()
{
     int array[] = { 1, 2, 3, 4, 5};
    combination(array,  53);
    
    system( " pause ");
     return  0;
}

template< class T>
void combination(T* array,  int N,  int m)
{
    T* result =  new T[m +  1];
    result[ 0] = m;  // result[0]记录元素的个数
    combination(array, result, N, m);
    delete []result;
}
template< class T>
void combination(T* array, T* result,  int N,  int m)
{
     if(m <  1)
    {
         // output a[ 0 ~ m-1 ];
         for( int i =  1; i <= result[ 0]; ++i)
            cout<<result[i]<<ends;
        cout<<endl;
         return;
    }
     for( int i = N -  1; i >= m -  1; --i)  // 第m个数,可以从array[m-1] 取到 array[N-1];
    {
        result[m] = array[i];
        combination(array, result, i, m -  1);  // 注意这里是 i, 从前i个元素再选m - 1个;
    }


 3、求1~N所有的幂集(即所有的子集)

      当然可以按2依次求C(N,1), C(N,2)...C(N,N)

      但是有更快的算法,就是迭代 1~2^N所有的整数,统计每个整数中含有的1的个数(二进制中)

      二进制位取1表示取该元素,取0表示不取该元素。

-------------------------------------------------

     跑题: 

     把一个问题映射到二进制数,有很多好玩儿的题目:

     有1000瓶老鼠药和10只老鼠,其中有一瓶老鼠药是有毒的,老鼠喝了有毒的老鼠药2天就会挂掉,如何在两天之内找到哪瓶老鼠药是有毒的?

     (tips:老鼠列成一排,每只老鼠看成一个bit位,1~1000给对应bit位是1的老鼠喝,最后挂掉的老鼠是 药瓶编号的 bit位1(其它是0),该瓶老鼠药是有毒的)

        

     

     

posted on 2012-04-24 19:05  worldisimple 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/worldisimple/archive/2012/04/24/2468743.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值