C++STL的next_permutation


C++STL的next_permutation

 

在標准庫算法中,next_permutation應用在數列操作上比較廣泛.這個函數可以計算一組數據的全排列.但是怎么用,原理如何,我做了簡單的剖析.

首先查看stl中相關信息.
函數原型:

template<class BidirectionalIterator>
   bool next_permutation(
      BidirectionalIterator _First,
      BidirectionalIterator _Last
   );
template<class BidirectionalIterator, class BinaryPredicate>
   bool next_permutation(
      BidirectionalIterator _First,
      BidirectionalIterator _Last,
      BinaryPredicate _Comp
   );


兩個重載函數,第二個帶謂詞參數_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<class _BidIt> 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

這樣,就可以得到一個十進制數和一個排列之間的一一對應的關系。
現在排列數和有序的十進制數有了一一對應的關系(通過改變對應關系,可以使十進制數升序)。

 

 

 

練習題:

HDU 1027

http://acm.hdu.edu.cn/showproblem.php?pid=1027


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM