Learning Objectives
By the end of this lesson, you will be able to:
- Show understanding of linear and binary searching methods
- Write algorithms to implement linear and binary searches
- Explain the conditions necessary for binary search
- Describe how binary search performance varies with data size
- Show understanding of insertion sort and bubble sort methods
- Write algorithms to implement insertion sort and bubble sort
- Explain how sorting performance depends on initial data order and size
- Show understanding of and use Abstract Data Types (ADT)
- Write algorithms to find, insert, and delete items in various ADTs
- Show understanding that a graph is an example of an ADT
- Describe key features of graphs and justify their use
- Explain how ADTs can be implemented from other ADTs
- Describe and implement various ADTs from built-in types
- Compare algorithms using time and memory criteria
- Use Big O notation to specify time and space complexity
Key Terms
Linear Search
Searching method that checks each item sequentially until target is found
Binary Search
Searching method that repeatedly divides sorted data in half
Insertion Sort
Sorting method that builds sorted array one item at a time
Bubble Sort
Sorting method that repeatedly steps through list, comparing adjacent items
Abstract Data Type (ADT)
Mathematical model for data types defined by behavior from user perspective
Stack
LIFO (Last In First Out) data structure
Queue
FIFO (First In First Out) data structure
Linked List
Linear collection of nodes where each node points to the next
Binary Tree
Hierarchical data structure with each node having at most two children
Dictionary
Collection of key-value pairs with unique keys
Graph
Collection of nodes (vertices) connected by edges
Algorithm Complexity
Measure of resources (time/memory) required by an algorithm
Big O Notation
Mathematical notation describing algorithm complexity in worst-case scenario
Time Complexity
Amount of time an algorithm takes to complete as function of input size
Space Complexity
Amount of memory an algorithm uses as function of input size
19.1.1 Searching Algorithms
Searching algorithms are used to find specific items in a collection of data. Two fundamental searching methods are linear search and binary search.
Linear Search
How Linear Search Works
- Start at the beginning of the list
- Compare each item with the target value
- If a match is found, return the position/index
- If the end of the list is reached without finding the item, return "not found"
- Works on both sorted and unsorted data
Real-Life Example
Imagine looking for your friend in a classroom:
- Start from the first row, first seat
- Look at each student one by one
- Continue until you find your friend
- If you check every seat and don't find them, they're not in class
This is exactly how linear search works!
Linear Search Algorithm (CIE Pseudocode)
FUNCTION linearSearch(list, target)
FOR i ← 0 TO LENGTH(list) - 1
IF list[i] = target THEN
RETURN i // Found at position i
ENDIF
NEXT i
RETURN -1 // Not found
ENDFUNCTION
Linear Search Simulation
How it works: The red-highlighted element is currently being compared. Linear search checks each element sequentially until it finds the target.
Binary Search
How Binary Search Works
- Data must be sorted first
- Compare target with middle element
- If target equals middle element, search is complete
- If target is less than middle, search left half
- If target is greater than middle, search right half
- Repeat until found or search space is empty
Real-Life Example
Imagine looking for a word in a dictionary:
- Open to the middle of the dictionary
- Compare your word with words on that page
- If your word comes before, search first half
- If your word comes after, search second half
- Repeat until you find the word
This "divide and conquer" approach is binary search!
Conditions for Binary Search
- Data must be sorted - This is the most important requirement
- Works only with random access data structures (like arrays)
- Not suitable for linked lists (no direct access to middle element)
- More efficient than linear search for large datasets
Binary Search Algorithm (CIE Pseudocode)
FUNCTION binarySearch(list, target)
low ← 0
high ← LENGTH(list) - 1
WHILE low ≤ high
mid ← (low + high) DIV 2
IF list[mid] = target THEN
RETURN mid
ELSEIF list[mid] < target THEN
low ← mid + 1
ELSE
high ← mid - 1
ENDIF
ENDWHILE
RETURN -1 // Not found
ENDFUNCTION
Binary Search Performance
The performance of binary search varies according to the number of data items:
- Time Complexity: O(log n)
- Best case: O(1) - target is the middle element
- Worst case: O(log n) - target is at beginning or end
- Average case: O(log n)
Searching 1,000,000 items takes only about 20 comparisons with binary search!
Linear vs Binary Search Comparison
| Linear Search | Binary Search |
|---|---|
| Works on any data (sorted or unsorted) | Requires sorted data |
| Time Complexity: O(n) | Time Complexity: O(log n) |
| Simple to implement | More complex implementation |
| Good for small datasets | Excellent for large datasets |
| Sequential access | Random access required |
Binary Search Simulation
How it works: Orange = currently comparing, Red = search space eliminated. Binary search halves the search space each time!
Activity 1: Search Algorithm Analysis
Given this sorted array: [5, 12, 19, 26, 33, 40, 47, 54, 61, 68]
- Trace the steps of linear search looking for value 47
- Trace the steps of binary search looking for value 33
- How many comparisons does each search require?
- What is the maximum number of comparisons needed to find that value 70 is not in the array using binary search?
Solution:
- Linear search for 47:
- Compare 5 (no match)
- Compare 12 (no match)
- Compare 19 (no match)
- Compare 26 (no match)
- Compare 33 (no match)
- Compare 40 (no match)
- Compare 47 (FOUND!)
- Total: 7 comparisons
- Binary search for 33:
- Middle = index 4 (33) → FOUND!
- Total: 1 comparison
- Comparisons: Linear = 7, Binary = 1
- Binary search for 70 (not in array):
- Compare middle (33) → 70 > 33, search right
- Compare middle of right (61) → 70 > 61, search right
- Compare (68) → 70 > 68, search right
- Search space empty → NOT FOUND
- Maximum comparisons: 3 (log₂(10) ≈ 3.32, rounded up)
Activity 2: Algorithm Implementation
Write a CIE pseudocode algorithm for:
- A modified linear search that counts how many times the target appears in the array
- A binary search that works on a descending sorted array (largest to smallest)
Solution:
- Modified linear search:
FUNCTION countOccurrences(list, target) count ← 0 FOR i ← 0 TO LENGTH(list) - 1 IF list[i] = target THEN count ← count + 1 ENDIF NEXT i RETURN count ENDFUNCTION - Binary search for descending array:
FUNCTION binarySearchDescending(list, target) low ← 0 high ← LENGTH(list) - 1 WHILE low ≤ high mid ← (low + high) DIV 2 IF list[mid] = target THEN RETURN mid ELSEIF list[mid] > target THEN // Target is smaller, search right half (descending order) low ← mid + 1 ELSE // Target is larger, search left half (descending order) high ← mid - 1 ENDIF ENDWHILE RETURN -1 ENDFUNCTION
Check Your Understanding: Searching Algorithms
1. What is the main advantage of binary search over linear search? [2 marks]
Answer
- [1 mark] Binary search is much faster for large datasets
- [1 mark] Time complexity is O(log n) compared to O(n) for linear search
- [Additional] Example: Searching 1 million items takes ~20 comparisons vs 1 million comparisons
2. What condition must be met before using binary search? [1 mark]
Answer
- [1 mark] The data must be sorted in ascending or descending order
- [Additional] Binary search won't work on unsorted data because it relies on comparing with the middle element to decide which half to search next
3. In the binary search algorithm, what does the DIV operator do? [2 marks]
Answer
- [1 mark] DIV performs integer division
- [1 mark] It returns the whole number part of division, discarding any remainder
- [Additional] Example: 7 DIV 2 = 3, 10 DIV 3 = 3
4. Why would you use linear search instead of binary search? [2 marks]
Answer
- [1 mark] When the data is unsorted and sorting would take more time than searching
- [1 mark] When working with small datasets where the difference in performance is negligible
- [Additional] When working with data structures that don't support random access (like linked lists)
5. How does binary search performance vary with the number of data items? [2 marks]
Answer
- [1 mark] Binary search time complexity is O(log n), meaning it grows logarithmically with data size
- [1 mark] Doubling the data size only adds one more comparison in worst case
- [Additional] Example: 16 items → 4 comparisons, 32 items → 5 comparisons, 1 million items → 20 comparisons
6. Write the pseudocode for checking if an item exists in an array using linear search. [3 marks]
Answer
FUNCTION linearSearchExists(list, target)
FOR i ← 0 TO LENGTH(list) - 1
IF list[i] = target THEN
RETURN TRUE
ENDIF
NEXT i
RETURN FALSE
ENDFUNCTION
19.1.2 Sorting Algorithms
Sorting algorithms arrange data in a specific order (usually ascending or descending). Two fundamental sorting methods are insertion sort and bubble sort.
Insertion Sort
How Insertion Sort Works
- Builds the sorted array one element at a time
- Takes each element and inserts it into its correct position
- Like sorting a hand of playing cards
- Efficient for small datasets or nearly sorted data
- Time Complexity: O(n²) worst case, O(n) best case
Real-Life Example
Imagine organizing books on a shelf:
- Start with first book - it's already "sorted"
- Take second book, compare with first, swap if needed
- Take third book, compare with first two, insert in correct position
- Continue until all books are in order
This is exactly how insertion sort works!
Insertion Sort Algorithm (CIE Pseudocode)
FUNCTION insertionSort(list)
FOR i ← 1 TO LENGTH(list) - 1
key ← list[i]
j ← i - 1
// Move elements greater than key one position ahead
WHILE j ≥ 0 AND list[j] > key
list[j + 1] ← list[j]
j ← j - 1
ENDWHILE
list[j + 1] ← key
NEXT i
ENDFUNCTION
Bubble Sort
How Bubble Sort Works
- Repeatedly steps through the list
- Compares adjacent elements and swaps if they're in wrong order
- Largest elements "bubble up" to the end
- Simple but inefficient for large lists
- Time Complexity: O(n²) in worst and average cases
Real-Life Example
Imagine bubbles rising in a glass of soda:
- Compare first two bubbles, swap if wrong order
- Compare next two bubbles, swap if needed
- Continue to end - largest bubble reaches top
- Repeat process ignoring already sorted elements
Each pass moves the largest unsorted element to its correct position!
Bubble Sort Algorithm (CIE Pseudocode)
FUNCTION bubbleSort(list)
n ← LENGTH(list)
FOR i ← 0 TO n - 2
FOR j ← 0 TO n - i - 2
IF list[j] > list[j + 1] THEN
// Swap elements
temp ← list[j]
list[j] ← list[j + 1]
list[j + 1] ← temp
ENDIF
NEXT j
NEXT i
ENDFUNCTION
Sorting Performance Factors
The performance of a sorting routine may depend on:
1. Initial Order of Data
- Insertion sort: O(n) for nearly sorted data, O(n²) for reverse sorted
- Bubble sort: O(n) for already sorted (with optimization), O(n²) otherwise
- Some algorithms perform better on certain data patterns
2. Number of Data Items
- Both algorithms are O(n²) - performance degrades quickly with size
- For small n (≤ 100), simple sorts are acceptable
- For large n, more efficient algorithms (like quicksort) are needed
Sorting Algorithms Comparison
Insertion Sort
Bubble Sort
Key differences: Green = sorted elements, Orange = comparing, Red = swapping. Insertion sort builds sorted portion, bubble sort bubbles largest to end.
Insertion Sort vs Bubble Sort
| Insertion Sort | Bubble Sort |
|---|---|
| Builds sorted array one element at a time | Repeatedly swaps adjacent elements |
| Efficient for small or nearly sorted data | Simple but generally inefficient |
| Best case: O(n) (already sorted) | Best case: O(n) (with optimization) |
| Worst case: O(n²) (reverse sorted) | Worst case: O(n²) |
| Stable sort (preserves order of equal elements) | Stable sort |
| In-place sorting (requires O(1) extra space) | In-place sorting |
| Good for linked lists | Not suitable for linked lists |
Activity 3: Sorting Algorithm Trace
Trace the execution of insertion sort and bubble sort on this array:
- Show each pass of insertion sort
- Show each pass of bubble sort
- Count the total number of comparisons and swaps for each algorithm
- Which algorithm performs better on this specific data? Why?
Solution:
- Insertion sort trace:
- Pass 1: [34, 64, 25, 12, 22, 11, 90] (insert 34)
- Pass 2: [25, 34, 64, 12, 22, 11, 90] (insert 25)
- Pass 3: [12, 25, 34, 64, 22, 11, 90] (insert 12)
- Pass 4: [12, 22, 25, 34, 64, 11, 90] (insert 22)
- Pass 5: [11, 12, 22, 25, 34, 64, 90] (insert 11)
- Pass 6: [11, 12, 22, 25, 34, 64, 90] (insert 90)
- Total comparisons: ~15, swaps: ~15
- Bubble sort trace:
- Pass 1: [34, 25, 12, 22, 11, 64, 90] (64 bubbles to position 6)
- Pass 2: [25, 12, 22, 11, 34, 64, 90] (34 bubbles to position 5)
- Pass 3: [12, 22, 11, 25, 34, 64, 90] (25 bubbles to position 4)
- Pass 4: [12, 11, 22, 25, 34, 64, 90] (22 bubbles to position 3)
- Pass 5: [11, 12, 22, 25, 34, 64, 90] (12 bubbles to position 2)
- Pass 6: [11, 12, 22, 25, 34, 64, 90] (no swaps, sorted)
- Total comparisons: 21, swaps: ~12
- Counts: Insertion: ~15 comparisons/swaps, Bubble: 21 comparisons, ~12 swaps
- Better algorithm: Insertion sort performs slightly better on this data because it requires fewer comparisons. Both are O(n²) but insertion sort has better constant factors.
Activity 4: Optimized Bubble Sort
The basic bubble sort algorithm can be optimized. Write pseudocode for:
- Bubble sort with early termination (stops if no swaps in a pass)
- Insertion sort that sorts in descending order
- Explain how each optimization improves performance
Solution:
- Optimized bubble sort:
FUNCTION optimizedBubbleSort(list) n ← LENGTH(list) swapped ← TRUE WHILE swapped = TRUE swapped ← FALSE FOR j ← 0 TO n - 2 IF list[j] > list[j + 1] THEN temp ← list[j] list[j] ← list[j + 1] list[j + 1] ← temp swapped ← TRUE ENDIF NEXT j n ← n - 1 // Last element is now sorted ENDWHILE ENDFUNCTIONImprovement: Stops early if array becomes sorted, reducing unnecessary passes.
- Descending insertion sort:
FUNCTION insertionSortDescending(list) FOR i ← 1 TO LENGTH(list) - 1 key ← list[i] j ← i - 1 // Change comparison operator for descending order WHILE j ≥ 0 AND list[j] < key list[j + 1] ← list[j] j ← j - 1 ENDWHILE list[j + 1] ← key NEXT i ENDFUNCTIONChange: Use
list[j] < keyinstead oflist[j] > keyfor descending order. - Performance improvements:
- Optimized bubble sort reduces from O(n²) to O(n) for already sorted data
- Both optimizations maintain O(n²) worst case but improve average/best cases
- Early termination prevents unnecessary comparisons when data is sorted early
Check Your Understanding: Sorting Algorithms
1. What is the main difference between insertion sort and bubble sort? [2 marks]
Answer
- [1 mark] Insertion sort builds the sorted array one element at a time by inserting each element in its correct position
- [1 mark] Bubble sort repeatedly swaps adjacent elements to move larger elements to the end
- [Additional] Insertion sort is generally more efficient, especially for nearly sorted data
2. Why is insertion sort efficient for nearly sorted data? [2 marks]
Answer
- [1 mark] Each element only needs to be compared with a few elements before finding its correct position
- [1 mark] The inner while loop executes very few times (sometimes not at all) for nearly sorted data
- [Additional] Time complexity becomes O(n) instead of O(n²) for nearly sorted data
3. What does it mean when we say bubble sort is "stable"? [2 marks]
Answer
- [1 mark] A stable sorting algorithm preserves the relative order of equal elements
- [1 mark] If two elements have the same value, their original order is maintained after sorting
- [Additional] This is important when sorting by multiple criteria (e.g., sort by grade, then by name)
4. How does the initial order of data affect sorting performance? [3 marks]
Answer
- [1 mark] Already sorted data: insertion sort is O(n), bubble sort (optimized) is O(n)
- [1 mark] Reverse sorted data: both algorithms are O(n²) worst case
- [1 mark] Random data: both are O(n²) average case
- [Additional] Some algorithms (like quicksort) perform poorly on nearly sorted data but well on random data
5. Write pseudocode for swapping two elements in an array. [2 marks]
Answer
PROCEDURE swap(list, i, j)
temp ← list[i]
list[i] ← list[j]
list[j] ← temp
ENDPROCEDURE
6. When would you choose bubble sort over insertion sort? [2 marks]
Answer
- [1 mark] When simplicity is more important than efficiency (teaching/learning purposes)
- [1 mark] When you know the data is already nearly sorted and you use the optimized version
- [Additional] Insertion sort is generally preferred in practice due to better performance
19.1.3 Abstract Data Types (ADT)
An Abstract Data Type (ADT) is a mathematical model for data types defined by their behavior (operations) from the user's perspective, independent of implementation.
What is an ADT?
- Defines what operations can be performed
- Does not specify how operations are implemented
- Separates interface from implementation
- Examples: Stack, Queue, Linked List, Binary Tree, Graph, Dictionary
Real-Life Example
Think of a TV remote control:
- You know what buttons do (volume, channel, power)
- You don't need to know how they work internally
- The remote is an "abstract interface" to the TV
- Different TV brands can have different internal implementations
ADTs work the same way - define interface, hide implementation!
Stack ADT
Stack Characteristics
- LIFO - Last In First Out
- Operations: push (add), pop (remove), peek (view top)
- Like a stack of plates - add/remove from top only
- Applications: function call stack, undo operations, expression evaluation
Stack Visualization
Insert into Stack (Push)
PROCEDURE push(stack, item)
// Add item to top of stack
stack.append(item) // Assuming array implementation
ENDPROCEDURE
Delete from Stack (Pop)
FUNCTION pop(stack)
IF isEmpty(stack) THEN
RETURN NULL // Stack underflow
ELSE
item ← stack[LENGTH(stack) - 1]
REMOVE stack[LENGTH(stack) - 1]
RETURN item
ENDIF
ENDFUNCTION
Queue ADT
Queue Characteristics
- FIFO - First In First Out
- Operations: enqueue (add to rear), dequeue (remove from front)
- Like a queue of people - first in line gets served first
- Applications: printer queues, message queues, breadth-first search
Queue Visualization
Insert into Queue (Enqueue)
PROCEDURE enqueue(queue, item)
// Add item to rear of queue
queue.append(item) // Assuming array implementation
ENDPROCEDURE
Delete from Queue (Dequeue)
FUNCTION dequeue(queue)
IF isEmpty(queue) THEN
RETURN NULL // Queue underflow
ELSE
item ← queue[0]
REMOVE queue[0] // Remove first element
RETURN item
ENDIF
ENDFUNCTION
Linked List ADT
Linked List Characteristics
- Collection of nodes where each node points to next node
- Dynamic size - can grow/shrink during execution
- Efficient insertion/deletion at any position
- Sequential access (unlike random access in arrays)
- Types: Singly linked, Doubly linked, Circular linked
Linked List Visualization
Find in Linked List
FUNCTION findLinkedList(head, target)
current ← head
WHILE current ≠ NULL
IF current.data = target THEN
RETURN current
ENDIF
current ← current.next
ENDWHILE
RETURN NULL // Not found
ENDFUNCTION
Insert into Linked List
PROCEDURE insertLinkedList(head, newData)
newNode ← Node(newData)
newNode.next ← head
head ← newNode
ENDPROCEDURE
This inserts at beginning. Insertion at other positions requires finding the right node first.
Delete from Linked List
PROCEDURE deleteLinkedList(head, target)
IF head = NULL THEN RETURN
// Case 1: Delete head
IF head.data = target THEN
head ← head.next
RETURN
ENDIF
// Case 2: Delete middle/end
current ← head
WHILE current.next ≠ NULL
IF current.next.data = target THEN
current.next ← current.next.next
RETURN
ENDIF
current ← current.next
ENDWHILE
ENDPROCEDURE
Binary Tree ADT
Binary Tree Characteristics
- Hierarchical data structure
- Each node has at most two children: left and right
- Root node at top, leaf nodes at bottom
- Applications: File systems, database indexing, expression trees
- Special types: Binary Search Tree (BST), Heap, AVL Tree
Binary Tree Visualization
Find in Binary Tree
FUNCTION findBinaryTree(root, target)
IF root = NULL THEN
RETURN NULL
ENDIF
IF root.data = target THEN
RETURN root
ENDIF
// Search left subtree
leftResult ← findBinaryTree(root.left, target)
IF leftResult ≠ NULL THEN
RETURN leftResult
ENDIF
// Search right subtree
rightResult ← findBinaryTree(root.right, target)
RETURN rightResult
ENDFUNCTION
Insert into Binary Search Tree
PROCEDURE insertBST(root, newData)
IF root = NULL THEN
root ← Node(newData)
ELSEIF newData < root.data THEN
insertBST(root.left, newData)
ELSE
insertBST(root.right, newData)
ENDIF
ENDPROCEDURE
For Binary Search Tree (BST): left < root < right
Dictionary ADT
Dictionary Characteristics
- Collection of key-value pairs
- Keys are unique (no duplicates)
- Operations: insert, delete, search by key
- Also called: Map, Associative Array, Hash Table
- Applications: Database indexing, symbol tables, caches
Dictionary Example
Graph ADT
Graph Characteristics
- Collection of nodes (vertices) connected by edges
- Can be directed or undirected
- Can have weighted edges
- Key features: vertices, edges, adjacency, paths, cycles
- Applications: Social networks, maps, network routing
Graph Example
Justifying Graph Use for a Given Situation
When to use a graph ADT:
- Social networks: Users are vertices, friendships are edges
- Transportation maps: Cities are vertices, roads are edges
- Website links: Pages are vertices, hyperlinks are directed edges
- Dependency graphs: Tasks are vertices, dependencies are edges
Key justification: Use graphs when you need to represent and analyze relationships/connections between entities.
ADT Implementation
ADTs from Other ADTs
ADTs can be implemented from other ADTs or built-in types:
- Stack from Array: Use array with top pointer
- Queue from Linked List: Use linked list with head/tail pointers
- Dictionary from Array of Records: Key-value pairs in array
- Binary Tree from Records: Nodes with left/right pointers
Built-in Type Implementation
Some ADTs can be implemented directly from built-in types:
- Stack using Array: Python list with append/pop
- Queue using List: Python list with append/pop(0)
- Dictionary using Hash Table: Python dict type
- Linked List using Classes: Python class with next pointer
Activity 5: ADT Algorithm Design
Design algorithms for the following ADT operations:
- Find the size (number of elements) in a linked list
- Check if a binary tree is a Binary Search Tree (BST)
- Implement a queue using two stacks
- Find if there's a path between two vertices in an undirected graph
Solution:
- Linked list size:
FUNCTION linkedListSize(head) count ← 0 current ← head WHILE current ≠ NULL count ← count + 1 current ← current.next ENDWHILE RETURN count ENDFUNCTION - Check if BST:
FUNCTION isBST(node, min, max) IF node = NULL THEN RETURN TRUE ENDIF IF node.data ≤ min OR node.data ≥ max THEN RETURN FALSE ENDIF RETURN isBST(node.left, min, node.data) AND isBST(node.right, node.data, max) ENDFUNCTION // Initial call: isBST(root, -∞, +∞) - Queue using two stacks:
// Stack1 for enqueue, Stack2 for dequeue PROCEDURE enqueue(stack1, item) push(stack1, item) ENDPROCEDURE FUNCTION dequeue(stack1, stack2) IF isEmpty(stack2) THEN // Transfer all elements from stack1 to stack2 WHILE NOT isEmpty(stack1) push(stack2, pop(stack1)) ENDWHILE ENDIF IF isEmpty(stack2) THEN RETURN NULL // Queue empty ELSE RETURN pop(stack2) ENDIF ENDFUNCTION - Graph path finding (simplified):
FUNCTION hasPath(graph, start, end, visited) IF start = end THEN RETURN TRUE ENDIF visited[start] ← TRUE FOR EACH neighbor IN graph.adjacencyList[start] IF NOT visited[neighbor] THEN IF hasPath(graph, neighbor, end, visited) THEN RETURN TRUE ENDIF ENDIF NEXT neighbor RETURN FALSE ENDFUNCTION
Activity 6: ADT Selection Justification
For each scenario, choose the most appropriate ADT and justify your choice:
- Implementing an undo feature in a text editor
- Storing student records accessed by student ID
- Managing processes in a CPU scheduler
- Representing friendships in a social network
- Storing a hierarchical file system structure
Solution:
- Undo feature: STACK - Operations are LIFO (last action undone first), push for each action, pop to undo.
- Student records by ID: DICTIONARY - Student ID is unique key, provides fast O(1) lookup by key.
- CPU scheduler: QUEUE - Processes should be FIFO (first come first served), enqueue new processes, dequeue for execution.
- Social network friendships: GRAPH - Users are vertices, friendships are edges, can analyze connections and find paths between users.
- File system: BINARY TREE - Hierarchical structure with parent-child relationships, directories can have subdirectories (children).
Key principle: Choose ADT based on required operations and data relationships.
Check Your Understanding: Abstract Data Types
1. What is an Abstract Data Type (ADT)? [3 marks]
Answer
- [1 mark] Mathematical model for data types defined by behavior from user perspective
- [1 mark] Specifies what operations can be performed, not how they're implemented
- [1 mark] Separates interface from implementation
- [Additional] Examples: Stack, Queue, Linked List, Binary Tree, Graph, Dictionary
2. Explain LIFO and FIFO with examples. [4 marks]
Answer
LIFO (Last In First Out):
- Last element added is first removed
- Example: Stack of plates
- ADT: Stack
- Operations: push (add), pop (remove)
FIFO (First In First Out):
- First element added is first removed
- Example: Queue of people
- ADT: Queue
- Operations: enqueue (add), dequeue (remove)
3. How can a queue be implemented using a linked list? [3 marks]
Answer
- [1 mark] Maintain two pointers: front (head) and rear (tail)
- [1 mark] Enqueue: Add new node at rear, update rear pointer
- [1 mark] Dequeue: Remove node from front, update front pointer
- [Additional] Linked list allows dynamic size, no fixed capacity like array
4. Why is a graph considered an ADT? [2 marks]
Answer
- [1 mark] Graph defines operations (add vertex, add edge, find path) without specifying implementation
- [1 mark] Can be implemented in different ways (adjacency matrix, adjacency list)
- [Additional] Abstract concept of vertices and edges that can model real-world relationships
5. Write pseudocode to find an item in a binary tree. [3 marks]
Answer
FUNCTION findBinaryTree(root, target)
IF root = NULL THEN
RETURN NULL
ENDIF
IF root.data = target THEN
RETURN root
ENDIF
leftResult ← findBinaryTree(root.left, target)
IF leftResult ≠ NULL THEN
RETURN leftResult
ENDIF
rightResult ← findBinaryTree(root.right, target)
RETURN rightResult
ENDFUNCTION
6. Describe how a dictionary ADT can be implemented. [3 marks]
Answer
- [1 mark] Array of records: Each element is a key-value pair
- [1 mark] Hash table: Use hash function to map keys to array indices
- [1 mark] Binary search tree: Store key-value pairs, ordered by key for efficient search
- [Additional] Built-in types: Python dict, Java HashMap, C++ unordered_map
19.1.4 Algorithm Complexity
Algorithm complexity measures how resources (time and memory) required by an algorithm scale with input size. Different algorithms performing the same task can be compared using these criteria.
Comparison Criteria
- Time taken to complete task: How runtime grows with input size
- Memory used: How much memory (space) algorithm requires
- Worst-case vs Average-case: Different scenarios may have different performance
- Best-case: Minimum resources needed
Real-Life Example
Imagine two ways to find a book in a library:
- Method 1: Check every shelf (linear search) - time increases linearly with library size
- Method 2: Use catalog system (binary search) - time increases logarithmically
- For small library, difference is small
- For huge library, Method 2 is much faster
This is algorithm complexity in action!
Big O Notation
What is Big O?
- Mathematical notation describing algorithm complexity
- Measures worst-case scenario growth rate
- Ignores constants and lower-order terms
- Focuses on how algorithm scales with input size (n)
- Examples: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ)
Common Complexities
Algorithm Complexities Summary
| Algorithm | Time Complexity | Space Complexity | Explanation |
|---|---|---|---|
| Linear Search | O(n) | O(1) | Check each element once |
| Binary Search | O(log n) | O(1) | Halve search space each time |
| Insertion Sort | O(n²) | O(1) | Nested loops compare all pairs |
| Bubble Sort | O(n²) | O(1) | Nested loops, compare/swap adjacent |
| Stack Operations | O(1) | O(n) | Push/pop at top only |
| Queue Operations | O(1) | O(n) | Enqueue/dequeue at ends only |
| Linked List Search | O(n) | O(n) | Traverse nodes sequentially |
| Binary Tree Search | O(log n)* | O(n) | *For balanced BST, O(n) worst |
Time Complexity
Measures how runtime increases with input size:
- O(1): Access array element by index
- O(log n): Binary search on sorted array
- O(n): Linear search, traverse array
- O(n log n): Efficient sorts (merge sort, quick sort)
- O(n²): Simple sorts (bubble, insertion)
- O(2ⁿ): Fibonacci recursive, traveling salesman
Space Complexity
Measures how memory usage increases with input size:
- O(1): Constant extra space (in-place algorithms)
- O(n): Linear extra space (copy of input)
- O(n²): Quadratic space (adjacency matrix for graph)
- In-place vs Out-of-place: Some algorithms need extra memory, others don't
- Auxiliary space: Extra space needed beyond input storage
Complexity Growth Rates Visualization
Growth rates: As input size (n) increases:
- O(1) stays flat - best
- O(log n) grows slowly - very good
- O(n) grows linearly - acceptable
- O(n log n) grows faster but manageable
- O(n²) grows quickly - poor for large n
- O(2ⁿ) grows extremely fast - impractical for large n
Calculating Big O Notation
Rules for determining Big O:
- Ignore constants: O(5n) = O(n), O(3n² + 2n + 1) = O(n²)
- Worst-case dominates: O(n² + n log n) = O(n²)
- Add for sequential statements: O(n) + O(n²) = O(n²)
- Multiply for nested loops: O(n) × O(n) = O(n²)
- Different variables: O(n × m) if loops use different input sizes
Activity 7: Complexity Analysis
Analyze the time and space complexity of these algorithms:
- Algorithm that finds maximum element in array
- Algorithm that finds all pairs of elements in array
- Algorithm that recursively calculates factorial
- Algorithm that merges two sorted arrays into one sorted array
- Explain why O(2n) is the same as O(n) in Big O notation
Solution:
- Find maximum: Time: O(n) - check each element once; Space: O(1) - only need few variables
- All pairs: Time: O(n²) - nested loops; Space: O(1) - no extra storage needed
- Recursive factorial: Time: O(n) - n recursive calls; Space: O(n) - n stack frames
- Merge sorted arrays: Time: O(n+m) - linear pass through both; Space: O(n+m) - need result array
- O(2n) = O(n): Big O ignores constant factors. It describes growth rate, not exact time. As n→∞, 2n grows at same rate as n (linear). Constants don't change the fundamental scaling behavior.
Activity 8: Algorithm Comparison
Compare these algorithms for searching in different scenarios:
| Scenario | Data Size | Data State |
|---|---|---|
| A | 100 items | Unsorted |
| B | 1,000,000 items | Sorted |
| C | 10 items | Sorted |
| D | Frequently updated | Search rarely |
- For each scenario, recommend linear or binary search and justify
- Estimate worst-case comparisons for each scenario
- What other factors besides time complexity might affect choice?
Solution:
- Recommendations:
- A: Linear search - data unsorted, binary search requires sorting first
- B: Binary search - large sorted dataset, O(log n) much faster than O(n)
- C: Either works - small dataset, difference negligible; linear simpler
- D: Linear search - if updates frequent, maintaining sorted order expensive
- Worst-case comparisons:
- A: Linear - 100, Binary - would need sorting first (extra cost)
- B: Linear - 1,000,000, Binary - ~20 (log₂1,000,000 ≈ 20)
- C: Linear - 10, Binary - ~4
- D: Depends on size; linear better if sorting cost > search benefit
- Other factors:
- Memory usage (space complexity)
- Implementation complexity
- Data structure limitations (binary needs random access)
- Frequency of search vs updates
- Whether data is already sorted
Check Your Understanding: Algorithm Complexity
1. What does Big O notation measure? [2 marks]
Answer
- [1 mark] Worst-case time or space complexity of an algorithm
- [1 mark] How resource requirements grow as input size increases
- [Additional] Provides upper bound on growth rate, ignores constants and lower-order terms
2. Why is O(n²) worse than O(n log n)? [2 marks]
Answer
- [1 mark] O(n²) grows much faster than O(n log n) as n increases
- [1 mark] For large n, O(n²) algorithms become impractical while O(n log n) remain usable
- [Additional] Example: n=1,000,000: n²=1 trillion, n log n≈20 million (50× smaller)
3. What is the difference between time and space complexity? [3 marks]
Answer
Time Complexity:
- Measures runtime growth with input size
- How many operations algorithm performs
- Examples: O(1), O(n), O(n²)
Space Complexity:
- Measures memory usage growth
- How much extra memory algorithm needs
- Examples: O(1), O(n), O(n²)
4. Why do we ignore constants in Big O notation? [2 marks]
Answer
- [1 mark] Big O describes growth rate as input size approaches infinity
- [1 mark] Constants become negligible compared to the growth function for large n
- [Additional] Focus is on scalability, not exact runtime which depends on hardware/implementation
5. What is the time complexity of this algorithm? [3 marks]
Answer
FUNCTION exampleAlgorithm(n)
sum ← 0
FOR i ← 0 TO n - 1
FOR j ← 0 TO n - 1
sum ← sum + 1
NEXT j
NEXT i
FOR k ← 0 TO n - 1
sum ← sum + k
NEXT k
RETURN sum
ENDFUNCTION
- First nested loops: O(n × n) = O(n²)
- Second loop: O(n)
- Total: O(n² + n) = O(n²) (dominated by n²)
6. When comparing algorithms, why might you choose O(n²) over O(n log n)? [2 marks]
Answer
- [1 mark] When working with very small input sizes where constants matter more than growth rate
- [1 mark] When the O(n²) algorithm is simpler to implement/maintain/debug
- [Additional] When the O(n²) algorithm uses less memory (better space complexity) and memory is constrained
Key Takeaways
- Linear search checks each element sequentially - O(n) time, works on any data but inefficient for large datasets
- Binary search requires sorted data - O(log n) time, much faster for large datasets using divide-and-conquer
- Insertion sort builds sorted array one element at a time - O(n²) worst case, efficient for small/nearly sorted data
- Bubble sort repeatedly swaps adjacent elements - O(n²), simple but inefficient, good for teaching concepts
- Abstract Data Types (ADTs) define behavior not implementation - Separate interface from implementation
- Stack is LIFO - Last In First Out, operations: push/pop
- Queue is FIFO - First In First Out, operations: enqueue/dequeue
- Linked list uses nodes with pointers - Dynamic size, efficient insertion/deletion, sequential access
- Binary tree has hierarchical structure - Each node has at most two children, applications in searching/sorting
- Dictionary stores key-value pairs - Unique keys, fast lookup by key
- Graph represents relationships - Vertices and edges, used for networks, maps, dependencies
- Algorithm complexity measures scalability - Time (operations) and space (memory) requirements
- Big O notation describes worst-case growth rate - Ignores constants, focuses on behavior as n→∞
- Common complexities: O(1) constant, O(log n) logarithmic, O(n) linear, O(n²) quadratic
- Choose algorithms based on: Data size, data state (sorted/unsorted), operation frequency, memory constraints
Question Bank
1. Compare linear and binary search algorithms. [6 marks]
Marking Scheme & Answer
Linear Search:
- Checks each element sequentially
- Works on sorted/unsorted data
- Time Complexity: O(n)
- Simple implementation
- Good for small datasets
- Inefficient for large n
Binary Search:
- Requires sorted data
- Divide and conquer approach
- Time Complexity: O(log n)
- More complex implementation
- Excellent for large datasets
- Needs random access data structure
2. Write pseudocode for insertion sort and explain each step. [6 marks]
Marking Scheme & Answer
FUNCTION insertionSort(list)
FOR i ← 1 TO LENGTH(list) - 1
key ← list[i]
j ← i - 1
// Move elements greater than key one position ahead
WHILE j ≥ 0 AND list[j] > key
list[j + 1] ← list[j]
j ← j - 1
ENDWHILE
list[j + 1] ← key
NEXT i
ENDFUNCTION
- Outer loop (i): Iterates through each element starting from second
- Key: Current element to insert into sorted portion
- Inner while loop: Shifts elements greater than key right to make space
- Insertion: Places key in correct position in sorted portion
- Result: After each iteration, elements [0..i] are sorted
3. What is an Abstract Data Type? Give three examples. [4 marks]
Marking Scheme & Answer
- [2 marks] Definition: An Abstract Data Type (ADT) is a mathematical model for data types defined by their behavior (operations) from the user's perspective, without specifying implementation details. It separates interface from implementation.
- [2 marks] Three examples (1 mark for 3 correct):
- Stack: LIFO structure with push/pop operations
- Queue: FIFO structure with enqueue/dequeue operations
- Dictionary: Key-value pairs with insert/delete/search operations
- Other examples: Linked List, Binary Tree, Graph
4. Write algorithms to insert and delete from a stack. [4 marks]
Marking Scheme & Answer
Insert (Push):
PROCEDURE push(stack, item)
// Add item to top of stack
stack.append(item)
ENDPROCEDURE
Adds element to top of stack.
Delete (Pop):
FUNCTION pop(stack)
IF isEmpty(stack) THEN
RETURN NULL // Stack underflow
ELSE
item ← stack[LENGTH(stack) - 1]
REMOVE stack[LENGTH(stack) - 1]
RETURN item
ENDIF
ENDFUNCTION
Removes and returns top element.
5. Explain Big O notation with examples. [5 marks]
Marking Scheme & Answer
- [2 marks] Definition: Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends toward infinity. In computer science, it's used to classify algorithms by how their runtime or space requirements grow as input size grows.
- [3 marks] Examples:
- O(1): Constant time - accessing array element by index
- O(log n): Logarithmic - binary search on sorted array
- O(n): Linear - finding maximum element in array
- O(n²): Quadratic - bubble sort, insertion sort
- O(2ⁿ): Exponential - recursive Fibonacci without memoization
- Key properties: Ignores constants, focuses on worst-case, describes growth rate not exact time.
6. Describe how sorting performance depends on initial data order and size. [5 marks]
Marking Scheme & Answer
1. Initial Data Order:
- Already sorted: Insertion sort O(n), Bubble sort O(n) with optimization
- Reverse sorted: Both algorithms O(n²) worst case
- Random order: Both O(n²) average case
- Nearly sorted: Insertion sort performs well (close to O(n))
2. Number of Data Items:
- Both algorithms are O(n²) - performance degrades quadratically with size
- For small n (≤ 100), simple sorts are acceptable
- For large n, O(n²) becomes impractical
- More efficient algorithms (O(n log n)) needed for large datasets
7. Write pseudocode to find an item in a linked list. [4 marks]
Marking Scheme & Answer
FUNCTION findLinkedList(head, target)
current ← head
WHILE current ≠ NULL
IF current.data = target THEN
RETURN current // Found
ENDIF
current ← current.next
ENDWHILE
RETURN NULL // Not found
ENDFUNCTION
- Start at head (first node)
- Traverse list using next pointers
- Compare each node's data with target
- Return node if found, NULL if end reached without finding
- Time Complexity: O(n), Space Complexity: O(1)
8. How can ADTs be implemented from other ADTs? Give two examples. [4 marks]
Marking Scheme & Answer
- [2 marks] Concept: ADTs define interfaces, not implementations. One ADT can be implemented using the operations of another ADT, or using built-in types. This demonstrates abstraction and code reuse.
- [2 marks] Two examples:
- Stack using Array: Use array with top index pointer. Push = add to end, Pop = remove from end.
- Queue using Linked List: Use linked list with head (front) and tail (rear) pointers. Enqueue = add to tail, Dequeue = remove from head.
- Other examples: Dictionary using Binary Search Tree, Graph using adjacency list (array of linked lists).
9. Compare time and space complexity of linear and binary search. [4 marks]
Marking Scheme & Answer
| Aspect | Linear Search | Binary Search |
|---|---|---|
| Time Complexity | O(n) | O(log n) |
| Best Case | O(1) - first element | O(1) - middle element |
| Worst Case | O(n) - last element or not found | O(log n) - not in array |
| Space Complexity | O(1) - in-place | O(1) - iterative, O(log n) - recursive |
| Data Requirement | Any order | Must be sorted |
| Efficiency for large n | Poor - grows linearly | Excellent - grows logarithmically |
10. Justify using a graph ADT for a social network application. [3 marks]
Marking Scheme & Answer
- [1 mark] Social networks involve relationships between users - graphs naturally represent vertices (users) connected by edges (friendships)
- [1 mark] Graph algorithms can find connections: shortest path between users, mutual friends, friend suggestions
- [1 mark] Can represent directed/undirected relationships: following (directed) vs mutual friendship (undirected)
- [Additional] Other graph features useful: weighted edges (relationship strength), clustering coefficients (community detection), centrality measures (influential users)