这种洗牌算法还不是完全随机?

haitao 2012-10-24 11:36:57
这种洗牌算法还不是完全随机?
算法a:顺序在数组s的n个元素里填入0-(n
循环i=1到n
x=rand(n+1)
if x<>i then 交换s[i]和s[x]
据说完全随机的应该是
算法b:循环i=n到2
x=rand(i)
if x<>i then 交换s[i]和s[x]

据说原因是:算法a第1次移动后,第1个数还在第1个位置的概率是1/n,后续移动只会减少这个概率
感觉不对啊,后续移动也可能把移到别的位置的1换回第1个位置。。。。
...全文
383 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
haitao 2012-10-25
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 的回复:]
将7楼代码shuffle.c的输出重定向到文本文件中
比如在cmd窗口里面
shuffle.exe >out.txt
然后统计一下out.txt文件中1~5在对应位置出现的概率。
[/Quote]

改了一下程序,可以检测每个数的对应位置概率,基本与1相同。。。。。
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 的回复:]
后续是不可能把1在移动回1来的,仔细想一下就知道了
假定2和1换位置,之后1无论怎么换都换不回去了
[/Quote]
算法a,可能:
i=1时,x=3,则3214567.。。。
i=2时,x=5,则3514267....
i=3时,x也可能=1,则153267.。。

按理说,算法a的每次交换,都是一个独立的随机过程,它没有存在任何偏好啊
nice_cxf 2012-10-24
  • 打赏
  • 举报
回复
后续是不可能把1在移动回1来的,仔细想一下就知道了
假定2和1换位置,之后1无论怎么换都换不回去了
haitao 2012-10-24
  • 打赏
  • 举报
回复
伪随机算法的效果验证,好像一直是很困难的事情
本例是利用伪随机算法的一个应用算法了,应该简单很多
冷月清晖 2012-10-24
  • 打赏
  • 举报
回复
我建议楼主 读一下 《编程珠玑一》的12.1节到12.3节 讲一个抽样问题的。
冷月清晖 2012-10-24
  • 打赏
  • 举报
回复
包括我在内,我觉得现在的中国程序员都比较注重算法理论,但是确不重视解决当前存在的问题。

祝:1024中国程序员节快乐!
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 的回复:]
后面确实有可能会把1移到原来的地方,但是这样发生的概率更小,可以忽略不计,总的来说这个概率是减小的,到最后,趋近于结束
[/Quote]

按理说,a和c应该也是等价的,c肯定没有1的位置问题吧

不过,实际测试的结果,c反而是很糟糕的
这个可能是n次交换里,c的做法可能导致未一些位置没被换过,而5n次交换,就没有问题了
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 的回复:]
打乱0~n-1:

C/C++ code
for (i=n;i>0;i--) {
a=i-1;b=rand()%i;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}

打乱1~n:

C/C++ code
for……
[/Quote]

这个就是算法b了
a和b的差别就是在于 rand(i) 还是 rand(n)
赵4老师 2012-10-24
  • 打赏
  • 举报
回复
将7楼代码shuffle.c的输出重定向到文本文件中
比如在cmd窗口里面
shuffle.exe >out.txt
然后统计一下out.txt文件中1~5在对应位置出现的概率。
dxasu 2012-10-24
  • 打赏
  • 举报
回复
后面确实有可能会把1移到原来的地方,但是这样发生的概率更小,可以忽略不计,总的来说这个概率是减小的,到最后,趋近于结束
赵4老师 2012-10-24
  • 打赏
  • 举报
回复
打乱0~n-1:
            for (i=n;i>0;i--) {
a=i-1;b=rand()%i;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}

打乱1~n:
            for (i=n;i>1;i--) {
a=i;b=rand()%i+1;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}
赵4老师 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 的回复:]
引用 7 楼 的回复:
洗牌算法参考下面:

