【问题标题】:find median with minimum time in an array在数组中找到最短时间的中位数
【发布时间】:2012-06-16 16:11:50
【问题描述】:

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

但是每次我将值从一个数组放到另一个数组时,我的初始数组的值都保持不变。其次,我已经排序了,所以这个过程花费了很多 **time**。 所以我想知道是否有任何方法可以与reduce my computation time 不同。

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

【问题讨论】:

  • 你是如何对数组进行排序的?
  • 我正在使用内置算法

标签: algorithm math data-structures


【解决方案1】:

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

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

【讨论】:

  • 注意:这是一个可变算法,它可能会重新排序一些其他项目。
  • 但是因为它扭曲了数组,我必须制作我必须排序的数组的副本,这需要很多时间,我可以做些什么来解决这个问题
【解决方案2】:

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

【讨论】:

【解决方案3】:

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

具有 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

【讨论】:

  • +1,如果在同一个数组上进行多个查询,这是要走的路。
  • @ffao,justin, 你能告诉更多关于如何对分段树进行范围中值查询吗?
  • @A.06 我添加了范围最小值的示例,但它可以很容易地适应范围中值。
  • 但是在范围最小值查询中,我们可以找到多个子范围中的最小值并取其中的最小值,但我认为在中位数的情况下不会相同,因为中位数在一个范围不一定是其子范围中位数的中位数。
  • @A.06 我添加了一个中间段树示例。
【解决方案4】:

要找到少于9个元素的数组的中位数,我认为最有效的方法是使用插入排序之类的排序算法。复杂度很差,但是对于这么小的数组,因为k中的复杂度更好的算法比如快速排序,插入排序是非常高效的。做你自己的基准测试,但我可以告诉你,插入排序会比 shell 排序或快速排序有更好的结果。

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      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. 1234563这是在恒定时间中完成的)。现在我们再次计算中位数并继续此操作,直到我们处理所有子范围。

      【讨论】:

        猜你喜欢
        • 2012-05-28
        • 2013-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-07
        • 2012-04-15
        相关资源
        最近更新 更多