【问题标题】:How could I rearrange an array to get the minimum, median and maximum faster?如何重新排列数组以更快地获得最小值、中值和最大值?
【发布时间】:2015-07-03 05:44:24
【问题描述】:

我想重复重新排列一个数组或std::vector,以便最小值是第一个元素,最大值是最后一个元素,arr[(0+lastIdx)/2] 是中位数,中位数之前的元素小于中位数,中位数之后的元素会更大。每次查询最小值、最大值和中值后,我都会对数据进行更改,我想再次快速查询这三个值。

每次我想重新排列数组时,数组都是一个大小相同的不同数组。

使用std::nth_element 我可以在正确的位置获得中位数,然后我可以迭代数组以获得最小值和最大值。对于单个数组,这实现了 O(n) 复杂度,显然这无法改进。 (除了O(n)前面的复杂度常数)

我需要对数组进行操作,首先我重新排列数组,然后再做一些其他的事情,这会使排列后的数组再次完全未排列,但不会插入新值。然后,我一遍又一遍地重复这个过程。

【问题讨论】:

  • 所以基本上......你想对一个数组进行排序。
  • @DavidGrinberg 不,他不想排序,因为它可以比排序更快地完成(排序可以在O(n log n) 中完成,找到最小值/最大值/中值可以在@987654325 中完成@甚至更快(不知道如何)
  • @FalconUA,我认为O(n) 可能只是最佳复杂度,我只是想知道我是否可以获得更好的常数因子。
  • 所有重排都会发生在同一个数组中(添加或删除一些元素)?多久一次?
  • 如果只是洗牌,那么最小值、最大值和中值不会改变,这可以简化问题。

标签: c++ algorithm


【解决方案1】:

O(n) 似乎是你可以做的最好的排列数组,你做的方式似乎很好。

在你的最后一段中,你声明你对数组做了一些事情,使它再次处于随机顺序,但没有添加新值。也许不像一些人建议的那样创建一个堆,仅仅制作一个排列好的数组的副本还不够吗?

专业人士:您只需排列一次数组。

缺点:你需要两倍的内存。

【讨论】:

  • 正确阅读问题,提问者也提供O(n)的解决方案,比std::sort快得多
  • 不,我只希望最小/最大/中位数在正确的位置,使用std::nth_element,我已经可以在 O(n) 中完成工作,而对数组进行排序需要 O(nlgn) 时间.
  • 那么,您已经知道最小值、最大值和中位数了吗?问题只是,以正确的顺序获取元素?
  • have to sort the vector 不,你没有
【解决方案2】:

如果你有很多内存,你可以使用两个堆来存储中位数和两个变量来存储最小值/最大值:

优点:

  • MinimumMaximum可以在O(1)更新;
  • 中位数可以在O(log N)更新;

缺点:

  • 您将需要额外的内存,N 用于两个堆(每个 N/2)和 N 用于您的向量 - 2N 内存而不是 O(N) 解决方案中的 N .

C++ 中有一个内置的 STL 函数可以帮助您轻松地构建和更新堆:std::make_heapstd::push_heapstd::pop_heap

here 描述了有关如何使用两个堆来查找中值的更多详细信息。

好吧,您还可以变得更棘手 - 使用两个向量来保存您的数据。在这种情况下,您只需要 N 个内存,但您的数据将被分成两个向量。

【讨论】:

  • 我觉得你需要解释一下
  • 这两个堆实际上不需要额外的内存,您可以使用相同的输入数组来执行此操作,将较低的堆存储在开头,将较高的堆存储在末尾。
  • 这实际上并不能解决所有的 OP 问题 - 他仍然需要一个数组,其中元素 1(size/2) 小于中位数,元素 (size/2)size 更多.
  • @DavidGrinberg 是的,他将需要大量额外的内存,或者可能会有点棘手并使用这些堆来存储元素而不是存储在一个数组中。
  • 我提供的链接解释了如何使用最小和最大堆来跟踪中值,这应该涵盖所有 OP 问题
【解决方案3】:

当然你不能比 O(N) 更快,因为你必须查看所有元素甚至找到最小值。

当然,您可以谈论在 O(N) 复杂度内优化代码(即优化 N 之前的常量)。

或者你会在稍微修改的数组上多次执行相同的操作吗?

【讨论】:

  • 使用堆可以比 O(N) 做得更好。是的,您需要浏览所有元素一次。但是每次插入只是(logN)并且检索是O(1)。当然,这取决于插入是否比读取更频繁,或者相反,但这是可能的!
  • @SamyArous,问题作者从未说过任何关于插入或检索的事情。他问的是给一个数组重新排列它。你不能比 O(N) 更快。
  • @SamyArous 如果他需要在稍微修改过的数组上运行几次,这就是我最后一段的内容。
  • 是的,他基本上说他们需要多次重新排列,这对我来说意味着插入+删除。错过了你的最后一段,很抱歉。
【解决方案4】:

即使您设法将查找最小值、最大值、中值的成本降低到零, 您仍然需要将错位的元素放在中位数下方或上方。这意味着每次都是n/2 - 1 permutations 的最坏情况。

  1. 第一遍,求最小值及其位置(O(n)时间,O(1)空间)
  2. 第二遍,求最大值及其位置(O(n)时间,O(1)空间)
  3. 第三遍,find the median 及其使用median of median algorithm 的位置(O(n) 时间,O(1) 空格)
  4. 通过交换将最小值、最大值、中值放在各自的位置(0、n-1、n/2)
  5. 现在您有两个索引:一个从0 开始在下面,一个从n-1 开始在上面。虽然当前below 元素在它应该在的位置,但增加该索引。当当前above 元素在它应该在的位置时,减少该索引。当找到错位的元素时,上下交换。重复below < above。 (O(n)时间,O(1)空间)

第 4、5 步的逻辑:应该在中位数以下的元素数量正好是在上面和应该在下面的元素数量。

当然你可以将 pass 1, 2 ,3 合并到一个函数中,但这不会影响复杂度

让我们运行:

{3, 7, 9, 6, 8, 1, 4, 5, 2}

通过 1、2、3:min_pos = 5, max_pos = 2, median_pos = 7, median = 5

swap (0, min_pos)    -> {1, 7, 9, 6, 8, 3, 4, 5, 2}
swap (9, max_pos)    -> {1, 7, 2, 6, 8, 3, 4, 5, 9}
swap (4, median_pos) -> {1, 7, 2, 6, 5, 3, 4, 8, 9}

现在below_pos = 0above_pos = 8。元素不会错位。 下一个错位是位置 1 的 7。下一个错位的上方是位置 6 的 4。

swap (1, 6) -> {1, 4, 2, 6, 5, 3, 7, 8, 9}

下一个错位是 6 在位置 3。下一个错位是 3 在位置 5。

swap (3, 5) -> {1, 4, 2, 3, 5, 6, 7, 8, 9}

以及算法输出

{1、4、2、3、5、6、7、8、9}

【讨论】:

  • 你不会同时进行第一步和第二步吗?
  • @NathanOliver 复杂度是一样的。
  • @UmNyobe 在每次插入后您将如何跟踪中位数?如果它们被删除,那么 min-max 呢?还是在这些情况下您必须重建索引?
  • 鉴于 min、max 和 median 是它们应该在排序序列中的位置,您永远不会在第 5 步中交换它们的索引。希望我理解正确。
  • @NathanOliver 在这两种情况下实际上都是 O(2n) 。您需要在这两种情况下进行 2n 次比较。
【解决方案5】:

如果您进行许多更改和多次查找,您可以在 O (log n sqrt (n)) 摊销时间内完成此操作。

最初,您对数组进行排序。然后提取最小的 sqrt (n)、最大的排序 (n) 和中位数周围的 sqrt (n) 值。所以你会知道“有 n1 个元素

要获得最小值、最大值或中值,您需要检查 sqrt (n) 个元素。

当您对数组元素进行更改,或者添加或插入一个元素时,您根据发生的情况调整值 n1、n2、n3 和 n4,以及数字 0 到 n1、n2 到 n3 的列表,和 n4 到 n-1。在进行太多更改后,数字 n1、n2、n3、n4 将被更改,因此搜索 min、max、medium 需要太长时间或不再有任何用处。发生这种情况时,您再次对数据进行排序。

顺便说一句。我认为堆的想法会更好。

【讨论】:

    猜你喜欢
    • 2012-09-09
    • 2010-11-23
    • 2018-10-30
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 2017-05-24
    • 1970-01-01
    • 2015-09-10
    相关资源
    最近更新 更多