Data Structures, Abstract Data Types, and Algorithm Analysis

Fundamental Data Structures: Arrays & Object References

  • An array is a sequenced, contiguous collection of variables all sharing the exact same data type.

  • Each slot or cell in an array is identified by a unique integer position known as an index, which begins at index 00 and extends to length−1\text{length} - 1.

  • The values stored within array cells are referred to as elements.

  • Array Capacity: The maximum number of elements an array can hold, determined at the time of allocation and remaining fixed throughout its lifecycle.

  • In Java, the length of an array named a is accessed via the property a.length.

Array Declaration and Instantiation

  • Literal Initialization Syntax:   elementType[] arrayName={initialValue0,initialValue1,…,initialValueN−1};\text{elementType}[]\,\text{arrayName} = \{\text{initialValue}_0, \text{initialValue}_1, \dots, \text{initialValue}_{N-1}\};

  • Operator Instantiation Syntax:   elementType[] arrayName=new elementType[length];\text{elementType}[]\,\text{arrayName} = \mathbf{new}\,\text{elementType}[\text{length}];

  • Memory for array cells is allocated contiguously in heap memory.

Case Study: Game Entries & High Score Board

  • A game high-score collection maintains top scores sorted in descending numerical order.

Array of GameEntry references pointing to player name and score objects
GameEntry Class Implementation
public class GameEntry {
  private String name; // name of person earning this score
  private int score;   // the score value

  /** Constructs a game entry with given parameters. */
  public GameEntry(String n, int s) {
    name = n;
    score = s;
  }

  /** Returns the name field. */
  public String getName() { return name; }

  /** Returns the score field. */
  public int getScore() { return score; }

  /** Returns a string representation of this entry. */
  public String toString() {
    return "(" + name + ", " + score + ")";
  }
}
Scoreboard Class Implementation
public class Scoreboard {
  private int numEntries = 0;     // number of actual entries
  private GameEntry[] board;      // array of game entries (names & scores)

  /** Constructs an empty scoreboard with given capacity. */
  public Scoreboard(int capacity) {
    board = new GameEntry[capacity];
  }

  /** Attempt to add a new score to the collection (if it is high enough) */
  public void add(GameEntry e) {
    int newScore = e.getScore();
    // is the new entry e really a high score?
    if (numEntries < board.length || newScore > board[numEntries - 1].getScore()) {
      if (numEntries < board.length) {
        numEntries++; // no score drops from the board, so overall number increases
      }
      // shift any lower scores rightward to make room for the new entry
      int j = numEntries - 1;
      while (j > 0 && board[j - 1].getScore() < newScore) {
        board[j] = board[j - 1]; // shift entry from j-1 to j
        j--;                     // and decrement j
      }
      board[j] = e;              // when done, add new entry
    }
  }

  /** Remove and return the high score at index i. */
  public GameEntry remove(int i) throws IndexOutOfBoundsException {
    if (i < 0 || i >= numEntries) {
      throw new IndexOutOfBoundsException("Invalid index: " + i);
    }
    GameEntry temp = board[i]; // save the object to be removed
    for (int j = i; j < numEntries - 1; j++) {
      board[j] = board[j + 1]; // move one cell to the left
    }
    board[numEntries - 1] = null; // null out the old last score
    numEntries--;
    return temp; // return the removed object
  }
}
Shifting lower scores rightward to insert Jill with score 740Removing entry at index 3 and shifting remaining entries leftward

Object References, Memory Allocation, and Aliasing

  • A reference variable holds the memory address location where an object resides, rather than storing the object data inline.

  • Default Reference Values:

    • Uninitialized object reference variables defined as instance or class data fields default to null.

    • Local reference variables declared within methods possess no default value and must be explicitly assigned prior to use.

  • Trace of Reference Manipulations:

