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