【问题标题】:Expected number of maxima期望的最大值数
【发布时间】:2013-09-29 06:14:26
【问题描述】:

我有一个算法,它接受一个数组作为参数,并返回它的最大值。

find_max(as) :=
    max = as[0]
    for i = 1 ... len(as) {
        if max < as[i] then max = as[i]
   }
    return max

我的问题是:鉴于数组最初处于(一致)随机排列并且其所有元素都是不同的,max 变量的预期更新次数是多少(忽略初始分配)。

例如,如果as = [1, 3, 2],则max 的更新次数将为 1(读取值 3 时)。

【问题讨论】:

  • 如果您希望您的问题与 stackoverflow 相关,您应该显示一些源代码。我不明白您在考虑哪种算法,但找到最大值是线性复杂度
  • 其实算法中只有一个条件,就是当你改变变量而不是在迭代中花费时间。
  • 考虑伪代码: max = array[0] ; for(i=0;i
  • 对于 1,3,4,你不改变 max 变量两次吗? 1->3 和 3->4?
  • 取自某个有限集合的值吗?像 [1,M] 区间?与替换?没有?你不能生成没有边界的均匀分布的随机数。

标签: java c++ algorithm big-o time-complexity


【解决方案1】:

假设原始数组包含值 1、2、...、N。

令 X_i, i = 1..N 是随机变量,如果 i 在算法过程中的某个时刻是最大值,则取值为 1。

那么算法取的最大值就是随机变量:M = X_1 + X_2 + ... + X_N。

平均值为(根据定义)E(M) = E(X_1 + X_2 + ... + X_N)。使用线性期望,这是 E(X_1) + E(X_2) + .. + E(X_N),即 prob(1 出现为最大值) + prob(2 出现为最大值) + ... + prob (N 显示为最大值)(因为每个 X_i 取值 0 或 1)。

我什么时候出现最大值?它是当它首先出现在 i、i+1、i+2、...、N 中的数组中时。这种概率是 1/(N-i+1)(因为这些数字中的每一个都同样可能成为第一)。

所以... prob(i 出现一个最大值) = 1/(N-i+1),总体期望是 1/N + 1/(N-1) + ..+ 1/3 + 1/2 + 1/1

这是谐波 (N),它由 ln(N) + emc 非常近似,其中 emc ~= 0.5772156649,欧拉-马斯切罗尼常数。

由于在问题中您没有将最大值的初始设置计为第一个值,因此实际答案是 Harmonic(N) - 1,或大约 ln(N) - 0.4227843351。

快速检查一些简单的案例:

  • N=1,只有一个排列,没有最大更新。谐波 (1) - 1 = 0。
  • N=2,排列为 [1, 2] 和 [2, 1]。第一个更新最大值一次,第二个零次,所以平均值是 1/2。谐波 (2) - 1 = 1/2。
  • N=3,排列为 [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [ 3、2、1]。最大更新分别为 2、1、1、1、0、0。平均值为 (2+1+1+1)/6 = 5/6。谐波 (3) - 1 = 1/2 + 1/3 = 5/6。

所以理论上的答案看起来不错!

【讨论】:

  • 这是最好的答案。也许您可以就如何得出E(h(N)) = sum(i=1..N) P(ancestor(i,N)) 给出更多解释?
  • @justhalf 解释并删除了关于二叉树的部分:将问题视为排列更容易。
【解决方案2】:

经验解

可以执行和分析许多不同阵列大小的模拟,每个试验都有多个试验:

#include <iostream>
#include <fstream>
#include <cstdlib>
#define UPTO 10000
#define TRIALS 100

using namespace std;

int arr[UPTO];

int main(void){
  ofstream outfile ("tabsep.txt");
  for(int i = 1; i < UPTO; i++){
    int sum = 0;
    for(int iter = 0; iter < TRIALS; iter++){
      for(int j = 0; j < i; j++){
        arr[j] = rand();
      }
      int max = arr[0];
      int times_changed = 0;
      for(int j = 0; j < i; j++){
        if (arr[j] > max){
          max = arr[j];
          times_changed++;
        }
      }
      sum += times_changed;
    }
    int avg = sum/TRIALS;
    outfile << i << "\t" << avg << "\n";
    cout << "\r" << i;
  }
  outfile.close();
  cout << endl;
  return 0;
}

当我绘制这些结果时,复杂性似乎是对数的:


我认为可以确定时间复杂度为 O(log n)


理论解:

  • 假设数字在 0...n 范围内
  • 你有一个暂定的最大 m
  • 下一个最大值将是 m+1...n 范围内的随机数,其平均值为 (m+n)/2
  • 这意味着每次您找到一个新的最大值时,您都将可能的最大值范围除以 2
  • 重复除法相当于一个对数
  • 因此找到新最大值的次数为 O(log n)

【讨论】:

  • 另外,如果你能给我算法的理论描述,平均步数是 log(n)
  • 我想每次你发现一个大于最大值的数字时,你都会减少可能大于最大值的数字数量。因此,它变成了重复除法 AKA 对数。
  • @notsogeek 我编辑了我的答案以添加直观的解释。
  • 我没听懂...不应该是 m...n 范围内的随机数,下一个范围内所有元素的概率为 1/(n-m)。
  • 因为下一个最大值必须在 m+1...n 的范围内,所以它的平均值是 (m+n)/2。要点是新的最大值将后续最大值的可接受范围缩小了一些因素。
【解决方案3】:

最坏的情况(通常是寻求的)是 O(n)。如果列表以相反的顺序排序,每一个都会产生一个赋值。

但是,如果您的分配是最昂贵的操作,为什么不只存储它的索引并且只复制一次,如果有的话?在这种情况下,您将有 1 次分配和 n-1 次比较。

【讨论】:

    猜你喜欢
    • 2013-01-26
    • 2014-01-17
    • 2013-05-23
    • 2021-05-03
    • 2019-08-11
    • 2021-05-31
    • 2013-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多