算法导论上的一维邮局问题。
已知n个点p1,p2,…,pn及与它们相联系的权重w1,w2,…,wn。我们希望能找到一点p(不一定是输入点中的一个),使和式
最小,此处d(a,b)表示点a和点b之间的距离。
可以证明此点必须是n个点p1,p2,…,pn的带权中位数,证明过程比较简单。只需假设最优点为Pk, 然后分别计算P(k+1), P(k-1)的开销,最优点的开销必须分别小于P(k+1), P(k-1)点的开销。
网上搜到的那个答案看了让我发笑,http://pleasetojava.iteye.com/blog/1270476
这个是naive版本的快速排序然后在搜索带权中位数,显而易见复杂度为O(NlogN)
下面是我自己写的复杂度为O(n)的算法。其思想仍然是快排,不同的是快排的时候带上两个参数(lmax, rmax) 其中lmax是当前数组中产生的划分左边的最大值,rmax是产生的划分右边数组的最大值。每次划分都算出此次划分左边所有元素的和,记为lsum 和 右边所有元素的和,记为rsum。
快排函数, weight_middle_number(Node* nl, int p, int q, double lmax, double rmax);
划分函数, int divide(Node* nl, int p, int q, double& lsum, double& rsum)
如果每次划分过后, lsum < lmax and rsum<=rmax, 证明此次划分已满足我们要求。返回划分节点。
如果 lsum>lmax, 则对左边重新划分 weight_middle_number(nl, p, r-1, lmax, rmax – rsum – nl[r].weight);
如果 rsum>rmax, 则对右边重新划分 weight_middle_number(nl, r+1, q, lmax – lsum – nl[r].weight, rmax);
复杂度分析
很容易发现复杂度与选择中位数的复杂度相同。 T(n)=T(n/2)+O(n), 所以期望复杂度为O(n)。 最坏情况下为O(n2)(每次都产生最坏的划分)
不多说上C++源码
#include <iostream>
using namespace std;
struct Node
{
int value;
double weight;
};
int divide(Node* nl, int p, int q, double& lsum, double& rsum)
{
int key = nl[q].value;
int i=p-1;
lsum=0, rsum=0;
for(int j=p; j<=q-1; j++)
{
if(nl[j].value<key)
{
i++;
Node temp = nl[j];
nl[j] = nl[i];
nl[i] = temp;
lsum+=nl[i].weight;
}
else
{
rsum+=nl[j].weight;
}
}
i++;
Node temp = nl[q];
nl[q] = nl[i];
nl[i] = temp;
return i;
}
Node weight_middle_number(Node* nl, int p, int q, double lmax, double rmax)
{
if(p<q)
{
double lsum, rsum;
int r = divide(nl, p, q, lsum, rsum);
//cout<<r<<", "<<"("<<lsum<<", "<<rsum<<")"<<", ("<<lmax<<", "<<rmax<<")"<<endl;
if(lsum < lmax and rsum<=rmax)
return nl[r];
else
{
if(lsum >=lmax)
{
rmax = rmax - rsum - nl[r].weight;
return weight_middle_number(nl, p, r-1, lmax, rmax);
}
else
{
lmax = lmax - lsum - nl[r].weight;
return weight_middle_number(nl, r+1, q, lmax, rmax);
}
}
}
return nl[p];
}
void printNode(Node n)
{
cout<<"("<<n.value<<", "<<n.weight<<")"<<endl;
}
int main(int argc, char** argv)
{
const int n=10;
Node nl[n];
for(int i=0; i<n; i++)
{
cout<<"Please enter value:"<<endl;
cin>>nl[i].value;
cout<<"Please enter its weight:"<<endl;
cin>>nl[i].weight;
}
for(int i=0; i<n; i++)
{
printNode(nl[i]);
}
double sumWeight = 0;
for(int i=0; i<n; i++)
sumWeight += nl[i].weight;
//cout<<sumWeight<<endl;
Node middleNode = weight_middle_number(nl, 0, n-1, sumWeight/2, sumWeight/2);
cout<<"weighted median node is: "<<endl;
printNode(middleNode);
return 0;
}