固定时间和摊销固定时间是否有效地等同?


16

我需要编写一个RandomQueue,以允许在恒定时间(O(1))中进行追加和随机删除。

我的第一个想法是使用某种Array(我选择ArrayList)来支持它,因为数组可以通过索引进行恒定访问。

虽然查看文档,但我意识到ArrayLists的添加被视为摊销固定时间,因为添加可能需要重新分配基础数组O(n)。

摊销的固定时间和固定时间实际上是否相同,还是我需要查看某种结构,不需要每次添加都进行完全重新分配?

我之所以这样问是因为除了基于数组的结构(据我所知,它将始终具有摊销的固定时间),我想不出任何满足要求的东西:

  • 任何基于树的事物最多都具有O(log n)访问权限
  • 链表可能会增加O(1)(如果保留对尾部的引用),但是随机删除最多应为O(n)。

这是完整的问题;万一我对一些重要细节感到困惑:

设计并实现一个RandomQueue。这是Queue接口的一种实现,其中remove()操作将删除一个元素,该元素是在队列中当前所有元素中随机选择的。(将RandomQueue当作一个包,我们可以在其中添加元素或进入并盲目删除一些随机元素。)RandomQueue中的add(x)和remove()操作每个操作应在恒定的时间内运行。


作业是否指定如何执行随机删除?您是否有要删除的索引或对队列元素的引用?

它没有给出任何细节。需求只是实现Queue接口并具有O(1)添加和删除的结构。
Carcigenicate

顺便说一句–增长O(n)的可调整大小的数组不一定要加上O(1):这取决于我们如何增长数组。增长一个常数a仍然是O(n)进行加法(我们有1/a机会进行O(n)运算),但是增长一个常数因数a > 1是将O(1)摊销以进行加法:我们有(1/a)^n一个O 的机会(n)运算,但大的概率接近零n
阿蒙2015年

ArrayLists使用后者正确吗?
Carcigenicate

1
问题(我)的作者正在考虑分摊的固定时间解决方案。我将在下一版中对此进行澄清。(尽管最坏情况下的恒定时间可以通过使用摊销技术来实现。)
Pat Morin15年

Answers:


10

摊销恒定时间几乎总是可以被认为等同于恒定时间,并且在不了解您的应用程序的详细信息以及您打算对此队列使用的使用类型的情况下,大部分机会都将被覆盖。

数组列表的概念是 容量,它基本上等于到目前为止所需的最大大小/长度/项目数。因此,将发生的情况是,在开始向数组列表中添加数组时,数组列表将继续重新分配自身以增加其容量,但是在某些时候,每单位时间添加的平均条目数将不可避免地与平均条目数匹配每单位时间删除一次(否则您最终还是会耗尽内存),此时数组将停止重新分配自身,并且所有追加将在O(1)的恒定时间被满足。

但是,请记住,默认情况下,从数组列表中随机删除不是O(1),而是O(N),因为数组列表会将所有已删除项目后的所有项目向下移动一个位置以代替已删除项目。项目。为了实现O(1),您将必须重写默认行为,以用数组列表的最后一项的副本替换已删除的项,然后再删除最后一项,以便不移动任何项。但是,如果这样做,您将不再有队列。


1
该死,搬家好点;我没有考虑。既然我们是随机删除元素,那么从技术上来讲这是否就意味着它不再是队列了?
Carcigenicate

是的,这确实意味着您并没有真正将其视为队列。但是我不知道您打算如何查找要删除的项目。如果您的发现它们的机制希望它们按照添加的顺序出现在队列中,那么您就不走运了。如果您不关心项目的顺序是否乱码,那就很好。
Mike Nakis 2015年

2
期望我RandomQueue实现该Queue接口,并期望所提供的remove方法随机删除而不是弹出头部,因此应该没有任何方法可以依赖特定的顺序。我认为,鉴于其随机性,用户不应期望它保持任何特定顺序。我在问题中引用了作业以进行澄清。谢谢。
Carcigenicate

2
是的,那么,如果您只是确保按照我建议的方式完成项目删除,就可以了。
Mike Nakis

