前些天在蘑菇街的面试中碰到一道洗牌的算法题,拿出来和大家分享一下!

  原题是:54张有序的牌,如何无序的发给3个人?

  这个题是运用经典的洗牌算法完成。首先介绍一种经典的洗牌算法--Fisher-Yates.现在大家在网上看到,大多是Fisher-Yates算法的变形。将本来O(n2),简化到了O(n).代码如下:

#include<stdio.h>
#include <stdlib.h> void func(char *, int); void main()
{
char a[7] = {'a','b','c','d','e','f'};
func(a,6);
//a[7] = '\0';
puts(a);
} void
func(char *date, int length){
char t; //t为交换字符空间
int i, j;
while(--length){
srand(time(0));
i = rand()%(length+1);
t = date[i];
date[i] = date[length];
date[length] = t;
}
}

  算法的思路就是:

      a,选中未操作的最后1个位置,在1至该位置之间产生一个随机数(包含这两个值),

      b,然后交换这两个值,造成的结果就是在未操作的所有牌中随机的取一张放到未操作的牌底(数组尾),然后length-1使未操作的范围减一,

      c,重复上面步骤,直到未操作的值为1停止

  代码写的很清晰,也很简单。网上还有很多种方法。但通过大量的测试,可以给出肯定,改进后的Fisher-Yates算法,即上述代码,已经是洗牌算法中很好的一个了。即简单,随机性又好。所以如果在面试中给出上述答案,就基本可以进行下一个问题了。如果面试官还觉得不够。可以从代码的完整性和鲁棒性(剑指offer中有提到具体做法),如参数正确性,遇到无效输入等方面改进。或者优化随机值。

  关于优化随机值,这里我们还可以再提一点。

  首先必须肯定的一点是:计算机中的随机数并不是真正的随机数,它是一种伪随机数,是通过某些公式计算出来的随机数,通过改变式子中参数的值从而达到随机的效果。虽然是伪随机数,但其实已经可以达到我们编程中的要求了。

  C语言中生成随机数的函数有4个,两两搭配使用:

     int rand(void);                  返回 0 ------- RAND_MAX 之间的一个 int 类型整数,该函数为非线程安全函数。并且生成随机数的性能不是很好,已经不推荐使用
          void srand(unsigned int seed);          设置种子值,一般与“当前时间 + 进程ID”作为种子,如果没用调用该函数,则通过rand返回的默认种子值为1。    

    long int random(void);      返回 0 ------- RAND_MAX 之间的一个 long 类型整数,该函数会产生一个非常大的随机值,最大为 16*((2**31)-1)。

                 random 函数使用非线性反馈随机数发生器生成默认大小为31个长整数表所返回的连续伪随机数。    

       void srandom(unsigned int seed);      设置种子值,一般与“当前时间 + 进程ID”作为种子,如果没用调用该函数,则通过random返回的默认种子值为1。

  上面的这四个函数都是C语言产生随机数时用到的函数,

    如果你使用 srandom 种植种子, 则你应该使用 random 返回随机数, 如果你使用 srand 种植种子, 则你应该使用rand返回随机数。

    不过srand和rand官方已经不推荐使用。原因是产生随机数的性能不是很好, 另外是随机数的随机性没有random好, 再者就是不是线程安全。

    至于rand的线程不安全,如果你在linux下看过它的源代码的话,这句话就很容易理解了。

/*

   这两个函数是C库中产生随机数的程序。你需要先

   使用srand()函数赋随机数种子值。然后再使用

   rand()函数来产生随机数。但是产生随机数的算法

   较简单,srandom()和random()函数是对这两个函数

   的改良,用法也很类似。

*/

#define RANDOM_MAX 0x7FFFFFFF

static long my_do_rand(unsigned long *value)

{

   /*

      这个算法保证所产生的值不会超过(2^31 - 1)

这里(2^31 - 1)就是 0x7FFFFFFF。而 0x7FFFFFFF

      等于127773 * (7^5) + 2836,7^5 = 16807。

      整个算法是通过:t = (7^5 * t) mod (2^31 - 1)

      这个公式来计算随机值,并且把这次得到的值,作为

下次计算的随机种子值。

   */

   long quotient, remainder, t;

   quotient = *value / 127773L;

   remainder = *value % 127773L;

   t = 16807L * remainder - 2836L * quotient;

   if (t <= )

      t += 0x7FFFFFFFL;

   return ((*value = t) % ((unsigned long)RANDOM_MAX + ));

}

static unsigned long next = ;

int my_rand(void)

{

   return my_do_rand(&next);

}

void my_srand(unsigned int seed)

