顺序统计量
前言
本文内容主要是 寻找最大最小值,寻找第i小值,以及随机函数的改写。在一个大小为n的集合里面,第i个顺序统计量是指该集合中第i小的元素。
寻找最大最小值
普遍算法是遍历数组,设置max和min存储最大最小值。每个元素都与max和min比较,然后分别改变max和min的值(如果需要改变的话)。如此算来该算法需要进行比较的次数为2n次。那么如果想缩短比较的次数该肿么办呢?
假设数组A的长度为奇数,max = min = A[0],然后A[1]…..A[n-1]一共n-1个(偶数个)元素。每次取两个元素比较,将比较后的较大值与max比较,将较小值与min比较,然后相应的修改max和min值,一共比较三次。按照上面的描述,比较的总次数应该为 3*(n-1)/2。
假设数组A的长度为偶数,比较A[0]和A[1],然后将较大值赋值给Max,较小值赋值给min。然后对A[2]……A[n-1]一共n-2个(偶数个)元素进行间隔为2的遍历,每次与上面类似,每次取两值,所以比较的总次数应该为3*(n-2)/2 + 1。
综合所述,长度为n的数组按照上述做法比较的总次数可以从2n缩减到3n/2左右。
具体实现略。
寻找第i小值
寻找最大值和最小值可以遍历寻找,但是寻找第i小值肿么办?第一种办法是先排序,然后直接索引。但是基于比较的排序的执行时间底线是nlogn。而非基于比较的排序又需要各种条件。有木有寻找一种查找效率为O(n)的算法呢,既然是O(n),那么肯定不能排序了,那不排序又怎么找呢?大家可以回想一下快速排序的分区算法,没执行一次能够找出基准值的最终位置,即左边的值都不大于基准值,右边的值都不小于基准值,这从而让人想起了折半查找,分治思想。
1: //在区间[low, high)搜索第k小的元素
2: int RandomizedSearchK(int A[], int low, int high, int k)
3: {
4: if(low == high-1)
5: return A[low];
6:
7: int r = Partition(A, low, high); //返回基准值索引
8: int n = r - low + 1; // A[low.....r]的元素个数
9: if (n == k) //基准值正好是第k小
10: return A[r];
11: else if (n < k)
12: RandomizedSearchK(A, r+1, high, k - n); //查找基准值右边第k-n小
13: else
14: RandomizedSearchK(A, low, r, k); // 查找基准值左边第k小
15: }
不了解RandomizedPartition函数的可以去查看我的另一篇博文。
随机函数
我们常用的伪随机函数rand(),它是返回一个[0, RAND_MAX]的随机数,其中RAND_MAX号称是大整数,在我的系统中是32767。如果要取得[0, n)的随机值,只需rand()%n即可。但是问题来了,比如当n = 20000的时候,rand() = 10000 或者 30000 最后rand()%n的结果都是10000,但是rand() = 15000的时候 rand()%n的结果为15000,也就是说随机到的10000的次数是15000的次数两倍,对每个数的概率不一样,也就不是随机了。所以要更随机一点,可以改写随机算法。
因为 mod 2 == 0或者1的值的个数不好确定,如上例mod 10000最后概率都不相同。所以不能采取mod策略。改写的随机算法是利用 RAND_MAX >= n * bucketsize。其中n表示随机范围,也可以抽象成n个桶,然后bucketsize是每个桶的大小,而且可知每个桶的大小都是相等的(里面的数据一样多),因此随机到的桶的编号的概率是相等的。
1: // 随机范围[0, n)
2: int myrand(int n)
3: {
4: int bucketsize = RAND_MAX / n;
5: int r;
6: do
7: {
8: r = rand() / bucketsize;
9: }while (r >= n); //即如果 出现的rand()值大于等于 n * bucketsize的值就重新随机
10: return r; // 返回桶子的编号
11: }