字典序全排列算法研究

  最近對數學方面頗有興趣,週末和同窗去大學蹭課,其中在講排列組合的時候講到了全排列的字典序生成算法,我以爲這個想法真的挺好,去網上找了找,貌似都是遞歸求全排列,沒有講到這個算法的,今天我將這個算法寫出來了,發在這裏,之後學習。算法

  非遞歸方法(字典序法):ide

  這種算法被用在了C++的STL庫中。學習

  對給定的字符集中的字符規定了一個前後關係,在此基礎上規定兩個全排列的前後是從左到右逐個比較對應的字符的前後。spa

 [例]字符集{1,2,3},較小的數字較先,這樣按字典序生成的全排列是:

         123,132,213,231,312,321

  ※ 一個全排列可看作一個字符串,字符串可有前綴後綴code

  生成給定全排列的下一個排列.所謂一個的下一個就是這一個與下一個之間沒有其餘的。這就要求這一個與下一個有儘量共同前,也即變化限制在儘量後綴上。blog

 [例]839647521是1--9的排列。1—9的排列最前面的是123456789,最後面的987654321,從右向左掃描若都是增的,就到了987654321,也就沒有下一個了。不然找出第一次出現降低的位置。
 【例】 通常而言,設P是[1,n]的一個全排列。
      P=P1P2…Pn=P1P2…Pj-1PjPj+1…Pk-1PkPk+1…Pn
    find:  j=max{i|Pi<Pi+1}
         k=max{i|Pi>Pj}       1, 對換Pj,Pk,
      2, 將Pj
+1…Pk-1PjPk+1…Pn翻轉
P’= P1P2…Pj-1PkPn…Pk+1PjPk-1…Pj+1即P的下一個
【例】 如何獲得346987521的下一個 1,從尾部往前找第一個P(i-1) < P(i)的位置
            3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
        最終找到6是第一個變小的數字,記錄下6的位置i-1
    2,從i位置日後找到最後一個大於6的數
            3 4 6 -> 9 -> 8 -> 7 5 2 1
        最終找到7的位置,記錄位置爲m
    3,交換位置i-1和m的值
            3 4 7 9 8 6 5 2 1
    4,倒序i位置後的全部數據
            3 4 7 1 2 5 6 8 9
    則347125689爲346987521的下一個排列

  依照上面的講述不難將代碼寫出來,以下:排序

        private static void PermutationList()
        {
            int fromIndex, endIndex, changeIndex;
            Sort(0, length - 1);
            do
            {
                // 輸出一種全排列
                Output();
                fromIndex = endIndex = length - 1;
                // 向前查找第一個變小的元素
                while (fromIndex > 0 && words[fromIndex] < words[fromIndex - 1]) --fromIndex;
                changeIndex = fromIndex;
                if (fromIndex == 0) break;
                // 向後查找最後一個大於words[fromIndex-1]的元素
                while (changeIndex + 1 < length && words[changeIndex + 1] > words[fromIndex - 1]) ++changeIndex;
                Swap(fromIndex - 1, changeIndex);   // 交換兩個值
                InvertArray(fromIndex, endIndex);   // 對後面的全部值進行反向處理
            } while (true);
        }

  遞歸方法求全排列:遞歸

  遞歸方法很容易理解:分別將每一個位置交換到最前面位,以後全排列剩下的位。字符串

【例】遞歸全排列 1 2 3 4 5
    1,for循環將每一個位置的數據交換到第一位
            swap(1,1~5)
    2,按相同的方式全排列剩餘的位

  因爲遞歸方法很容易理解,並且網上也有不少的資料,因此不過多講述,代碼以下:input

        /// <summary>
        /// 遞歸方式生成全排列的方法
        /// </summary>
        /// <param name="fromIndex">全排列的起始位置</param>
        /// <param name="endIndex">全排列的終止位置</param>
        private static void PermutationList(int fromIndex, int endIndex)
        {
            if (fromIndex == endIndex)
                Output();
            else
            {
                for (int index = fromIndex; index <= endIndex; ++index)
                {
                    // 此處排序主要是爲了生成字典序全排列,不然遞歸會打亂字典序
                    Sort(fromIndex, endIndex);
                    Swap(fromIndex, index);
                    PermutationList(fromIndex + 1, endIndex);
                    Swap(fromIndex, index);
                }
            }
        }

以上代碼只截取了部分關鍵代碼,全部代碼以下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Algorithms
{
    class Permutation
    {
        private static int MAXLENGTH = 20;

        private static int length;
        private static char[] words = new char[MAXLENGTH];

        private static void Swap(int indexX, int indexY)
        {
            if (indexX != indexY)
            {
                char ch = words[indexX];
                words[indexX] = words[indexY];
                words[indexY] = ch;
            }
        }

        private static void InvertArray(int fromIndex, int endIndex)
        {
            for (; fromIndex < endIndex; ++fromIndex, --endIndex)
                Swap(fromIndex, endIndex);
        }

        private static void Sort(int fromIndex, int endIndex)
        {
            Array.Sort(words, fromIndex, endIndex - fromIndex + 1);
        }

        private static void Output()
        {
            for (int index = 0; index < length; ++index)
                Console.Write(words[index] + " ");
            Console.WriteLine();
        }

        private static void PermutationList()
        {
            int fromIndex, endIndex, changeIndex;
            Sort(0, length - 1);
            do
            {
                // 輸出一種全排列
                Output();
                fromIndex = endIndex = length - 1;
                // 向前查找第一個變小的元素
                while (fromIndex > 0 && words[fromIndex] < words[fromIndex - 1]) --fromIndex;
                changeIndex = fromIndex;
                if (fromIndex == 0) break;
                // 向後查找最後一個大於words[fromIndex-1]的元素
                while (changeIndex + 1 < length && words[changeIndex + 1] > words[fromIndex - 1]) ++changeIndex;
                Swap(fromIndex - 1, changeIndex);   // 交換兩個值
                InvertArray(fromIndex, endIndex);   // 對後面的全部值進行反向處理
            } while (true);
        }

        /// <summary>
        /// 遞歸方式生成全排列的方法
        /// </summary>
        /// <param name="fromIndex">全排列的起始位置</param>
        /// <param name="endIndex">全排列的終止位置</param>
        private static void PermutationList(int fromIndex, int endIndex)
        {
            if (fromIndex == endIndex)
                Output();
            else
            {
                for (int index = fromIndex; index <= endIndex; ++index)
                {
                    // 此處排序主要是爲了生成字典序全排列,不然遞歸會打亂字典序
                    Sort(fromIndex, endIndex);
                    Swap(fromIndex, index);
                    PermutationList(fromIndex + 1, endIndex);
                    Swap(fromIndex, index);
                }
            }
        }

        public static void PermutationTest()
        {
            Console.Write("please input the permutation words:");
            words = Console.ReadLine().ToCharArray();
            length = words.Count();
            PermutationList();
            Console.ReadLine();
        }
    }
}
View Code

 

運行結果:

相關文章
相關標籤/搜索