Reference variable p and q manipulation steps
  • Step (a): Integer p; Integer q; — Both variables p and q are declared containing uninitialized reference slots.

  • Step (b): p = new Integer(5); — Allocates an Integer object with value 55; reference p points to this instance.

  • Step (c): p = new Integer(6); — Allocates a new Integer object with value 66 and updates p to point to it. The object containing value 55 becomes unreferenced and eligible for garbage collection.

  • Step (d): q = p; — Variable q is assigned the memory reference stored in p. Both p and q now point to the exact same Integer object holding 66 (aliasing).

  • Step (e): q = new Integer(9); — Allocates a distinct Integer object with value 99 and points q to it; p remains pointing to 66.

  • Step (f): p = null; — Reference p is assigned null. Object holding 66 loses its reference and becomes candidate for garbage collection.

  • Step (g): q = p; — Reference q is set to null (copied from p). Object holding 99 becomes unreferenced and eligible for garbage collection.

Reference-Based Linked Lists and Memory Dynamics

  • A reference-based linked list consists of discrete node objects linked dynamically via pointer references.

  • Unlike arrays, linked lists can dynamically grow or shrink in size on demand and do not require contiguous memory blocks or shifting elements during insertions and deletions.

Singly linked list with head pointer and nodes containing item and next reference

Node Class Definition

  • In Java, a singly linked list node is represented as follows:

package List;

class Node {
  Object item;
  Node next;

  Node(Object item) {
    this.item = item;
    this.next = null;
  }

  Node(Object item, Node next) {
    this.item = item;
    this.next = next;
  }
}

Singly Linked List Operations

  • A singly linked list is unidirectional; traversal can only proceed sequentially from the head pointer to the end (null).

Insertion of node 60 and deletion of node 45 in a linked list
Displaying Contents / Traversal
  • To display all items in a linked list, iterate using a reference variable curr:

for (Node curr = head; curr != null; curr = curr.next) {
    System.out.println(curr.item);
}
Pointer Determination for Sorted Insertion or Deletion
  • To locate insertion or deletion points in a sorted linked list, set prev and curr pointers:

for (prev = null, curr = head; 
     (curr != null) && (newValue.compareTo(curr.item) > 0); 
     prev = curr, curr = curr.next) {
    // Iterates until reaching insertion/deletion position
}
Deleting a Node
  1. Locate target node curr and preceding node prev.

  2. Disconnect target node: prev.next = curr.next;.

  3. Clean up deleted node references: curr.next = null; curr = null;.

  4. Special Case (Deleting Head Node): head = head.next;.

Inserting a Node
  1. Determine position (prev and curr).

  2. Create new node: newNode = new Node(item);.

  3. Connect references: newNode.next = curr; prev.next = newNode;.

  4. Special Case (Inserting at Head): newNode.next = head; head = newNode;.

Linked List Variations: Tail Pointers, Circular Lists, Dummy Nodes, and Doubly Linked Lists

Tail References

  • Maintaining an explicit tail pointer referencing the final node in the list allows constant time O(1)O(1) append operations at the end.

  • Append Execution: tail.next = new Node(request, null); tail = tail.next;.

Singly linked list with head and tail references

Circular Linked Lists

  • In a circular linked list, the final node's next pointer references the first node (head) instead of storing null.

  • Every node in the circular chain has a defined successor.

Circular linked list where tail node points back to the head node

Dummy Head Nodes

  • A dummy head node (or sentinel node) is a permanent placeholder node placed at index −1-1 that precedes the first actual data node.

  • It is present even when the list is empty.

  • It eliminates edge cases during head insertions and deletions by guaranteeing that every data-containing node always has a valid preceding node prev.

Linked list starting with a dummy head node before index 0 data

Doubly Linked Lists

  • Nodes in a doubly linked list contain two pointers: next referencing the successor node, and prev referencing the predecessor node.

  • Enables bidirectional traversal across the linear structure.

Doubly linked list where each node has previous and next pointers

Abstract Data Types: List Collections

  • A list is a linear collection that offers greater flexibility than strict stacks or queues, allowing insertion, access, and deletion at either end or at any intermediate point.

Three Primary Types of List Collections

  1. Ordered Lists:

    • Elements are sorted automatically based on an inherent characteristic (e.g., names in alphabetical order, scores in ascending numeric order).

    • The elements themselves determine their precise location within the list.

An ordered list showing element 54 inserted between 49 and 57
  1. Unordered Lists:

    • Elements maintain a specific sequence, but this order is not dictated by element characteristics.

    • The user or program determines position explicitly by inserting at the front, rear, or directly after an existing element.

An unordered list adding elements at front, rear, or middle
  1. Indexed Lists:

    • Elements are referenced directly by a zero-based numeric index position.

    • Index positions are recalculated and updated automatically whenever elements are added or removed.

An indexed list with numeric indices from 0 to 7

Lists in the Java Collections Framework (Java API)

  • The Java API supports indexed and unordered list abstractions primarily through the List<E> interface, implemented by ArrayList<E> and LinkedList<E>.

  • The standard Java API does not provide a direct class implementing an ordered list ADT out of the box.

  • LinkedList<E> Constructors & Key Methods:

    • LinkedList(): Constructs an empty list.

    • LinkedList(Collection<? extends E> c): Constructs a list containing elements of the specified collection in iteration order.

    • add(E e) / addLast(E e): Appends specified element to the end of the list.

    • add(int index, E element): Inserts element at specified index position.

    • addFirst(E e): Inserts element at beginning of the list.

    • clear(): Removes all elements from the list.

    • get(int index): Returns element at specified index position.

    • getFirst() / getLast(): Returns first / last element in the list.

    • indexOf(Object o): Returns index of first occurrence of specified object, or −1-1 if not found.

    • remove() / removeFirst(): Retrieves and removes first element from the list.

    • remove(int index): Removes element at specified index position.

    • removeLast(): Removes and returns final element in the list.

    • iterator(): Returns an Iterator<E> object to traverse list elements sequentially (hasNext() tests for remaining items; next() retrieves the subsequent item).

The Queue ADT: Operations, Implementations, and Applications

  • A Queue is an Abstract Data Type that enforces a First-In, First-Out (FIFO) processing order.

  • Elements are added at the rear (back) and removed from the front.

Queue Interface Operations

  • Primary Operations:

    • enqueue(object): Inserts an element at the rear of the queue.

    • object dequeue(): Removes and returns the element at the front of the queue.

  • Auxiliary Operations:

    • object peek() / first(): Returns the front element without removing it.

    • integer size(): Returns the count of elements currently stored.

    • boolean isEmpty(): Returns true if empty, false otherwise.

  • Boundary Conditions: Calling dequeue() or peek() on an empty queue returns null or raises an exception.

Example Operation Sequence

Method Call

Return Value

Queue Contents (Front to Rear)

enqueue(5)

—

(5)

enqueue(3)

—

(5, 3)

dequeue()

5

(3)

enqueue(7)

—

(3, 7)

dequeue()

3

(7)

peek()

7

(7)

dequeue()

7

()

dequeue()

null

()

isEmpty()

true

()

enqueue(9)

—

(9)

enqueue(7)

—

(9, 7)

size() / peek()

2 / 9

(9, 7)

enqueue(3)

—

(9, 7, 3)

enqueue(5)

—

(9, 7, 3, 5)

dequeue()

9

(7, 3, 5)

Applications of Queues

  • Direct Applications: Print job queues, customer waiting lists, operating system task scheduling, shared resource access.

  • Indirect Applications: Breadth-first search algorithms, buffering data streams, components in complex data structures.

Queue Implementations

Naive Array Queue vs. Circular Array Queue
  • A naive array implementation shifts the front index rightward during deletions, resulting in "rightward drift" where array slots at lower indices become unusable, making a non-full queue appear prematurely full.

Naive array queue and rightward drift problem
  • Circular Array Strategy: Uses an array of size NN combined with modular arithmetic.

    • f: Index of the front element.

    • sz: Current total count of stored elements.

    • The index r for inserting a new element is computed as:     r=(f+sz) mod Nr = (f + sz) \bmod N

Java Circular Array Queue Implementation (ArrayQueue<E>)
/** Implementation of the queue ADT using a fixed-length array. */
public class ArrayQueue<E> implements Queue<E> {
  private E[] data;      // generic array used for storage
  private int f = 0;     // index of the front element
  private int sz = 0;    // current number of elements
  public static final int CAPACITY = 1000;

  public ArrayQueue() { this(CAPACITY); }

  @SuppressWarnings("unchecked")
  public ArrayQueue(int capacity) {
    data = (E[]) new Object[capacity]; // safe cast; compiler may give warning
  }

  public int size() { return sz; }
  public boolean isEmpty() { return (sz == 0); }

  /** Inserts an element at the rear of the queue. */
  public void enqueue(E e) throws IllegalStateException {
    if (sz == data.length) throw new IllegalStateException("Queue is full");
    int avail = (f + sz) % data.length; // use modular arithmetic
    data[avail] = e;
    sz++;
  }

  /** Returns, but does not remove, the first element of the queue (null if empty). */
  public E first() {
    if (isEmpty()) return null;
    return data[f];
  }

  /** Removes and returns the first element of the queue (null if empty). */
  public E dequeue() {
    if (isEmpty()) return null;
    E answer = data[f];
    data[f] = null; // dereference to help garbage collection
    f = (f + 1) % data.length;
    sz--;
    return answer;
  }
}
Reference-Based Queue Implementation
  • Uses a singly linked list maintaining two pointers: firstNode pointing to the front node, and lastNode pointing to the rear node.

  • enqueue(o) attaches new nodes after lastNode in O(1)O(1) time.

  • dequeue() removes nodes from firstNode in O(1)O(1) time.

Reference-based queue using linear linked list with firstNode and lastNode

The Stack ADT: Operations, Implementations, and Applications

  • A Stack is an Abstract Data Type operating under a Last-In, First-Out (LIFO) protocol.

  • Analogous to a spring-loaded cafeteria plate dispenser where the last item pushed onto the stack is the first item popped off.

Spring-loaded plate dispenser representing a stack

Stack Operations

  • Primary Operations:

    • push(object): Inserts an element onto the top of the stack.

    • object pop(): Removes and returns the top element from the stack.

  • Auxiliary Operations:

    • object top() / peek(): Returns the top element without removing it.

    • integer size(): Returns the number of elements contained.

    • boolean isEmpty(): Indicates whether the stack contains zero elements.

Stack Operations Trace

Method Call

Return Value

Stack Contents (Bottom to Top)

push(5)

—

(5)

push(3)

—

(5, 3)

size()

2

(5, 3)

pop()

3

(5)

isEmpty()

false

(5)

pop()

5

()

isEmpty()

true

()

pop()

null

()

push(7)

—

(7)

push(9)

—

(7, 9)

top()

9

(7, 9)

push(4)

—

(7, 9, 4)

size()

3

(7, 9, 4)

pop()

4

(7, 9)

push(6)

—

(7, 9, 6)

push(8)

—

(7, 9, 6, 8)

pop()

8

(7, 9, 6)

Stack Implementations

Array-Based Stack
  • Uses an array S and an integer index variable t representing the top index (initialized to −1-1 when empty).

  • Algorithm Push:

  if t == S.length - 1 then throw Exception
  t = t + 1
  S[t] = o
&nbsp;&nbsp;```
- **Algorithm Pop**:

text if isEmpty() then return null o = S[t] S[t] = null t = t - 1 return o   ```

  • Performance: Each operation runs in constant time O(1)O(1). Space used is O(n)O(n).

  • Limitation: Fixed capacity allocated upfront; pushing to a full array stack causes overflow.

Reference-Based Stack
  • Uses a singly linked list where a single external reference top points to the head node.

  • push inserts a new node at the head (head = new Node(o, head)).

  • pop removes the node at the head (o = head.item; head = head.next; return o).

  • Provides unlimited dynamic capacity limited only by available memory.

Reference-based stack showing top pointer pointing to linked nodes

