RANDOMIZE-IN-PLACE随机排列算法

本文介绍了RANDOMIZE-IN-PLACE算法,用于在原地随机排列数组,时间复杂度为O(n),并与PERMUTE-BY-SORTING算法进行了对比,提供了算法原理、伪码和C语言实现示例。
摘要由CSDN通过智能技术生成

给定一个长度为 n n n的数组,如何构造出一个随机排列呢?《算法导论》给了我们一个名为RANDOMIZE-IN-PLACE的随机算法,该算法在数组原址上进行排序,时间复杂度为 O ( n ) O(n) O(n)。下面本文将介绍RANDOMIZE-IN-PLACE的设计思想及代码实现。

1. 背景知识

在介绍RANDOMIZE-IN-PLACE的设计思想之前,我们首先来了解一些背景知识,什么是随机算法,什么是随机排列数组,以及为什么要随机排列数组。

1.1 随机算法

如果一个算法的行为不只是由输入决定,同时也由随机数生成器所产生的数值决定,则称这个算法是随机的。

1.2 随机排列数组

随机排列数组的目标是产生一个均匀随机排列,使得 n ! n! n!种排列中的每一种被取到的概率相等,等于 1 n ! \frac{1}{n!} n!1

许多随机算法通过排列给定的输入数组使得输入随机化。

2. 随机排列数组的两种算法

PERMUTE-BY-SORTING和RANDOMIZE-IN-PLACE是实现随机排列数组的两种算法。

2.1 PERMUTE-BY-SORTING

设数组 A = [ 1 , 2 , … , n ] A=[1, 2, …, n] A=[1,2,,n],PERMUTE-BY-SORTING为其中的每个元素 A [ i ] A[i] A[i]赋一个随机数优先权 P [ i ] P[i] P[i],然后根据这些优先权对数组 A A A进行排序。伪码如下:

PERMUTE-BY-SORTING(A)
n = length[A]
for(i=1; i<=n; i++)
	P[i] = RANDOM(1, n^3)
sort A, using P as sort keys
return A

在上述伪码中,使用 1 ~ n 3 1~n^3 1n3间的 n n n个随机优先权的原因有两点:

  1. 能在 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)时间内排序完成。
  2. 以很高的概率使得 n n n个优先权都不相同。

2.2 RANDOMIZE-IN-PLACE

RANDOMIZE-IN-PLACE也称为Fisher-Yates Shuffle或Knuth Shuffle算法,与PERMUTE-BY-SORTING算法相比,它有三方面优势:

  1. 省去了排序的代价,时间复杂度为 O ( n ) O(n) O(n)
  2. 随机数产生器所需范围更小(从 1 ~ n 3 1~n^3 1n3降为 1 ~ n 1~n 1n)。
  3. 不需要额外的辅助空间。

伪码如下:

RANDOMIZE-IN-PLACE(A, n)
for(i=1; i<=n; i++)
	swap(A[i], A[RANDOM(i, n)])

即在第 i i i次迭代时,从 A [ i … n ] A[i…n] A[in]随机选取元素与 A [ i ] A[i] A[i]交换位置;在第 i i i次迭代后, A [ i ] A[i] A[i]不再发生改变。

3. RANDOMIZE-IN-PLACE的正确性证明

下面我们来证明RANDOMIZE-IN-PLACE确实可以产生一个均匀随机排列。

证明:
循环不变式:for循环的第 i i i次迭代之前,对每个可能的 ( i − 1 ) (i-1) (i1)-排列,子数组 A [ 1 … i − 1 ] A[1…i-1] A[1i1]包含这个 ( i − 1 ) (i-1) (i1)-排列的概率为 ( n − i + 1 ) ! / n ! (n-i+1)! / n! (ni+1)!/n!

初始化: i = 1 i=1 i=1 A [ 1 … 0 ] A[1…0] A[10]包含某个0排列的概率为 ( n − i + 1 ) ! / n ! = 1 (n-i+1)!/n!=1 (ni+1)!/n!=1,其中 A [ 1 … 0 ] A[1…0] A[10],0排列则是不包含任何元素的序列。

