O(log n)中的中值算法

时间:2010-09-03 00:42:23

标签: algorithm median

我们如何删除时间复杂度为O(log n)的集合的中位数?有些想法?

9 个答案:

答案 0 :(得分:18)

如果对该集合进行排序,则查找中位数需要O(1)个项目检索。如果项目是任意顺序的,则无法在不检查大部分项目的情况下确定地确定中位数。如果一个人检查了大部分但不是全部的项目,那么将允许人们保证中位数将在某个范围内[如果列表包含重复项,则上限和下限可能匹配],但检查大多数项目列表中的项目意味着O(n)项目检索。

如果一个集合中的信息没有完全排序,但已知某些排序关系,那么所需的时间可能需要在O(1)和O(n)项目检索之间的任何地方,具体取决于已知的排序关系。

答案 1 :(得分:5)

对于未排序的列表,重复执行O(n) partial sort,直到知道位于中间位置的元素。但这至少是 O(n)

是否有关于要排序的元素的信息?

答案 2 :(得分:4)

对于一般的未排序集,不可能在O(n)时间内可靠地找到中值。您可以在O(1)中找到有序集的中位数,或者您可以在O(n log n)时间内自己对集合进行排序,然后在O(1)中找到中位数,得到O(n logn n)算法。或者,最后,有更聪明的中值选择算法可以通过分区而不是排序并产生O(n)性能来工作。

但是如果集合没有特殊属性并且您不允许任何预处理步骤,那么您将永远不会低于O(n),因为您需要至少检查一次所有元素以确保你的中位数是正确的。

答案 3 :(得分:4)

这是基于TreeSet的Java解决方案:

public class SetWithMedian {
    private SortedSet<Integer> s = new TreeSet<Integer>();
    private Integer m = null;

    public boolean contains(int e) {
        return s.contains(e);
    }
    public Integer getMedian() {
        return m;
    }
    public void add(int e) {
        s.add(e);
        updateMedian();
    }
    public void remove(int e) {
        s.remove(e);
        updateMedian();
    }
    private void updateMedian() {
        if (s.size() == 0) {
            m = null;
        } else if (s.size() == 1) {
            m = s.first();
        } else {
            SortedSet<Integer> h = s.headSet(m);
            SortedSet<Integer> t = s.tailSet(m + 1);
            int x = 1 - s.size() % 2;
            if (h.size() < t.size() + x)
                m = t.first();
            else if (h.size() > t.size() + x)
                m = h.last();
        }
    }
}

删除中位数(即“s.remove(s.getMedian())”)需要O(log n)时间。

编辑:为了帮助理解代码,这里是类属性的不变条件:

private boolean isGood() {
    if (s.isEmpty()) {
        return m == null;
    } else {
        return s.contains(m) && s.headSet(m).size() + s.size() % 2 == s.tailSet(m).size();
    }
}

以人类可读的形式:

  • 如果设置“s”为空,则必须为“m” 空。
  • 如果设置“s”不为空,则必须 包含“m”。
  • 设x为元素数 严格小于“m”,让y成为 元素的数量大于 或等于“m”。然后,如果总计 元素的数量是偶数,x必须是 等于y;否则,x + 1必须是 等于y。

答案 4 :(得分:4)

尝试Red-black-tree。它应该工作安静,使用二进制搜索,你得到你的日志(n)。它还具有log(n)的删除和插入时间,并且在log(n)中也可以进行重新平衡。

答案 5 :(得分:2)

我知道一种时间复杂度为O(n)的随机算法。

以下是算法:

输入:n个数组的数组A [1 ... n] [不失一般性我们可以假设n是偶数]

输出:排序数组中的第n / 2个元素。

算法(A [1..n],k = n / 2):

从1 ... n

中随机选择一个转轴 - p

将数组分为两部分:

L - 具有元素&lt; = A [p]

R - 具有元素&gt; A [p]

if(n / 2 == | L |)A [| L | + 1]是中位数

if(n / 2&lt; | L |)重新诅咒(L,k)

否则重新诅咒(R,k - (| L | + 1)

复杂性:  上)  证明都是数学的。一页长。如果你有兴趣打我。

答案 6 :(得分:2)

当然,尤达大师的随机算法的最小复杂度n与任何其他算法一样,n的预期复杂度( not log n )和像Quicksort这样的n平方的最大复杂度。它仍然非常好。

在实践中,“随机”枢轴选择有时可能是固定位置(不涉及RNG),因为已知初始数组元素足够随机(例如,不同值的随机排列,或独立且相同分布)或从输入值的近似或确切已知分布推导出。

答案 7 :(得分:2)

如前面的答案所述,没有触及数据结构的每个元素都无法找到中位数。如果您要查找的算法必须按顺序执行,那么您可以做的最好的是O(n)。确定性选择算法(中位数中值)或BFPRT算法将解决O(n)的最坏情况下的问题。您可以在此处找到有关此内容的更多信息:http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm

然而,中位数算法的中位数可以比O(n)运行得更快,使其平行。由于它的分裂和征服性质,算法可以“轻松”并行。例如,当将输入数组除以5的元素时,您可以为每个子数组启动一个线程,对其进行排序并找到该线程中的中位数。完成此步骤后,将连接线程,并使用新形成的中位数阵列再次运行算法。

请注意,此类设计仅对非常大的数据集有益。产生线程的额外开销和合并它们使得它对于较小的集合来说是不可行的。这有一点见解:http://www.umiacs.umd.edu/research/EXPAR/papers/3494/node18.html

请注意,您可以找到渐近更快的算法,但它们对于日常使用来说不够实用。你最好的选择是已经提到的顺序中位数中值算法。

答案 8 :(得分:0)

扩展rwong的答案:这是一个示例代码

// partial_sort example
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;


int main () {
  int myints[] = {9,8,7,6,5,4,3,2,1};
  vector<int> myvector (myints, myints+9);
  vector<int>::iterator it;

  partial_sort (myvector.begin(), myvector.begin()+5, myvector.end());

  // print out content:
  cout << "myvector contains:";
  for (it=myvector.begin(); it!=myvector.end(); ++it)
    cout << " " << *it;

  cout << endl;

  return 0;
}

输出: myvector包含:1 2 3 4 5 9 8 7 6

中间的元素是中位数。