【问题标题】:Dynamic Array with O(1) removal of any element删除任何元素的 O(1) 动态数组
【发布时间】:2009-06-29 08:22:00
【问题描述】:

这个问题是关于我想到的一个数据结构。它是一个动态数组,类似于 C++ 中的 std::vector,只是删除算法不同。

在一个普通的动态数组中,当一个元素被移除时,剩下的所有元素都必须下移,即O(n),除非是最后一个元素,即O(1)。

在这一个中,如果有任何元素被移除,则将其替换为最后一个元素。这当然会失去元素的顺序。但是现在移除任何元素都是常数时间。

一个列表将具有相同的删除时间,但此结构具有随机访问权限。唯一需要注意的是你不知道你正在访问什么,因为排序可能会很混乱,所以无论如何使用随机访问。另外,列表不会弄乱任何指向元素的指针/迭代器。

嗯,这个结构似乎没什么用,除了严格遍历元素并可能沿途移除它们的非常具体的任务。列表也可以做同样的事情,但缓存性能更好。

那么,这个奇怪/无用的结构有名字吗?它有什么用途吗?还是只是一场不错的小头脑风暴?

【问题讨论】:

  • 通常(IMO),当我们需要随机访问时,我们也需要排序。不过,这个想法非常有趣。

标签: algorithm data-structures


【解决方案1】:

Knuth (Fisher–Yates) shuffle 中使用了这个想法。随机选取的元素将替换为数组中的最后一个元素。由于无论如何我们想要的是随机排列,因此重新排序并不重要。

【讨论】:

  • 重新排序确实很重要;这就是最终排列的均匀随机性的来源!
  • @ShreevatsaR:我的意思是尚未选择的元素的重新排序 - 它们无论如何都会重新排序,因此通过选择其他元素引入的顺序更改无关紧要。当然,这需要(一个简单的)证明这种重新排序不会真正影响最终分布的均匀性。
  • 谢谢,我选择了这个,因为它最接近我的想法,而且它让我觉得我几乎和 Knuth 一样聪明。
【解决方案2】:

那么,这个奇怪/无用的结构有名字吗,有什么用途吗?

我在多进程系统的模拟中使用了类似的东西。

在作为状态机实现的进程的调度程序中,每个进程要么等待外部事件,要么处于活动状态,要么已完成。调度程序有一个指向进程的指针数组。

最初每个进程都是活动的,调度程序有最后一个等待和第一个完成的进程的索引,最初为零和数组的长度。

V-- waiting
[ A-active, B-active, C-active, D-active ]
                             completed --^
^- run

为了让进程进入下一个状态,调度程序遍历数组并依次运行每个进程。如果一个进程报告它正在等待,它将与数组中最后一个等待进程之后的进程交换。

           V-- waiting
[ A-waiting, B-active, C-active, D-active ]
                              completed --^
             ^- run

如果它报告它已经完成,它会与第一个完成数组之前的进程交换。

           V-- waiting
[ A-waiting, D-active, C-active, B-completed ]
                   completed --^
             ^- run

因此,当调度程序运行并且进程从活动转换为等待或完成时,数组变得有序,所有等待的进程在开始,所有活动在中间,完成的在结束。

                      V-- waiting
[ A-waiting, C-waiting, D-active, B-completed ]
                   completed --^
                        ^- run

经过一定次数的迭代,或者当没有更多的活动进程时,将完成的进程从数组中清除并处理外部事件:

                      V-- waiting
[ A-waiting, C-waiting, D-completed, B-completed ]
          completed --^
                        ^- run == completed so stop

这类似,因为它使用交换从集合中移除项目,但它是从两端移除项目,而不是将“集合”留在中间。

【讨论】:

    【解决方案3】:

    我记得以前用过这种方法很多次。但我不知道它的名字。

    简单示例:在计算机游戏中,您正在迭代所有“坏人”并计算他们的动作等。可能发生在他们身上的一件事是消失(他们的尸体已经消失,现在 99% 透明)。此时,您可以像以前一样将其从列表中删除,并在不增加迭代计数器的情况下恢复迭代器。

    删除项目时在Binary Heap 中完成类似的操作,但下一步是维护堆规则 - O(log n)。

    【讨论】:

      【解决方案4】:

      我不知道它的名字,但在某些情况下它比列表更好。

      特别是,对于非常小的数据,这将大大优于单链表或双链表。 因为您连续存储所有内容,所以每个元素没有额外的指针开销。

      【讨论】:

        【解决方案5】:

        嗯,那个算法真的有 O(1) 的移除时间吗?

        这意味着

        1. 找到要删除的元素是 O(1)
        2. 找到最后一个元素(将替换已删除的元素)是 O(1)
        3. 查找倒数第二个元素(新的“最后一个”元素)是 O(1)

        ...这在我能想到的任何数据结构中都是不可能的。尽管双链表可以满足这些约束条件,但前提是您已经有了一个指向要删除的元素的指针。

        【讨论】:

        • 什么?删除时间与搜索时间无关。所以是的,这有 O(1) 的删除时间。
        • 将数组 SIZE 的大小和指针 PTR 存储在表示数组的结构中。 (1) 是 PTR+n,其中 n 是要移除的元素,即 O(1)。 (2) 是 PTR+SIZE,即 O(1)。 (3) 是 PTR+(SIZE-1),可能通过 SIZE 实现——但仍然是 O(1)。
        • 标准数组?我相信 GMan 的意思是按索引而不是按价值删除。
        • 啊,当然。我假设按价值删除,这就是最近工作中的相关内容。
        【解决方案6】:

        它被称为Set

        【讨论】:

        • 通常,移除集合元素是O(log(n))。
        猜你喜欢
        • 2018-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多