Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Amortized Analysis: Aggregate and Accounting Methods

Recommended resources

Hello! Welcome to the final lesson in our module on complexity analysis.

In our previous lessons, we've focused on worst-case analysis of individual algorithms, using tools like Big-O notation, recurrence relations, and the Master Theorem. This approach is fundamental, but sometimes it can be overly pessimistic. It tells us the cost of the most expensive single operation, but doesn't capture the full picture when operations have varying costs within a sequence.

Today, we address this by exploring a different perspective. The learning outcome is to explain amortized analysis using the aggregate and accounting methods. This technique allows us to determine the average cost per operation over a sequence in the worst case. For you, this is a key concept that mathematically explains the high performance of common data structures you've architected systems with, like Java's ArrayList or Python's dynamic lists, even when they occasionally perform very expensive operations like resizing.

1. The Limits of Single-Operation Worst-Case Analysis

Let's consider a familiar data structure: a dynamic array (like std::vector or ArrayList). It provides an append operation that adds an element to the end.

  • The "cheap" case: If the underlying array has space, this is an O(1) operation. You just place the element at the next index.
  • The "expensive" case: If the array is full, the data structure must perform a costly resize:
    1. Allocate a new, larger array (typically double the size).
    2. Copy every element from the old array to the new one.
    3. Add the new element.
      This single operation takes O(n) time, where n is the current number of elements.

A strict worst-case analysis would conclude that the append operation is O(n). If we need to perform m appends, does this mean the total time is O(m \cdot n)? This feels incorrect, and it is. The expensive O(n) cost happens only rarely. Amortized analysis provides a formal way to prove that the average cost is, in fact, much lower.


2. The Aggregate Method: A Total Cost Perspective

The aggregate method is the most straightforward approach to amortized analysis. The process is simple:

  1. Determine the total worst-case time, , for a sequence of operations.
  2. The amortized cost per operation is then calculated as .

Let's apply this to the multi-pop stack example, a classic introductory problem. A stack supports PUSH (cost 1), POP (cost 1), and MULTIPOP(k) (cost k). A naive analysis would say a sequence of n operations could cost O(n^2) if all were MULTIPOP(n). However, this is impossible.

To see how to derive a tighter bound, let's watch a clear explanation.

Amortized Analysis: Aggregate, Accounting and Potential Method (with examples)

This first video from CS Simplified provides an excellent, step-by-step application of the aggregate method to the multi-pop stack. It clearly demonstrates the core insight: an element can only be popped once, regardless of whether it's via POP or MULTIPOP.

Watch the segment from the beginning until 11:09. Focus on the logical argument that the total number of pops (from all operations combined) is bounded by the total number of pushes. This insight is what allows for a tight bound on the total cost.

As the video showed, for any sequence of operations on an initially empty stack:

  • The total number of PUSH operations is at most . Total cost for pushes is O(n).
  • An element must be pushed onto the stack before it can be popped.
  • Therefore, the total number of items popped (summed across all POP and MULTIPOP calls) cannot exceed the total number of items pushed. This is at most .
  • The total cost for all POP and MULTIPOP operations combined is therefore O(n).

So, the total cost for any sequence of operations is O(n) + O(n) = O(n).
The amortized cost per operation is O(n) / n = O(1).

This is a powerful result: despite some operations being potentially expensive, the average cost over any sequence is constant.


3. The Accounting Method: Pre-paying for Future Work

The accounting method provides a different, often more intuitive, lens. We use a financial analogy:

  • We assign a specific amortized cost (a "payment" or "charge") to each type of operation. This is our analytical cost, not the actual cost.
  • When an operation's amortized cost is higher than its actual cost, we "overpay" and store the difference as credit.
  • When an operation's amortized cost is lower than its actual cost, we use the stored credit to pay for it.
  • The Golden Rule: The total credit stored in the data structure must never become negative.
Accounting Method for Amortized Analysis
This diagram illustrates the core idea of the accounting method. We assign an amortized cost to each operation. The difference between the amortized and actual cost is saved as 'credit' (coins in a piggy bank). This credit must always be non-negative, ensuring that the total amortized cost is an upper bound on the total actual cost.

Let's see how this works with our two main examples: the multi-pop stack and the dynamic array.

Amortized Analysis: Aggregate Analysis and Accounting Method

The following video from Algorithms Lab explains the accounting method using a 'coin' analogy. It applies the method to the multi-pop stack, a binary counter, and the dynamic array, providing three clear applications of the same core principle.

Please watch two segments from this video: Multi-pop Stack (08:56 - 17:02): See how assigning an amortized cost of 2 to PUSH allows us to 'save' a coin with each element, which then pays for its eventual removal by POP or MULTIPOP (amortized cost 0). Dynamic Array (29:06 - 37:00): Observe how assigning an amortized cost of 3 to append lets us save up enough credit over time to pay for the expensive O(n) resize operation when it occurs. Focus on how the 'invariant' (credit ≥ 0) is maintained in each case.

