Chinaunix首页 | 论坛 | 博客
  • 博客访问: 233072
  • 博文数量: 42
  • 博客积分: 1650
  • 博客等级: 中尉
  • 技术积分: 490
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-13 19:54
文章分类

全部博文(42)

文章存档

2011年(19)

2010年(23)

分类: C/C++

2011-03-18 22:49:38

在标准库算法中,next_permutation应用在数列操作上比较广泛.这个函数可以计算一组数据的全排列.但是怎么用,原理如何,我做了简单的剖析.

首先查看stl中相关信息.
函数原型:

template
   bool next_permutation(
      BidirectionalIterator ,
      BidirectionalIterator
   );
template
   bool next_permutation(
      BidirectionalIterator ,
      BidirectionalIterator ,
      BinaryPredicate
   );


两个重载函数,第二个带谓词参数_Comp,其中只带两个参数的版本,默认谓词函数为"小于".

返回值:bool类型

分析next_permutation函数执行过程:

假设数列 d1,d2,d3,d4……

范围由[first,last)标记,调用next_permutation使数列逐次增大,这个递增过程按照字典序。例如,在字母表中,abcd的下一单词排列为abdc,但是,有一关键点,如何确定这个下一排列为字典序中的next,而不是next->next->next……

若当前调用排列到达最大字典序,比如dcba,就返回false,同时重新设置该排列为最小字典序。

返回为true表示生成下一排列成功。下面着重分析此过程:

根据标记从后往前比较相邻两数据,若前者小于(默认为小于)后者,标志前者为X1(位置PX)表示将被替换,再次重后往前搜索第一个不小于X1的数据,标记为X2。交换X1,X2,然后把[PX+1,last)标记范围置逆。完成。

要点:为什么这样就可以保证得到的为最小递增。

从位置first开始原数列与新数列不同的数据位置是PX,并且新数据为X2。[PX+1,last)总是递减的,[first,PX)没有改变,因为X2>X1,所以不管X2后面怎样排列都比原数列大,反转[PX+1,last)使此子数列(递增)为最小。从而保证的新数列为原数列的字典序排列next。

明白了这个原理后,看下面例子:

int main(){
int a[] = {3,1,2};
do{
     cout << a[0] << " " << a[1] << " " << a[2] << endl;
}
while (next_permutation(a,a+3));
return 0;
}

输出:312/321         因为原数列不是从最小字典排列开始。

所以要想得到所有全排列

int a[] = {3,1,2};   change to   int a[] = {1,2,3};

另外,库中另一函数prev_permutation与next_permutation相反,由原排列得到字典序中上一次最近排列。

所以

int main(){
int a[] = {3,2,1};
do{
     cout << a[0] << " " << a[1] << " " << a[2] << endl;
}
while (prev_permutation(a,a+3));
return 0;
}

才能得到123的所有排列。

**************************************************************************************************

**************************************************************************************************

next_permutation在algorithm头文件里,可以用它来生成全排列。它的源代码加分析如下

template inline
bool _Next_permutation(_BidIt _First, _BidIt _Last)
{ // permute and test for pure ascending, using operator<

//-----------------------------------------------\
_DEBUG_RANGE(_First, _Last);
_BidIt _Next = _Last;
if (_First == _Last || _First == --_Next)
   return (false);

//上面这一块是检查边界范围的

//-----------------------------------------------/

for (; ; )
   { // find rightmost element smaller than successor
   _BidIt _Next1 = _Next;
   if (_DEBUG_LT(*--_Next, *_Next1))
    { // swap with rightmost element that's smaller, flip suffix
    _BidIt _Mid = _Last;
    for (; !_DEBUG_LT(*_Next, *--_Mid); )
     ;
    std::iter_swap(_Next, _Mid);

//本身带的注释已经说得很明白,从最右边开始比较两两相邻的元素,直至找到右边比左边大的一对,左边那个

//就是将要被替换的,再从最右边开始找比这个元素大的第一个,交换他们两个
    std::reverse(_Next1, _Last);

//交换之后,翻转交换元素的后面的所有元素
    return (true);
    }

   if (_Next == _First)
    { // pure descending, flip all
    std::reverse(_First, _Last);
    return (false);
    }
   }
}

关键是确定一个排列的下一个排列是什么,我看着明白却说不明白,于是转贴一段,以下来自 http://www.cppblog.com/yindf/archive/2010/02/24/108312.html

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

这样,就可以得到一个十进制数和一个排列之间的一一对应的关系。
现在排列数和有序的十进制数有了一一对应的关系(通过改变对应关系,可以使十进制数升序)。

阅读(2212) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~