Stack Applications

  • Direct Applications: Web browser page history back-tracking, text editor undo history chains, Java Virtual Machine (JVM) method execution stack frames.

  • Indirect Applications: Expression parsing, depth-first search, compiler syntax parsing.

Expression Conversion and Evaluation using Stacks

Infix to Postfix Conversion

  • In an infix expression, operators sit between operands (e.g., a−ba - b).

  • In a postfix expression, operators follow their operands (e.g., a b −a\,b\,-).

  • Conversion Rules:

    1. Operands retain their exact original order.

    2. Operators move to the right of their respective operands based on precedence.

    3. All explicit parentheses are removed in postfix form.

Conversion Algorithm Pseudocode
create empty stack to hold Strings / characters
postfix = ""
for next token from infix:
    if token is an operand (not "+", "-", "*", "/", "(", or ")") then
        append token to postfix
    else if token is "(" then
        push "(" onto stack
    else if token is ")" then
        while top of stack is not "(" do
            pop operator from stack and append to postfix
        pop the "(" off the stack
    else // token is an operator
        while stack is not empty and top of stack is operator of equal or greater precedence than token and top is not "(" do
            pop operator from stack and append to postfix
        push token onto stack

while stack is not empty do
    pop remaining operator from stack and append to postfix
Complete Conversion Trace: a - (b + c * d) / e
Trace table converting infix expression a - (b + c * d) / e to postfix

Token ch

Stack (Bottom to Top)

postfixExp

Notes / Action

a

()

a

Append operand

-

(-)

a

Push operator

