我被要求设计一个数据结构,它将像堆栈一样,不受大小的限制,在给定的运行时限制下,它将支持以下方法。
推送 - 推送到数据结构 - O(1)
pop() - 删除并返回插入的最后一个元素 O(1)
middle() - 按插入顺序返回索引为n / 2的元素(不删除),其中n是数据结构中当前元素的数量。 - O(1)
peekAt(k) - 按插入顺序返回第k个元素(堆栈底部为k = 1) - O(log(k))
我想过使用链表,并始终保持指向中间元素的指针,但后来我遇到了实现peekAt(k)的问题。任何想法如何实现这个?
答案 0 :(得分:3)
如果O(1)限制可以放宽到摊销O(1),那么典型的可变长度数组实现就可以了。为当前长度为N的数组分配空间时,保留最后N个额外空间。一旦你超越这个边界,按照相同的策略重新分配新的大小,复制那里的旧内容并释放旧的内存。当然,您必须保持堆栈的已分配长度和实际长度。操作middle
和peekAt
可以在O(1)中完成。
相反,如果需要的话,如果阵列占用的阵列少于分配空间的1/4,也可以缩小阵列。
所有操作将摊销O(1)。这一点的确切含义是,对于自启动以来的任何K堆栈操作,您必须总共执行O(K)指令。特别是,N次推送后的重新分配数量为O(log(N)),由于重新分配而复制的元素总量不会超过1 + 2 + 4 + 8 ... + N <= 2N = O(N)。
这可以渐进地完成,对于每个操作都需要非摊销的O(1),前提是内存管理器的allocate
和free
在O(1)中执行任何大小。基本思想是保持当前分配的堆栈和2x更大的未来堆栈,并提前开始准备更大的副本。每次将值推送到当前堆栈时,请将另外两个元素复制到将来的堆栈中。当前堆栈已满时,其所有元素将已复制到将来的堆栈中。之后,丢弃当前堆栈,声明未来堆栈现在是当前堆栈,并分配新的未来堆栈(当前为空,但分配比当前堆栈大2倍)。
如果您还需要缩小,当您的堆栈占用分配空间的1/2到1/4时,您可以以类似的方式维护较小的副本。
正如您在描述中所看到的,虽然这在理论上可能更好,但它通常较慢,因为它必须维护堆栈的两个副本而不是一个。但是,如果您对每个操作都有严格的实时O(1)要求,则此方法非常有用。
答案 1 :(得分:0)
使用双向链表的实现对我来说很有意义。 Push
和Pop
将被实现,因为它通常是为堆栈完成的;访问中间&#39;元素将使用另一个引用来完成,该引用将在Push
和Pop
上更新,具体取决于所包含元素的数量是否会从偶数变为奇数,反之亦然。可以使用二分查找来完成peekAt
操作。