1. 程式人生 > >[翻譯]基於詞典序的生成下一排列算法

[翻譯]基於詞典序的生成下一排列算法

更新 很大的 將在 assertion 通過 描述 機械 排列 ext

翻譯來源https://www.nayuki.io/page/next-lexicographical-permutation-algorithm

簡介

假設對於一個有限長度的數組序列(0, 3, 3, 5, 8),需要生成對應的所有全排列。有什麽好的辦法做到?

最原始的方案是使用自頂向下的遞歸方式。首先選舉出第一個位置的元素,然後遞歸選擇第二個元素從剩下的元素中,直到剩余一個元素。但是這種方法很復雜因為它需要遞歸、堆棧存儲和去重。而且,如果堅持操作序列(不使用臨時數組),那麽這種方法在生成字典序排列的使用會有很大的困難。

最有效生成所有排列的方式是以字典序最小的開始,重復進行計算下一個排列。這個簡單而又快速的算法將在本頁進行闡述。我們將會使用具體的例子來解釋算法的每一個步驟。

算法描述

序列(0, 1, 2, 5, 3, 3, 0)作為測試數據。

本算法的關鍵步驟是當我們計算生成下一個排列時,必須盡可能小的增加序列值。就像我們數數一樣,我們總是盡可能的去修改最右側(低位)的數據,保持左側(高位)的數字不動一樣。例如:從{0,1}到{0,2},完全不需要將第一個元素從0改到1,改動第二個元素將獲得更小的增加。事實上,第二個元素也無需改動,這將是接下來要說明的。

首先,標記出最長的非升序後綴子串(微弱下降)。在例子中這個後綴子串就是(5, 3, 3, 0)。最長非升序後綴已經是後綴串所有排列中的最大排列了,所以不可能計算它的下一個組合,如果要獲得下一個組合,就必須和左側的元素作出交換。(標記最大非升序後綴字串可以在通過從右往左搜索在O(n)時間內完成,還有後綴至少含有一個元素,因為一個元素的子串是非升序的)。

第二步,取出後綴子串左側緊鄰的元素(本例子中是2)稱之為pivot。(如果沒有這個元素,那就說明整個字串是非升序的,那麽這個字符串就是最大的子串了)。pivot 必須小於後綴子串的第一個元素(5),故後綴中存在部分元素大於pivot.當我們把pivot和後綴中的大於pivot的最小元素進行交換後,就得到了最小的目標前綴。(前綴就是序列中去掉後綴的部分)。在這個例子中,最終得到了前綴(0, 1, 3)和更新後的後綴(5, 3, 2, 0)。(如果後綴有多個可選項,那就要選擇最右側的選項,接下來進入下一步。)

最後,對後綴進行非降序排序,因為前一步已經增大了前綴,所有我們希望後綴盡可能的小。事實上,完全可以避免排序而是進行簡單的倒置後綴,原因在於替換元素沒有改變排序。這樣就得到了序列(0, 1, 3, 0, 2, 3, 5)就是我們想要計算出的的下一個排列。
技術分享圖片

簡明的數學描述:

找出最大的索引i 確保有array[i ? 1] < array[i](如果不存在i,那說明序列已經是最大排列)

找出最大索引 j 使得 j ≥ i 同時 array[j] > array[i ? 1]。

交換array[j] 和 array[i ? 1]。

倒置以array[i]開頭的後綴子串。

如果你真的讀懂了這個算法,這裏還有一個拓展練習:設計一個算法計算出前一個最大的字典序排列。

樣例代碼 (Java)

boolean nextPermutation(int[] array) {
    // Find longest non-increasing suffix
    int i = array.length - 1;
    while (i > 0 && array[i - 1] >= array[i])
        i--;
    // Now i is the head index of the suffix

    // Are we at the last permutation already?
    if (i <= 0)
        return false;

    // Let array[i - 1] be the pivot
    // Find rightmost element that exceeds the pivot
    int j = array.length - 1;
    while (array[j] <= array[i - 1])
        j--;
    // Now the value array[j] will become the new pivot
    // Assertion: j >= i

    // Swap the pivot with j
    int temp = array[i - 1];
    array[i - 1] = array[j];
    array[j] = temp;

    // Reverse the suffix
    j = array.length - 1;
    while (i < j) {
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        i++;
        j--;
    }

    // Successfully computed the next permutation
    return true;
}

這個代碼可以按照你的理解機械翻譯為其他的編程語言。(註意:在Java中,數組從0開始)。

再次聲明翻譯https://www.nayuki.io/page/next-lexicographical-permutation-algorithm

龍城飛將,歡迎關註公眾號 可樂小數據[xiaokele_data]
技術分享圖片

[翻譯]基於詞典序的生成下一排列算法