文章目录
1. 选择问题2. 解决方法3. 代码示例顾名思义,“线性时间选择”就是“选择问题”的“线性时间”算法。
1. 选择问题
元素选择问题:给定一个能够线性排序的集合(该集合中有 n 个元素)和 一个整数 k(1≤k≤n1 \le k \le n1≤k≤n) ,找出这 n 个元素中第 k 小的元素。
时间下界:
当 k=1或k=nk = 1 或 k = nk=1或k=n时,时间复杂度为 O(n)O(n)O(n)
当 k≤n/log(n)或k≥n−n/log(n)时k \le n/log(n) 或 k \ge n - n/log(n)时k≤n/log(n)或k≥n−n/log(n)时,时间复杂度为O(n+klog(n))=O(n)O(n + klog(n)) = O(n)O(n+klog(n))=O(n)(堆排序)
2. 解决方法
方法一:先排序,再找第 k 小的数。O(nlogn)O(nlogn)O(nlogn)时间
方法二:随机选择算法:使用快速排序方法, 最多对一段继续分解 最坏时间O(n2)O(n^2)O(n2), 平均时间O(n)O(n)O(n)
RamdomizedPartition(a, p, r) 快排中的分解算法
i=Ramdom(p,r) //在p:r中随机选择一个数i
交换 a[i] 与 a[p] //将a[i]换到左端点
执行Partition(a,p,r)
RamdomizedPartition(a, p, r)//排序a[p:r] {i = Ramdom(p, r); swap(a[i], a[p]);return Partition(a, p, r);}
RandomSelect(a, p, r)
RSelect(a,p,r,k)//选择a[p:r]中第k小数{if (p == r)return a[p];mid=RamdomizePartition(a, p, r); if( mid >= k)return (RSelect(a, p, mid, k)); else return (RSelect(a, mid + 1, r, k - mid); } //初略时间分析: T(n) = T(9n/10) + O(n) = O(n)
方法三:线性时间选择算法 Select() :对快速排序的改进,最坏时间O(n)O(n)O(n)。
将 n 个元素,分成⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉组,取出每组的中位数(第三小的元素)取出⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉个中位数的中位数(Select函数可以取中位数)利用快排中的分解函数 Partition(),以所求中位数为基准,划分 a[p : r] 为两段。取其中一段进行递归。
template <typename Type>Type Select(Type a[], int p, int r, int k){if( r - p < 75 ) {直接对数组a[p:r]排序; return a[p+k-1];}for( int i = 0; i <= (r - p - 4) / 5 ; i++ ) //分 n/5 组, 取各组中位数{swap(a[p + 5*i]至a[p+5*i+4]的第3小元素, a[p+i]);}Type x = Select(a, p, p+(r-p-4)/5, (r-p-4)/10); //取中位数的中位数, T(n/5)int i = Partition(a, p, r, x), j = i - p + 1; if ( k == j ) return a[i];else if ( k < j ) return Select(a,p,i-1,k); //选择左片递归, 最多T(3n/4)else return Select(a,i+1,r,k-j); //选择右片递归, 最多T(3n/4)}
时间复杂度分析:
总结:算法优化的历程
3. 代码示例
附:line-time-select.c
#include <stdio.h>#define MAX 100000int num[MAX];// 选择排序void slsort(int p, int q){for (int i = p + 1; i <= q; i++){int temp = num[i], j = i - 1;while (j >= p){if (num[j] > temp){num[j + 1] = num[j];j--;}elsebreak;}num[j + 1] = temp;}}// 分解函数int Partition(int p, int q, int mid){int i = p, j = q;while (i <= q && j >= p){while (num[i] < mid){i++;}while (num[j] > mid){j--;}if (i >= j)break;else{int temp = num[i];num[i] = num[j];num[j] = temp;i++, j--;}}return j;}// 选择函数int Select(int p, int q, int k){if (q - p < 75){slsort(p, q);return num[p + k - 1];}// 选出 n/5 组中每个组的中位数for (int i = 0; i <= (q - p - 4) / 5; i++){slsort(p + 5 * i, p + 5 * i + 4);int temp = num[p + 5 * i + 2];num[p + 5 * i + 2] = num[p + i];num[p + i] = temp;}// 选出各种中位数的中位数 midint mid = Select(p, p + (q - p - 4) / 5, ((q - p - 4) / 5 + 1) / 2);// 以 mid 为基准进行分解int mid_id = Partition(p, q, mid);int mid_rank = mid_id - p + 1;// 递归条件判断if (k == mid_rank){return num[mid_id];}else if (k < mid_rank){return Select(p, mid_id, k);}else{return Select(mid_id + 1, q, k - mid_rank);}}int main(int argc, char const *argv[]){int i = 0, k;while (scanf("%d", &num[i]) != EOF) {i++;}scanf("%d", &k); // 选择第几小的元素if( k > i)printf("error!\n");elseprint("%d\n", Select(0, i, k));return 0;}