字典序全排列算法研究

  最近对数学方面很有兴趣,周末和同学去大学蹭课,其中在讲排列组合的时候讲到了全排列的字典序生成算法,我觉得这个想法真的挺好,去网上找了找,貌似都是递归求全排列,没有讲到这个算法的,今天我将这个算法写出来了,发在这里,以后学习。

  非递归方法(字典序法):

  这种算法被用在了C++的STL库中。

  对给定的字符集中的字符规定了一个先后关系,在此基础上规定两个全排列的先后是从左到右逐个比较对应的字符的先后。

 [例]字符集{1,2,3},较小的数字较先,这样按字典序生成的全排列是:

         123,132,213,231,312,321

  ※ 一个全排列可看做一个字符串,字符串可有前缀后缀

  生成给定全排列的下一个排列.所谓一个的下一个就是这一个与下一个之间没有其他的。这就要求这一个与下一个有尽可能共同前,也即变化限制在尽可能后缀上。

 [例]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,按相同的方式全排列剩余的位

  由于递归方法很容易理解,而且网上也有很多的资料,所以不过多讲述,代码如下:

        /// <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

 

运行结果:

posted @ 2013-12-04 20:17  pmars  阅读(34921)  评论(2编辑  收藏  举报