9

编程小知识之生成排列

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/89685118
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
编程小知识之生成排列_tkokof1的专栏-CSDN博客

本文简述了两种生成排列的方法

生成排列的方法不少,一种经典的方法就是采用递归,假设我们需要求解 1 ~ n 的所有排列,那么我们假设已经求解了 1 ~ n - 1 的所有排列,然后对于求解出的每一种排列(1 ~ n - 1 的一种排列),我们将 n 放置于该排列中可能的 n 个空位上,即可完成 1 ~ n 的排列求解.

说的有些抽象,举个实际的例子:

假设我们要求解 1 ~ 3 这三个数字的所有排列,并且我们已经求解出了 1 ~ 2 的所有排列,求解出的排列如下所示:

1 , 2 2 , 1

1,22,11,22,1
1,22,1​

其中每个排列都有 3 个空位:

e m p t y   1 , e m p t y   2   e m p t y e m p t y   2 , e m p t y   1   e m p t y

empty 1,empty 2 emptyempty 2,empty 1 emptyempty1,empty2emptyempty2,empty1empty
empty​ 1,empty​ 2 empty​empty​ 2,empty​ 1 empty​​

我们将 3 依序填入每个空位即可完成 1 ~ 3 排列的求解:

3 , 1 , 2 1 , 3 , 2 1 , 2 , 3 3 , 2 , 1 2 , 3 , 1 2 , 1 , 3

3,1,21,3,21,2,33,2,12,3,12,1,33,1,21,3,21,2,33,2,12,3,12,1,3
3​,1,21,3​,21,2,3​3​,2,12,3​,12,1,3​​

求解排列的另外一种思路,就是给每个可能的排列规定一个大小次序,仍然拿 1 ~ 3 的排列举例,我们可以规定:

1 , 2 , 3 < 1 , 3 , 2 < 2 , 1 , 3 < 2 , 3 , 1 < 3 , 1 , 2 < 3 , 2 , 1

amp;1,2,3lt;1,3,2lt;2,1,3lt;2,3,1lt;3,1,2lt;3,2,1amp;1,2,3lt;1,3,2lt;2,1,3lt;2,3,1lt;3,1,2lt;3,2,1
​1,2,3<1,3,2<2,1,3<2,3,1<3,1,2<3,2,1​

即排列 1 , 2 , 3 1, 2, 3 1,2,3 最小,排列 3 , 2 , 1 3, 2, 1 3,2,1 最大,扩展到 1 ~ n 的排列来讲,如果给定一个现有排列(初始即为 1 ~ n 的顺序排列: 1 , 2 , 3 , . . . , n 1, 2, 3, ..., n 1,2,3,...,n),我们生成大于该排列的最小排列,依次进行下去便可生成所有排列.

那么如何生成大于该排列的最小排列呢?方法如下:

假设当前给定的排列为 a 1 , a 2 , a 3 , a 4 , . . . a n a_1, a_2, a_3, a_4, ... a_n a1​,a2​,a3​,a4​,...an​

  1. 从后( a n a_n an​ 开始)往前查找,找到第一个满足 a i < a i + 1 a_i < a_{i+1} ai​<ai+1​ 的元素
  2. 再次从后( a n a_n an​ 开始)往前查找,找到第一个满足 a j > a i a_j > a_i aj​>ai​ 的元素( a i a_i ai​ 为上一步骤的结果元素)
  3. 交换 a i a_i ai​ 和 a j a_j aj​
  4. 将 a i + 1 a_{i+1} ai+1​ 至 a n a_n an​ 的元素重新排序

(这里我们只给出了过程描述,更多细节大家可以参考相关书籍)

有了算法步骤,我们便可以进行代码实现了,过程中有两点值得一提:

  • 如果第 1 步中找不到满足 a i < a i + 1 a_i < a_{i+1} ai​<ai+1​ 的元素怎么办?这种情况会出现在元素逆序的排列中(譬如排列: 3 , 2 , 1 3, 2, 1 3,2,1),处理方法很简单,我们直接跳过 2, 3 步骤,直接执行第 4 步中的排序即可,由于此时元素是逆序的,我们直接反转(reverse)排列元素即可完成排序(排序之后排列又回到了"原点").
  • 如果正常进行了 1, 2, 3 这三个步骤,第 4 步中的重新排序操作我们仍然可以通过直接反转(reverse)排列元素来完成:

考虑第 1 步骤的操作,当找到目标元素 a i a_i ai​ 时,我们可知以下元素关系:

a i < a i + 1 > a i + 2 > a i + 3 > . . . > a n a_i < a_{i+1} > a_{i+2} > a_{i+3} > ... > a_n ai​<ai+1​>ai+2​>ai+3​>...>an​

考虑第 2 步骤的操作,当找到目标元素 a j a_j aj​ 时,我们可知以下元素关系:

. . . > a j − 1 > a j > a i > a j + 1 > . . . ... > a_{j-1} > a_j > a_i > a_{j+1} > ... ...>aj−1​>aj​>ai​>aj+1​>...

第 3 步骤我们执行了 a i a_i ai​ 与 a j a_j aj​ 的交换,根据之前的元素关系,我们可知:

a j < a i + 1 > a i + 2 > . . . > a j − 1 > a i > a j + 1 > . . . > a n \boxed{a_j} < a_{i+1} > a_{i+2} > ... > a_{j-1} > \boxed{a_i} > a_{j+1} > ... > a_n aj​​<ai+1​>ai+2​>...>aj−1​>ai​​>aj+1​>...>an​

所以 a i + 1 a_{i+1} ai+1​ 至 a n a_n an​ 间的元素在 1, 2, 3 步骤之后仍然是逆序关系,自然我们就可以通过直接反转(reverse)这些元素来对它们进行排序了,最终的示例代码如下:

// C#
public static List<int> NextPermutation(List<int> curPermutation)
{
    var swapIndex = -1;

    var index = curPermutation.Count - 2;
    while (index >= 0)
    {
        if (curPermutation[index] < curPermutation[index + 1])
        {
            swapIndex = index;
            break;
        }
        --index;
    }

    if (swapIndex >= 0)
    {
        // valid swap index, swap with the rest smallest big number
        var swapElement = curPermutation[swapIndex];
        var smallIndex = swapIndex + 1;
        for (int i = curPermutation.Count - 1; i >= swapIndex + 2; --i)
        {
            if (curPermutation[i] > swapElement)
            {
                smallIndex = i;
                break;
            }
        }

        // do swap
        curPermutation[swapIndex] = curPermutation[smallIndex];
        curPermutation[smallIndex] = swapElement;
    }

    // do sort
    var startIndex = swapIndex + 1;
    var endIndex = curPermutation.Count - 1;
    while (endIndex > startIndex)
    {
        var temp = curPermutation[startIndex];
        curPermutation[startIndex] = curPermutation[endIndex];
        curPermutation[endIndex] = temp;
        ++startIndex;
        --endIndex;
    }

    return curPermutation;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK