To measure code speed, we compare our actual code's operation count to a simplified reference "ruler".
| Function | What it is | Example |
|---|---|---|
| Your actual function (exact operations in your code) | ||
| The reference function (simplified "ruler") |
The 5 Bound Types
This notation tells you how and compare asymptotically as the input size () approaches infinity.
| Bound Type | Notation | Plain English | Analogy | Limit () |
|---|---|---|---|---|
| Strict Upper | grows strictly slower than | |||
| Upper | grows no faster than | Constant or | ||
| Tight | and have the same growth rate | Positive constant | ||
| Lower | grows at least as fast as | Positive constant or | ||
| Strict Lower | grows strictly faster than |
Mathematical Definition
In computer science, we use two anchor points to prove these bounds:
- (The Multiplier): A fixed positive constant used to scale the reference function.
- (The Threshold): The point on a graph beyond which the inequality holds for all .
The Five Rules of Growth
1. Big O (): The Ceiling (Upper Bound)
- The Rule: for some and all .
- Meaning: Growth stays at or below the reference curve (Worst-case ceiling).
2. Big Omega (): The Floor (Lower Bound)
- The Rule: for some and all .
- Meaning: Growth stays at or above the reference curve (Best-case floor).
3. Big Theta (): The Sandwich (Tight Bound)
- The Rule: for constants and all .
- Meaning: is asymptotically bounded above and below by the same curve.
4. Little o (): Strictly Under
- The Rule: for all and sufficiently large .
- Meaning: grows strictly slower than (i.e., dominates completely).
5. Little omega (): Strictly Over
- The Rule: for all and sufficiently large .
- Meaning: grows strictly faster than (i.e., dominates completely).
Proof via Limits
To evaluate these asymptotic bounds using calculus, compare the ratio of the functions as
- i.e. becomes
Tight Bound:
This is the "exact" growth rate.
- Result: because , which is a positive constant.
Upper Bound:
This is the "ceiling." grows no faster than .
- Result: and because the limits evaluate to and , respectively.
Lower Bound:
This is the "floor." grows at least as fast as .
- Result: For , both and are valid because the limits evaluate to and , respectively.
Strict Upper Bound:
This is a "loose ceiling." must eventually grow strictly faster than .
- Result: because the limit evaluates to .
Strict Lower Bound:
This is a "loose floor." must be strictly slower than .
- Result: because the limit evaluates to .
What about "" (Constant Time)
When , the operation count does not depend on the input size ().
- (Exact Constant): The algorithm always takes a constant number of operations (e.g., accessing an array element by index:
arr[0]). - (Constant Ceiling): The algorithm takes at most a constant amount of time.
- (Constant Floor): The algorithm takes at least some constant amount of time. (Since any executed instruction requires non-zero time, is a trivial lower bound for almost every program).
Examples
| Function | Tight Bound | Valid Upper | Valid Lower | Strict Upper | Strict Lower |
|---|---|---|---|---|---|
Asymptotically Incomparable Functions
Sometimes neither function bounds the other due to oscillation.
Example:
- Let
- Let
Because oscillates continuously between and :
- When , , making larger.
- When , , making larger.
Since their relative order oscillates indefinitely as , and are asymptotically incomparable.
Data-Dependent Bounds (No Single Tight Bound)
for (int i = 0; i < n; i++) {
if (arr[i] == target) { // Data-dependent
for (int j = 0; j < n; j++) {
sum++;
}
}
}- Best Case (): Target is never found. The inner loop never executes; only the outer loop runs ().
- Worst Case (): Every element matches the target. The inner loop executes on every iteration ().
- Conclusion: Without assumptions about the input data distribution, there is no single bound describing the algorithm across all inputs.
The RA Machine Architecture
The Random Access (RA) model is a theoretical abstraction bridging the gap between Turing machines and real silicon. Instead of a single sequential tape, the RA model provides an infinite array of discrete registers and an accumulator.
- Accumulator (
r0): All arithmetic and logic operations occur here. - Registers (
r1to ): Memory cells holding arbitrarily large integers. - Input/Output Tapes: Sequential read-only input and write-only output.
The Instruction Set
(Note: In the x86 mappings below, eax corresponds to Accumulator r0, and ebx/esi correspond to general registers).
| RA Instruction | Operation | C++ Equivalent | x86 Assembly Equivalent |
|---|---|---|---|
LOAD ri | r0 ← ri | r0 = reg[i]; | mov eax, ebx |
LOADI c | r0 ← c | r0 = c; | mov eax, c |
LOAD *ri | r0 ← reg[ri] | r0 = reg[reg[i]]; | mov eax, dword ptr [esi] |
STORE ri | ri ← r0 | reg[i] = r0; | mov ebx, eax |
STORE *ri | reg[ri] ← r0 | reg[reg[i]] = r0; | mov dword ptr [esi], eax |
ADD ri | r0 ← r0 + ri | r0 += reg[i]; | add eax, ebx |
SUB ri | r0 ← r0 - ri | r0 -= reg[i]; | sub eax, ebx |
MULT ri | r0 ← r0 * ri | r0 *= reg[i]; | imul ebx |
DIV ri | r0 ← r0 / ri | r0 /= reg[i]; | idiv ebx |
INC ri | ri ← ri + 1 | reg[i]++; | inc ebx |
DEC ri | ri ← ri - 1 | reg[i]--; | dec ebx |
JUMP label | pc ← label | goto label; | jmp label |
JUMPZ label | if r0 == 0 jump | if (r0 == 0) goto label; | cmp eax, 0; je label |
JUMPP label | if r0 > 0 jump | if (r0 > 0) goto label; | cmp eax, 0; jg label |
READ ri | ri ← input | std::cin >> reg[i]; | (Syscall) |
WRITE | output ← r0 | std::cout << r0; | (Syscall) |
HALT | Stop execution | exit(0); | hlt |
Example 1: Iterative Summation (The N-Numbers Problem)
The Problem:
- Read an integer from the input tape.
- Sum sequential values starting at register
r100(i.e.,r[100]throughr[100 + N - 1]). - Output the final sum to the tape.
The Code Solution:
READ r1 // Tape -> r1 (r1 holds counter N)
LOADI 100 // r0 = 100
STORE r2 // r2 = 100 (pointer to array elements)
LOADI 0 // r0 = 0
STORE r3 // r3 = 0 (running sum)
LOOP:
LOAD r1 // Check loop counter
JUMPZ FINISH // If N == 0, terminate loop
LOAD *r2 // r0 = reg[r2] (dereference pointer)
ADD r3 // r0 = reg[r2] + running_sum
STORE r3 // running_sum = r0
INC r2 // Advance memory pointer
DEC r1 // Decrement counter N
JUMP LOOP // Next iteration
FINISH:
LOAD r3 // Load total sum into Accumulator
WRITE // Output Accumulator to tape
HALTExample 2: Pointer Indirection (Nested Lookup)
The Problem:
- Read the base address of array
afrom the input tape. - Read index
ifrom the input tape. - Array
ais stored contiguously starting at register (reg[A] = a[0],reg[A+1] = a[1], etc.). - Compute
a[a[i]]and write the result to the output tape.
(Assume to prevent collisions with working registers r1–r4).
INPUT TAPE:
[ Row 1: A ] <-- Tape Head (Base address)
[ Row 2: i ] <-- Index
MAIN MEMORY (Registers):
r1 : [ base address A ]
r2 : [ index i ]
r3 : [ pointer to a[i] ]
r4 : [ pointer to a[a[i]] ]
...
r[A] : [ a[0] ]
r[A+1] : [ a[1] ]And this is how we should it:
Read inputs: We need to pull
Aandioff the tape and store them in working registers (r1andr2).Find
a[i]: The memory address ofa[i]is simply the base address plus the index (A + i). We calculate this, store the pointer inr3, and then use indirect loading (LOAD *r3) to get the actual value ofa[i].Find
a[a[i]]: Now we repeat the process. The memory address ofa[a[i]]is the base address plus our newly found value (A + a[i]). We calculate this, store the pointer inr4, and indirectly load it to get our final answer.
The Code Solution:
READ r1 // r1 = A
READ r2 // r2 = i
LOAD r1 // r0 = A
ADD r2 // r0 = A + i
STORE r3 // r3 = A + i (pointer to a[i])
LOAD *r3 // r0 = a[i] (dereference first pointer)
ADD r1 // r0 = A + a[i] (address of a[a[i]])
STORE r4 // r4 = A + a[i]
LOAD *r4 // r0 = a[a[i]] (dereference second pointer)
WRITE // Output result
HALT