随机排列生成算法

这篇文章主要是一个闲文。如果您正在寻求一个理想的随机排列生成算法,直接阅读方法3,或是直接使用STL里提供的random_shuffle()方法
另外请注意,这里所讨论的算法并不是新的。

什么是随机排列?

一个随机排列是一组位于随机位置的对象。
给定一个对象,1, 2, 3 ... n,随机排列看起来就是,
p1, p2, p3 ... pn
其中px是从原来的对象集合中选取的随机值。

随机排列对于扑克牌洗牌,随机产生益智游戏,产生随机序列,或者生成一个随机子集合集(从 n 个对象中随机选出 k 个对象),非常有用。

随机排列生成算法从天真到成熟,我的真实经验

为了解释算法,我会用一个辅助函数来产生随机数。
int random(int min, int max);
其结果是一个大于或等于 min 且小于 max 的一个随机数。
也就是说,结果是位于左闭右开区间内。

方法1,天真的方式

在随机位置交换两个元素。重复足够的次数。

伪代码:

Cpp代码  

  1. array data(1..n);
  2. for(enough iterations) {
  3. swap(data[random(0, n)], data[random(0, n)]);
  4. }

这种方法非常直观,很简单,它真的有效,前提是有足够的迭代,比如对10个元素迭代100次。没错,它真的可以工作,我用过很多次。

但最大的问题是,迭代次数要远远高于对象数(N),因为在两次中选择相同位置的两个元素的概率是相当大的,概率为1 /(n * n)相当的高。
因此,用这种方法,我们要么得到糟糕的性能(使用非常高的迭代),要么是比较差的随机性(低迭代)。

方法2,从篮子里取小球

假设所有的对象都是球。我们把所有的球到一个篮子,然后从篮子里随机拿出一个球,如是重复直到篮子变空。

伪代码:

Cpp代码  

  1. array data(1..n);
  2. basket = new array;
  3. for(i = 0 to N - 1) {
  4. basket.push(data[i]);
  5. }
  6. for(i = 0 to N - 1) {
  7. int index = random(0, basket.length);
  8. data[i] = basket[index];
  9. basket.remove(index);
  10. }

这种方法也很直观,因为在现实中,彩票抽奖正是用这种方法,而且用的是真正的篮子和球。
而且这种方法性能很好,具有O(n)的时间复杂度。
理论上,其结果是能保证足够随机的,因为所有的球是从篮子里随机选择。

方法3,演进 - 在篮球里原地选择

第二种方法是很好的实现,而且很容易操作。但是,在计算机世界中,它有一个缺点:它需要一个额外的临时缓冲区来作为篮子。
在大多数情况下这没什么,不是个问题,但我们是否可以做得更好呢?
当然可以!我们可以在就在篮子里选择。

实际的 C++ 代码:

Cpp代码  

  1. int random(int minValue, int maxValue)
  2. {
  3. assert(minValue <= maxValue);
  4. if(minValue != maxValue) {
  5. return rand() % (maxValue - minValue) + minValue;
  6. }
  7. else {
  8. return minValue;
  9. }
  10. }
  11. template <typename T>
  12. void randomPermutation(T & data, int count)
  13. {
  14. using std::swap;
  15. for(int i = 0; i < count; ++i) {
  16. swap(data[i], data[random(i, count)]);
  17. }
  18. }

C 版本的非模板randomPermutation(用你需要的数据类型替换 "int" ,并自行实现 swap 函数)

Cpp代码  

  1. void randomPermutation(int * data, int count)
  2. {
  3. for(int i = 0; i < count; ++i) {
  4. swap(&data[i], &data[random(i, count)]);
  5. }
  6. }

上面的代码正是篮子方法的实现,不过比较隐晦。

了解原理

让我们假设篮子是有N个槽的长形篮子。则篮子是线性的。
那么初始篮子的样子,
1,2,3,4,5,6,...,N
现在假设我们随机选择5,那么篮子里的样子,
1,2,3,4,E,6,...,N
E表示空的槽。
接下来我们不删除E,我们把 5 之前的所有槽向后移动一个位置,并把 5 放在第一个槽里
5,1,2,3,4,6,...,N
下次如果我们选择3,我们只是移动 3 之前 5 之后的所有槽,然后把3个在那里,
5,3,1,2,4,6,...,N
重复N次