最后一件事,如果你不介意的话。我已经考虑了更多,而且似乎不可能同时拥有“ true” O(1)和“ true” O(1)随机删除;这将是2之间的折衷。您要么具有一个单独分配的结构(如数组),但提供删除但不添加,或者具有一个块分配的结构(如Linked-List),该结构提供了添加但不删除。这是真的?再次谢谢你。
Carcigenicate

14

这个问题似乎专门要求固定时间,而不是摊销固定时间。因此,对于引用的问题,不,它们实际上不是相同的*。但是它们是否在实际应用中?

摊销常数的典型问题是,您有时必须偿还累积的债务。因此,尽管插入通常是恒定的,但有时在分配新块时,您不得不承受重新插入所有内容的开销。

恒定时间与摊销的恒定时间之间的差异与应用有关的情况取决于这种偶尔的非常慢的速度是否可以接受。对于很多域,通常都可以。特别是如果容器具有有效的最大大小(例如缓存,临时缓冲区,工作容器),则可以有效地支付它们在执行期间的成本。

在关键应用程序中,这些时间可能无法接受。如果您需要满足短时间的周转保证,则不能依赖偶尔会超出该算法的算法。我之前曾从事过此类项目,但它们极为罕见。

这也取决于实际成本是多少。向量往往表现良好,因为它们的重新分配成本相对较低。但是,如果您使用哈希图,则重新分配可能要高得多。同样,对于大多数应用程序来说,可能还不错,尤其是寿命较长的服务器,并且容器中各项的上限。

*虽然这里有一个问题。为了使任何通用容器成为恒定的插入时间,必须满足以下两个条件之一:

  • 容器必须具有固定的最大尺寸;要么
  • 您可以假设单个元素的内存分配是恒定时间。

“肝脏服务器”似乎在这里使用很奇怪。您是说“实时服务器”吗?
Pieter Geerkens 2015年

6

这取决于–是针对吞吐量还是针对延迟进行优化:

  • 对延迟敏感的系统需要一致的性能。对于这种情况,我们必须强调系统的最坏情况行为。例如,实时的软实时系统,例如想要获得一致帧率的游戏,或必须在一定紧迫的时间范围内发送响应的Web服务器:浪费CPU周期总比迟到好。
  • 吞吐量经过优化的系统无需担心偶尔的停顿,只要可以长期处理最大数量的数据即可。在这里,我们主要对摊销业绩感兴趣。号码处理或其他批处理作业通常是这种情况。

请注意,一个系统可以具有不同的组件,必须对它们进行不同的分类。例如,现代的文本处理器将具有对延迟敏感的UI线程,但对诸如拼写检查或PDF导出等其他任务的吞吐量进行了优化的线程。

同样,算法复杂性通常没有我们想像的那么重要:当一个问题被限制到一定数量时,那么实际的和测量的性能特征比“对于非常大的n ” 的行为更为重要。


不幸的是,我的背景很少。问题结尾为:“ RandomQueue中的add(x)和remove()操作应在每个操作中以恒定的时间运行”。
Carcigenicate

2
@Carcigenicate,除非您知道系统是延迟敏感的事实,否则使用摊余的复杂度来选择数据结构就足够了。
阿蒙2015年

我觉得这可能是编程练习或测试。当然不是一件容易的事。绝对正确,这很少有关系。
gnasher729

1

如果要求您提供“摊销固定时间”算法,则您的算法有时可能花费很长时间。例如,如果您在C ++中使用std :: vector,则此类矢量可能已为10个对象分配了空间,而当您分配第11个对象时,将为20个对象分配空间,将复制10个对象,并添加第11个对象,需要相当长的时间。但是,如果添加一百万个对象,则可能有999,980个快速操作和20个缓慢的操作,平均时间很快。

如果要求您提供“恒定时间”算法,则对于每个操作,您的算法必须始终保持快速状态。这对于实时系统非常重要,在实时系统中,您可能需要确保每个操作始终保持快速。通常不需要“固定时间” ,但绝对不同于 “摊销固定时间”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.