《算法基础》——第2章 数值算法 2.1 随机化数据

第2章 数值算法

数值算法就是对数值进行计算,这些算法执行随机取数、分解质因数、寻找最大公约数和几何面积计算等任务。
所有的这些算法在不同情况下有各自的用途,但是它们也展示了有效的算法技巧,比如自适应算法、蒙特卡罗方法和用表格来存储中间结果。

2.1 随机化数据

随机化在许多应用中起到重要作用。这种算法让一个程序模仿随机过程,测试算法在随机输入时的表现并寻找复杂问题的解决方案。2.5节描述的蒙特卡罗积分,即采用随机选点的办法来估算复杂几何面积的大小。
在任何一种随机的算法中,第一步都是建立随机数表。
2.1.1 随机数生成
尽管许多程序员谈到“随机”数发生器,但只要是用计算机来生成数据的算法,就并非真正的随机。如果知道该算法的细节和其内部状态,就能正确地预见它生成的“随机”数。
为了获得真正的不可预见的随机状态,需要一个并非计算机程序的数据来源。例如,可以采用一台辐射探测仪,测量从辐射试样中散发出来的粒子来获取随机数值。因为没有人能够精确预见什么时候粒子将出现,这是真正的随机。
其他可能是真正随机数据来源的还有掷骰子、分析电磁波中的静电干扰和研究布朗运动。Random.org测量大气噪音来生成随机数。
不幸的是,由于这类真随机数发生器(True Random Number Generator,TRNG)相对复杂并且速度较慢,所以大多数应用场合采用一种更快的伪随机数发生器(Pseudo Random Number Generator,PRNG)来替代。对于多数应用场景,如果数据在某种程度上足够随机,程序就可以利用它们并取得好的结果。
1.生成随机数
线性同余发生器是一个简单并且常见的伪随机数生成的方式,这种发生器用以下关系生成数:

其中A、B和M是常数。
X0的值初始化这个发生器,这样不同的X0值就会产生不同的数组。用来初始化伪随机数发生器的值叫做种子,比如例子中的X0。
由于在一个数组中的所有数值都和M同余,在最多M个数后,发生器会产生一个它之前产生过的数,然后这个数组从这个点开始重复。
举一个十分简单的例子,假设A=7,B=5,M=11。如果以X0=0开始,之前的方程式会给出以下的数组:
X0=0
X1=(7×0+5) Mod 11=5
X2=(7×5+5) Mod 11=40 Mod 11=7
X3=(7×7+5) Mod 11=54 Mod 11=0
X4=(7×10+5) Mod 11=75 Mod 11=9
X5=(7×9+5) Mod 11=68 Mod 11=2
X6=(7×2+5) Mod 11=19 Mod 11=8
X7=(7×8+5) Mod 11=61 Mod 11=6
X8=(7×6+5) Mod 11=47 Mod 11=3
X9=(7×3+5) Mod 11=26 Mod 11=4
X10=(7×4+5) Mod 11=33 Mod 11=0
由于X10= X0= 0,这个数组开始重复。
数值0、5、7、10、9、2、8、6、3、4看起来十分随机,但是既然已经知道了这个程序用来产生这些数的方式,如果给出该程序所产生的一个确切的数,你就能够正确地推断出接下来的数是什么。
一些伪随机数发生器算法使用有着多个常数的多元线性同余发生器,然后从每一步生成的数值中选择一些来使这些数看起来更加随机,并且增加数组的重复周期。这能够使程序产生更多看起来随机的结果,但是那些方法仍然不是真正地随机。
注 大多数程序设计语言内置伪随机数发生方法,读者可以直接使用这些方法而不是编写自己的算法。这些方法普遍是相当快的,并且在发生重复前会产生一段非常长的数组,因此对大多数程序来说,可以直接使用它们而不是编写自己的算法。
伪随机数发生器的一个特点或者说优点是:可以使用一个特定的种子来重复生成相同的“随机”数组。这也许看起来是一个缺点,因为这意味着这些数将会更容易被预测,但是重复使用相同的数可以使一些程序调试起来更加简单。
重复使用相同的数组也可以使一些应用程序以非常紧凑的方式存储复杂的数据。例如,假设一个程序需要使对象在地图上进行一段漫长而复杂的伪随机运动。该程序会生成路线并且存储该路线的所有坐标,以便它能在以后重新绘制这条路线。另外一个选择是这个程序可以只存储一个种子值,然后每当它需要绘制路线时,可以用种子重新启动一个伪随机数发生器来产生相同的路线。

