算法导论第九章中位数和顺序统计量(选择问题)
2015-10-02 17:34
447 查看
本章如果要归结成一个问题的话,可以归结为选择问题,比如要从一堆数中选择最大的数,或最小的数,或第几小/大的数等, 这样的问题看似很简单,似乎没有什么可研究的必要,因为我们已经知道了排序算法,运用排序+索引的方式不就轻松搞定了?但细想,排序所带来的时间复杂度是不是让这个问题无形之中变得糟糕。那算法研究不就是要尽可能避免一个问题高复杂度地解决,让那些不敢肯定有无最优解的问题变得不再怀疑,这也是算法研究者所追求的一种极致哲学。既然排序让这个问题解决的性能无法确定,那我们就抛开排序,独立研究问题本身,看有没有确定性的,且更优的解决之道,所以,这就是本章所探讨的问题。
一、中位数和顺序统计量
中位数:用非形式化的语言描述:中位数表示这样的一位数,它所属集合的“中点元素”。如果集合元素n为奇数,则中位数为(n+1)/2处;如果n为偶数,则中位数出现在n/2(下中位数)和n/2+1(上中位数)处,一般无特殊说明,我们都取下中位数。
顺序统计量:在一个n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。
最大值:第1个顺序统计量。
最小值:第n个顺序统计量。
选择问题:给定一个包含n个元素的集合A和一个整数i,1<=i<=n,我们需要得到一个整数x,其中有i-1个元素小于它,即第i个顺序统计量。
前面说过,这个问题最直观的解法是通过排序+索引的方式,但排序算法有多种,且时间复杂度略高。我们需要更低时间复杂度来解决这个问题,要求线性时间,即O(n)。我们总结下算法导论上提出的方法,一步步展示如何O(n)来解决这个问题。
二、最大值、最小值
1、O(n)求最大值、最小值
这个采用最直观朴素的解法就能解决,我们取个名字吧,叫做“锦标赛法”。就是一个个比较,时间复杂度O(n),已经没有比这更优的了。代码如下:
2、3/2n次比较同时求最大最小值
按照锦标赛法,同时求最大最小值,需要2(n-1)次比较,但是换一种思路,我们没必要一个元素比较两次,而是两个元素比较一次,然后得出大小关系,在分别和最大、最小值比较,这样两个元素就只用比较3次,总共就是3/2n次。这里要分奇偶数看待,但不管奇偶,都需要3/2n次。比较次数减少了,时间也就降低了。代码如下:
View Code
一、中位数和顺序统计量
中位数:用非形式化的语言描述:中位数表示这样的一位数,它所属集合的“中点元素”。如果集合元素n为奇数,则中位数为(n+1)/2处;如果n为偶数,则中位数出现在n/2(下中位数)和n/2+1(上中位数)处,一般无特殊说明,我们都取下中位数。
顺序统计量:在一个n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。
最大值:第1个顺序统计量。
最小值:第n个顺序统计量。
选择问题:给定一个包含n个元素的集合A和一个整数i,1<=i<=n,我们需要得到一个整数x,其中有i-1个元素小于它,即第i个顺序统计量。
前面说过,这个问题最直观的解法是通过排序+索引的方式,但排序算法有多种,且时间复杂度略高。我们需要更低时间复杂度来解决这个问题,要求线性时间,即O(n)。我们总结下算法导论上提出的方法,一步步展示如何O(n)来解决这个问题。
二、最大值、最小值
1、O(n)求最大值、最小值
这个采用最直观朴素的解法就能解决,我们取个名字吧,叫做“锦标赛法”。就是一个个比较,时间复杂度O(n),已经没有比这更优的了。代码如下:
/***********线性时间求最小值************/ int Minimun(int arr[], int n) { int nMin = arr[0]; for(int i = 1; i < n; i++) //min if(nMin > arr[i]) nMin = arr[i]; //max // if (nMax < arr[i]) // nMax = arr[i]; return nMin; }
2、3/2n次比较同时求最大最小值
按照锦标赛法,同时求最大最小值,需要2(n-1)次比较,但是换一种思路,我们没必要一个元素比较两次,而是两个元素比较一次,然后得出大小关系,在分别和最大、最小值比较,这样两个元素就只用比较3次,总共就是3/2n次。这里要分奇偶数看待,但不管奇偶,都需要3/2n次。比较次数减少了,时间也就降低了。代码如下:
#include <iostream> #include <cassert> using namespace std; void Swap(int &m , int &n); int Partition(int arr[], int nLeft, int nRight, int nMedian); int Find(int arr[], int nLeft, int nRight); int Insert(int arr[], int nLeft, int nRight); int Select(int arr[], int nLeft, int nRight, int nMin); int Select(int arr[], int nLeft, int nRight, int nMin) { assert(nLeft <= nRight); assert(nMin <= (nRight-nLeft+1)); if (nLeft == nRight) return arr[nLeft]; int nMedian = Find(arr, nLeft, nRight); int nMid = Partition(arr, nLeft, nRight, nMedian); int k = nMid - nLeft + 1; if (k == nMin) return arr[nMid]; else if (k > nMin) return Select(arr, nLeft, nMid - 1, nMin); else return Select(arr, nMid + 1, nRight, nMin-k); } //找到数组中中位数的中位数 int Find(int arr[], int nLeft, int nRight) { int nLen = nRight - nLeft + 1; int *pMedian = new int[nLen/5+1]; int nStart, nEnd; int j = 0; //表示有几个组 for (int i = 0; i < nLen; i ++) { if (i%5 == 0) nStart = nLeft + i; if ((i+1)%5 == 0 || i == nLen - 1) { nEnd = nLeft + i; j ++; int nRet = Insert(arr, nStart, nEnd); pMedian[j-1] = nRet; } } int nMedian = Select(pMedian, 0, j-1, (j-1)/2); return nMedian; } //对每组5个元素的数组进行插入排序,找到每组的中位数 int Insert(int arr[], int nLeft, int nRight) { int nLen = nRight - nLeft + 1; for (int j = 1; j < nLen; j ++) { int key = arr[j]; int i = j - 1; while (i >= 0 && arr[i] > key) { arr[i+1] = arr[i]; i--; } arr[i+1] = key; } return arr[nLen/2]; } //略作修改过的Partition函数 int Partition(int arr[], int nLeft, int nRight, int nMedian) { //把中位数与看做主元,与最后一个元素交换 for (int i = nLeft; i <= nRight; i++) { if (arr[i] == nMedian){ Swap(arr[i], arr[nRight]); break; } } int nTemp = arr[nRight]; int i = nLeft-1; int j = nLeft; while (j < nRight) { if (arr[j] <= nTemp) { Swap(arr[i+1], arr[j]); i ++; } j ++; } Swap(arr[i+1], arr[nRight]); return i + 1; } void Swap(int &m , int &n) { int t = m; m = n; n = t; } int main() { int arr[] = {7,4,6,9,2,1,5,8,3,0,12,23,78}; cout << Select(arr, 0, 12, 11) << endl; return 0; }
View Code
相关文章推荐
- 最大熵模型的简单理解
- 581C
- 基础算法 KMP
- typedef
- 使用堆栈将缺少左括号的表达式补全并计算其值
- light oj 1384 - Stream My Contest (最小树形图+二分)
- apache 占用内存总量与每个apache进程的平均内存占用量计算
- Android Material Design的FloatingActionButton,Snackbar和CoordinatorLayout
- hihoCoder 1234 Fractal
- 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- 计算器
- ASCII、Unicode、GBK和UTF-8字符编码的区别联系
- 2015 四川省赛 I Travel(bfs)
- hdu 3790 双权值最短路径
- 软工之测试与维护
- 弱校联萌十一大决战之强力热身 A. Easy Math (水)
- 【分享】如何尊重一个程序员?
- Peacock:大规模主题模型及其在腾讯业务中的应用
- js 验证表单 js提交验证类
- Android数据存储通用策略