《算法导论》第九章----中位数和顺序统计学
本章主要讲了如何在一个集合里找出第i个顺序统计量(即第i个小的元素),可以定义选择问题(n个元素中的第i个小的元素,即在n个元素里找出一个元素,这个元素大于其他i-1个元素)。
如果我们用堆排序或合并排序(Ο(nlgn))对该集合进行排序,然后直接找出第i个元素即可。这样一来,选择问题运行时间为Ο(nlgn)。但是本章讲其他两种方法可以使的选择问题的运行时间为O(n),分别为以期望线性时间做选择和最坏情况线性时间的选择。(PS:本人能力不足,对于最坏情况线性时间的选择的算法还在看,不能将其实现。。。。所以下文不会出现。。。)
最小值最大值
最小值和最大值都可以通过n-1次比较找出,先假设最小值(最大值)为第一个元素,再与剩下的元素比较,在比较过程中找出最小值(最大值)。
1 int minimum(int A[], int length){ 2 int min = A[0]; 3 int i; 4 for(i = 1; i < length; i++){ 5 if(min > A[i]) 6 min = A[i]; 7 } 8 9 return min; 10 }
同时找出最小值最大值
如果独立找出最小值和最大值总共用了2n-2次比较。但是可以用3*floor(n / 2)次比较就可以同时找出最小值和最大值。方法为成对进行比较,先一对元素互相比较,然后较小的元素与最小值比较,较大的元素与最大值比较,这样每两个元素只需要比较3次。对于最小值和最大值初值,我们将元素数目分为奇偶数两种处理:如果为奇数,最小值和最大值的初值都为第一个元素的值,然后成对处理;如果为偶数,第一对元素先比较,较小的为最小值的初值,较大的为最大值的初值。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(){ 5 int number, i; 6 int min, max; 7 int t_max, t_min; 8 scanf("%d", &number); 9 int *array = malloc(number * sizeof(int)); 10 11 for(i = 0; i < number; i++) 12 scanf("%d", &array[i]); 13 14 if(number % 2){ //奇数 15 min = max = array[0]; 16 i = 1; 17 } 18 else{ //偶数 19 if(array[0] > array[1]){ 20 max = array[0]; 21 min = array[1]; 22 } 23 else{ 24 max = array[1]; 25 min = array[0]; 26 } 27 i = 2; 28 } 29 30 for(; i < number; i += 2){ 31 if(array[i] > array[i+1]){ //每对元素先进行比较 32 t_max = array[i]; 33 t_min = array[i+1]; 34 } 35 else{ 36 t_max = array[i+1]; 37 t_min = array[i]; 38 } 39 40 if(t_max > max) //较大的元素与最大值比较 41 max = t_max; 42 if(t_min < min) //较小的元素与最大值比较 43 min = t_min; 44 } 45 46 printf("Min is %d, Max is %d.\n", min, max); 47 return 0; 48 }
以期望线性时间做选择
很多人会觉得在n个元素里面找出第i个小的值会比找出最小值要困难,但是以期望线性时间做选择的算法的渐进运行时间为Θ(n)。本章介绍的该算法是分治算法,以快速排序算法为基础,对数组进行递归划分。但是该算法不需要对划分的两边进行处理,而只需要处理划分的一边,这样一来该算法的期望运行时间为Θ(n)。
该算法为随机算法,使用了快速排序中的随机化版本(链接在此)的randomized_partition方法(PS:调用方法的注释见前链接)。
代码中还有迭代版本(练习9.2-3)
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int randomized_partition(int A[], int p, int r); 5 6 int partition(int A[], int p, int r); 7 8 int randomized_select(int A[], int p, int r, int i); 9 10 int randomized_select_iteration(int A[], int p, int r, int i); 11 12 int main(){ 13 int number; 14 scanf("%d", &number); 15 int *array = malloc(number * sizeof(int)); 16 17 int i; 18 for(i = 0; i < number; i++) 19 scanf("%d", &array[i]); 20 21 int which; 22 scanf("%d", &which); 23 24 //int select = randomized_select(array, 0, number-1, which); 25 int select = randomized_select_iteration(array, 0, number-1, which); 26 printf("%d\n", select); 27 return 0; 28 } 29 30 int randomized_partition(int A[], int p, int r){ 31 int i = p + rand() % (r - p + 1); 32 int temp = A[i]; 33 A[i] = A[r]; 34 A[r] = temp; 35 return partition(A, p, r); 36 } 37 38 int partition(int A[], int p, int r){ 39 int x = A[r]; 40 int i = p - 1; 41 int j; 42 for(j = p; j <= r -1; j++){ 43 if(A[j] <= x){ 44 i++; 45 int temp = A[i]; 46 A[i] = A[j]; 47 A[j] = temp; 48 } 49 } 50 int temp = A[i+1]; 51 A[i+1] = A[r]; 52 A[r] = temp; 53 54 return i+1; 55 } 56 57 /* 58 * 在数组下标为p到r中找出第i小的元素 59 */ 60 int randomized_select(int A[], int p, int r, int i){ 61 if(p == r) 62 return A[p]; 63 int q = randomized_partition(A, p, r); //在下标为p到r中随机找一个元素,以该元素为主元素划分数组,并返回该元素的位置 64 int k = q - p + 1; //主元素在数组下标为p到r中里为第几小的元素 65 66 if(i == k) //如果i等于k,主元素为要找的值 67 return A[q]; 68 else if(i < k) //如果i小于k,说明要找的第i小的元素比主元素要小,所以只出现在主元素的左边的子数组里,而这时,第i小的元素在左边子数组依然为第i小。 69 return randomized_select(A, p, q-1, i); 70 else //如果i大于,说明要找的第i小元素比主元素要大,所以只出现在主元素的右边的子数组里,而这时,第i小的元素在右边子数组应该为i-k小。子数组的位置为q+1,而q位置的元素为第k小的元素。 71 return randomized_select(A, q+1, r, i-k); 72 } 73 74 75 /* 76 * 下面为迭代实现 77 */ 78 int randomized_select_iteration(int A[], int p, int r, int i){ 79 if(p == r) 80 return A[p]; 81 82 while(1){ 83 int q = randomized_partition(A, p, r); 84 int k = q - p + 1; 85 86 if(i == k) 87 return A[q]; 88 else if(i < k){ 89 r = q - 1; 90 } 91 else{ 92 p = q + 1; 93 i = i - k; 94 } 95 } 96 }
这个算法看起来会递归调用含有0个元素的子数组,但是这种情况不会发生。(长度为0的数组,会直接返回,不会递归调用)
这个算法的最坏情况运行时间为Θ(n2),即每次划分都是按余下的元素中最大的进行划分。但是该算法的平均情况性能较好,而且是随机化,故没有哪一种特殊的输入会导致最坏情况的发生。
具体关于期望的计算请见算导原文。。。。(有很多的计算和证明还在看。。。。)
虽然本章的页数很少,但是有很多习题和思考题。。。。以后一定要好好做了,并把最坏情况线性时间的选择也补上。。。。
继续努力!!!