如何有效地找到数字流中元素的等级?

时间:2018-04-03 09:33:50

标签: c++ algorithm ranking median data-stream

最近我试图找到具有以下条件的数字流的中位数:

  1. 3遍算法
  2. O(nlog(n))时间
  3. O(sqrt(n))space
  4. 输入重复3次,包括n,整数个数,后跟n个整数a_i,这样:

    1. n很奇怪
    2. 1≤n≤10^ 7
    3. | A_I | ≤2^ {30}
    4. 输入数据的格式如下所示:

      5
      1 3 4 2 5
      5
      1 3 4 2 5
      5
      1 3 4 2 5
      

      到目前为止,我的代码如下所示:

      #ifdef STREAMING_JUDGE
      #include "io.h"
      #define next_token io.next_token
      #else
      #include<string>
      #include<iostream>
      using namespace std; 
      string next_token()
      {
          string s;
          cin >> s;
          return s;
      }
      #endif
      
      #include<cstdio>
      #include<cstdlib>
      #include<vector>
      #include<algorithm>
      #include<iostream>
      #include<math.h>
      
      using namespace std;
      
      int main()
      {
          srand(time(NULL));
          //1st pass: randomly choose sqrt(n) numbers from the given stream of numbers
          int n = atoi(next_token().c_str());
          int p = (int)ceil(sqrt(n));
          vector<int> a;
          for(int i=0; i<n; i++)
          {
              int s=atoi(next_token().c_str());
              if( rand()%p == 0 && (int)a.size() < p )
              {
                  a.push_back(s);
              }
          }
          sort(a.begin(), a.end());
          //2nd pass: find the k such that the median lies in a[k] and a[k+1], and find the rank of the median between a[k] and a[k+1]
          next_token();
          vector<int> rank(a.size(),0);
          for( int j = 0; j < (int)a.size(); j++ )
          {
              rank.push_back(0);
          }
          for( int i = 0; i < n; i++ )
          {
              int s=atoi(next_token().c_str());
              for( int j = 0; j < (int)rank.size(); j++ )
              {
                  if( s<=a[j] )
                  {
                      rank[j]++;
                  }
              }
          }
          int median = 0;
          int middle = (n+1)/2;
          int k;
          if( (int)a.size() == 1 && rank.front() == middle )
          {
              median=a.front();
              cout << median << endl;
              return 0;
          }
          for( int j = 0; j < (int)rank.size(); j++ )
          {
              if( rank[j] == middle )
              {
                  cout << rank[j] << endl;
                  return 0;
              }
              else if( rank[j] < middle && rank[j+1] > middle )
              {
                  k = j;
                  break;
              }
          }
          //3rd pass: sort the numbers in (a[k], a[k+1]) to find the median
          next_token();
          vector<int> FinalRun;
          if( rank.empty() )
          {
              for(int i=0; i<n; i++)
              {
                  a.push_back(atoi(next_token().c_str()));
              }
              sort(a.begin(), a.end());
              cout << a[n>>1] << endl;
              return 0;
          }
          else if( rank.front() > middle )
          {
              for( int i = 0; i < n; i++ )
              {
                  int s = atoi(next_token().c_str());
                  if( s < a.front() )  FinalRun.push_back(s);
              }
              sort( FinalRun.begin(), FinalRun.end() );
              cout << FinalRun[middle-1] << endl;
              return 0;
          }
          else if ( rank.back() < middle )
          {
              for( int i = 0; i < n; i++ )
              {
                  int s = atoi(next_token().c_str());
                  if( s > a.back() )  FinalRun.push_back(s);
              }
              sort( FinalRun.begin(), FinalRun.end() );
              cout << FinalRun[middle-rank.back()-1] << endl;
              return 0;
          }
          else
          {
              for( int i = 0; i < n; i++ )
              {
                  int s = atoi(next_token().c_str());
                  if( s > a[k] && s < a[k+1] )  FinalRun.push_back(s);
              }
              sort( FinalRun.begin(), FinalRun.end() );
              cout << FinalRun[middle-rank[k]-1] << endl;
              return 0;
          }
      }
      

      但我仍然无法达到O(nlogn)时间复杂度。 我想瓶颈在于排名部分(即通过找到a [i]中的采样a [i]的等级来找到(a [k],a [k + 1])中位数的等级。输入数字流。)在第二遍。这部分在我的代码中有O(nsqrt(n))。

      但我不知道如何提高排名效率...... 是否有提高效率的建议?提前谢谢!

      对&#34; rank&#34;的进一步说明:采样数的等级计算流中小于或等于采样数的数量。例如:在如上给出的输入中,如果数字a [0] = 2,a [1] = 4和a [2] = 5被采样,则rank [0] = 2,因为有两个数字( 1和2)在流中小于或等于a [0]。

      感谢您的所有帮助。特别是@ alexeykuzmin0的建议确实可以加快第二次传球到O(n * logn)的时间。但是还有一个问题:在第一遍中,我以1 / sqrt(n)的概率对数字进行采样。当没有采样数(最坏情况)时,矢量a为空,导致不能执行以下通过(即,发生分段故障(核心转储))。 @Aconcagua,做什么意思&#34;选择所有剩余的元素,如果不再需要超过&#34;?感谢。

1 个答案:

答案 0 :(得分:2)

你是对的,你的第二部分在O(n√n)时间内工作:

for( int i = 0; i < n; i++ )                    // <= n iterations
  ...
    for( int j = 0; j < (int)rank.size(); j++ ) // <= √n iterations

要解决这个问题,我们需要摆脱内循环。例如,我们可以先计算落入每个区间的数组元素数量,而不是直接计算初始数组的元素数量,而不是阈值:

// Same as in your code
for (int i = 0; i < n; ++i) {
    int s = atoi(next_token().c_str());
    // Find index of interval in O(log n) time
    int idx = std::upper_bound(a.begin(), a.end(), s) - a.begin();
    // Increase the rank of only that interval
    ++rank[idx];
}

然后计算阈值元素的等级:

std::partial_sum(rank.begin(), rank.end(), rank.begin());

由此产生的复杂性为O(n log n) + O(n) = O(n log n)

这里我使用了两种STL算法:

  1. std::upper_bound使用二进制搜索方法找到排序数组中的第一个元素,该元素在对数时间内严格大于给定数字。
  2. std::partial_sum计算以线性时间给出的数组的部分和。