{

   next = seed;

}

  从代码中可以清晰的看到,srand()函数是将参数赋值给了一个全局变量,这也是为什么srand()使用int型参数且没有返回值却可以影响rand()的值。srand()修改了next的值后,rand()函数可能隐式的调用了该参数(间接调用)。看到这里,你就能明白为什么它是线程不安全函数,如果再多线程操作中,一个线程线调用srand,再未完成时另一个线程修改了next则该次操作无意义,这就是基本的多线程编程中的进程的同步与互斥。random()的改进就是基于这一点进行改进。除此之外,我们还可以看到rand()的返回值是基于一个类似于y=ax+b的函数计算出来的。而该表达式中a,b都是常量值,而这里的x就是随机种子,即通过srand()修改种子。

    最后要提的一点是:

     假如你想产生 1 ------10 之间的一个随机数, 建议您像下面这样编码

        j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

      而不是下面这样的代码
       j = 1 + (rand() % 10);

    第一行代码的优势在于使用实型代替整形,数据是连续的,从而随机的更均匀,从而提高随机性。

  如果你想要产生1-8的随机数,j = 1 + (int) (8.0 * (rand() / (RAND_MAX + 1.0)));  //即可

  1-x:    j = 1 + (int) (x.0 * (rand() / (RAND_MAX + 1.0)));      //即可

洗牌算法Fisher-Yates以及C语言随机数的产生的更多相关文章

  1. Fisher–Yates shuffle 洗牌算法(zz)

    1,缘起 最近工作上遇到一个问题,即将一组数据,比如[A,B,C,D,E]其中的两个B,E按随机排列,其他的仍在原来的位置: 原始数组:[A,B,C,D,E] 随机字母:[B,D] 可能结果:[A,B ...

  2. 由乱序播放说开了去-数组的打乱算法Fisher–Yates Shuffle

    之前用HTML5的Audio API写了个音乐频谱效果,再之后又加了个播放列表就成了个简单的播放器,其中弄了个功能是'Shuffle'也就是一般播放器都有的列表打乱功能,或者理解为随机播放. 但我觉得 ...

  3. 洗牌算法shuffle

    对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...

  4. random array & shuffle 洗牌算法 / 随机算法

    random array & shuffle shuffle 洗牌算法 / 随机算法 https://en.wikipedia.org/wiki/Fisher–Yates_shuffle ES ...

  5. 洗牌算法Fisher_Yates原理

    1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...

  6. js 随机数 洗牌算法

    function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...

  7. knuth洗牌算法

    首先来思考一个问题: 设计一个公平的洗牌算法 1. 看问题,洗牌,显然是一个随机算法了.随机算法还不简单?随机呗.把所有牌放到一个数组中,每次取两张牌交换位置,随机 k 次即可. 如果你的答案是这样, ...

  8. C# 洗牌算法

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精   C#洗牌算法如下: class Program { ...

  9. 519. Random Flip Matrix(Fisher-Yates洗牌算法)

    1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...

随机推荐

  1. csv导出文件中有html

    最近遇到再导出csv文件时,csv文件中包含html代码 一开始以为导出的数据量太大,减少数据后仍然出现html代码,此时想到应该与数据有关,仔细观察csv中的数据,有的单元里面是空值, 对比原始数据 ...

  2. MQTT压力测试之Tsung的使用

    简介 Tsung 是一个压力测试工具,可以测试包括HTTP, WebDAV, PostgreSQL, MySQL, LDAP, and XMPP/Jabber等服务器.针对 HTTP 测试,Tsung ...

  3. 数据库性能优化之SQL语句优化1

    一.问题的提出 在 应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实 际应用后,随着数据库中数据的增加, ...

  4. Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL

    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL 1.1. $ 美元字符, php 黑头1 1.2. 默认变量的范围和声明:1 1.3 ...

  5. string::find_last_of

    今天在代码中用到string的这个方法,一不小心就用错了. 这是http://www.cplusplus.com/关于这个方法的解释. Find character in string from th ...

  6. Say goodbye to 重复代码---Eclipse代码模板的使用

    我们在开发过程中,有些代码是经常重复编写的,而且是必要的,如单例模式,观察者模式. 每次都是重复重复再重复. 那么如何提高我们的效率呢? 要记住,我们使用的是IDE,不是文本编辑器.善用工具,事半功倍 ...

  7. 程序的记事本--log4net

    你是否在遇到程序执行问题时常常百度?你是否在遇到执行错误时常常去询问别人?假设有那么是时候改变啦,对于一个Developer来说那是不专业的表现,专业的Developer都会首先查看程序的执行日志.先 ...

  8. vmware workstation 9.0.2安装教程

    现在为您分享最新的VMware Workstation 9.0正式版(VMware Workstation 9.0.0 Build 812388)的下载,同时附上注册机或者注册激活码. 下载地址:   ...

  9. Oracle----oracle小知识总结

    1,表列的五种约束 not null, unique,primary key, foreign key, check 2,权限分配 grant 权限 on 表 to 用户 3,表和视图的区别 视图是一 ...

  10. 第一百八十一节,jQuery-UI,知问前端--自动补全 UI--邮箱自动补全

    jQuery-UI,知问前端--自动补全 UI--邮箱自动补全 学习要点: 1.调用 autocomplete()方法 2.修改 autocomplete()样式 3.autocomplete()方法 ...