生成隨機排列

黃志斌發表於2016-06-25

演算法

要生成 [1..n] 的隨機排列,可以使用演算法 A:
[《計算機程式設計藝術 卷2:半數值演算法(第3版)》演算法 3.4.2P(第 110 頁)]

  • 生成排列 [1..n],記為 a,其中 a[1] = 1, a[2] = 2, ..., a[n] = n。
  • 生成 [1,n] 範圍內的隨機數 k,交換 a[k] 和 a[n]。
  • 生成 [1,n-1] 範圍內的隨機數 k,交換 a[k] 和 a[n-1]。
  • ...
  • 生成 [1,3] 範圍內的隨機數 k,交換 a[k] 和 a[3]。
  • 生成 [1,2] 範圍內的隨機數 k,交換 a[k] 和 a[2]。

注意,不能使用演算法 B:

  • 生成排列 [1..n],記為 a,其中 a[1] = 1, a[2] = 2, ..., a[n] = n。
  • 生成 [1,n-1] 範圍內的隨機數 k,交換 a[k] 和 a[n]。
  • 生成 [1,n-2] 範圍內的隨機數 k,交換 a[k] 和 a[n-1]。
  • ...
  • 生成 [1,2] 範圍內的隨機數 k,交換 a[k] 和 a[3]。
  • 生成 [1,1] 範圍內的隨機數 k,交換 a[k] 和 a[2]。

測試程式

根據演算法 A,我們有以下 C# 程式:

using System;
using System.Collections.Generic;

static class RandomPermutation
{
  static readonly Random rand = new Random();
  static readonly byte[] seq = {1, 2, 3, 4};
  static readonly int len = seq.Length;
  static readonly int total = 24000000;

  static void Swap<T>(ref T a, ref T b) { T t = a; a = b; b = t; }

  static int ToValue(byte[] seq)
  {
    var z = 0;
    foreach (var i in seq) z = z * 10 + i;
    return z;
  }

  static int GetRandomPermutation()
  {
    var a = (byte[])seq.Clone();
    for (var n = len; n > 1; n--)
      Swap(ref a[rand.Next(n)], ref a[n - 1]);
    return ToValue(a);
  }

  static void Main()
  {
    var dict = new SortedDictionary<int, int>();
    for (var i = 0; i < total; i++) {
      var key = GetRandomPermutation();
      int count;
      dict.TryGetValue(key, out count);
      dict[key] = count + 1;
    }
    foreach (var kvp in dict)
      Console.WriteLine("{0}: {1,7}", kvp.Key, kvp.Value);
  }
}

這個程式的 GetRandomPermutation() 方法實現了演算法 A,其餘的是測試程式碼。

要注意的是,C# 語言中,陣列的下標是從 0 開始的。而前面描述演算法 A 時,下標是從 1 開始的。

還有,Random.Next(n) 方法返回 [0,n-1] 範圍內的隨機數。

執行結果

這個程式的某次執行的結果是(冒號後面的數是該排列出現的次數):

1234: 999306
1243: 998918
1324: 1002026
1342: 999857
1423: 1001389
1432: 999892
2134: 1001955
2143: 999700
2314: 999546
2341: 999570
2413: 998781
2431: 999752
3124: 1001541
3142: 999276
3214: 1000892
3241: 1000928
3412: 1001220
3421: 998861
4123: 1000518
4132: 1000431
4213: 1000458
4231: 997699
4312: 997567
4321: 999917

如果把上述程式的 GetRandomPermutation() 方法中的 rand.Next(n) 改為 rand.Next(n-1),則該程式實現了演算法 B,某次執行的結果如下所示:

2341: 4001963
2413: 4003021
3142: 3997535
3421: 3998602
4123: 4000183
4312: 3998696

可見,演算法 B 生成的排列的第 k 位決不會出現 k,所以不是隨機的。

相關文章