线性时间选择算法
- 模仿快速排序算法
- 对数组使用分治策略
例题一
问题描述
对于给定的n个元素的数组a[0:n—1],要求从中找出第k小的元素
输入:输入有多组测试例。
对每一个测试例有2行
第一行是整数n和k(1≤k<n≤1000)
第二行是n个整数
输出:第k小的元素
问题分析
我们知道,快速排序算法的一次排序的思想是:
找到一个数字作为标准,把比该数小的放左边,比该数大的放右边
要找到第k小的元素,最粗暴的就是全部排序,但这样做了很多多余的工作,借鉴快速排序算法的一次排序思想,我们可以以数组首位元素作为一个标准,把小于它的放左边,大于它的放右边:
- 当这个标准左边的元素和它加起来为k的话,就找到第k小的数了
- 当这个标准左边的元素和它加起来小于k的话,就向右边继续找第(k-1-该数下标)小的数
- 当这个标准左边的元素和它加起来大于k的话,就向左边继续找第k小的数
算法实现
#include <iostream>
#include <algorithm>
#define N 100
using namespace std;
//一维数组容器
int a[N];
//线性选择算法寻找第k小的元素
int linearTimeSelection(int,int,int);
int main()
{
int n,k;
cout<<"输入数组大小:";
cin>>n;
if(n>N || n<1) {
cout<<"预留空间不足或数组大小非法!";
exit(0);
}
cout<<"输入数组元素:";
for(int i=0;i<n;i++) cin>>a[i];
cout<<"查找第几小的元素:";
cin>>k;
if(k > n || k < 1){
cout<<"查找位置非法!";
exit(0);
}
cout<<linearTimeSelection(0,n-1,k);
return 0;
}
/*
left 进行线性选择的首位下标
right 进行线性选择的末尾下标
k 寻找第k位小的元素
*/
int linearTimeSelection(int left,int right,int k){
if(left >= right) return a[left];
int point = a[left];
int i = left,
j = right+1;
while(1){
do{i++;}while(a[i] < point);
do{j--;}while(a[j] > point);
if(i>=j) break;
swap(a[i],a[j]);
}
if(j-left+1 == k) return point;
a[left] = a[j];
a[j] = point;
if(j-left+1 < k) return linearTimeSelection(j+1,right,k-(j+1-left)); //向右找
return linearTimeSelection(left,j-1,k); //向左找
}
例题二
问题描述
某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。
如果给定n口油井的位置,即它们的x坐标(东西向)和y坐标(南北向),编程计算各油井到主管道之间的输油管道最小长度总和。
输入
第1行是一个整数n,表示油井的数量(1≤n≤10 000)
接下来n行是油井的位置,每行两个整数x和y(-10 000≤x,y≤10 000)
输出
各油井到主管道之间的输油管道最小长度总和
输入样例
5
1 2
2 2
1 3
3 -2
3 3
输出样例
6
样例图
问题分析
- 如何确定主输油管道位置?
由上面我对输入样例画出的分析图,可以清晰的看到这个主管道只由y坐标确定位置,而且主管道存在于y值的值域内(即所输入的y坐标的最大最小值之间),因为要找油井到主管道间的距离和最小,所以找到油井位置的y值的中位数即可。 - 如何计算最小距离和?
找到主管道位置后,我们对每个油井都计算到主管道的距离,加起来即可 - 关于中位数求法
1、直接排序,找n/2下标的元素;
2、使用线性时间选择算法选择第n/2小的元素;(我们使用这种方法)
算法实现
#include <iostream>
#include <algorithm>
#define N 10000
using namespace std;
//存储油井的y值
int a[N];
//线性时间选择算法
int linearTimeSelection(int,int,int);
int main()
{
int n,temp;
cout<<"请输入油井的数量:";
cin>>n;
cout<<"请输入油井坐标:";
for(int i=0;i<n;i++)
cin>>temp>>a[i];
//主管道位置
int mid = linearTimeSelection(0,n-1,n/2);
int sum = 0;
for(int i=0;i<n;i++){
sum += abs(a[i]-mid);
}
cout<<sum<<endl;
return 0;
}
int linearTimeSelection(int left,int right,int k){
if(left >= right) return a[left];
int i = left,
j = right+1;
int point = a[left];
while(1){
do{i++;}while(a[i]<point);
do{j--;}while(a[j]>point);
if(i>=j) break;
swap(a[i],a[j]);
}
if(j-left+1 == k) return point;
a[left] = a[j];
a[j] = point;
if(j-left+1 < k) return linearTimeSelection(j+1,right,k-(j-left+1));
return linearTimeSelection(left,j-1,k);
}