排列組合技術

<algorithm> 庫裏有一個名不見經傳的函數叫作:std::next_permutationios

簡單寫個用法示例:面試

cpp#include <iostream>
#include <array>
#include <algorithm>

int main()
{
    std::array<int, 4> A = {1,2,3,4};
    do {
        for (auto i : A)
            std::cout << i << " ";
        std::cout << std::endl;
    } while (std::next_permutation(A.begin(), A.end()));
}

輸出(截取部分):算法

1 2 3 4 // 2->3->4 : ascending
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2 // 4->3->2 : descending
2 1 3 4
2 1 4 3
2 3 1 4

恐怕你已經發現了,這個方法能夠幫助咱們列出某個容器的全套排列組合。它其實還有一個兄弟函數,名曰 std::prev_permutation,一個是向前排列,一個是向後排列,算法上大同小異。函數


探祕code

提到這個,緣由一是這對姐妹藏在深閨無人知,科普一下;更重要的緣由,是我對於其排列的算法很感興趣,它是按照什麼順序來進行排列組合的?這個 STL 算法函數若是讓我來實現,會如何實現?leetcode

實際上,仔細觀察上面輸出,大致上咱們能夠看出一點端倪:get

  1. 整體上是從小到大。如從 1 開頭,到 2 開頭。
  2. 當 1 開頭時,將所有 1 開頭的組合列完。
  3. 如何保證第 2 條?發現規律是,除去開頭的 1 ,剩下三個數,呈這樣的規律:從遞增排列到遞減
  4. 第 3 條是大方向,咱們再細究每一步。發現局部範圍內,也是將遞增變爲遞減,如 3,4 -> 4,3

綜上,咱們若要獲得下一步的組合,應該將注意力集中在遞增的子序列上,設置兩個迭代器,start 與 last,分別指向遞增子序列的手尾,用此來重點分析一下從第二行到第三行的轉變。it

1 2 4 3
  ^ ^
  s l

這種狀況下, s 指向了 2,意味着 1,2 開頭的組合已經排列完畢,根據第 1 條,咱們但願去排列 1,3 開頭的組合了。而此刻 3 在行尾,咱們就但願將其提取到 2 的前頭去。即變成:io

1 3 2 4

果真就是我們想要的了。但是這個插入過程實際上應分爲兩步:ast

  1. swap(2, 3);
  2. 4 之後的元素所有逆序

解釋一下第二步,當 last 指向 4,這已經意味着 4 之後必定是降序的,即便交換了 2,3 也沒法改變這個順序。而咱們所但願的,顯然是保持 2,4 這樣的升序,做爲 1,3 開頭排列的起始。因此纔有了第二步的逆序。


動手

講明算法,再來理一理實現思路。

  • 首先,容器爲空,或是僅有一個元素,天然返回 false。
  • 而後,須要初始化 start 的位置,咱們但願從後往前找,故初始狀態下,指向最後一個元素。
  • 最後,開始迭代過程,定位 start 與 last 的位置,詳細直接見代碼:
cpp#include <algorithm>

template<class Iterator>
bool my_next_permutation(Iterator beg, Iterator end)
{
    if (beg == end) return false;    // 容器爲空
    Iterator start = end;
    if (beg == --start) return false;    // 僅有一個元素

    for(;;) {
        Iterator last = start--;    // last 指向最後一個元素,並將 start 前移

        if (*start < *last) {    // 直至定位到一個升序序列
            Iterator riter = end;    // 依舊從尾部開始
            while (!(*start < *--riter))    // 找到下一個排列的開頭,恰是第一個大於當前開頭的元素
                ;
            std::iter_swap(start, riter);    // 交換
            std::reverse(last, end);    // 逆序
            return true;
        }
        if (start == beg) {    // 特殊狀況,即所有排列完畢(總體降序),回到初始(總體升序)
            std::reverse(beg, end);
            return false;
        }
    }
}

面試

實際上,這道題也是一道面試題,請見:Next Permutation on LeetCode

動手作作吧~

相關文章
相關標籤/搜索