在图2-1中的随机树程序使用种子值来代表随机树。输入一个种子值,然后点击“Go”来生成一个随机树。即使两个种子有很小的差别,也会产生完全不同的结果。
随机树程序使用你输入的种子值来生成绘制参数,例如每一步产生的树枝数、子树枝相对其母树枝弯曲的角度,以及每一条树枝相对其母树枝缩短的程度。读者可以从本书网站上下载程序来查看详细信息。
如果输入了同一个种子数两次,则会产生两个相同的随机树。
基于密码安全性的伪随机数发生器
任何线性同余发生器都存在一个重复周期,因此它不能用于加密。
例如,使用伪随机数发生器,为消息中的每一个字母生成一个值,然后将这个值与字母相加,通过这种方式将一段消息加密。举个例子,字母A加3是D,因为D是字母表中A之后的第三个字母。如果累加到了Z,那么再加1的时候就回到A。这样,举例来说,Y加3是B。
只要数组中的数是随机的,这种技术就会运行得十分良好,但是一个线性同余发生器对于某个种子值只能产生有限的数。破解密码时需要做的就是通过尝试所有的可能种子值来解密。对于每一个可能的解密结果,系统可以通过检测字母频率来检验这个结果是否看起来是真正的文本。如果使用了错误的种子,每个字母的出现频率应当大致相同。如果猜到了正确的种子,一些字母(例如E和T),将会比其他字母(例如J和X)出现得多很多。如果字母是十分不平均地分配,也许就是猜到了正确的种子值。
这也许看起来工作量很大,但是在现代计算机上这不难实现。如果种子值是一个32位整数,只可能有大约 40亿个不同种子值。一台现代计算机可以在几秒钟、最多几分钟之内,检验每一个可能的种子。
基于密码安全性的伪随机数发生器(Cryptographically Secure Pseudorandom Number Generator,CSPRNG)使用更多的复杂算法来生成难以预测的数,并且生成长得多的数组而无需进入循环。它们通常使用更大值的种子。伪随机数发生器可能会使用32位长的种子,而基于密码安全性的伪随机数发生器可能会使用1000位长的种子来初始化算法。
基于密码安全性的伪随机数发生器是十分有趣并且“随机的”,但是它们也有一些缺点。其复杂性导致其运算比其他更简单的算法慢。它们也许不能手动初始化,因此可能不能简单地生成一个可重复的数组。如果要使用相同的数组一次以上,应当使用较简单的伪随机数发生器。所幸大部分算法不需要基于密码安全性的伪随机数发生器,这样可以仅用简单些的伪随机数发生器。

2.确保公平性
通常程序需要使用一个公平的伪随机数发生器。公平伪随机数发生器按相同概率产生可能输出结果。不均衡的伪随机数发生器是有偏斜的。例如,一个三分之二概率正面向上的硬币是有偏斜的。
许多程序设计语言有着在任何所需范围内产生随机数的方法。但是如果需要编写代码将伪随机数发生器产生的数值转换到一个特定范围内,要注意以均衡的方式来完成。
线性同余发生器产生一个范围大于等于0而小于M的数,M是生成器方程中的模。
Xn+1=(A×Xn+B) Mod M
通常一个程序需要一个范围不在0到M之间的随机数。以下方程式显然可以产生满足最大值和最小值范围的随机数,但并不恰当:
result=Min+number Mod (Max-Min+1)
例如,为了获得一个1到100之间的随机数,需要计算下列算式:
result=1+number Mod (100-1+1)
这种方法的问题在于,相对于其他方法它产生的结果可能更加相似。
为说明原因,请思考这样一个例子:M=3,Min=0,Max=1。如果发生器以合理的方式运行,它会以大致相同的概率产生数值0、1和2。如果将这三个值代入之前的方程式,你会得到如表2-1所示的结果。

