固定摊销时间


Answers:


776

摊销时间用简单的术语解释:

如果您执行一百万次操作,那么您实际上并不关心该操作的最坏情况或最佳情况-您所关心的是,重复一百万次操作总共要花费多少时间。

因此,如果操作偶尔会非常缓慢,则无关紧要,只要“偶尔执行一次”足以使缓慢性被淡化就足够了。本质上,摊销时间是指“如果执行多次操作,则平均每次操作花费的时间”。摊销时间不必恒定。您可以使用线性和对数摊销时间或其他方式。

让我们以动态数组为例,向其反复添加新项目。通常,添加项目需要固定的时间(即O(1))。但是,每次阵列满时,您将分配两倍的空间,将数据复制到新区域中,并释放旧空间。假设分配和释放以恒定的时间运行,则此扩展过程将花费一些O(n)时间,其中n是数组的当前大小。

因此,每次放大时,您花费的时间是上一次放大时的两倍。但是您也已经等待了两倍的时间!因此,每次扩大的成本可以在插入物之间“摊开”。这意味着从长远来看,将m个项目添加到数组所需的总时间为O(m),因此摊销时间(即每次插入的时间)为O(1)


61
只是表示法上的一个注释:O(n)的摊销常数执行时间通常写为O(n)+,而不是O(n)。加号的加法表示不能保证执行时间为O(n),实际上可以超过该执行时间。
Jeffpowrs 2014年

1
就分配空间而言,是从堆分配的吗?
committedandroider

3
我不同意“您真的不在乎最坏的情况”。这取决于用例。如果最后您只对所引用的100万次运算的结果感兴趣,那么您实际上并不在乎。但是,如果这是一个实时应用程序,需要不断读取数据然后对其进行响应,则可能会遇到很大的问题,如果每处理一百万个数据项,处理该数据所花费的时间比正常时间长一百万倍!
凯·佩兹克

2
@Jeffpowrs我认为O(n)是线性时间,O(1)是恒定时间。那么,这是否意味着O(1)+将被摊销为固定时间,而O(n)+将被摊销为线性时间?
约翰·迈耶

1
@JohnMeyer是的。
Artelius

55

这意味着随着时间的流逝,最坏的情况将默认为O(1)或恒定时间。一个常见的例子是动态数组。如果我们已经为新条目分配了内存,则将其添加为O(1)。如果我们尚未分配,我们将通过分配例如当前数量的两倍来分配。这个特定的插入将不是 O(1),而是其他东西。

重要的是该算法保证了在一系列操作之后,昂贵的操作将被摊销,从而使整个操作成为O(1)。

或者更严格地说,

