《算法导论》读书笔记之第9章 中位数和顺序统计学
摘要:
本章所讨论的问题是在一个由n个不同数值构成的集合中选择第i个顺序统计量问题。主要讲的内容是如何在线性时间内O(n)时间内在集合S中选择第i小的元素,最基本的是选择集合的最大值和最小值。一般情况下选择的元素是随机的,最大值和最小值是特殊情况,书中重点介绍了如何采用分治算法来实现选择第i小的元素,并借助中位数进行优化处理,保证最坏保证运行时间是线性的O(n)。
1、基本概念
顺序统计量:在一个由n个元素组成的集合中,第i个顺序统计量是值该集合中第i小的元素。例如最小值是第1个顺序统计量,最大值是第n个顺序统计量。
中位数:一般来说,中位数是指它所在集合的“中间元素”,当n为奇数时,中位数是唯一的,出现位置为n/2;当n为偶数时候,存在两个中位数,位置分别为n/2(上中位数)和n/2+1(下中位数)。
2、选择问题描述
输入:一个包含n个(不同的)数的集合A和一个数i,1≤i≤n。
输出:元素x∈A,它恰大于A中其他的i-1个元素。
最直接的办法就是采用一种排序算法先对集合A进行排序,然后输出第i个元素即可。可以采用前面讲到的归并排序、堆排序和快速排序,运行时间为O(nlgn)。接下来书中由浅入深的讲如何在线性时间内解决这个问题。
3、最大值和最小值
要在集合中选择最大值和最小值,可以通过两两元素比较,并记录最大值和最小值,n元素的集合需要比较n-1次,这样运行时间为O(n)。举个例子来说明,现在要求和集合A={32,12,23,67,45,78}的最大值,开始假设第一个元素最大,即max=1,然后从第二个元素开始向后比较,记录最大值的位置。执行过程如下图所示:
书中给出的求最小值的伪代码如下:
1 MINMUN(A) 2 min = A[1] 3 for i=1 to length(A) 4 do if min > A[i] 5 then min >= A[i] 6 return min
问题:
(1)同时找出集合的最大值和最小值
方法1:按照上面讲到的方法,分别独立的找出集合的最大值和最小值,各用n-1次比较,共有2n-2次比较。
方法2:可否将最大值和最小值结合在一起寻找呢?答案是可以的,在两两比较过程中同时记录最大值和最小值,这样最大需要3n/2次比较。现在的做法不是将每一个 输入元素与当前的最大值和最小值进行比较,而是成对的处理元素,先将一对输入元素进行比较,然后把较大者与当前最大值比较,较小者与当前最小者比较,因此每两个元素需要3次比较。初始设置最大值和最小值方法:如何n为奇数,就将最大值和最小值都设置为第一个元素的值,然后成对的处理后续的元素。如果n为偶数,那么先比较前面两个元素的值,较大的设置为最大值,较小的设置为最小值,然后成对处理后续的元素。这样做的目的保证能够成对的处理后续的元素。举个例子说明这个过程,假设现在要找出集合A={32,23,12,67,45,78,10,39,9,58}最大值和最小值,执行过程如下:
从图中可以看出方法2要比方法一要好,减少了元素之间的比较次数。现在用C语言实现方法2,程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 //return max and min value by pointer 5 void get_max_min(int *datas,int length,int* ptrmax,int* ptrmin) 6 { 7 int i,maxtmp,mintmp; 8 //judge length is even or odd 9 if(length %2 == 0) 10 { 11 if(datas[0] > datas[1]) 12 { 13 *ptrmax = datas[0]; 14 *ptrmin = datas[1]; 15 } 16 else 17 { 18 *ptrmax = datas[1]; 19 *ptrmin = datas[0]; 20 } 21 } 22 else 23 { 24 *ptrmax = datas[0]; 25 *ptrmin = datas[0]; 26 } 27 for(i=2;i<length;i+=2) 28 { 29 if(datas[i] > datas[i+1]) 30 { 31 maxtmp = datas[i]; 32 mintmp = datas[i+1]; 33 } 34 else 35 { 36 maxtmp = datas[i+1]; 37 mintmp = datas[i]; 38 } 39 if(*ptrmax < maxtmp) 40 *ptrmax = maxtmp; 41 if(*ptrmin > mintmp) 42 *ptrmin = mintmp; 43 } 44 } 45 46 int main() 47 { 48 int max,min; 49 int i; 50 int datas[10] = {23,12,34,26,78,45,87,15,60,19}; 51 get_max_min(datas,10,&max,&min); 52 printf("All elements in set are:\n"); 53 for(i=0;i<10;++i) 54 printf("%d ",datas[i]); 55 putchar('\n'); 56 printf("max=%d\tmin=%d\n",max,min); 57 exit(0); 58 }
程序测试结果如下:
(2)如何找出找出n个元素中的第2小元素。
解答:类似与上面的同时找出最大值和最小值的方法2,变成同时找最小值和第2小元素值。先初始化最小值和第2小的值,然后成对比较后续的元素,找出较小的元素与当前最小值和第二小值进行比较,在三者中找出最小值和第二小值。
4、以期望线性时间做选择
一般的选择问题似乎要比选择最大值和最小值要难,但是这两种问题的运行时间是相同的,都是θ(n)。书中介绍了采用分治算法解决一般的选择问题,其过程与快速排序过程中划分类似。每次划分集合可以确定一个元素的最终位置,根据这个位置可以判断是否是我们要求的第i小的元素。如果不是,那么我们只关心划分产出两个子部分中的其中一个,根据i的值来判断是前一个还是后一个,然后接着对子数组进行划分,重复此过程,直到找到第i个小的元素。划分可以采用随机划分,这样能够保证期望时间是θ(n)(假设所有元素是不同的)。
给个例子说明此过程,假设现有集合A={32,23,12,67,45,78,10,39,9,58},要求其第5小的元素,假设在划分过程中以总是以最后一个元素为主元素进行划分。执行过程如下图所示:
书中给出了返回A[p...r]中的第i小元素的伪代码:
1 RANDOMIZED_SELECT(A,p,r,i) 2 if p==r 3 then return A[p] 4 q = RANDOMIZED_PARTITION(A,p,r) 5 k = q-p+1; 6 if i==k 7 then return A[q] 8 else if i<k 9 then return RANDOMIZED_SELECT(A,p,q-1,i) 10 else 11 return RANDOMIZED_SELECT(A,p,q-1,i-k)
RANDOMIZED_SELECT通过对输入数组的递归划分来找出所求元素,该算法要保证对数组的划分是个好划分才更加高效。RANDOMIZED_SELECT的最坏情况运行时间为θ(n^2),即使是选择最小元素也是如此。因为在每次划分过程中,导致划分后两边不对称,总好是按照剩下元素中最大的划分进行。为了更好的选择过程,我采用C语言实现求集合A={32,23,12,67,45,78,10,39,9,58}的第i小的元素,完成程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 5 size_t randomized_partition(int* datas,size_t beg,size_t last); 6 void swap(int* a,int *b); 7 int randomized_select_one(int* datas,int beg,int last,int i); 8 int randomized_select_two(int* datas,int length,int i); 9 10 int main() 11 { 12 int datas[10]={32,23,12,67,45,78,10,39,9,58}; 13 int i,ret; 14 printf("The array is: \n"); 15 for(i=0;i<10;++i) 16 printf("%d ",datas[i]); 17 printf("\n"); 18 for(i=1;i<=10;++i) 19 { 20 //ret=randomized_select_one(datas,0,9,i); 21 ret=randomized_select_two(datas,10,i); 22 printf("The %dth least number is: %d \n",i,datas[i-1]); 23 } 24 exit(0); 25 } 26 /* 27 参数介绍:datas是待划分的数组,数组下标从0开始。 28 beg代表开始位置,last代表结束位置、封闭区间[beg,last] 29 */ 30 size_t randomized_partition(int* datas,size_t beg,size_t last) 31 { 32 int len,i,j,index; 33 len = last-beg+1; 34 //随机获取一个主元 35 srand(time(NULL)); 36 index = beg + rand()%len; 37 //将主元交换到末尾 38 swap(datas+index,datas+last); 39 //从第一个元素开始向后查找主元的位置 40 i=beg; 41 for(j=beg;j<last;j++) 42 { 43 if(datas[j] < datas[last]) 44 { 45 swap(datas+i,datas+j); 46 i++; 47 } 48 } 49 //最终确定主元的位置 50 swap(datas+i,datas+last); 51 return i; 52 } 53 /* 54 参数介绍:datas是待查找的数组,数组下标从0开始。 55 beg代表开始位置,last代表结束位置、封闭区间[beg,last] 56 i表示要要查找第i小元素,i从1开始 57 */ 58 int randomized_select_one(int* datas,int beg,int last,int i) 59 { 60 int pivot,k; 61 if(beg == last) 62 return datas[beg]; 63 pivot = randomized_partition(datas,beg,last); 64 k = pivot-beg+1; 65 if(k == i) 66 return datas[pivot]; 67 else if(k < i) 68 randomized_select_one(datas,pivot+1,last,i-k); 69 else 70 randomized_select_one(datas,beg,pivot-1,i); 71 } 72 /* 73 参数介绍:datas是待查找的数组,数组下标从0开始。 74 length表示数组的长度,数组下标范围[0,length-1] 75 i表示要要查找第i小元素,i从1开始 76 */ 77 int randomized_select_two(int* datas,int length,int i) 78 { 79 int pivot,k,j; 80 if(length == 1) 81 return datas[length-1]; 82 pivot = randomized_partition(datas,0,length-1); 83 //确定是主元是第k小 84 k = pivot+1; 85 if(k == i) 86 return datas[pivot]; 87 else if(k < i) 88 randomized_select_two(datas+k,length-k,i-k); 89 else 90 randomized_select_two(datas,pivot,i); 91 } 92 93 void swap(int* a,int *b) 94 { 95 int temp = *a; 96 *a = *b; 97 *b = temp; 98 }
程序测试结果如下所示:
程序中要注意的细节问题是:C语言中数组的小标是从0开始的,而要求第i小元素中的i是从1开始的,即第1小的元素对应与最终的主元位置0,依次类推。
5、最坏情况线性时间的选择
SELECT算法的思想是要保证对数组的划分是个好的划分,对PARTITION过程进行了修改。现在通过SELECT算法来确定n个元素的输入数组中的第i小的元素,具体操作步骤如下:
(1)将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,且至多只有一个组有剩下的n%5个元素组成。(为何是5,而不是其他数,有点不明白。)
(2)寻找每个组织中中位数。首先对每组中的元素(至多为5个)进行插入排序,然后从排序后的序列中选择出中位数。
(3)对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数x。(如果是偶数去下中位数)
(4)调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。
(5)如果i=k,则返回x。否则,如果i<k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。
SELECT算法通过中位数进行划分,可以保证每次划分是对称的,这样就能保证最坏情况下运行时间为θ(n)。举个例子说明此过程,求集合A={32,23,12,67,45,78,10,39,9,58,125,84}的第5小的元素,操作过程如下图所示:
现在采用C语言实现上面的例子,完整程序如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int partition(int* datas,int beg,int last,int mid); 5 int select(int* datas,int length,int i); 6 void swap(int* a,int *b); 7 8 int main() 9 { 10 int datas[12]={32,23,12,67,45,78,10,39,9,58,125,84}; 11 int i,ret; 12 printf("The array is: \n"); 13 for(i=0;i<12;++i) 14 printf("%d ",datas[i]); 15 printf("\n"); 16 for(i=1;i<=12;++i) 17 { 18 ret=select(datas,12,i); 19 printf("The %dth least number is: %d \n",i,datas[i-1]); 20 } 21 exit(0); 22 } 23 24 int partition(int* datas,int beg,int last,int mid) 25 { 26 int i,j; 27 swap(datas+mid,datas+last); 28 i=beg; 29 for(j=beg;j<last;j++) 30 { 31 if(datas[j] < datas[last]) 32 { 33 swap(datas+i,datas+j); 34 i++; 35 } 36 } 37 swap(datas+i,datas+last); 38 return i; 39 } 40 41 int select(int* datas,int length,int i) 42 { 43 int groups,pivot; 44 int j,k,t,q,beg,glen; 45 int mid; 46 int temp,index; 47 int *pmid; 48 if(length == 1) 49 return datas[length-1]; 50 if(length % 5 == 0) 51 groups = length/5; 52 else 53 groups = length/5 +1; 54 pmid = (int*)malloc(sizeof(int)*groups); 55 index = 0; 56 for(j=0;j<groups;j++) 57 { 58 beg = j*5; 59 glen = beg+5; 60 for(t=beg+1;t<glen && t<length;t++) 61 { 62 temp = datas[t]; 63 for(q=t-1;q>=beg && datas[q] > datas[q+1];q--) 64 swap(datas+q,datas+q+1); 65 swap(datas+q+1,&temp); 66 } 67 glen = glen < length ? glen : length; 68 pmid[index++] = beg+(glen-beg)/2; 69 } 70 for(t=1;t<groups;t++) 71 { 72 temp = pmid[t]; 73 for(q=t-1;q>=0 && datas[pmid[q]] > datas[pmid[q+1]];q--) 74 swap(pmid+q,pmid+q+1); 75 swap(pmid+q+1,&temp); 76 } 77 //printf("mid indx = %d,mid value=%d\n",pmid[groups/2],datas[pmid[groups/2]]); 78 mid = pmid[groups/2]; 79 pivot = partition(datas,0,length-1,mid); 80 //printf("pivot=%d,value=%d\n",pivot,datas[pivot]); 81 k = pivot+1; 82 if(k == i) 83 return datas[pivot]; 84 else if(k < i) 85 return select(datas+k,length-k,i-k); 86 else 87 return select(datas,pivot,i); 88 89 } 90 91 void swap(int* a,int *b) 92 { 93 int temp = *a; 94 *a = *b; 95 *b = temp; 96 }
程序测试结果如下所示:
总结
本章中的选择算法之所以具有线性运行时间,是因为这些算法没有进行排序,线性时间的行为并不是因为对输入做假设所得到的结果。
转载于:https://www.cnblogs.com/Anker/archive/2013/01/25/2877311.html
《算法导论》读书笔记之第9章 中位数和顺序统计学相关推荐
- 算法导论读书笔记-第十九章-斐波那契堆
算法导论第19章--斐波那契堆 可合并(最小)堆(mergeable min-heap) : 支持以下5种操作的一种数据结构, 其中每一个元素都有一个关键字: MAKE-HEAP(): 创建和返回一个 ...
- 算法导论读书笔记(8)
算法导论读书笔记(8) 目录 计数排序 计数排序的简单Java实现 基数排序 基数排序的简单Java实现 桶排序 计数排序 计数排序 假设 n 个输入元素中的每一个都是介于0到 k 之间的整数,此处 ...
- 算法导论读书笔记(7)
算法导论读书笔记(7) 目录 快速排序 快速排序的简单Java实现 快速排序的性能 最坏情况划分 最佳情况划分 快速排序的随机化版本 比较排序 快速排序 快速排序是一种原地排序算法,对包含 n 个数的 ...
- 算法导论读书笔记(19)
http://www.cnblogs.com/sungoshawk/p/3802553.html 算法导论读书笔记(19) 目录 最优二叉搜索树 步骤1:一棵最优二叉查找树的结构 步骤2:一个递归解 ...
- 算法导论读书笔记 第4章 分治策略
在第2章中,归并排序算法使用了分治策略.即在分治策略中,递归地求解一个问题,在每层递归中应包含三个步骤: 分解(Divide)步骤将问题画分为一些子问题,子问题的形式与原问题一样,只是规模更小. 解决 ...
- 【算法导论学习笔记】第3章:函数的增长
原创博客,转载请注明: http://www.cnblogs.com/wuwenyan/p/4982713.html 当算法的输入n非常大的时候,对于算法复杂度的分析就显得尤为重要,虽然有时我们能通 ...
- 算法导论读书笔记(20)van Emde Boas树
第五部分 高级数据结构 第20章 van Emde Boas树 van Emde Boas树支持优先队列操作以及一些其他操作,每个操作最坏情况运行时间为O(lglgn).而这种数据结构限制关键字必须为 ...
- 算法导论中C语言代码,算法导论-学习笔记与进度
算法导论 阅读进度 第一部分 基础知识 第一章 计算中算法的角色 Done 1.1 算法 输入与输出 算法可以解决哪些问题 数据结构 技术 一些比较难的问题 1.2 作为一种技术的算法 效率 算法和其 ...
- 计算机系统导论第九章,计算机系统导论 -- 读书笔记 -- 第三章 程序的机器级表示 (持续更新)...
计算机系统导论 -- 读书笔记 -- 第三章 程序的机器级表示 (持续更新) 第三章 程序的机器级表示 3.1 历史观点 3.2 程序编码 1. 命令行 (1)编译 Linux> gcc -Og ...
最新文章
- 从指纹到眼球识别:漫谈手机安全方案
- Focal Loss改进版 GFocal Loss
- Linux下的shell脚本实战之用户创建
- Intel Realsense D435 如何获取摄像头的内参?get_profile() video_stream_profile() get_intrinsics()
- Netty HTTP on Android
- 语法分析器自动生成工具一览
- nagios整合cacti2011版(五)
- redis sentinel集群与spring集成
- 本地上传文件到Linux云服务器
- [vue-router] Duplicate named routes definition
- 有趣的算法:1元=1分
- 部分手机浏览器存在将ajax请求当成广告过滤的情况,及解决方案
- appassembler-maven-plugin插件打包本地依赖的jar
- cenOS 安装opencv(for matlab)
- matlab人工神经网络教程,人工神经网络作业MATLAB仿真(共3篇)
- docker ssh连接
- 刀片服务器显示如何切换,刀片机服务器切换
- 云计算、大数据、人工智能三者究竟有什么关系
- 华为rh2288型号服务器,华为RH2288H V2服务器外部简介
- 不用找了,这300家公司面试不考算法
热门文章
- 7-5 考试座位号 (15 分)
- 3004基于二叉链表的二叉树的双序遍历(附题意解释)
- JDBC编程可能遇到的错误:java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or ..
- CSUOJ修墙壁C语言,棋牌挂怎么编写 -棋牌挂怎么编写V6.1.16
- java推荐系统算法,阿里“推荐系统”背后的算法介绍
- mysql 时间函数大全_mysql常用的日期函数汇总
- 解决java poi导出excel2003不能超过65536行的问题
- RMAN备份与恢复(三)--备份相关概念
- 在手机上查询药品信息?PEP移动掌上药物信息参考
- GridView的常用操作(增删改查)