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

  原题是: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. Android开发之用双缓冲技术绘图

    双缓冲技术主要用在画图,动画效果上,其原理就是:将资源先载入到缓冲区,然后再将缓冲区整个载入到View上面去. 双缓冲技术可以有效防止闪烁,提高显示质量. DrawView.java: package ...

  2. PHP--关于模板的原理和解析(php模板原理)

    此内容用作笔记,以备日后查看,此内容为学习李炎恢课程而来,并非自己所创,如有问题请私信~ 将PHP代码和静态HTML代码进行分离,使代码的可读性和维护性得到显著提高. 使用模板引擎: 我们所说的模板是 ...

  3. Atitit.编程语言新特性 通过类库框架模式增强 提升草案 v3 q27

    Atitit.编程语言新特性 通过类库框架模式增强 提升草案 v3 q27 1. 修改历史2 2. 适用语言::几乎所有编程语言.语言提升的三个渠道::语法,类库,框架,ide2 2.1. 单根继承  ...

  4. PLSQL Developer 9注册码

    Product Code:46jw8l8ymfmp2twwbuur8j9gv978m2q2duserial Number:307254password:xs374ca

  5. valgrind的编译和使用

    ubuntu 平台: valgrind 3.8.1 一. 编译 ./configure --prefix=/home/frank/test/valgrind/PC/local 报错:checking ...

  6. iOS swift 代理协议

    swift中的代理实现和oc中是有区别的 protocol HXQLimitedTextFieldDelegate{ func test() } 代理中默认所有方法都是required,如果需要某个代 ...

  7. JPA动态查询封装

    一.定义一个查询条件容器 /** * 定义一个查询条件容器 * * @param <T> */ public class Criteria<T> implements Spec ...

  8. Android中makfile的随记

    什么是makefile?也许非常多Winodws的程序员都不知道这个东西,由于那些Windows的IDE都为你做了这个工作,但我认为要作一个好的Android底层程序员. makefile还是要懂. ...

  9. nefu 118 n!后面有多少个0 算数基本定理,素数分解

    n!后面有多少个0 Time Limit 1000ms Memory Limit 65536K description 从输入中读取一个数n,求出n! 中末尾0的个数. input 输入有若干行.第一 ...

  10. Struts1标签

    Struts1 标签库  说明 Struts提供了五个标签库,即:HTML.Bean.Logic.Template和Nested. HTML 标签 : 用来创建能够和Struts 框架和其他相应的HT ...