找到阵列中最小时间的中位数

时间:2012-06-16 16:11:51

标签: c++ c algorithm math data-structures

我有一个数组可以说a = { 1,4,5,6,2,23,4,2}; 现在我必须找到数组位置的中位数从2到6(奇数总项),所以我做了什么,我已经a[1] a[5] arr[0]转到arr[4]然后我对它进行了排序并将arr[2]写为中位数。

但是每次我从一个数组输入值到另一个数组时,我的初始数组的值保持不变。其次我已经排序了,所以这个程序花了很多**time**。 所以我想知道是否有任何办法可以用less my computation time的不同方式来做。

任何网站,要了解的材料,做什么以及如何做?

6 个答案:

答案 0 :(得分:22)

使用<algorithm>中的std::nth_element,即O(N):

nth_element(a, a + size / 2, a + size);
median = a[size/2];

答案 1 :(得分:15)

可以在O(n)时间内找到中位数而不排序;执行此操作的算法称为selection algorithms

答案 2 :(得分:4)

如果您在同一个阵列上进行多个查询,那么您可以使用段树。它们通常用于执行范围最小/最大和范围求和查询,但您可以将其更改为范围中位数。

具有n个间隔的集合的分段树使用O(n log n)存储,并且可以在O(n log n)时间内构建。范围查询可以在O(log n)中完成。

范围段树中位数的示例:

您从下往上构建分段树(从上到下更新):

                    [5]
        [3]                     [7]
 [1,2]        [4]         [6]         [8] 
1     2     3     4     5     6     7     8

节点所涵盖的指数:

                    [4]
        [2]                     [6]
 [0,1]        [3]         [5]         [7] 
0     1     2     3     4     5     6     7

对于范围指数为4-6的中位数的查询将沿着这条值的路径走下去:

                    [4]
                          [5]
0     1     2     3     4     5     6     7

搜索中位数,您知道查询中的总元素数(3),该范围中的中位数将是第二个元素(索引5)。所以你基本上在搜索包含该索引的第一个节点,该节点是值为[1,2]的节点(索引0,1)。

搜索范围3-6的中位数有点复杂,因为你必须搜索恰好位于同一节点的两个索引(4,5)。

                    [4]
                                [6]
                          [5] 
0     1     2     3     4     5     6     7

Segment tree

Range minimum query on Segment Tree

答案 3 :(得分:1)

要查找少于9个元素的数组的中位数,我认为最有效的方法是使用排序算法,如插入排序。复杂性很差,但对于这样一个小数组,由于k更好的算法(如quicksort)的复杂性,插入排序非常有效。做你自己的基准测试,但我可以告诉你,插入排序比使用shell排序或快速排序更好。

答案 4 :(得分:0)

我认为最好的方法是使用中位数算法计算数组的第k个最大元素。你可以在这里找到算法的整体概念:Median of Medians in Java,在维基百科上:http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm或者只是浏览互联网。在实现期间可以进行一些一般性改进(避免在选择特定数组的中值时进行排序)。但请注意,对于少于50个元素的数组,使用插入排序比使用中位数算法的中位数更有效。

答案 5 :(得分:0)

在某些情况下,所有现有答案都有一些缺点:

  1. 对整个子范围进行排序不是很有效,因为不需要对整个数组进行排序就可以得到中位数,如果要找到多个子范围的中位数,则需要一个额外的数组。
  2. 使用std::nth_element效率更高,但它仍然会突变子范围,因此仍然需要一个额外的数组。
  3. 使用段树可以为您提供有效的解决方案,但您需要自己实现结构或使用第三方库。

由于这个原因,我发布了使用std::map并受选择排序算法启发的方法:

  1. 首先将第一个子范围中元素的频率收集到std::map<int, int>的对象中。
  2. 使用此对象,我们可以有效地找到长度为subrangeLength的子范围的中位数:

    double median(const std::map<int, int> &histogram, int subrangeLength)
    {
        const int middle{subrangeLength / 2};
        int count{0};
    
    
        /* We use the fact that keys in std::map are sorted, so by simply iterating
           and adding up the frequencies, we can find the median. */
        if (subrangeLength % 2 == 1) {
            for (const auto &freq : histogram) {
                count += freq.second;
                /* In case where subrangeLength is odd, "middle" is the lower integer bound of
                   subrangeLength / 2, so as soon as we cross it, we have found the median. */
                if (count > middle) {
                    return freq.first;
                }
            }
        } else {
            std::optional<double> medLeft;
            for (const auto &freq : histogram) {
                count += freq.second;
                /* In case where subrangeLength is even, we need to pay attention to the case when
                   elements at positions middle and middle + 1 are different. */
                if (count == middle) {
                    medLeft = freq.first;
                } else if (count > middle) {
                    if (!medLeft) {
                        medLeft = freq.first;
                    }
                    return (*medLeft + freq.first) / 2.0;
                }
            }
        }
    
        return -1;
    }
    
  3. 现在,当我们想获取下一个子范围的中位数时,我们只需通过降低要删除元素的频率来更新直方图,然后为新元素添加/增加(使用{{1} },这是在恒定时间中完成的)。现在,我们再次计算中值,并继续进行直到处理所有子范围。