C/C++ code
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int d[6];
int i,n,a,b,t;
int c,j;
void main() {
srand(time(NULL));
printf("shuffle 0..n-1 demo\n"……
循环 n! 次,计算量大了一点

最好是 0(n) 的算法
[/Quote]
循环n!次仅为测试。
核心算法就
for (i=n;i>0;i--) {/* 打乱0~n-1 */
a=i-1;b=rand()%i;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}

for (i=1;i<=n;i++) d[i]=i;/* 填写1~n */
for (i=n;i>1;i--) {/* 打乱1~n */
a=i;b=rand()%i+1;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}

haitao 2012-10-24
  • 打赏
  • 举报
回复
每个算法每次都洗5遍,则大家都差不多了:

100171 100195 100592 99613 100052 99918 100367 99759 100001 99332
1215922
100516 100077 99679 100325 99588 99954 99927 100443 100117 99374
1259854
99775 100310 100275 100326 99658 100043 99580 99792 99727 100514
1005828

100137 100162 100426 99756 100151 99960 100149 99972 99795 99492
633500
100254 100129 99870 99879 99905 100027 100386 99907 99713 99930
367366
100298 100029 100050 100043 100269 99907 99973 99712 100333 99386
746562

100612 100144 99809 100349 100117 99799 99591 99274 100173 100132
1349362
100183 99665 99926 99737 100476 99638 100015 99697 100131 100532
970198
99674 100183 99882 99742 99951 100577 99694 100215 100103 99979
706494

100490 99980 100350 100053 99431 100229 99984 99633 99701 100149
988558
99914 99540 99684 100163 99977 99985 100279 100184 99973 100301
549202
100281 99775 100043 100243 100207 99991 99610 99788 99778 100284
560398
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 的回复:]
洗牌算法参考下面:

C/C++ code
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int d[6];
int i,n,a,b,t;
int c,j;
void main() {
srand(time(NULL));
printf("shuffle 0..n-1 demo\n");
……
[/Quote]

循环 n! 次,计算量大了一点

最好是 0(n) 的算法
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]
循环i=1到n
x=rand(n+1)
if x<>i then 交换s[i]和s[x]



循环i=1到n
y=rand(n+1)
x=rand(n+1)
if x<>y then 交换s[y]和s[x]

应该是一样的效果吧

如果按那个否定的原因,x、y的交换就没有那个1/n的问题了?
[/Quote]

算法c
循环i=1到n
y=rand(n+1)
x=rand(n+1)
if x<>y then 交换s[y]和s[x]

刚才写了delphi程序把3个算法都演示了100万次
计算:每次结果里,1在每个位置出现的次数,以它的方差(是这个词吧)为标准
发现算法c的方差反而最大!a和b其实差不多。。。。

99871 99765 100530 99988 100019 100038 99705 100539 99760 99785
836086
99921 99589 100306 100009 100270 99663 100485 99281 99861 100615
1605080
195903 89164 89722 89423 89009 89317 89614 88985 89557 89306
1629925602

99976 99833 99663 100404 100176 100126 99969 100254 99943 99656
539164
99644 100416 99867 100286 99809 100674 99717 100253 99449 99885
1350958
197071 89278 89280 89561 89487 89130 89306 89146 89034 88707
1880332880

100510 99108 99826 99596 100159 100052 100244 100100 99911 100494
1598734
100303 100493 100005 100063 99910 100276 99749 99729 99553 99919
765940
197136 89039 88993 89574 88943 89141 89309 89374 89793 88698
1894769490

100312 99760 99802 99699 100307 99883 100181 99792 100315 99949
570538
99820 100070 100102 100368 99558 100028 99923 100332 99753 100046
558554
197516 89013 89277 89320 88728 88957 89776 89433 88750 89230
1976947860
nice_cxf 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 的回复:]

引用 8 楼 的回复:
显然是不一样的,以3个数为例,一共27种可能,但是1在开头的只有7种

第一次是1,以后不能有1,一共4种122 ,123,132,133
第一次不是1的只有213,212,321这3种可能


不应该出现相同的数字
应该是n个不同数的全排列
可能性是 n!,而不是 n^n
[/Quote]

你看清楚算法1了么?
x=rand(n+1),n是常量,
,自然是n^n
而算法2
x=rand(i),i是递减的,是 n!
haitao 2012-10-24
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 的回复:]
显然是不一样的,以3个数为例,一共27种可能,但是1在开头的只有7种

第一次是1,以后不能有1,一共4种122 ,123,132,133
第一次不是1的只有213,212,321这3种可能
[/Quote]

不应该出现相同的数字
应该是n个不同数的全排列
可能性是 n!,而不是 n^n
nice_cxf 2012-10-24
  • 打赏
  • 举报
回复
显然是不一样的,以3个数为例,一共27种可能,但是1在开头的只有7种

第一次是1,以后不能有1,一共4种122 ,123,132,133
第一次不是1的只有213,212,321这3种可能
赵4老师 2012-10-24
  • 打赏
  • 举报
回复
洗牌算法参考下面:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int d[6];
int i,n,a,b,t;
int c,j;
void main() {
srand(time(NULL));
printf("shuffle 0..n-1 demo\n");
for (n=1;n<=5;n++) {/* 测试1~5个元素 */
printf("_____n=%d_____\n",n);
j=1;
for (c=1;c<=n;c++) j=j*c;/* j为n! */
j*=n*2;
for (c=1;c<=j;c++) {/* 测试n*2*n!次 */
for (i=0;i<n;i++) d[i]=i;/* 填写0~n-1 */
for (i=n;i>0;i--) {/* 打乱0~n-1 */
a=i-1;b=rand()%i;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}
printf("%04d:",c);
for (i=0;i<n;i++) printf("%d",d[i]);
printf("\n");
}
}
printf("shuffle 1..n demo\n");
for (n=1;n<=5;n++) {/* 测试1~5个元素 */
printf("_____n=%d_____\n",n);
j=1;
for (c=1;c<=n;c++) j=j*c;/* j为n! */
j*=n*2;
for (c=1;c<=j;c++) {/* 测试n*2*n!次 */
for (i=1;i<=n;i++) d[i]=i;/* 填写1~n */
for (i=n;i>1;i--) {/* 打乱1~n */
a=i;b=rand()%i+1;
if (a!=b) {t=d[a];d[a]=d[b];d[b]=t;}
}
printf("%04d:",c);
for (i=1;i<=n;i++) printf("%d",d[i]);
printf("\n");
}
}
}
haitao 2012-10-24
  • 打赏
  • 举报
回复
循环i=1到n
x=rand(n+1)
if x<>i then 交换s[i]和s[x]



循环i=1到n
y=rand(n+1)
x=rand(n+1)
if x<>y then 交换s[y]和s[x]

应该是一样的效果吧

如果按那个否定的原因,x、y的交换就没有那个1/n的问题了?

3,881

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 其它技术问题
社区管理员
  • 其它技术问题社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