今天想写一个where_to_eat_lunch的小程序,帮助选择中午吃饭的地方,其实是练习随机选择的问题。(让我想到了Knuth大神的n个中均匀抽样m个数并且有序的方法,这个还是自行再去看看编程珠玑的抽样问题吧)
本来以为是一个很容易搞定的问题,结果遇到了各种坑。
坑一:
time() 函数的问题。 开始的时候没有#include <time.h> 竟然编译通过了,但是这个函数有问题,至今没有搞懂奇怪的行为。描述一下。
1 | [root@localhost ~]# cat 1.c |
看似就是普通glibc里的函数。但是竟然不要参数!!! 在<time.h>里的time是需要参数的!而且这个a.out运行会coredump
1 | [root@localhost ~]# ./a.out |
能不能不coredump呢,能!改一下code, 非常神奇。
1 | [root@localhost ~]# cat 1.c |
好吧,这里暂且不管这个神奇的time()。这里主要记住的就是库函数time()是要有参数的!要include<time.h>. 参数可以是NULL但是一定要有。
坑二:
也不算是一个坑。突然想起来看过一篇文章说,谈到人的认知错觉,当人在听歌的时候选择在列表里随机播放,当听到一首歌,下一次再听到,就总觉得听到这一首歌的概率很大,感觉概率是不均匀的。苹果的播放器开始就是随机的播放,但是由于人的这个错觉,乔布斯只好改进了一下,将刚听过的歌曲再次出现的概率降低。也就是降低再次出现的权重。
这里我也想引入权重,每次被选择后权重降低。听起来不错。实现起来发现并没有太好的时间空间复杂度的算法(也许有好的算法,但没想出来~~!)。
先写一个放在这里吧。思路是:
1 每个饭馆有一个对应的rate
2 将rate值累加得到另外一个数组cumulative,像这样
0号饭馆 | 1号饭馆 | 2号饭馆 | 3号饭馆 | |
---|---|---|---|---|
rate | 1 | 2.2 | 3.0 | 4.5 |
cumulative | 1 | 3.2 | 6.2 | 10.7 |
总的rate是1 + 2.2 + 3.0 + 4.5 = 10.7
3 每次随机选择一个100以内的数,算一个百分比,比如随机选择了32,那么百分比就是0.32
4 总的rate 乘以这个百分比,这个例子中就是10.7 * 0.32(我说不好这叫什么化,反正就是把100的尺度按比例尺拉成了总的rate这个尺度)
5 那么第4步的这个乘积落在哪个rate的区间上,就取那个rate的index位置,也就是对应的那个饭馆的index位置。
6 用float不好理解,用整型就好理解了
0号饭馆 | 1号饭馆 | 2号饭馆 | 3号饭馆 | |
---|---|---|---|---|
rate | 1 | 3 | 3 | 3 |
cumulative | 1 | 4 | 7 | 10 |
也就是说,0号饭馆的权重是1, 1号饭馆的权重是3,2号饭馆的权重是3,3号饭馆的权重是3。一共是10份,0号占1份,1,2和3号各占3份。随机的时候,100个数随机到了10以内,就是0号,随机到了10-40,就是1号。随机到了40-70就是2号,贼好理解。
1 | [root@localhost ~]# cat where_to_eat.c |
这里是有坑的。
1 time(NULL)这玩意返回的和Epoch(1970.1.1)之间的秒数,同一秒内得到的结果一样,也就是seed一样
2 因为机器是伪随机的,每次调用get_random_via_weight的时候,重新开始随机,seed如果一样随机的结果是一样一样的。如果在同一秒内,rand()的返回值是一样的。
所以调整一下,全局的事情全局做,my_seed 弄成了全局的,只srand一次。
1 | [root@localhost ~]# cat where_to_eat.c |
这个算法在饭馆数组特别大的时候,就不算太好了,累加那么多次。有改进的方案是数组分块,每一块大小可以是sqrt(total_array_size)。 然后每次更新权重只是影响一块,块间的cumulative要重新计算。然后随机落到哪个块里了,在那个块里再细细掰扯。