最坏情况为线性时间的选择算法
2014-10-16 10:56
447 查看
求给定输入中第k大的数的算法。
这是一个常见面试题,通常的解法也很明显,使用类似快排的思想。
每趟运行,把数组的值分成两部分,一部分比pivot大,一部分比pivot小,因为我们知道pivot在数组中的位置,所以比较k和pivot的位置就知道第k大的值在哪个范围,我们不断的进行recursion, 直到pivot就是第k大的值。
这个算法的时间预期是O(n)。这里需要注意的是讲的仅限于它的预期,对于这个算法,其在最差情况下,时间复杂度则为n的平法。
参阅快速排序的无敌对手一文,我们是可以构建出一个这样的序列的。最简单的情况,每趟快排的时候我们以第一个为主元,那么对于一个已经排序好的序列,我们要找最大的数,最后的时间花费就退化成了n的平方。
《算法导论》9.3章给出了一个最差情况也为线性O(n)的算法。
Step 1:把数组划分为若干个子数组,每个子数组里包含5个数,因为会有无法整除的可能,所以最后一个子数组会小于5.
Step 2:用插入排序把这5个数排序,然后找出中位数,也就是第3个。
Step 3:把获得的中位数又排序(这个地方错误,不是排序,应该递归调用SELECT),找出中位数的中位数x(如果有偶数个中位数,为了方便,约定x是较小的中位数)。
Step 4:把原来的数组使用类似快排的方法,分成两个部分。让k比划分的低区中的元素数目多1,因此x是第k小元素,并且有n-k个元素在划分的高区.
Step 5:如果i =k,返回x。如果 i < k, 则在低区递归调用来找出第i小的元素.如果i> k,则在高区递归查找第i- k小的元素.
整个过程中,第1,2,4步所需时间为O(n), 注意第2步的复杂度不为O(n^2),第3步的复杂度为 T(n/5),第五步的复杂度为 T(7n/10)。
注意这里第2步虽然我们使用的是插入排序,但是待排的序列长度为常数5,所以对一组的排序时间花费为O(1),对于n/5个组,其时间预期是O(n/5),即O(n)。
时间预期为:
T(n) <= T( n/5 ) + T(7n/10+6) + O(n)
(书中通过数学方法最后推得时间预期是O(n)。因为需要较多的数学准备知识,这里不继续介绍。)
在这章的习题中,基于这个算法,要求证明原先Step 1中划分为每组3个和7个的情况的复杂度。7个的情况证明结果和5是一样的。但是对于3的情况,其结果最后可以证明出复杂度并非O(n)。
尝试证明关键步骤如下:
对于划分为3个元素的情况,可以得到递推式(过程略):
T(n) <= T( n/3 ) + T(2n/3+4) + O(n)
假设存在某个适当大的常数c,使得T(n)<=cn(为什么这样可查阅《算法导论》第一章),用an替代O(n)(因为O(n)代表的这部分的时间花费是线性的,那么必然存在一个常数a,使得an为这部分时间花费)用cn代换掉式中的T(n)那么有:
T(n)<= c(n/3) + c(2n/3+4) + an <= cn/3 + c + 2cn/3 + 4c + O(n)= cn + 5c + an
根据假设,T(n)的最大值是cn,那么又有:
cn + 5c + an <= cn
5c + an <=0
显然又 a, n > 0,那么欲使等式成立,必有c<=0。与我们假设的矛盾。所以我们的假设不成立。
因此,当我们尝试用3划分的时候,该算法的无法在线性复杂度内运行。
这个算法的实现代码比较复杂。对于每组划分5个元素的情况, 实现代码如下(该代码输出的是第i大的元素,上面的解释是输出第i小的元素):
下面这个算法比较靠谱:
这是一个常见面试题,通常的解法也很明显,使用类似快排的思想。
每趟运行,把数组的值分成两部分,一部分比pivot大,一部分比pivot小,因为我们知道pivot在数组中的位置,所以比较k和pivot的位置就知道第k大的值在哪个范围,我们不断的进行recursion, 直到pivot就是第k大的值。
这个算法的时间预期是O(n)。这里需要注意的是讲的仅限于它的预期,对于这个算法,其在最差情况下,时间复杂度则为n的平法。
参阅快速排序的无敌对手一文,我们是可以构建出一个这样的序列的。最简单的情况,每趟快排的时候我们以第一个为主元,那么对于一个已经排序好的序列,我们要找最大的数,最后的时间花费就退化成了n的平方。
《算法导论》9.3章给出了一个最差情况也为线性O(n)的算法。
Step 1:把数组划分为若干个子数组,每个子数组里包含5个数,因为会有无法整除的可能,所以最后一个子数组会小于5.
Step 2:用插入排序把这5个数排序,然后找出中位数,也就是第3个。
Step 3:把获得的中位数又排序(这个地方错误,不是排序,应该递归调用SELECT),找出中位数的中位数x(如果有偶数个中位数,为了方便,约定x是较小的中位数)。
Step 4:把原来的数组使用类似快排的方法,分成两个部分。让k比划分的低区中的元素数目多1,因此x是第k小元素,并且有n-k个元素在划分的高区.
Step 5:如果i =k,返回x。如果 i < k, 则在低区递归调用来找出第i小的元素.如果i> k,则在高区递归查找第i- k小的元素.
整个过程中,第1,2,4步所需时间为O(n), 注意第2步的复杂度不为O(n^2),第3步的复杂度为 T(n/5),第五步的复杂度为 T(7n/10)。
注意这里第2步虽然我们使用的是插入排序,但是待排的序列长度为常数5,所以对一组的排序时间花费为O(1),对于n/5个组,其时间预期是O(n/5),即O(n)。
时间预期为:
T(n) <= T( n/5 ) + T(7n/10+6) + O(n)
(书中通过数学方法最后推得时间预期是O(n)。因为需要较多的数学准备知识,这里不继续介绍。)
在这章的习题中,基于这个算法,要求证明原先Step 1中划分为每组3个和7个的情况的复杂度。7个的情况证明结果和5是一样的。但是对于3的情况,其结果最后可以证明出复杂度并非O(n)。
尝试证明关键步骤如下:
对于划分为3个元素的情况,可以得到递推式(过程略):
T(n) <= T( n/3 ) + T(2n/3+4) + O(n)
假设存在某个适当大的常数c,使得T(n)<=cn(为什么这样可查阅《算法导论》第一章),用an替代O(n)(因为O(n)代表的这部分的时间花费是线性的,那么必然存在一个常数a,使得an为这部分时间花费)用cn代换掉式中的T(n)那么有:
T(n)<= c(n/3) + c(2n/3+4) + an <= cn/3 + c + 2cn/3 + 4c + O(n)= cn + 5c + an
根据假设,T(n)的最大值是cn,那么又有:
cn + 5c + an <= cn
5c + an <=0
显然又 a, n > 0,那么欲使等式成立,必有c<=0。与我们假设的矛盾。所以我们的假设不成立。
因此,当我们尝试用3划分的时候,该算法的无法在线性复杂度内运行。
这个算法的实现代码比较复杂。对于每组划分5个元素的情况, 实现代码如下(该代码输出的是第i大的元素,上面的解释是输出第i小的元素):
#include <stdlib.h> #include <stdio.h> #define swap(a,b) (a)^=(b);(b)^=(a);(a)^=(b) #define MAX 1000 void sort(int* input, int size){ printf ( "sort arry size = %d\n", size ); int i,j; for(i = 0; i< size ; i++){ for(j = 0; j<size-i-1;j++){ if(input[j]<input[j+1]){ swap(input[j],input[j+1]); } } } } void output(int * input, int size){ for(;size>0 && *input;size--,input++){ printf("%d ", *input); } printf("\n"); } int partion(int *input, int size, int key){ printf ( "--------------Step4---------------\n" ); printf("key = %d \n", input[key]); int *head, *tail; head = input; tail = head + size - 1; swap(*head, input[key]); int *k = head; while(head<tail){ while(*tail && *k >= *tail){ tail--; } if(tail<=head) break; swap(*k,*tail); k = tail; while(*head && *k < *head) head++; if(head>=tail) break; swap(*k,*head); k = head; } output(input, size); printf ( "--------------Step4 done--------------\n" ); return k-input+1; } int kselect(int *input, int size, int k){ printf ( "start element : %d \n", *input ); if(size<=5){ sort(input, size); return input[k-1]; } int mid[MAX] = {0}; int midvalue[MAX] = {0}; int groups = size/5; int i; printf ( "-----------------step 1, 2--------------\n" ); for(i = 0; i<groups;i++){ sort(input+i*5, (i*5+5 > size) ? (size-1):5); printf ( "sorted group %d:\n", i ); output(input+i*5, 5); mid[i] = i*5 + 2; midvalue[i] = input[i*5 + 2]; } printf ( "-----------------step 1, 2 done--------------\n" ); printf ( "---------step3-------------\n" ); sort(midvalue, groups); printf ( "---------step3 done-------\n" ); int m = -1; for(i = 0; i<5;i++){ if(input[mid[i]] == midvalue[groups/2]){ m = partion(input, size, mid[i]); } } if(m == k){ return input[m-1]; } if(k<m){ return kselect(input,m,k); } else{ return kselect(input+m, size - m, k-m); } return 0xffff; } int main(){ int input[] = {1,3,2,10,5,11, 12, 8 ,6, 7}; /*输出第7大的元素.*/ int r = kselect(input,sizeof(input)/sizeof(int), 7); printf("result %d \n", r); return 0; }
下面这个算法比较靠谱:
#include <iostream> #include <time.h> using namespace std; const int num_array = 13; const int num_med_array = num_array / 5 + 1; int array[num_array]; int midian_array[num_med_array]; //冒泡排序(晚些时候将修正为插入排序) /*void insert_sort(int array[], int left, int loop_times, int compare_times) { for (int i = 0; i < loop_times; i++) { for (int j = 0; j < compare_times - i; j++) { if (array[left + j] > array[left + j + 1]) swap(array[left + j], array[left + j + 1]); } } }*/ /* //插入排序算法伪代码 INSERTION-SORT(A) cost times 1 for j ← 2 to length[A] c1 n 2 do key ← A[j] c2 n - 1 3 Insert A[j] into the sorted sequence A[1 ‥ j - 1]. 0...n - 1 4 i ← j - 1 c4 n - 1 5 while i > 0 and A[i] > key c5 6 do A[i + 1] ← A[i] c6 7 i ← i - 1 c7 8 A[i + 1] ← key c8 n - 1 */ //已修正为插入排序,如下: void insert_sort(int array[], int left, int loop_times) { for (int j = left; j < left+loop_times; j++) { int key = array[j]; int i = j-1; while ( i>left && array[i]>key ) { array[i+1] = array[i]; i--; } array[i+1] = key; } } int find_median(int array[], int left, int right) { if (left == right) return array[left]; int index; for (index = left; index < right - 5; index += 5) { insert_sort(array, index, 4); int num = index - left; midian_array[num / 5] = array[index + 2]; } // 处理剩余元素 int remain_num = right - index + 1; if (remain_num > 0) { insert_sort(array, index, remain_num - 1); int num = index - left; midian_array[num / 5] = array[index + remain_num / 2]; } int elem_aux_array = (right - left) / 5 - 1; if ((right - left) % 5 != 0) elem_aux_array++; // 如果剩余一个元素返回,否则继续递归 if (elem_aux_array == 0) return midian_array[0]; else return find_median(midian_array, 0, elem_aux_array); } // 寻找中位数的所在位置 int find_index(int array[], int left, int right, int median) { for (int i = left; i <= right; i++) { if (array[i] == median) return i; } return -1; } int q_select(int array[], int left, int right, int k) { // 寻找中位数的中位数 int median = find_median(array, left, right); // 将中位数的中位数与最右元素交换 int index = find_index(array, left, right, median); swap(array[index], array[right]); int pivot = array[right]; // 申请两个移动指针并初始化 int i = left; int j = right - 1; // 根据枢纽元素的值对数组进行一次划分 while (true) { while(array[i] < pivot) i++; while(array[j] > pivot) j--; if (i < j) swap(array[i], array[j]); else break; } swap(array[i], array[right]); /* 对三种情况进行处理:(m = i - left + 1) 1、如果m=k,即返回的主元即为我们要找的第k小的元素,那么直接返回主元a[i]即可; 2、如果m>k,那么接下来要到低区间A[0....m-1]中寻找,丢掉高区间; 3、如果m<k,那么接下来要到高区间A[m+1...n-1]中寻找,丢掉低区间。 */ int m = i - left + 1; if (m == k) return array[i]; else if(m > k) //上条语句相当于if( (i-left+1) >k),即if( (i-left) > k-1 ),于此就与2.2节里的代码实现一、二相对应起来了。 return q_select(array, left, i - 1, k); else return q_select(array, i + 1, right, k - m); } int main() { //srand(unsigned(time(NULL))); //for (int j = 0; j < num_array; j++) //array[j] = rand(); int array[num_array]={0,45,78,55,47,4,1,2,7,8,96,36,45}; // 寻找第k最小数 int k = 4; int i = q_select(array, 0, num_array - 1, k); cout << i << endl; return 0; }
相关文章推荐
- 最坏情况为线性时间的选择算法
- 算法导论-第九章-中位数和顺序统计量:最坏情况为线性时间的选择算法C++实现
- 算法导论第9章最坏情况为线性时间的选择算法
- (基于Java)算法之最坏情况下的线性时间选择
- <算法导论>第九章3 最坏情况线性时间的选择
- 最坏情况为线性时间的选择算法
- 算法导论:第9章 中位数和顺序统计量_2最坏情况为线性时间的选择算法
- CLRS 9.3最坏情况为线性时间的选择算法
- 最坏情况为线性时间的选择算法
- 一种最坏情况线性运行时间的选择算法 - The missing worst-case linear-time Select algorithm in CLRS.
- 算法之分治——最坏情况线性时间的选择
- 最坏情况为线性时间的选择算法---算法导论学习笔记(2)
- 最坏情况为线性时间的选择算法之Python实现
- 算法导论 最坏情况为线性时间的选择算法 9.3-8 9.3-9
- 第九章中位数和顺序统计学 之 “寻找第i小元素之最坏情况线性时间的选择 最坏运行时间就为O(n)算法”
- 最坏情况下的线性时间的选择算法
- 算法之线性时间选择(最坏情况下)
- 一种最坏情况线性运行时间的选择算法 - The missing worst-case linear-time Select algorithm in CLRS.
- 《算法导论》读书笔记之第9章 中位数和顺序统计学 最坏情况是线性时间的选择算法
- (p123)最坏情况为线性时间的选择算法