很好,是吗?我们不需要一个额外的缓冲区。但是,我们必须移动很多槽,不好玩。
如果第 C 次选择,我们只是把候选的元素与第 C 个元素交换,怎么样?
上面的迭代会进行以下变化,
1, 2, 3, 4, 5, 6, ..., N // 初始
5, 2, 3, 4, 1, 6, ..., N // 随机选择 5, 和 1 交换
5, 3, 2, 4, 1, 6, ..., N // 随机选择 3, 和 2 交换

这正是上面代码做的事情。

原文地址:http://kbasm.iteye.com/blog/1050113

时间: 2024-12-29 11:27:16

随机排列生成算法的相关文章

等高线生成算法(转载)

等高线生成算法 输入:离散的采样点坐标和高度值(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) 输出:等高线图,如下所示 wiki上的Marching squares算法对此有很好的说明,我也是按照wiki上面的步骤来实现这个算法的,下面对该算法的步骤进行简要说明. 输入参数: 1.点的集合(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) ; 2.高度值

清华版CG 实验2 直线生成算法实现

1.实验目的: 理解基本图形元素光栅化的基本原理,掌握一种基本图形元素光栅化算法,利用OpenGL实现直线光栅化的DDA算法. 2.实验内容: (1) 根据所给的直线光栅化的示范源程序,在计算机上编译运行,输出正确结果: (2) 指出示范程序采用的算法,以此为基础将其改造为中点线算法或Bresenham算法,写入实验报告: (3) 根据示范代码,将其改造为圆的光栅化算法,写入实验报告: (4) 了解和使用OpenGL的生成直线的命令,来验证程序运行结果. 3.实验原理: 示范代码原理参见教材直线

微信红包生成算法 (解)

/** * 微信红包生成算法 * * @param int $total 红包金额 * @param int $num 拆分数量 * @param int $min 拆分的红包最小金额数目 */function set_packet($total, $num, $min = 0.01){ for ($i = 1; $i < $num; $i++) { //随机安全上限 $safe_total = ($total-($num-$i)*$min)/($num-$i); //红包金额 $money =

[迷宫中的算法实践]迷宫生成算法&mdash;&mdash;Prim算法

       普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现:1959年,艾兹格·迪科斯彻再次发现了该算法.因此,在某些场合,普里姆

计算机图形学(二)输出图元_6_OpenGL曲线函数_1_圆生成算法

OpenGL曲线函数 生成圆和椭圆等基本曲线的函数并未作为图元功能包含在OpenGL核心库中.但该库包含了显示Bezier样条的功能,该曲线是由一组离散点定义的多项式.OpenGL实用库(GLU)中包含有球面和柱面等三维曲面函数以及生成B样条的函数,它是包含简化Bezier曲线的样条曲线的总集.我们可以使用有理B样条显示圆.椭圆和其他二维曲线.此外,OpenGL实用工具包(GLUT)中还有可以用来显示某些三维曲面(如球面.锥面和其他形体)的函数.然而,所有这些函数比本章中介绍的基本图元应用得更多

闪电生成算法

儿时想搞明白的闪电生成算法, 今天终于想起来并且看明白了. 算法很简单. 把起点和终点不断二分, 直到一个极限值, 然后再全部连接. 1 void drawLightning(HDC hdc, const POINT &start, const POINT &end, float diff) 2 { 3 if (diff < s_minDiff) 4 { 5 MoveToEx(hdc, start.x, start.y, nullptr); 6 LineTo(hdc, end.x,

排列生成算法--C++

1 #include<iostream> 2 using namespace std; 3 //初始化,注意两边的初始化,简化算法 4 void Init(int n , int *arr , bool * flags) 5 { 6 arr[0] = 0xfffffff; 7 for(int i = 1;i <= n;i++) 8 { 9 arr[i] = i; 10 flags[i] = false; 11 } 12 arr[n+1] = 0xfffffff; 13 } 14 //检查

[原创][网页游戏]数独生成算法及实例

[ 程序修正 2015/02/23 补充及订正方法:iphone上的Safari会自动对看起来像是电话号码的数字串(包括已经加入连字符或括号格式化过的)添加电话链接,点击之后会询问用户是否想要拨打该号码. 关闭方法: <meta name="format-detection" content="telephone=no" /> 单独开放方法: <a href="tel:13800138000">13800138000<

等高线生成算法

输入:离散的采样点坐标和高度值(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) 输出:等高线图,如下所示 wiki上的Marching squares算法对此有很好的说明,我也是按照wiki上面的步骤来实现这个算法的,下面对该算法的步骤进行简要说明. 输入参数: 1.点的集合(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) ; 2.高度值数组,即每条等高