在表2-1中,结果0出现的次数是结果1的两倍,因此最终结果存在偏斜。
在一个真正的伪随机数发生器中,模M可能非常大,这时这个问题变得微小,但仍然存在。
较好的方法是将伪随机数发生器生成的值转换到0和1之间,然后再与所需要范围相乘,如下列方程式所示:
result=min+(number÷M)×(Max-Min)
另一种将一个伪随机数从一个范围转换到另一范围的方法是简单地忽略掉所需范围之外的所有结果。在前例中,可以使用有限的伪随机数发生器来生成一个0和2之间的值。如果你得到2,它超过了所需范围,可以忽视掉2而留下其他数。
举一个稍微现实些的例子,假设将一块饼干给四位朋友中的一位,并且有一个六面骰子。这种情况下可以重复地抛骰子,直到得到一个1和4之间的值。
3.从偏斜的来源获得公平性
即使一个伪随机数发生器是偏斜的,也可能存在一个生成公平随机数的方法。例如,假设你认为一枚硬币是偏斜的,但不知道正面向上或背面向上的概率,不过预想到可能性一定不是0.5。在这种情况下,以下算法产生一个公平的硬币抛掷结果:

来看看为何这么做,假设偏斜的硬币正面向上的概率是P,那么背面向上的概率就是1-P。从而第一次正面向上、第二次背面向上的几率就是P×(1-P)。同理,第一次背面向上、第二次正面向上的几率就是(1-P)×P。这两个几率相同,因此这个算法输出正面向上或背面向上的几率是相同的,也就是说结果是公平的。
如果偏斜的硬币两次正面向上或者两次背面向上,需要重复这个算法。如果很不走运,或者硬币是十分偏斜的,也许需要重复这个算法很多次才能得到一个公平的结果。例如,如果P=0.9,该硬币两次正面向上的可能性是81%,而两次背面向上的可能性是1%。
注 使用偏斜的硬币来生成公平硬币抛掷在实际程序中未必有效。但是它是概率的一个不错的应用,并且可能成为一个面试中的有趣问题,因此它值得理解。
可以使用相似的技术来扩展伪随机数发生器。例如,假设想将一块饼干给五位朋友中的一位,并且获得随机数的唯一途径是一枚公平硬币。在这种情况下,可以抛掷这枚硬币三次然后将结果看做一个二进制数,正面向上代表1,背面向上代表0。举个例子:抛掷结果为正面、背面、正面代表二进制数101,也就是十进制中的5。如果获得一个所需范围之外的结果(在本例中抛掷结果为正面、正面、正面代表二进制数111或十进制数8,比朋友的数多),则放弃这个结果并且重新开始。
总之,程序设计语言附带的伪随机数发生器工具可以适用于大部分的程序。如果需要更好的随机化,应当使用基于密码安全性的伪随机数发生器。使用公平硬币来获取一个1到100之间的随机数或者使用偏斜的信息来源去生成公平数,这在作为面试问题或一些不可思议的情况下是有用的。
2.1.2 随机化数组
生成随机化数组中的项是程序中一个十分常见的任务。例如,假设一个日程安排程序需要分配员工轮班工作。如果程序按照他们出现在数据库或其他固定顺序中的字母顺序分配员工工作,总是被分配在后半夜工作的员工会不满意。
一些算法也可以利用随机化来防止最坏情况的发生。例如,标准的快速排序算法通常运行良好,但如果该算法需要进行排序的值最初已经被排序,这时算法会运行得较差。一种避免这种情况发生的方法是在排序前随机化这些值。
下面的算法展示了随机化数组的一种方式:

该算法访问这个数组上的每个位置一次,因此它有一个对于大多数应用程序来说足够快的运行时间O(N)。
请注意重复这个算法并没有使该数组“更加随机化”。当洗一摞扑克牌时,开始时相邻的两张扑克牌有保持相邻的趋势(即使可能相距稍远些),因此需要洗很多次来得到一个相当随机的结果。该算法可以在一个过程中彻底随机化数组,所以重复运行它就是浪费时间了。
十分随机的数组
算法的另一个重要考虑因素是它是否产生一个公平的排列。换句话说,一个排列在任何给定位置结束的几率对于排列在所有位置来说相同吗?例如,如果在第一个位置开始的一个排列有半数几率在第一个位置结束,那么这种情况就是不公平的。
在介绍中说过本书不包含复杂的数学证明。所以可以跳过以下的讨论并且认为随机算法是公平的。但是如果懂得一些概率,就会发现下面的讨论是有几分趣味的。
对于一个数组中的确定项,考虑它被放置在位置k的可能性。为了被放置在位置k,它必须不能被放置在位置1、2、3、…、k-1上,然后才能被放置在位置k上。
定义P-i为某项没有被放置在位置i的几率,前提是该项之前没有被放置在位置1、2、…、i-1。同理定义Pk为某项被放置在位置k的几率,前提是该项之前没有被放置在

位置1、2、…、k-1。然后该项被放置在k上的所有几率是P-1×P-2×P-3×…× P-(k-1)×Pk.
P1是1/N,所以P-1是1-P1=1-1/N=(N-1)/N。
在第一项确定后,第N-1项可以被放置在位置2上,所以P-2是1-P2=1-1/(N-1)=(N-2)/(N-1)。
概括地说,Pi=1/(N-(i-1)),P-i=1-Pi=1-1/(N-(i-1))=(N-(i-1)-1)/(N-(i-1))=(N-i)/(N-i+1)。
将所有几率乘在一起,P-1×P-2×P-3×…×P-(k-1)×Pk,得到如下方程式:

观察以上方程式,会发现可以约分每一项的分子和其后一项的分母。完成所有约分后,方程式被简化为1/N。
这意味着无论k为何值,某项被放置在位置k的几率都是1/N,因此这个排列是公平的。

一个和随机化数组生成十分相似的任务是从无重复数组中选择某一确定数量的随机项。
例如,假设某作家正在举行一场赠送五本著作的抽签活动(我有时这样做),有100位参与者。一种找出这五位幸运儿的方法是将这100个名字编成一个数组,随机化该数组,然后向这个随机名单中的前五位赠书。每个确定姓名出现在五个获奖位置的几率是相同的,因此这次抽签是公平的。
2.1.3 生成不均匀分布
一些程序需要生成不均匀分布的伪随机数。这些程序通常模仿其他形式的随机数生成过程。例如,一个程序也许会通过模仿两个六面骰子的滚动来生成一个2和12之间的数。
这种情况下不可以简单地取2和12之间的一个伪随机数,因为滚动两个骰子所得到各个数的几率是不同的。
解决方法是实际模拟一个骰子的滚动,生成两个1和6之间的数,再将它们相加。

时间: 2024-08-30 14:16:45

《算法基础》——第2章 数值算法 2.1 随机化数据的相关文章

《算法技术手册》一第3章 算法基础

第3章 算法基础 虽说开发软件是为了解决问题,但程序员们往往太执着于是否能够解决问题本身,而不去确认这一问题是否已有解决之法.即便程序员们知道前人已在类似情况下解决了问题,但"已有的解决之法"最终是否适用于特写的问题仍是一个未知数.更重要的是,要找到完全不需要修改或者只需要稍作修改便能解决手头问题的代码并不容易.不同的人对待算法的态度各有千秋.很多人就只是简单地在一本书中或者网站上找个算法,复制代码,运行一次,然后可能还会测试一次,如果结果正确,就开始做下一个任务.但是,在我们看来,这

《算法基础》——第1章 算法基础知识 1.1 方法

第1章 算法基础知识 在开始算法学习之前,你需要一点背景知识.简单地说,首先需要知道的是算法是完成某些事情的方法.它定义了用某个方法执行一个任务的步骤.这个定义看起来足够简单,但是没有人为了做一件十分简单的工作而写算法.没有人写指令来获取数组中的第四个元素.这只是假设这是一个数组定义的一部分,并且你知道怎么去做(如果你知道如何在这个问题中使用编程语言).一般来说,人们只是为了完成复杂的任务而写算法.算法解释了如何找到一个复杂代数问题的答案,如何在一个包含数千条街道的网络中找到最短路径,抑或是如何

《算法基础:打开算法之门》一第3章 排序算法和查找算法

第3章 Algorithms Unlocked 排序算法和查找算法 在第2章中,我们看到了在数组上进行线性查找的三个算法.我们能做得更好吗?答案是:看情况.如果不清楚数组中的元素是否有序,我们是不可能做得更好的.在最坏情况下,我们必须查找数组的所有n个元素,因为如果在前n-1个元素中不能找到要找的值,那么要查找的元素可能在第n个位置上.因此,当我们不清楚数组中的元素是否有序时,我们不可能实现比Θ(n)更好的最坏情况运行时间. 然而,假定数组是以非递减顺序排序的,那么根据"非递减"的含义

《算法基础》——导读

**前言**算法是使高效的程序成为可能的方法.它们解释了如何排列记录.搜索项.计算数值(比如质因子分解).查找一个街道网络中的最短路径.确定可能通过通信网络的最大流.算法好坏的差别可能意味着是在一秒.一个小时内解决问题,还是永远也不能解决问题.学习算法使你能建立有用的方法工具来解决具体的问题.它能帮助你理解在不同的情况下,哪个算法是最有效的,所以对于一个特定的问题,你就能选择最适合的算法.对某些数据而言性能优异的算法可能对其他的数据而言表现糟糕.所以知道如何选择一个最适合当前情况的算法是很重要的

《趣题学算法》—第0章0.7节算法的程序实现

0.7 算法的程序实现 有了解决问题的正确算法,就可以利用一种计算机程序设计语言将算法实现为可在计算机上运行的程序.本书选用业界使用最广泛.最成熟的C++语言来实现解决每一个问题的算法.C++语言是面向对象的程序设计语言,它为程序员提供了一个庞大的标准库.有趣的是,C++脱胎于C语言.所以,读者若具有C语言某种程度的训练,对于理解本书提供的C++代码一定是大有裨益的.闲话少说,让我们先来一睹C++语言程序的"芳容"吧. 解决问题0-1"计算逆序数"的C++程序如下.

《算法基础》——1.6 总结

1.6 总结 为了最有效地使用一个算法,不仅需要理解算法是如何工作的,也需要理解它的性能特点.本章解释了大O符号,你可以使用它研究算法的性能.如果你知道一个算法的时间复杂度,就能估计如果改变问题的大小,运行时间如何改变.这一章还描述了一些具有常见时间复杂度的算法情况.图1-2展示了这些方程的图像,从中能感觉到随着问题规模的增加,它们的增长有多快.作为一个经验法则,时间复杂度是多项式级的算法通常足够快,所以你能用它们解决中等规模的问题.然而,时间复杂度是指数或者阶乘的算法,随着问题规模的增加,运行

一道算法基础题 uva1586

问题描述 一道算法基础题 uva1586 题目链接在这儿 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=830&page=show_problem&problem=4461 我自己做的代码如下 但是通不过 测了好多数据都没问题 #include<cstdio> #include<cstring> using namespace std; in

《Adobe After Effects CS6完全剖析》——第一部分 工作基础 第1章 After Effects 中的合成 合成基础

第一部分 工作基础 第1章 After Effects 中的合成 本书是介绍有关使用Adobe After Effects创建视觉特效的内容的,Adobe After Effects是世界上应用最广泛的合成应用程序.它可以帮助你使用来自异种源的元素创建令人信服的.梦幻般的运动影像,并且可以让你尽可能地省事.本书的第一部分提供了一种快速学习的方法(针对初学者),或者让你复习一下After Effects工作流程方面的知识(针对已经是After Effects艺术家的人). 有效的视觉特效合成将使用

《趣题学算法》—第1章1.1节累积计数法

第1章 计数问题 趣题学算法 1.1 累积计数法 1.2 简单的数学计算 1.3 加法原理和乘法原理 1.4 图的性质 1.5 置换与轮换 人类的智力启蒙发端于计数.原始人在狩猎过程中为计数猎获物,手指.结绳等都是曾经使用过的计数工具.今天,我们所面对.思考的问题更加复杂.庞大,计数的任务需要强大的计算机来帮助我们完成.事实上,很多计算问题本身就是计数问题. 1.1 累积计数法 这样的问题在实际中往往要通过几个步骤来解决,每个步骤都会产生部分数据,问题的目标是计算出所有步骤产生数据的总和.对这样