Let's formalize the logic from the video:

Accounting for the Multi-pop Stack:

  • Amortized Costs: PUSH = 2, POP = 0, MULTIPOP = 0.
  • Actual Costs: PUSH = 1, POP = 1, MULTIPOP(k) = k.
  • Analysis:
    • When we PUSH an element, we pay 2 coins. 1 coin pays the actual cost of the push. The other coin is stored with the element on the stack.
    • When we POP or MULTIPOP an element, its actual cost of 1 is paid for by the coin stored with it.
    • The total credit is the number of items on the stack, which is always . The rule holds.
    • Since the highest amortized cost is 2, the amortized cost per operation is O(1).

Accounting for the Dynamic Array:

  • Amortized Cost: append = 3.
  • Actual Costs: append without resize = 1. append with resize = n + 1 (to copy n old items and add the new one).
  • Analysis:
    • When we append an element, we pay 3 coins.
    • 1 coin pays the actual cost of insertion.
    • 2 coins are stored as credit.
    • Let's consider an array that just resized from size m to 2m. It currently holds m elements. The next m insertions will fill the array.
    • During these m insertions, we collect m * 2 = 2m coins of credit.
    • When the (2m+1)-th element is added, a resize is triggered. The array has 2m elements to copy. The cost is 2m.
    • The 2m coins of credit we saved are used to pay for this copy. The system is solvent.
    • The amortized cost is a constant 3, so the operation is O(1).
Test your understanding!

Consider a k-bit binary counter that starts at 0. The only operation is increment, which costs 1 unit for every bit that is flipped. For example, incrementing from 3 (011) to 4 (100) costs 3 flips.

A naive worst-case analysis says an increment could cost O(k). Using either the aggregate or accounting method, can you show that the amortized cost of an increment operation is O(1)?

(Hint for aggregate: How many times does bit 0 flip in n increments? How many times does bit 1 flip? Bit i?)
(Hint for accounting: When a bit flips from 0 to 1, what if you "pre-pay" for its future flip back to 0?)

Show answer

Here's how both methods solve this:

1. Aggregate Method:
In a sequence of n increments:

  • Bit 0 (the rightmost bit) flips on every increment. Total: n flips.
  • Bit 1 flips on every 2nd increment. Total: ⌊n/2⌋ flips.
  • Bit i flips on every 2^i-th increment. Total: ⌊n/2^i⌋ flips.

The total number of flips is the sum over all bits:

The total cost for n increments is O(n). Therefore, the amortized cost per increment is O(n)/n = O(1).

2. Accounting Method:

  • Set the amortized cost of an increment to 2 coins.
  • Whenever a bit flips from 0 to 1, use 1 coin to pay for the flip, and store the 2nd coin as credit on that bit.
  • Whenever a bit flips from 1 to 0, use the credit stored on that bit to pay for the flip.
  • An increment operation involves flipping a sequence of 1s to 0s, and then flipping exactly one 0 to a 1.
  • Each 1-to-0 flip is paid for by the credit already on the bit.
  • The single 0-to-1 flip is paid for by one of the coins from our amortized cost. The second coin is then stored on this newly set bit.
  • The total credit in the system equals the number of 1s in the counter, which is always . The invariant holds.
  • The amortized cost is a constant 2, so the operation is O(1).

You can find detailed video walkthroughs for this problem in the CS Simplified video (LINK) at timestamp 11:09 (Aggregate) and 26:39 (Accounting).


Conclusion

This lesson concludes our foundational module on complexity analysis. We've moved from simple loop counting to analyzing complex recursive algorithms, and now to understanding the performance of sequences of operations.

Key Takeaways:

  • Amortized analysis provides a more practical measure of performance for data structures where some operations are occasionally expensive. It gives the worst-case average cost over a sequence.
  • The Aggregate Method calculates the total cost of a sequence of n operations and divides by n to find the amortized cost. It's conceptually simple but requires analyzing the entire sequence at once.
  • The Accounting Method uses a financial analogy of "pre-paying" for expensive operations. It assigns amortized costs to individual operation types and uses "credit" to balance payments, ensuring the system never goes into "debt".

Next Lesson Preview:
We are now ready to dive into the core data structures and algorithms that form the bedrock of coding interviews. In our next module, "Core Data Structures: Arrays, Strings & Hash Maps," we will begin by learning how to apply the two-pointer technique to solve problems on sorted arrays in O(n) time. This is a classic interview pattern that relies on the kind of efficiency thinking we've been developing.