(

(-, ()

a

Push open parenthesis

b

(-, ()

ab

Append operand

+

(-, (, +)

ab

Push operator

c

(-, (, +)

abc

Append operand

*

(-, (, +, *)

abc

Push higher precedence operator

d

(-, (, +, *)

abcd

Append operand

)

(-)

abcd*+

Pop operators until ( reached

/

(-, /)

abcd*+

Push / (higher precedence than -)

e

(-, /)

abcd*+e

Append operand

End

()

abcd*+e/-

Pop all remaining operators

Postfix Expression Evaluation Algorithm

  • Operands are pushed onto a stack when encountered.

  • When an operator is encountered, pop the top two operands: operand2 = pop(), operand1 = pop(), apply operation result = operand1 op operand2, and push result back onto stack.

Evaluation Trace for Infix 2 * (3 + 4) (Postfix 2 3 4 + *)
Action of postfix calculator evaluating 2 * (3 + 4)

Key Entered

Calculator Action

Stack (Bottom to Top)

2

push 2

2

3

push 3

2, 3

4

push 4

2, 3, 4

+

operand2 = pop() (44)
operand1 = pop() (33)
result = 3 + 4 (77)
push result

2, 7

*

operand2 = pop() (77)
operand1 = pop() (22)
result = 2 * 7 (1414)
push result

14

  • Final expression evaluation result = 1414.

Fundamentals of Algorithm Analysis

  • Algorithm analysis measures resource efficiency, focusing primarily on CPU running time and memory space relative to problem size nn.

Defining Problem Size nn

  • Searching: Size of search space pool.

  • Sorting: Total number of items to sort.

  • Text processing: Number of characters in string.

  • Dishwashing Example:

    • Problem size nn: Total dishes to wash.

    • Linear Strategy: Washing = 30 sec30\,\text{sec}, Drying = 30 sec30\,\text{sec}. Total time per dish = 60 sec60\,\text{sec}.     time(n)=n minutes=O(n)\text{time}(n) = n\,\text{minutes} = O(n)

    • Inefficient Quadratic Strategy: Re-dry all previously washed dishes after washing each new dish.     time(n)=n×(30 seconds wash time)+∑i=1n(i×30)\text{time}(n) = n \times (30\,\text{seconds wash time}) + \sum_{i=1}^{n} (i \times 30)     time(n)=30n+30n(n+1)2=15n2+45n seconds\text{time}(n) = 30n + \frac{30n(n+1)}{2} = 15n^2 + 45n\,\text{seconds}

Dishwashing Time Analysis Table

Number of Dishes (nn)

Term 15n215n^2 (sec)

Term 45n45n (sec)

Total Time 15n2+45n15n^2 + 45n (sec)

11

1515

4545

6060

22

6060

9090

150150

55

375375

225225

600600

1010

1,5001,500

450450

1,9501,950

100100

150,000150,000

4,5004,500

154,500154,500

1,0001,000

15,000,00015,000,000

45,00045,000

15,045,00015,045,000

10,00010,000

1,500,000,0001,500,000,000

450,000450,000

1,500,450,0001,500,450,000

100,000100,000

150,000,000,000150,000,000,000

4,500,0004,500,000

150,004,500,000150,004,500,000

1,000,0001,000,000

15,000,000,000,00015,000,000,000,000

45,000,00045,000,000

15,000,045,000,00015,000,045,000,000

10,000,00010,000,000

1,500,000,000,000,0001,500,000,000,000,000

450,000,000450,000,000

1,500,000,450,000,0001,500,000,450,000,000

Empirical (Experimental) Studies vs. Theoretical Analysis

  • Empirical Studies:

    • Involves writing fully runnable implementations, executing them over varying input sizes nn, measuring elapsed wall-clock time (System.currentTimeMillis()), and plotting results.

  long startTime = System.currentTimeMillis();
  /* (run the algorithm) */
  long endTime = System.currentTimeMillis();
  long elapsed = endTime - startTime;
&nbsp;&nbsp;```

  - **Limitations**: Requires complete implementation effort; results vary across different hardware/software environments; limited to test inputs used.
- **Theoretical Analysis**:
  - Evaluates high-level pseudocode independent of hardware/software environments.
  - Bounds running time mathematically as a function of problem size nn across all possible inputs.

# Mathematical Analysis & Primitive Operation Counting

## RAM Model & Primitive Operations

- Calculations assume the Random Access Machine (RAM) model where primitive operations take constant time O(1)O(1).
- Primitive operations include:
  - Assigning a value to a variable.
  - Evaluating a arithmetic expression or boolean conditional.
  - Comparing two numerical values.
  - Indexing into an array cell.
  - Calling a method.
  - Returning from a method.

## Detailed Operation Count Analysis (`arrayMax`)

java public static double arrayMax(double[] data) { int n = data.length; // 2 operations (array length, assignment) double currentMax = data[0]; // 2 operations (indexing, assignment) for (int j = 1; j < n; j++) { // 2 + 2n operations (init, loop comparisons, increments) if (data[j] > currentMax) { // 2(n - 1) operations (indexing, comparison) currentMax = data[j]; // 0 to 2(n - 1) operations (conditional assignment) } } return currentMax; // 1 operation }

- **Worst-Case Count**: 5n+55n + 5 primitive operations.
- **Best-Case Count**: 4n+54n + 5 primitive operations.
- Bounded by linear growth: Let aa be time of fastest operation and bb be time of slowest operation:
&nbsp;&nbsp;a(4n+5)≤T(n)≤b(5n+5)a(4n + 5) \le T(n) \le b(5n + 5)

# Asymptotic Notations: Big-O, Big-Omega, and Big-Theta

![Display of growth of functions commonly used in big-O estimates](https://assets.knowt.com/pdf-flow-prod/5244169f-7095-452c-9d84-50db9341ff31-figures/23.jpg)

## Formal Definitions

1. **Big-O Notation (OO - Upper Bound / Worst-Case)**:
&nbsp;&nbsp;&nbsp;f(n) is O(g(n))  ⟺  ∃ c>0,n0≥1 such that f(n)≤c⋅g(n)∀n≥n0f(n) \text{ is } O(g(n)) \iff \exists\, c > 0, n_0 \ge 1 \text{ such that } f(n) \le c \cdot g(n) \quad \forall n \ge n_0

![Big-O notation plot showing f(n) bounded above by c*g(n)](https://assets.knowt.com/pdf-flow-prod/5244169f-7095-452c-9d84-50db9341ff31-figures/29.jpg)

2. **Big-Omega Notation (Ω\Omega - Lower Bound / Best-Case)**:
&nbsp;&nbsp;&nbsp;f(n) is Ω(g(n))  ⟺  ∃ c>0,n0≥1 such that f(n)≥c⋅g(n)∀n≥n0f(n) \text{ is } \Omega(g(n)) \iff \exists\, c > 0, n_0 \ge 1 \text{ such that } f(n) \ge c \cdot g(n) \quad \forall n \ge n_0

![Big-Omega notation plot showing f(n) bounded below by c*g(n)](https://assets.knowt.com/pdf-flow-prod/5244169f-7095-452c-9d84-50db9341ff31-figures/30.jpg)

3. **Big-Theta Notation (Θ\Theta - Tight Bound / Average-Case)**:
&nbsp;&nbsp;&nbsp;f(n) is Θ(g(n))  ⟺  ∃ c′>0,c′′>0,n0≥1 such that c′⋅g(n)≤f(n)≤c′′⋅g(n)∀n≥n0f(n) \text{ is } \Theta(g(n)) \iff \exists\, c' > 0, c'' > 0, n_0 \ge 1 \text{ such that } c' \cdot g(n) \le f(n) \le c'' \cdot g(n) \quad \forall n \ge n_0

![Big-Theta notation plot showing f(n) bounded between c1*g(n) and c2*g(n)](https://assets.knowt.com/pdf-flow-prod/5244169f-7095-452c-9d84-50db9341ff31-figures/31.jpg)

## Mathematical Proofs for Big-O Bounding

- **Proof 1**: Show 2n+102n + 10 is O(n)O(n).
  - Set inequality: 2n+10≤c⋅n  ⟹  (c−2)n≥10  ⟹  n≥10c−22n + 10 \le c \cdot n \implies (c - 2)n \ge 10 \implies n \ge \frac{10}{c - 2}.
  - Select constant c=3c = 3, giving n0=10n_0 = 10. For all n≥10n \ge 10, 2n+10≤3n2n + 10 \le 3n. Thus 2n+102n + 10 is O(n)O(n).
- **Proof 2**: Prove n2n^2 is **not** O(n)O(n).
  - Assume n2≤c⋅n  ⟹  n≤cn^2 \le c \cdot n \implies n \le c. This contradicts the condition that cc is a fixed constant across arbitrary n≥n0n \ge n_0. Thus n2n^2 is not O(n)O(n).
- **Proof 3**: Show 7n−27n - 2 is O(n)O(n).
  - Satisfied for c=7c = 7 and n0=1n_0 = 1 (7n−2≤7n7n - 2 \le 7n).
- **Proof 4**: Show 3log⁡(n)+53 \log(n) + 5 is O(log⁡(n))O(\log(n)).
  - Satisfied for c=8c = 8 and n0=2n_0 = 2 (3log⁡(n)+5≤8log⁡(n)3 \log(n) + 5 \le 8 \log(n) for n≥2n \ge 2).

# Analyzing Code Constructs & Growth Function Comparisons

## Seven Critical Growth Functions

| Function Label | Mathematical Form | Growth Category |
| :--- | :--- | :--- |
| Constant | 11 / 1717 | O(1)O(1) |
| Logarithmic | log⁡(n)\log(n) | O(log⁡(n))O(\log(n)) |
| Linear | nn / 20n−420n - 4 | O(n)O(n) |
| N-Log-N | nlog⁡(n)n \log(n) | O(nlog⁡(n))O(n \log(n)) |
| Quadratic | n2n^2 / 3n2+5n−23n^2 + 5n - 2 | O(n2)O(n^2) |
| Cubic | n3n^3 / 8n3+3n28n^3 + 3n^2 | O(n3)O(n^3) |
| Exponential | 2n2^n | O(2n)O(2^n) |

### Values of Seven Core Functions across Input Sizes

| nn | log⁡2(n)\log_2(n) | nn | nlog⁡2(n)n \log_2(n) | n2n^2 | n3n^3 | 2n2^n |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 88 | 33 | 88 | 2424 | 6464 | 512512 | 256256 |
| 1616 | 44 | 1616 | 6464 | 256256 | 4,0964,096 | 65,53665,536 |
| 3232 | 55 | 3232 | 160160 | 1,0241,024 | 32,76832,768 | 4,294,967,2964,294,967,296 |
| 6464 | 66 | 6464 | 384384 | 4,0964,096 | 262,144262,144 | 1.84×10191.84 \times 10^{19} |
| 128128 | 77 | 128128 | 896896 | 16,38416,384 | 2,097,1522,097,152 | 3.40×10383.40 \times 10^{38} |
| 256256 | 88 | 256256 | 2,0482,048 | 65,53665,536 | 16,777,21616,777,216 | 1.15×10771.15 \times 10^{77} |
| 512512 | 99 | 512512 | 4,6084,608 | 262,144262,144 | 134,217,728134,217,728 | 1.34×101541.34 \times 10^{154} |

## Practical Sorting Efficiency: Insertion Sort vs. Merge Sort

- Insertion Sort running time: T(n)=n24T(n) = \frac{n^2}{4}.
- Merge Sort running time: T(n)=2nlog⁡2(n)T(n) = 2n \log_2(n).
- **Sorting 1,000,000 items**:
  - Insertion Sort requires ≈70 hours\approx 70\,\text{hours}.
  - Merge Sort requires ≈40 seconds\approx 40\,\text{seconds}.
- **On a 100×100\times Faster Processor**:
  - Insertion Sort requires ≈40 minutes\approx 40\,\text{minutes}.
  - Merge Sort requires <0.5 seconds< 0.5\,\text{seconds}.
- Conclusion: Hardware speedups cannot compensate for poor asymptotic growth complexity (O(n2)O(n^2) vs O(nlog⁡(n))O(n \log(n))).

## Impact of Processor Speedup (10×10\times Processor Speedup)

| Algorithm | Time Complexity | Max Problem Size Before Speedup | Max Problem Size After 10×10\times Speedup |
| :--- | :--- | :--- | :--- |
| A | nn | s1s_1 | 10s110 s_1 |
| B | n2n^2 | s2s_2 | 10s2≈3.16s2\sqrt{10} s_2 \approx 3.16 s_2 |
| C | n3n^3 | s3s_3 | 103s3≈2.15s3\sqrt[3]{10} s_3 \approx 2.15 s_3 |
| D | 2n2^n | s4s_4 | s4+log⁡2(10)≈s4+3.3s_4 + \log_2(10) \approx s_4 + 3.3 |

## Rules for Code Complexity Analysis

1. **Single Loops**:

java for (int count = 0; count < n; count++) { // sequence of O(1) steps }    ```    n iterations×O(1)=O(n)n \text{ iterations} \times O(1) = O(n).

  1. Logarithmic Loops:

   count = 1;
   while (count < n) {
       count *= 2;
       // sequence of O(1) steps
   }
&nbsp;&nbsp;&nbsp;```
&nbsp;&nbsp;&nbsp;Loop executes log⁡2(n)\log_2(n) times   ⟹  O(log⁡(n))\implies O(\log(n)).

3. **Nested Loops**:

java for (int count = 0; count < n; count++) { for (int count2 = 1; count2 < n; count2 *= 2) { System.out.println(count + ", " + count2); } }    ```    Outer loop executes nn times; inner loop executes log⁡2(n)\log_2(n) times. Total efficiency = O(nlog⁡(n))O(n \log(n)).

  1. Nested Identical Loops: java for (int count = 0; count < n; count++) { for (int count2 = 0; count2 < n; count2++) { // sequence of O(1) steps } } &nbsp;&nbsp;&nbsp;    $$n \text{ iterations} \times n \text{ iterations} = O(n^2)$.

  2. Method Calls inside Loops:

    • To determine inner body complexity, multiply loop iteration count by the Big-O complexity of the method called inside.