邮局选址问题
问题描述:
在一个按照东西和南北方向划分成规整街区的城市里,n个居民点散乱地分布在不同的街区中。用x 坐标表示东西向,用y坐标表示南北向。各居民点的位置可以由坐标(x,y)表示。街区中任意两点(x1,y1)和(x2,y2)之间的距离可以用数值|x1-x2|+|y1-y2|来度量。
邮局选址问题是,居民们希望在城市中选择建立邮局的最佳位置,使n个居民点到邮局的距离总和最小。
算法思想:
设邮局的位置为(x,y).那么所有居民点东西向距邮局x的距离和sumX=|x-x1|+|x-x2|+…+|x-xn|。由中位数定理可知,x为所有居民点东西向坐标的中位数。同理可知,y为所有居民点南北向坐标的中位数。所以,问题就转化为求一个无序数组的中位数(高效求解:线性时间选择)。
线性时间选择问题:给定线性集中n个元素和一个整数k,求这n个元素的第k小的元素,当k=(n+1)/2时,称为找中位数。
如果能在线性时间内找到一个划分基准,使得按这个基准划分出的两个子数组的长度都至少为原数组长度的ε倍(0<ε<1),那么在最坏情况下用O(n)时间可以完成选择任务。
划分基准的选择步骤:
第一,将n个元素划分成⌈n/5⌉,每组5个元素,最后一组可能少于5个元素。对每组的元素进行排序,并取出每组元素的中位数,共⌈ n/5⌉个。
第二,递归调用该函数,求⌈ n/5⌉个中位数的中位数,如果⌈ n/5⌉为偶数,求两个中间数较大的一个,以此作为基准进行划分。
找到基准后,可以以此基准进行一趟快速排序,将数组划分成两个子数组。然后比较左边子数组个数与整数k的大小,确定第k小元素在哪一个子数组中,达到减小问题规模的目的。最后,递归调用该函数,求出第k小元素。
代码展示:
void Swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
//当元素较少时,可以选择冒泡排序求解中位数
void BubbleSort(int *a, int p, int r){
int change = 1;
for(int i = r;i>p && change;--i){
change = 0;
for(int j = p;j<i;j++){
if(a[j]>a[j+1]){
Swap(a[j],a[j+1]);
change = 1;
}
}
}
}
//快速排序
int QuickSort(int *a, int p, int r, int partition){
int i = p-1, j = r+1;
while(1){
while(a[++i] < partition && i < r);
while(a[--j] > partition);
if(i >= j) break;
Swap(a[i],a[j]);
}
a[j] = partition;
return j;
}
//求第k小元素
int Select(int *a, int p, int r, int k){
if(r-p<75){ //元素个数小于75,使用冒泡排序
BubbleSort(a,p,r);
return a[p+k-1];
}
for(int i = 0;i<=(r-p-4)/5;i++){
BubbleSort(a,p+i*5,p+i*5+4); //先把每组的5个元素排好序
Swap(a[p+i],a[p+i*5+2]); //将每组的中位数交换到数组的前面
}
int x = Select(a, p, p+(r-p-4)/5, p+(r-p-4)/10); //求解每组中位数的中位数x
int i = QuickSort(a,p,r,x),j = i-p+1; //以x作为快排的基准,达到成倍减小数组规模的目的
if(k<=j){
return Select(a,p,r,k);
}else{
return Select(a,i+1,r,k-j);
}
}
运行结果:(可以使用随机数产生测试数据)
中间数据省略……