保持: 假设第 i i i次迭代前,任一可能的 ( i − 1 ) (i-1) (i1)-排列出现在 A [ 1 … i − 1 ] A[1…i-1] A[1i1]中的概率为 ( n − i + 1 ) ! / n ! (n-i+1)! / n! (ni+1)!/n!,则需证明在第 i i i次迭代后,任一 i i i-排列出现在 A [ 1 … i ] A[1…i] A[1i]中的概率为 ( n − i ) ! / n ! (n-i)! / n! (ni)!/n!

  • 考虑一个特殊的 i i i-排列 R = [ x 1 , x 2 , … , x i ] R=[x_1, x_2, … , x_i] R=[x1,x2,,xi],其中包含一个 ( i − 1 ) (i-1) (i1)-排列 [ x 1 , x 2 , … , x i − 1 ] [x_1, x_2, … , x_{i-1}] [x1,x2,,xi1],算法在 A [ i ] A[i] A[i]放置 x i x_i xi
  • 令事件 E 1 E_1 E1表示算法实际输出 ( i − 1 ) (i-1) (i1)-排列 R ∗ R^{*} R A [ 1 … i − 1 ] A[1…i-1] A[1i1],根据循环不变量, P r ( E 1 ) = ( n − i + 1 ) ! / n ! Pr(E_1)=(n-i+1)!/n! Pr(E1)=(ni+1)!/n!
  • 令事件 E 2 E_2 E2表示第 i i i次迭代后输出 A [ i ] A[i] A[i] x i x_i xi,则当且仅当 E 1 E_1 E1 E 2 E_2 E2同时发生时我们得到 i i i-排列 R R R A [ 1 … i ] A[1…i] A[1i],其概率 P r ( E 2 ∩ E 1 ) = P r ( E 2 ∣ E 1 ) ⋅ P r ( E 1 ) Pr(E_2\cap{E_1})=Pr(E_2|E_1)·Pr(E_1) Pr(E2E1)=Pr(E2E1)Pr(E1)
    • 给定 E 1 E_1 E1的条件下, A [ i ] A[i] A[i] ( n − i + 1 ) (n-i+1) (ni+1)种取法,取到 x i x_i xi的概率为 1 / ( n − i + 1 ) 1/(n-i+1) 1/(ni+1),即 P r ( E 2 ∣ E 1 ) = 1 / ( n − i + 1 ) Pr(E_2|E_1) = 1/(n-i+1) Pr(E2E1)=1/(ni+1)
    • 因此, P r ( E 2 ∩ E 1 ) = P r ( E 2 ∣ E 1 ) ⋅ P r ( E 1 ) = 1 n − i + 1 ⋅ ( n − i + 1 ) ! n ! = ( n − i ) ! / n ! Pr(E_2\cap{E_1})=Pr(E_2|E_1)·Pr(E_1)=\frac{1}{n-i+1}·\frac{(n-i+1)!}{n!}=(n-i)! / n! Pr(E2E1)=Pr(E2E1)Pr(E1)=ni+11n!(ni+1)!=(ni)!/n!

终止: 终止时, i = n + 1 i=n+1 i=n+1 A [ 1 … n ] A[1…n] A[1n]是一个给定 n n n-排列的概率为 ( n − ( n + 1 ) + 1 ) ! / n ! = 1 / n ! (n-(n+1)+1)!/n! = 1/n! (n(n+1)+1)!/n!=1/n!

4. 代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define N 10       //定义数组的长度为10 

void randomizeInPlace(int array[], int length) {  //对长度为length的数组array[]进行随机排列 
	int i;
	for (i = 0; i < length; i++) {
		int rnd = i + (rand() % (length - i));  //生成[i, length-1)范围内的一个随机下标 
		//交换 array[i]与array[rnd] 
		int temp = array[i];          
		array[i] = array[rnd];
		array[rnd] = temp;
	}	
}

int main() {
	srand(time(NULL));  //设置时间函数time(NULL)为随机数种子 
	int array[N];
	int i, j;
	for (i = 0; i < N; i++) {  //为原数组赋初值 
		array[i] = i + 1;
	}
	printf("原数组为:");      //打印输出原数组 
	for (i = 0; i < N; i++) {
		printf("%d ", array[i]);
	}
	printf("\n");
	int times;
	printf("请输入随机重排的次数:");
	scanf("%d", &times);       //用户指定随机重排的次数
	for (j = 0; j < times; j++) {
		randomizeInPlace(array, N);  //每次随机排列都是在上次随机排列的数组基础之上  
		printf("随机重排%d次的数组为:", j + 1); 
		for (i = 0; i < N; i++) {
			printf("%d ", array[i]);
		}
		printf("\n");
	}
	return 0;
} 

5. 运行结果

数组的初值为1,2,3,4,5,6,7,8,9,10。用户输入随机重排的次数k,按下回车键,程序打印输出连续k次随机重排的数组。

随机重排10次的结果如下:

随机重排10次的结果

随机重排20次的结果如下:

随机重排20次的结果

  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fufufunny

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值