| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 4350 人关注过本帖, 1 人收藏
标题:常见算法错误之随机洗牌算法
只看楼主 加入收藏
御坂美琴
Rank: 11Rank: 11Rank: 11Rank: 11
来 自:魔術の禁書目錄
等 级:小飞侠
威 望:9
帖 子:952
专家分:2929
注 册:2010-8-18
结帖率:96.15%
收藏(1)
已结贴  问题点数:20 回复次数:19 
常见算法错误之随机洗牌算法
比如,我们写一个生成包含 1 - 52 这些数字的数组,要求位置是打乱的,并且不允许有数字没出现,或者有数字重复出现
为了达到这个目标,典型的洗牌算法是,先生成一个 1 - 52 有序的数组,然后,做若干次交换操作,这样打乱效率较高,并且不会有数字重复的问题

于是,有人会写如下类似的代码(以下请看成伪代码):

#define SWAP(a, b, _t) do{_t t = a; a = b; b = t; }while(0)

void shuffle(int* arr, int len)
{
    int n;
    for (n = 0; n < len; ++n)
    {
        int i = myrand(len); //生成一个 0 至 len-1之间的随机数,也可能写成 rand() % len
        SWAP(arr[n], arr[i], int); //交换arr[n]和arr[i]
    }
}

运行时,看起来效果也不错,这也是最为常见的写法,
即每次生成一个 0 至 len-1之间的随机数i,和原数组里依次每个数交换,保证每个数都交换了至少一次













如此广为流传的写法,殊不知,却是错的!尽管应用时看起来似乎没有什么问题




以下是算法分析:
在这里,一共做了len次交换,每一次随机范围大小都是len,那么一共产生的可能总数是pow(n,n),即n的n次方

不过,len个数的全排列,是n!次,而pow(n,n)远比n!要大
而素数定理里,任意的n与2n之间至少存在一个素数,那么同理,n与n/2之间至少存在一个素数,并且这个素数是不能整除n的
所以,n!至少含有一个质因数,是pow(n, n)所不包含的,所以pow(n, n)绝不可能是n!的整数倍
所以,pow(n, n)所产生的组合,概率一定是不完全均等的,有一些排列出现概率高,有一些概率低,
所以,这种办法生成的排列,概率是不均匀分布的

为了更直观地说明问题,我用三个数1,2,3的排列,用此算法打乱,看看各种排列出现的次数:

随机值   最后结果排列(字母表示排列类型)
0 0 0    3 1 2 e
0 0 1    2 3 1 d
0 0 2    2 1 3 c
0 1 0    3 2 1 f
0 1 1    1 3 2 b
0 1 2    1 2 3 a
0 2 0    2 3 1 d
0 2 1    1 2 3 a
0 2 2    1 3 2 b
1 0 0    3 2 1 f
1 0 1    1 3 2 b
1 0 2    1 2 3 a
1 1 0    3 1 2 e
1 1 1    2 3 1 d
1 1 2    2 1 3 c
1 2 0    1 3 2 b
1 2 1    2 1 3 c
1 2 2    2 3 1 d
2 0 0    1 3 2 b
2 0 1    2 1 3 c
2 0 2    2 3 1 d
2 1 0    1 2 3 a
2 1 1    3 1 2 e
2 1 2    3 2 1 f
2 2 0    2 1 3 c
2 2 1    3 2 1 f
2 2 2    3 1 2 e

统计一下:
1 2 3 : 4
1 3 2 : 5
2 1 3 : 5
2 3 1 : 5
3 1 2 : 4
3 2 1 : 4

总排列数是6,而27种生成结果是无论如何也不可能平均下去的
其中,随机值是指myrand(len)的返回值,每次生成调用了三次,就有三个值
数据中,清晰可见,每种排列出现次数并不一样,而且,如果长度不止3,那么这种情况会更为严重,
次数差别会更大,更不均匀


在证明了这个算法是不对的以后,那正确的应该怎么写呢?
并不复杂,记住以下的代码吧:
#define SWAP(a, b, _t) do{_t t = a; a = b; b = t; }while(0)
void shuffle(int* arr, int len)
{
    int n;
    for (n = len; n > 1; --n)
    {
        int i = myrand(n);
        SWAP(arr[n - 1], arr[i], int);
    }
}

完.
收到的鲜花
  • yangfanconan2011-01-15 17:56 送鲜花  10朵   附言:好文章
搜索更多相关主题的帖子: shuffle 
2011-01-15 12:39
qq1023569223
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
来 自:湖南科技大学
等 级:贵宾
威 望:26
帖 子:2753
专家分:13404
注 册:2010-12-22
收藏
得分:3 
支持!

   唯实惟新 至诚致志
2011-01-15 12:46
huangapple
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:545
专家分:1790
注 册:2010-12-30
收藏
得分:3 
我来学习加接分。。。

勤能补拙,熟能生巧!
2011-01-15 12:46
观弈寒儒
Rank: 7Rank: 7Rank: 7
来 自:自 来
等 级:黑侠
帖 子:359
专家分:545
注 册:2011-1-9
收藏
得分:3 
回复 3楼 huangapple
不要本末倒置了。。。
来晚了。

事件记录,值得关注! http://bbs.bccn.net/z_court.php?fid=5
2011-01-15 13:11
刘定邦
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:687
专家分:1570
注 册:2010-9-21
收藏
得分:2 
学习...
2011-01-15 13:45
BlueGuy
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:29
帖 子:4476
专家分:4055
注 册:2009-4-18
收藏
得分:2 
学习了,不过从来没有听说过这算法,广为流传是不是有点夸张了
又或许能不能说明什么问题呢?

[ 本帖最后由 BlueGuy 于 2011-1-15 14:02 编辑 ]

我就是真命天子,顺我者生,逆我者死!
2011-01-15 13:47
瓦药墙
Rank: 7Rank: 7Rank: 7
等 级:黑侠
帖 子:218
专家分:556
注 册:2009-9-16
收藏
得分:2 
已经拷贝在身边
打算一用好多年
2011-01-15 13:54
点线面
Rank: 8Rank: 8
来 自:NO.-1
等 级:蝙蝠侠
帖 子:525
专家分:980
注 册:2011-1-3
收藏
得分:0 
用软件无论怎样只会生成伪随机数。只有用硬件才能生成真正随机数。

小代码,大智慧
2011-01-15 18:49
xdzsm
Rank: 2
等 级:论坛游民
帖 子:137
专家分:99
注 册:2010-10-26
收藏
得分:2 
学习!
2011-01-15 20:46
cacker
该用户已被删除
收藏
得分:2 
提示: 作者被禁止或删除 内容自动屏蔽
2011-01-15 22:22
快速回复:常见算法错误之随机洗牌算法
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.013637 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved