tkinter库的那篇博客(python笔记:可视化界面写作尝试)真的是写的我心力憔悴啊,其实东西并不难,就是多,然后一开始又没有找到比较靠谱的官方文档,搞得我没写一个组件的应用就得去看源码,然后自己写代码尝试,搞得累的半死。
唉,所以这里就休息一下,再写上一篇小文章休息一下好了,也算是劳逸结合了。
所以,这里,就让我们来看一下另外一道经典的算法题:随机数生成问题好了。
随机数生成这个经典算法题我相信大部分人都知道,尤其刷过leetcode或者有过面试经历的,无非就是给定一个随机数生成器,然后取生成另一个范围内的随机数。
一个典型的例子就是使用rand7
生成rand10
。
因此,这里,我们就以rand7
生成rand10
为例进行讨论,考察一下有哪些实现思路,并对其进行一定的拓展延伸。
显然的,如果用一个范围更大的随机数生成器去生成一个更小范围的随机数生成器是非常简单的一件事,比如使用rand7()
来生成rand5()
,就可以使用下述方法:
def rand5():
while True:
seed = rand7()
if seed <= 5:
return seed
显然,如此一来一个1到5的随机数生成器就完成了,当然,效率上会略有损失,每一个随机数的生成所需要的rand7()
的期望运行次数为1.4次,当时整体而言,这个值都不会高于2,因此,事实上大生成小的问题总是简单的。
那么,针对小生成大的问题,事实上也同样可以尝试将其拆解为大生成小的问题进行解决。
一种比较简单的思路就是,由于 10 = 2 × 5 10 = 2 \times 5 10=2×5,因此,我们可以使用rand7()
构造两个rand5()
生成器,然后合并成一个rand10()
生成器。
给出python代码实现如下:
def rand2():
while True:
seed = rand7()
if seed != 7:
return seed % 2 + 1
def rand10():
return 5 * (rand2()-1) + rand5()
其中,rand5()
我们已经在上述内容中进行了介绍,这里我们就不再多做说明了。
可以看到,整体而言,每一次随机数生成所需要调用的rand7()
的期望次数为 7 / 6 + 7 / 5 ≃ 2.57 7/6+7/5\simeq 2.57 7/6+7/5≃2.57。
但是上述算法的限制也十分的明显,需要目标范围可以进行因式分解为两个小数的乘积,否则就无法原模原样地照抄上述的算法,比如rand11()
,就无法采用分解的方式进行求解。
但是,这个问题也不是无解,上述相同的思路只要稍作调整,我们还是可以进行求解的。
可以看到,在上述算法中,最为核心的地方在于将问题从一个小生成大的问题转换为一个大生成小的问题。
而具体的实现方式上,上述思路采用的是大拆小的模型,将目标范围通过因式分解的方式拆分为若干个概率相同且可以被当前随机数生成覆盖的子范围,从而进行求解。
但是上述方法受限于拆分过程必须是拆分为等概率的几个子范围,即是说必须是因式分解可分的,但是如果目标范围是一个质数或者因子中存在一个数大于当前的随机数生成器,上述思路就会失效。
不过,我们可以将上述拆分的思路反着来,不是缩减目标范围,而是将当前随机数生成器进行等比例放大,使之可以覆盖住目标范围。
而放大的方式就是就是通过k进制的方式,不断地将其扩大k倍,那样的话就可以等概率地覆盖新的范围内的所有值。
给出python代码实现如下:
def rand10():
while True:
seed = (rand7()-1) * 7 + rand7()
if seed <= 40:
return seed % 10 + 1
同样的,我们同样分析可得,上述算法的期望值 2 × ( 49 / 40 ) = 2.45 2\times(49/40)=2.45 2×(49/40)=2.45。
可以看到,通过这种方式,我们就可以将问题不受限制的扩展到任意情况当中。
综上,我们给出了一道经典算法题——随机数生成问题的解答,并对其进行了一定的拓展,将其拓展到了任意两个随机数相互转换的问题,具体而言,可以拆解为大生成小以及小生成大的问题。
其中,针对大生成小的我们没有详细的讨论,因为事实上这还是比较明显的。
而对小生成大的问题,其核心的处理思想事实上也都是将其转换为大生成小的问题,我们具体给出了两种常见的实现方法,分别是分解目标范围以及扩展已有生成范围的方式。