比较洗牌算法的两种实现方法

开发 后端 算法
我们首先会介绍随机生成法,该方法要点就是从头开始逐个随机生成规定区域的数字,如果新生成随机数之前已经生成过就不保存该数;如果新生成的随机数之前没有生成过就保存该数;直到生成的数字的数量达到所需的数量。接下来我们还会介绍交换位置法。

方法一:随机生成法

首先,我介绍一种很常见的方法:随机生成法(我自己命名的),这方法我在扫雷游戏中随机分布雷的位置时用过(思想是一样的),该方法要点就是从头开始逐个随机生成规定区域的数字,如果新生成随机数之前已经生成过就不保存该数;如果新生成的随机数之前没有生成过就保存该数;直到生成的数字的数量达到所需的数量。

实现代码如下:

  1. size_t shuffle(char s[], int n) 
  2.     size_t t=0;//计算循环次数 
  3.     int c=0; 
  4.     while(c<n) 
  5.     { 
  6.         t++; 
  7.         int num = rand()%n; 
  8.         if (memchr(s,num,c) == NULL) 
  9.         { 
  10.             s[c++] = static_cast<char>(num); 
  11.         } 
  12.     } 
  13.     return t; 
  14.  
  15.  
  16. void printCards(char s[], int n) 
  17.     for (int i=0; i<n; i++) 
  18.     { 
  19.         cout << static_cast<int>(s[i]) << " "
  20.     } 
  21.     cout << endl; 

代码中使用了memchr函数(时间复杂度可能是O(n),没找到依据),即使是O(1),它的循环生成随机数的次数是不固定的(大于等于需要生成的个数)。

方法二:交换位置法

这种方法是我昨天在参加腾讯笔试考试时候想到的,今天回到学校后在寝室测试了一番,基本思路是:先初始化一串分布的数字,然后为每个位置依次生成一个与之交换的随机位置,如果生成的随机位置不是它本身就执行交换操作。

实现代码:

  1. void swap(int& a, int& b) 
  2.     a = a^b; 
  3.     b = a^b; 
  4.     a = a^b; 
  5.  
  6. size_t shuffle2(int s[], int n) 
  7.     size_t t=0;//计算循环次数 
  8.     for (int i=0; i<n; i++) 
  9.     { 
  10.         t++; 
  11.         s[i] = i; 
  12.     } 
  13.     for (int i=0; i<n; i++) 
  14.     { 
  15.         t++; 
  16.         int num = rand()%n; 
  17.         if (num != i) 
  18.         { 
  19.             swap(s[num],s[i]); 
  20.         } 
  21.     } 
  22.     return t; 
  23.  
  24. void printCards2(int s[], int n) 
  25.     for (int i=0; i<n; i++) 
  26.     { 
  27.         cout << s[i] << " "
  28.     } 
  29.     cout << endl; 

比较:在时间上方法二比方法一快好多,因为交换位置的次数的***值是限定了的(生成随机数的次数是固定的),而且省去了查找新生成数是否在已生成数中的时间。方法一中,当新生成的数在已生成的数中就需要从新生成一个随机数,从而随机生成数的次数是不固定的(有最小值)。

测试代码:

 

结果:

image

我还是不能确定第二种方法是不是更好的,因为是自己想到的,我的验证也不是很完善,也许有什么其他的缺点(比如说随机性太弱),也没在其他书上看到过,如果网友们在哪看到过就告诉下我吧,方法一是在《c和c++代码精粹》中文版P97中找到的。

后续补充:

谢谢chncwang的回复,方法二不是完全随机的,完全随机的修改如下:

  1. size_t shuffle22(int s[], int n) 
  2.     size_t t=0;//计算循环次数 
  3.     for (int i=0; i<n; i++) 
  4.     { 
  5.         t++; 
  6.         s[i] = i; 
  7.     } 
  8.     for (int i=n-1; i>0; --i) 
  9.     { 
  10.         t++; 
  11.         int num = rand()%(i+1); 
  12.         if (num != i) 
  13.         { 
  14.             swap(s[num],s[i]); 
  15.         } 
  16.     } 
  17.     return t; 
因为"第1次移动后,第1个数还在第1个位置的概率是1/n,后续移动只会减少这个概率。所以这个算法不是完全随机的"。修改后的算法其实就是使用C++的STL<algorithm>库中的random_shuffle()函数的实现方法。取随机数的时候的范围每次都随着i的改变而变动,这样就不会减少之前的位置的数还是原来的数的概率了(即后续交换操作不会影响到已经交换过的位置)。

使用标准库<algorithm>中的random_shuffle()函数实现很简单,代码如下:

  1. int main() 
  2.     vector<int> s_stl; 
  3.     for (int i=0; i<CARDS_COUNT; ++i) s_stl.push_back(i); 
  4.     random_shuffle(s_stl.begin(),s_stl.end()); 
  5.     cout << "使用C++算法库:"
  6.     for (vector<int>::iterator it=s_stl.begin(); it!=s_stl.end(); ++it) 
  7.         cout << " " << *it; 
  8.     return 0; 

 

原文链接:http://www.cnblogs.com/hanxi/archive/2012/10/15/2725047.html

【编辑推荐】

 

 

 

责任编辑:彭凡 来源: 博客园
相关推荐

2010-10-14 14:33:15

MySQL多表联查

2010-07-13 10:47:18

Perl面向对象

2011-08-09 13:50:01

iPhone动画UIView

2010-07-14 16:28:58

配线架

2009-07-31 14:04:11

C#时间比较大小

2009-04-21 11:23:56

Oraclespool比较

2010-04-25 17:34:30

负载均衡实现

2013-06-27 09:26:50

Android界面刷新

2022-02-09 07:03:01

SpringNacos服务注册

2010-09-13 13:05:03

sql server分

2020-09-23 09:24:01

堆栈开发实现

2009-06-19 17:05:08

MVC框架Struts和Spri

2010-09-06 17:26:54

SQL函数

2011-06-23 09:07:16

2017-11-16 09:20:20

内存虚拟化技术

2021-12-08 10:47:35

RabbitMQ 实现延迟

2010-07-14 10:30:26

Perl多线程

2022-02-21 08:18:38

option编程模式

2009-10-20 13:59:59

网络综合布线系统

2009-09-25 14:04:09

Hibernate eHibernate h
点赞
收藏

51CTO技术栈公众号