Divide-Conquer
例1、主油管道为东西向,确定主油管道的南北位置,使南北向油井喷油管道和最小。要求线性时间完成。
输入要求:
输入有油井数量行,第 K 行为第 K 油井的坐标 X ,Y 。其中, 0<=X<231,0<=Y<231 。
输出要求:
输出有一行, N 为主管道最优位置的最小值
注意:用快排做的不给分!!
友情提示:可以采用while(scanf("%d,%d",&x,&y) != EOF)的数据读入方式。
题目分析:
简易分析得出x坐标无用,问题的意思为求y坐标的中位数。
分治的入门题,主要是考察求中位数的算法。
关键在于pivot的选择,有点像快排
(一)RandomizedSelect算法
即随机选取pivot,最坏情况O(n^2), 所以必tle,只是为了理解入门,具体分治过程如下:
分:随机选一个数为pivot,这里以最后一个数为例子,把比pivot小的移到LHS,大的移到RHS。
治:定义递归终点为所找的第k大的数,即k = n/2
如果所找的pivot为结果,返回pivot,
比pivot小则向LHS递归,否则向RHS递归
Before:
-1 | 2 | 7 | 4 | 9 | 8 | -6 | 12 | 2 | 5 |
---|---|---|---|---|---|---|---|---|---|
head | tail | pivot |
After(one partition):
-1 | 2 | 2 | 4 | -6 | 5 | 9 | 12 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
pivot |
代码包括两个部分:Partition和Select
Partition:
//选定pivot,将比pivot小的数移到pivot左边 ,比pivot大的数移到pivot右边,并返回pivot所在位置
int Partition(int a[],int head, int tail){
int i, j ;
int pivot_value, pivot_index;
pivot_value = a[tail];
pivot_index = tail;
i = head;
j = tail - 1;
while(i < j)
{
while(a[i] < pivot_value)
{
i++;
}
while(a[j] > pivot_value)
{
j--;
}
if(i < j)
swap(a[i], a[j]);
}
swap(a[i], a[pivot_index]);
return i;
}
Select:
//找到第k大的数
int Select(int head, int tail, int k){
int pivot_index;
pivot_index = Partition(head, tail);
if(pivot_index - head + 1 == k)
{
return a[pivot_index];
}
else if(pivot_index - head + 1 > k)
{
return Select(head, pivot_index-1, k);
}
else
{
return Select(pivot_index + 1, tail, k - (pivot_index - head + 1));
}
}
之前已经提及,这个算法容易理解,但由于pivot选取是随机的,会导致算法不稳定,必tle,因此下面是改进的方法;
(二)BFPTR算法
先分组,找各组的中位数,再找出中位数的中位数
First:
-1 | 2 | 7 | 4 | 9 | 8 | -6 | 12 | 2 | 5 |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 |
-1 | 2 | 7 | 4 | 9 | 8 | -6 | 12 | 2 | 5 |
---|---|---|---|---|---|---|---|---|---|
pivot 1 | pivot 2 |
Then:
4 | 5 | 7 | -1 | 9 | 8 | -6 | 12 | 2 | 2 |
---|---|---|---|---|---|---|---|---|---|
pivot 1 | pivot 2 |
因此只需在RandomizedSelect的基础上+BFPTR找pivot的函数即可
find_pivot:
//预处理,找到比较接近真实中位数的分组中位数的中位数的值
int find_pivot(int a[], int head, int tail)
{
int i;
int num = 0;
for(i = head; i + 4 <= tail; i+=5)
{
sort(&a[i], &a[i+4]);
swap(a[head + num], a[i+2]);
num++;
}
if(tail > i)
{
sort(&a[i], &a[tail]);
swap(a[head + num], a[(i+tail) / 2]);
num++;
}
if(!num || num == 1)
{
return a[head];
}
else
{
return find_pivot(a, head, head + num - 1);
}
}
//预处理,找到比较接近真实中位数的分组中位数的中位数的位置
int find_pivot_index(int a[], int head, int tail, int pivot)
{
int i;
for(i = head; i <= tail; i++)
{
if(a[i] == pivot)
{
return i;
}
}
}
总结:思路还是很好想的,但中间细节也比较多,例如边界的确定,参考了其他大佬的思路,因为只是自己整理复习用,所以懒得标注了