有一个常数c,因此对于 长度为L的每个操作序列(也是以昂贵的操作结束的序列),时间不大于c * L(谢谢RafałDowgird


11
“经过大量的操作后”-恒定的摊销时间不需要此条件。有一个常数c,因此对于长度为L的每个操作序列(也是一个以昂贵的操作结束的操作),时间不大于c * L。
2008年

这是哪里分配的两倍?我们不应该分配一个条目吗?还是一个假设的例子?
talekeDskobeDa

@talekeDskobaDa这不是一个任意示例,而是一种广泛使用的算法。如果我们按照您的建议一次分配一个条目的空间,则插入单个值的摊销时间将为O(n)。如果在空间变满时将空间加倍,则摊销时间会更好(O(1))。需要明确的是,一次为一个项目分配空间的问题是数组需要很大的连续空间块。很容易从OS中获得更大的块,但是通常不可能扩展现有的块,因为在它之后可能会直接存储一些其他数据。
Artelius

23

要开发一种直观的思考方式,请考虑在动态数组中插入元素(例如,std::vector在C ++中)。让我们绘制一个图,该图显示在数组中插入N个元素所需的操作数(Y)的依赖性:

情节

黑色图的垂直部分对应于内存的重新分配,以扩展数组。在这里我们可以看到这种依赖性可以粗略地表示为一条线。该线方程为Y=C*N + b(在我们的例子中C为常数,b= 0)。因此,可以说,我们需要C*N平均花费一些操作才能将N个元素添加到数组中,或者C*1需要添加一个元素来添加操作(摊销固定时间)。


14

重复阅读3次后,我发现以下Wikipedia解释很有用:

资料来源:https : //zh.wikipedia.org/wiki/Amortized_analysis#Dynamic_Array

“动态阵列

动态数组的Push操作的摊销分析

考虑一个动态数组,该数组随着更多元素的添加而增大,例如Java中的ArrayList。如果我们从大小为4的动态数组开始,则需要花费恒定的时间将四个元素压入其中。然而,将第五个元素推到该数组上将需要更长的时间,因为该数组将必须创建一个两倍于当前大小(8)的新数组,将旧元素复制到新数组上,然后添加新元素。接下来的三个推入操作将类似地花费恒定的时间,然后随后的添加将需要将阵列大小再慢一倍。

通常,如果我们考虑对大小为n的数组进行任意次的推送n,我们会注意到推送操作需要恒定的时间,最后一个操作需要O(n)时间来执行大小加倍操作。由于总共进行了n次操作,我们可以取其平均值,然后发现将元素推入动态数组需要花费:O(n / n)= O(1),恒定时间。”

据我了解,这是一个简单的故事:

假设你有很多钱。而且,您想将它们堆叠在一个房间中。而且,只要您现在或将来需要,您的手和腿就很长。而且,您必须将所有房间都填满,所以很容易将其锁定。

因此,您直接进入房间的尽头/角落并开始堆叠它们。当您堆叠它们时,房间会慢慢用完空间。但是,在填充时很容易将它们堆叠起来。得到了钱,把钱放了。简单。是O(1)。我们不需要动用任何以前的钱。

一旦房间空间不足。我们需要另一个更大的房间。这里有一个问题,因为我们只能拥有1个房间,所以我们只能拥有1个锁,所以我们需要将该房间中所有现有的钱转移到新的更大的房间中。因此,将所有资金从小房间转移到更大的房间。也就是说,再次堆叠所有它们。因此,我们确实需要转移所有之前的资金。因此,它是O(N)。(假设N是前一金钱的总数)

换句话说,这很容易直到N次,只有1次操作,但是当我们需要移到更大的房间时,我们进行了N次操作。因此,换句话说,如果我们平均下来,则一开始是1个插入,而移到另一个房间时又移动了1个。总共2次操作,一次插入,一次移动。

假设即使在很小的房间中,N也像100万一样大,与N(100万)相比,这2次运算实际上不是一个可比较的数字,因此它被认为是常数或O(1)。

假设当我们在另一个更大的房间中执行上述所有操作时,又需要移动。还是一样。例如,N2(例如10亿)是更大房间中的新货币数量

因此,我们有N2(因为我们从较小的房间移到较大的房间,所以它包括以前的N)

我们仍然只需要2个操作,一个是插入到更大的房间,然后另一个移动到另一个更大的房间。

因此,即使对于N2(10亿),每个也要进行2次运算。再也没有了。因此,它是常数或O(1)

因此,随着N从N增加到N2或其他,这无关紧要。它仍然是常量,或者是每个N所需的O(1)运算。


现在假设,您的N为1,非常小,货币数量很小,并且您有一个很小的房间,只能容纳1个货币数量。

一旦您将钱填满房间,房间就会被填满。

当您进入更大的房间时,假设它只能容纳多一钱,总共2钱。这就是说,前一个已转移资金和另外1个。并再次填充。

这样,N正在缓慢增长,并且不再是常数O(1),因为我们将所有资金从先前的房间移走,但只能再容纳1个钱。

经过100次之后,新房间可以容纳以前的100笔钱,可以容纳1笔钱。这是O(N),因为O(N + 1)是O(N),即100或101的度数相同,所以两者都是数百个,与以前的说法相反,分别为百万和十亿。

因此,这是为我们的钱(变量)分配空间(或内存/ RAM)的低效率方法。


因此,一个好的方法是分配更多的空间(2的幂)。

第一个房间大小=可以容纳1个钱
第二个房间大小=可以容纳4个钱
第三个房间大小=可以容纳8个钱
4个房间大小=可以容纳16个钱
第五个房间大小=可以容纳32个钱
第六个房间大小=适合64个货币
第七个房间大小=可容纳128个货币
第八个房间大小=可容纳256个货币
第九个房间大小=可容纳512个货币
10个房间大小=可容纳1024个货币
第11个房间大小=可容纳2,048个货币
。 ..第
16个房间的大小=可以容纳65,536个货币
...
第32个房间的大小=可以容纳4,294,967,296货币
...
第64个房间的大小=可以容纳18,446,744,073,709,551,616个货币

为什么这样更好?因为与起初我们的RAM中的内存量相比,它在一开始看起来增长缓慢,而后来则更快。

这是有帮助的,因为在第一种情况下虽然很好,但是每笔钱要完成的工作总量是固定的(2),不能与房间大小(N)相提并论,因此我们在初始阶段占用的房间可能也太可能无法充分使用的大笔资金(100万),这取决于我们是否有足够的钱来节省第一种情况。

但是,在最后一种情况下,2的幂会在我们的RAM的范围内增长。因此,乘以2的幂增加,既使Armotized分析保持不变,而且对于我们目前拥有的有限RAM也很友好。


2
嗯,所以是O(最坏情况/操作次数)。我最喜欢这个答案。
核苷

1

上面的解释适用于聚合分析,即对多个操作取“平均值”的想法。我不确定它们如何应用于银行家方法或“摊销法”中的“物理学家方法”。

现在。我不确定正确答案。但这与物理学家+银行家的两种方法的基本条件有关:

(运营摊销成本之和)> =(实际运营成本之和)。

我面临的主要困难是,鉴于运营的摊销渐近成本与正常的渐近成本不同,我不确定如何对摊销成本的重要性进行评估。

那就是当有人给我摊销成本时,我知道它与正常渐近成本不一样。那么我从摊销成本中得出什么结论呢?

由于我们遇到了某些操作被高估而其他操作被低估的情况,因此一种假设可能是,引用单个操作的摊余成本是没有意义的。

例如:对于斐波那契堆,将减少密钥的摊销成本报价为O(1)是没有意义的,因为“通过早期操作在增加堆潜力方面所做的工作”可以降低成本。

要么

我们可能有另一个假设,说明有关摊销成本的原因,如下所示:

  1. 我知道,昂贵的操作之前将有多个LOW-COST操作。

  2. 为了分析起见,我将对一些低成本的操作收取过多的费用,以使它们的渐近成本不变。

  3. 随着这些低成本运营的增加,我可以证明昂贵的运营具有渐近的渐进成本。

  4. 因此,我已经改进/减少了n个运算成本的渐近边界。

因此,摊销成本分析+摊销成本界限现在仅适用于昂贵的工序。廉价操作的渐近摊销成本与正常渐进成本相同。


有趣的想法。
Lonnie Best

0

可以通过将“函数调用总数”除以“进行所有这些调用所花费的总时间”来平均任何函数的性能。即使是每次调用花费的时间越来越长的函数,也可以通过这种方式进行平均。

因此,执行的功能的实质Constant Amortized Time是,随着调用次数的不断增加,该“平均时间”达到一个上限,该上限不会超过。任何特定调用的性能可能会有所不同,但从长远来看,这种平均时间不会持续增长。

这是表现出色的本质优点Constant Amortized Time

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.