一维邮局问题 + 带权中位数复杂度O(n)算法


   算法导论上的一维邮局问题。

    已知n个点p1,p2,…,pn及与它们相联系的权重w1,w2,…,wn。我们希望能找到一点p(不一定是输入点中的一个),使和式

                                                                                          《一维邮局问题 + 带权中位数复杂度O(n)算法》

最小,此处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;
}

点赞