IB Computer - Algorithm

**Algorithm Design and Analysis** is a fundamental aspect of computer science for creating efficient solutions to problems. This section of the IB Computer Science HL syllabus focuses on understanding, designing, and evaluating algorithms. Here's an in-depth look at the key concepts:

**Definition**: An algorithm is a step-by-step procedure or a set of rules to be followed in calculations or other problem-solving operations, especially by a computer.**Characteristics**:**Unambiguous**: Each step is clearly defined.**Finite**: The algorithm terminates after a finite number of steps.**Effective**: Each step is basic enough to be carried out (computable).

**Pseudocode**: A high-level description of an algorithm using the structural conventions of programming languages but intended for human reading.**Purpose**: Helps plan and conceptualize an algorithm before coding.**Structure**:Uses common programming constructs like loops, conditionals, and variables.

Avoids syntactical details of actual programming languages.

**Example**:

`// A is an array of integers`

BEGIN

max ← A[0]

FOR i ← 1 TO length(A) - 1 DO

IF A[i] > max THEN

max ← A[i]

END IF

END FOR

RETURN max

END

**Big-O Notation**: A mathematical notation that describes the upper bound of an algorithm's running time or space requirements in terms of the input size (n).**Purpose**: To classify algorithms according to their worst-case or average-case performance and to compare the efficiency of different algorithms.**Common Big-O Classifications**:**O(1)**: Constant time – the operation's time is independent of the input size.**O(log n)**: Logarithmic time – the operation's time grows logarithmically with the input size.**O(n)**: Linear time – the operation's time grows linearly with the input size.**O(n log n)**: Linearithmic time – the operation's time grows log-linearly with the input size.**O(n^2)**: Quadratic time – the operation's time grows quadratically with the input size.**O(2^n)**: Exponential time – the operation's time grows exponentially with the input size.**O(n!)**: Factorial time – the operation's time grows factorially with the input size.

**Sequence**: Executing statements one after another in a specific order.**Example**: Setting variables, performing arithmetic operations.

**Selection**: Making decisions based on conditions (if-else statements).

`Example:`

IF (condition) THEN

// execute this block

ELSE

// execute this block

END IF

**Iteration**: Repeating a block of code (loops).**Example:**

`FOR i ← 1 TO n DO`

// execute this block

END FOR

**Recursion**: A function calls itself to solve smaller instances of the same problem.**Example**:

`Factorial computation`

FUNCTION factorial(n)

IF n = 0 THEN

RETURN 1

ELSE

RETURN n * factorial(n - 1)

END IF

END FUNCTION

**Real-World Problem Solving**: Algorithms are applied in various domains like sorting data, searching for information, network routing, and more.**Algorithmic Thinking**: Developing the ability to break down problems into smaller, manageable parts and solving them systematically.

**Basic Algorithm Constructs:**

In computer science, algorithms are built using fundamental constructs that allow for the execution of instructions in a specific order. The basic constructs are sequence, selection, iteration, and recursion. Here is a detailed explanation of each:

The sequence construct involves executing statements one after another in a specific order. This is the most straightforward control structure.

**Example**:

`x ← 5`

y ← 10

sum ← x + y

PRINT sum

In this example, the operations are performed in sequence: setting x to 5, y to 10, calculating the sum, and then printing the result.

Selection (also known as decision-making) allows the algorithm to choose different paths based on certain conditions. The most common selection constructs are if, else if, and else statements.

**Example**:

`IF (temperature > 30) THEN`

PRINT "It's hot outside."

ELSE IF (temperature > 20) THEN

PRINT "It's warm outside."

ELSE

PRINT "It's cold outside."

END IF

In this example, the algorithm selects different actions depending on the value of temperature.

Iteration (or looping) allows the algorithm to repeat a block of code multiple times. There are several types of loops, including for, while, and do-while loops.

**For Loop Example**:

`FOR i ← 1 TO 5 DO`

PRINT i

END FOR

This loop prints the numbers 1 to 5. The loop variable i starts at 1 and increments by 1 until it reaches 5.

**While Loop Example**:

`i ← 1`

WHILE (i ≤ 5) DO

PRINT i

i ← i + 1

END WHILE

This while loop achieves the same result as the for loop but checks the condition before each iteration.

Recursion is a technique where a function calls itself to solve a smaller instance of the same problem. This is particularly useful for problems that can be broken down into similar sub-problems.

**Factorial Example**:

`FUNCTION factorial(n)`

IF (n = 0) THEN

RETURN 1

ELSE

RETURN n * factorial(n - 1)

END IF

END FUNCTION

In this example, the factorial function calculates the factorial of a number n by calling itself with n - 1 until it reaches the base case (n = 0).

Understanding these constructs allows you to develop and implement algorithms for various tasks. Here are a few practical implementations using these constructs:

`FUNCTION findMax(array)`

max ← array[0]

FOR i ← 1 TO length(array) - 1 DO

IF (array[i] > max) THEN

max ← array[i]

END IF

END FOR

RETURN max

END FUNCTION

This function iterates through the array to find the maximum value.

`FUNCTION sum(n)`

IF (n = 0) THEN

RETURN 0

ELSE

RETURN n + sum(n - 1)

END IF

END FUNCTION

This recursive function calculates the sum of the first n natural numbers.

Linear data structures are collections of elements arranged sequentially, where each element has a unique successor except the last element. These structures are fundamental in computer science for organizing data and facilitating various operations such as searching, sorting, and modifying elements. Here are the main types of linear data structures:

An array is a collection of elements, each identified by an index. Arrays store elements of the same type in a contiguous memory location.

**Declaration**:

`// ARRAY numbers[5] OF INTEGER`

Initialization:

numbers[0] ← 10

numbers[1] ← 20

numbers[2] ← 30

numbers[3] ← 40

numbers[4] ← 50

**Accessing Elements**:

`PRINT numbers[2] // Outputs 30`

**Advantages**:Simple to use and implement.

Efficient access to elements via indexing (O(1) time complexity).

**Disadvantages**:Fixed size; cannot dynamically grow or shrink.

Insertion and deletion operations can be expensive (O(n) time complexity) if elements need to be shifted.

A linked list is a collection of elements called nodes, where each node contains a data part and a reference (or link) to the next node in the sequence.

**Types**:**Singly Linked List**: Each node points to the next node.**Doubly Linked List**: Each node points to both the next and the previous nodes.**Circular Linked List**: The last node points back to the first node.

**Node Structure (Singly Linked List)**:

`NODE`

data: INTEGER

next: NODE

**Example Operations**:

**Insertion at the Beginning**:

`newNode ← CREATE_NODE(data)`

newNode.next ← head

head ← newNode

**Deletion from the Beginning**:

`IF head ≠ NULL THEN`

temp ← head

head ← head.next

DELETE temp

END IF

**Advantages**:Dynamic size; can grow and shrink as needed.

Efficient insertion and deletion operations (O(1) time complexity) if done at the beginning.

**Disadvantages**:Inefficient access to elements (O(n) time complexity) since nodes must be traversed sequentially.

Extra memory is required for storing references.

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. Elements can be added and removed only from the top of the stack.

**Basic Operations**:**Push**: Add an element to the top of the stack.**Pop**: Remove and return the top element of the stack.**Peek/Top**: Return the top element without removing it.**isEmpty**: Check if the stack is empty.

**Example (Using an Array)**:

`STACK stack[SIZE]`

top ← -1

FUNCTION push(element)

IF top = SIZE - 1 THEN

PRINT "Stack Overflow"

ELSE

top ← top + 1

stack[top] ← element

END IF

END FUNCTION

`FUNCTION pop()`

IF top = -1 THEN

PRINT "Stack Underflow"

ELSE

element ← stack[top]

top ← top - 1

RETURN element

END IF

END FUNCTION

**Advantages**:Simple and easy to implement.

Useful in scenarios requiring backtracking (e.g., function calls, undo mechanisms).

**Disadvantages**:Limited access to elements; only the top element can be accessed directly.

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Elements are added at the rear and removed from the front.

**Basic Operations**:**Enqueue**: Add an element to the rear of the queue.**Dequeue**: Remove and return the front element of the queue.**Front/Peek**: Return the front element without removing it.**isEmpty**: Check if the queue is empty.

**Example (Using an Array)**:

`QUEUE queue[SIZE]`

front ← -1

rear ← -1

`FUNCTION enqueue(element)`

IF rear = SIZE - 1 THEN

PRINT "Queue Overflow"

ELSE

IF front = -1 THEN

front ← 0

END IF

rear ← rear + 1

queue[rear] ← element

END IF

END FUNCTION

`FUNCTION dequeue()`

IF front = -1 OR front > rear THEN

PRINT "Queue Underflow"

ELSE

element ← queue[front]

front ← front + 1

RETURN element

END IF

END FUNCTION

**Advantages**:Useful for scheduling and managing tasks in order (e.g., CPU scheduling, print queue).

**Disadvantages**:Limited access to elements; only the front and rear elements can be accessed directly.

Non-linear data structures are used to represent data that does not follow a sequential order. Unlike linear data structures (arrays, linked lists, stacks, queues), elements in non-linear data structures are connected in a hierarchical or networked manner. This allows for more complex relationships between data elements. The most common non-linear data structures are trees and graphs.

A tree is a hierarchical data structure consisting of nodes connected by edges. Each tree has a root node, and each node has zero or more child nodes. Trees are used to represent hierarchical data, such as file systems, organizational structures, and more.

**Basic Terminology**:**Root**: The topmost node in a tree.**Parent**: A node that has one or more child nodes.**Child**: A node that has a parent node.**Leaf**: A node with no children.**Sibling**: Nodes that share the same parent.**Subtree**: A tree consisting of a node and its descendants.

**Binary Trees**: A type of tree where each node has at most two children, referred to as the left child and the right child.**Binary Search Trees (BST)**: A binary tree where for each node, the left child's value is less than the node's value, and the right child's value is greater than the node's value. This property makes searching, insertion, and deletion efficient.**Tree Traversal Methods**:**In-order Traversal**: Visit the left subtree, the node, and then the right subtree.**Pre-order Traversal**: Visit the node, the left subtree, and then the right subtree.**Post-order Traversal**: Visit the left subtree, the right subtree, and then the node.**Level-order Traversal**: Visit nodes level by level from top to bottom.

**Example (Binary Search Tree)**:

`CLASS Node`

VALUE: INTEGER

LEFT: Node

RIGHT: Node

FUNCTION insert(root, value)

IF root = NULL THEN

RETURN new Node(value)

END IF

IF value < root.VALUE THEN

root.LEFT = insert(root.LEFT, value)

ELSE

root.RIGHT = insert(root.RIGHT, value)

END IF

RETURN root

END FUNCTION

A graph is a non-linear data structure consisting of vertices (nodes) and edges (connections between nodes). Graphs are used to represent networks, such as social networks, transportation systems, and the Internet.

**Basic Terminology**:**Vertex (Node)**: A fundamental part of a graph, representing an entity.**Edge**: A connection between two vertices.**Adjacent Vertices**: Vertices connected by an edge.**Degree**: The number of edges connected to a vertex.**Path**: A sequence of vertices connected by edges.**Cycle**: A path that starts and ends at the same vertex.

**Types of Graphs**:**Directed Graph (Digraph)**: A graph where edges have a direction, going from one vertex to another.**Undirected Graph**: A graph where edges do not have a direction.**Weighted Graph**: A graph where edges have weights (values) representing cost, distance, or other metrics.**Unweighted Graph**: A graph where edges do not have weights.

**Graph Representations**:

**Adjacency Matrix**: A 2D array where the element at row i and column j indicates the presence (and possibly weight) of an edge between vertices i and j.

`AdjacencyMatrix[3][3] = {`

{0, 1, 0},

{1, 0, 1},

{0, 1, 0}

}

**Adjacency List**: An array of lists where each list contains the adjacent vertices of a particular vertex.

`AdjacencyList = {`

0: [1],

1: [0, 2],

2: [1]

}

**Graph Traversal Algorithms**:**Breadth-First Search (BFS)**: Explores vertices level by level, using a queue.

`FUNCTION BFS(graph, startVertex)`

CREATE queue

ENQUEUE startVertex

MARK startVertex as visited

WHILE queue IS NOT EMPTY

vertex = DEQUEUE

FOR EACH adjacentVertex IN graph[vertex]

IF adjacentVertex NOT visited THEN

ENQUEUE adjacentVertex

MARK adjacentVertex as visited

END IF

END FOR

END WHILE

END FUNCTION**Depth-First Search (DFS)**: Explores as far as possible along each branch before backtracking, using a stack or recursion.

`FUNCTION DFS(graph, vertex, visited)`

MARK vertex as visited

FOR EACH adjacentVertex IN graph[vertex]

IF adjacentVertex NOT visited THEN

DFS(graph, adjacentVertex, visited)

END IF

END FOR

END FUNCTION

Searching algorithms are fundamental in computer science, used to retrieve information stored within a data structure or database. They are categorized based on the type of data structure they operate on and their efficiency. Here are the main types of searching algorithms covered in the IB Computer Science HL syllabus:

Linear search is the simplest searching algorithm. It works by sequentially checking each element of a list until a match is found or the whole list has been searched.

**Algorithm**:Start from the first element.

Compare each element with the target value.

If a match is found, return the index.

If the end of the list is reached without finding the target, return an indication that the target is not found.

**Time Complexity**: O(n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION linearSearch(array, target)`

FOR i ← 0 TO length(array) - 1 DO

IF array[i] = target THEN

RETURN i

END IF

END FOR

RETURN -1 // target not found

END FUNCTION

Binary search is a more efficient algorithm than linear search but requires that the list be sorted. It works by repeatedly dividing the search interval in half.

**Algorithm**:Start with the middle element of the sorted list.

If the middle element is the target, return its index.

If the target is less than the middle element, narrow the search to the lower half.

If the target is greater than the middle element, narrow the search to the upper half.

Repeat the process until the target is found or the interval is empty.

**Time Complexity**: O(log n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION binarySearch(array, target)`

left ← 0

right ← length(array) - 1

WHILE left ≤ right DO

middle ← (left + right) / 2

IF array[middle] = target THEN

RETURN middle

ELSE IF array[middle] < target THEN

left ← middle + 1

ELSE

right ← middle - 1

END IF

END WHILE

RETURN -1 // target not found

END FUNCTION

Depth-first search is used primarily on graphs and trees. It explores as far as possible along each branch before backtracking.

**Algorithm**:Start at the root (or any arbitrary node for a graph).

Mark the node as visited.

Recursively visit all the adjacent unvisited nodes.

Backtrack when no unvisited nodes are adjacent.

**Time Complexity**: O(V + E), where V is the number of vertices and E is the number of edges.

**Example (Pseudocode)**:

`FUNCTION DFS(graph, startVertex, visited)`

visited[startVertex] ← TRUE

FOR EACH vertex IN graph[startVertex] DO

IF NOT visited[vertex] THEN

DFS(graph, vertex, visited)

END IF

END FOR

END FUNCTION

Breadth-First Search is also used on graphs and trees. It explores all the nodes at the present depth level before moving on to nodes at the next depth level.

**Algorithm**:Start at the root (or any arbitrary node for a graph).

Mark the node as visited.

Add the node to a queue.

While the queue is not empty:

Dequeue a node.

Visit all its adjacent unvisited nodes.

Mark them as visited and enqueue them.

**Time Complexity**: O(V + E), where V is the number of vertices and E is the number of edges.

**Example (Pseudocode)**:

`FUNCTION BFS(graph, startVertex)`

CREATE queue

visited[startVertex] ← TRUE

ENQUEUE startVertex

WHILE queue IS NOT EMPTY DO

vertex ← DEQUEUE

FOR EACH adjVertex IN graph[vertex] DO

IF NOT visited[adjVertex] THEN

visited[adjVertex] ← TRUE

ENQUEUE adjVertex

END IF

END FOR

END WHILE

END FUNCTION

Sorting algorithms are fundamental in computer science, used to arrange data in a specific order, typically ascending or descending. Efficient sorting is crucial for optimizing the performance of other algorithms that require sorted data as input. Here's an in-depth look at some common sorting algorithms covered in the IB Computer Science HL syllabus:

Bubble Sort is a simple comparison-based sorting algorithm. It repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.

**Algorithm**:Compare each pair of adjacent elements.

Swap them if they are in the wrong order.

Repeat the process until no swaps are needed.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION bubbleSort(array)`

n ← length(array)

FOR i ← 0 TO n-1 DO

FOR j ← 0 TO n-i-2 DO

IF array[j] > array[j+1] THEN

SWAP array[j] WITH array[j+1]

END IF

END FOR

END FOR

END FUNCTION

Selection Sort divides the list into two parts: the sorted part at the left end and the unsorted part at the right end. It repeatedly selects the smallest (or largest) element from the unsorted part and moves it to the sorted part.

**Algorithm**:Find the minimum element in the unsorted part.

Swap it with the first element of the unsorted part.

Move the boundary between the sorted and unsorted parts one step to the right.

Repeat until the entire list is sorted.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION selectionSort(array)`

n ← length(array)

FOR i ← 0 TO n-1 DO

minIndex ← i

FOR j ← i+1 TO n-1 DO

IF array[j] < array[minIndex] THEN

minIndex ← j

END IF

END FOR

SWAP array[i] WITH array[minIndex]

END FOR

END FUNCTION

Insertion Sort builds the sorted array one item at a time. It picks each element from the unsorted part and inserts it into the correct position in the sorted part.

**Algorithm**:Start with the second element.

Compare it with the elements in the sorted part.

Insert it into the correct position.

Repeat for all elements.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION insertionSort(array)`

n ← length(array)

FOR i ← 1 TO n-1 DO

key ← array[i]

j ← i-1

WHILE j ≥ 0 AND array[j] > key DO

array[j+1] ← array[j]

j ← j-1

END WHILE

array[j+1] ← key

END FOR

END FUNCTION

Merge Sort is a divide-and-conquer algorithm. It divides the list into halves, recursively sorts each half, and then merges the sorted halves to produce the sorted list.

**Algorithm**:Divide the list into two halves.

Recursively sort each half.

Merge the sorted halves.

**Time Complexity**: O(n log n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION mergeSort(array)`

IF length(array) > 1 THEN

mid ← length(array) / 2

leftHalf ← array[0 TO mid-1]

rightHalf ← array[mid TO length(array)-1]

mergeSort(leftHalf)

mergeSort(rightHalf)

i ← 0

j ← 0

k ← 0

WHILE i < length(leftHalf) AND j < length(rightHalf) DO

IF leftHalf[i] < rightHalf[j] THEN

array[k] ← leftHalf[i]

i ← i + 1

ELSE

array[k] ← rightHalf[j]

j ← j + 1

END IF

k ← k + 1

END WHILE

WHILE i < length(leftHalf) DO

array[k] ← leftHalf[i]

i ← i + 1

k ← k + 1

END WHILE

WHILE j < length(rightHalf) DO

array[k] ← rightHalf[j]

j ← j + 1

k ← k + 1

END WHILE

END IF

END FUNCTION

Quick Sort is another divide-and-conquer algorithm. It picks a "pivot" element, partitions the array into elements less than the pivot and elements greater than the pivot, and then recursively sorts the partitions.

**Algorithm**:Choose a pivot element.

Partition the array around the pivot.

Recursively apply the above steps to the sub-arrays.

**Time Complexity**:Average case: O(n log n)

Worst case: O(n^2) (if the smallest or largest element is always chosen as the pivot)

**Example (Pseudocode)**:

`FUNCTION quickSort(array, low, high)`

IF low < high THEN

pivotIndex ← partition(array, low, high)

quickSort(array, low, pivotIndex - 1)

quickSort(array, pivotIndex + 1, high)

END IF

END FUNCTION

`FUNCTION partition(array, low, high)`

pivot ← array[high]

i ← low - 1

FOR j ← low TO high - 1 DO

IF array[j] < pivot THEN

i ← i + 1

SWAP array[i] WITH array[j]

END IF

END FOR

SWAP array[i + 1] WITH array[high]

RETURN i + 1

END FUNCTION

**Algorithm Design and Analysis** is a fundamental aspect of computer science for creating efficient solutions to problems. This section of the IB Computer Science HL syllabus focuses on understanding, designing, and evaluating algorithms. Here's an in-depth look at the key concepts:

**Definition**: An algorithm is a step-by-step procedure or a set of rules to be followed in calculations or other problem-solving operations, especially by a computer.**Characteristics**:**Unambiguous**: Each step is clearly defined.**Finite**: The algorithm terminates after a finite number of steps.**Effective**: Each step is basic enough to be carried out (computable).

**Pseudocode**: A high-level description of an algorithm using the structural conventions of programming languages but intended for human reading.**Purpose**: Helps plan and conceptualize an algorithm before coding.**Structure**:Uses common programming constructs like loops, conditionals, and variables.

Avoids syntactical details of actual programming languages.

**Example**:

`// A is an array of integers`

BEGIN

max ← A[0]

FOR i ← 1 TO length(A) - 1 DO

IF A[i] > max THEN

max ← A[i]

END IF

END FOR

RETURN max

END

**Big-O Notation**: A mathematical notation that describes the upper bound of an algorithm's running time or space requirements in terms of the input size (n).**Purpose**: To classify algorithms according to their worst-case or average-case performance and to compare the efficiency of different algorithms.**Common Big-O Classifications**:**O(1)**: Constant time – the operation's time is independent of the input size.**O(log n)**: Logarithmic time – the operation's time grows logarithmically with the input size.**O(n)**: Linear time – the operation's time grows linearly with the input size.**O(n log n)**: Linearithmic time – the operation's time grows log-linearly with the input size.**O(n^2)**: Quadratic time – the operation's time grows quadratically with the input size.**O(2^n)**: Exponential time – the operation's time grows exponentially with the input size.**O(n!)**: Factorial time – the operation's time grows factorially with the input size.

**Sequence**: Executing statements one after another in a specific order.**Example**: Setting variables, performing arithmetic operations.

**Selection**: Making decisions based on conditions (if-else statements).

`Example:`

IF (condition) THEN

// execute this block

ELSE

// execute this block

END IF

**Iteration**: Repeating a block of code (loops).**Example:**

`FOR i ← 1 TO n DO`

// execute this block

END FOR

**Recursion**: A function calls itself to solve smaller instances of the same problem.**Example**:

`Factorial computation`

FUNCTION factorial(n)

IF n = 0 THEN

RETURN 1

ELSE

RETURN n * factorial(n - 1)

END IF

END FUNCTION

**Real-World Problem Solving**: Algorithms are applied in various domains like sorting data, searching for information, network routing, and more.**Algorithmic Thinking**: Developing the ability to break down problems into smaller, manageable parts and solving them systematically.

**Basic Algorithm Constructs:**

In computer science, algorithms are built using fundamental constructs that allow for the execution of instructions in a specific order. The basic constructs are sequence, selection, iteration, and recursion. Here is a detailed explanation of each:

The sequence construct involves executing statements one after another in a specific order. This is the most straightforward control structure.

**Example**:

`x ← 5`

y ← 10

sum ← x + y

PRINT sum

In this example, the operations are performed in sequence: setting x to 5, y to 10, calculating the sum, and then printing the result.

Selection (also known as decision-making) allows the algorithm to choose different paths based on certain conditions. The most common selection constructs are if, else if, and else statements.

**Example**:

`IF (temperature > 30) THEN`

PRINT "It's hot outside."

ELSE IF (temperature > 20) THEN

PRINT "It's warm outside."

ELSE

PRINT "It's cold outside."

END IF

In this example, the algorithm selects different actions depending on the value of temperature.

Iteration (or looping) allows the algorithm to repeat a block of code multiple times. There are several types of loops, including for, while, and do-while loops.

**For Loop Example**:

`FOR i ← 1 TO 5 DO`

PRINT i

END FOR

This loop prints the numbers 1 to 5. The loop variable i starts at 1 and increments by 1 until it reaches 5.

**While Loop Example**:

`i ← 1`

WHILE (i ≤ 5) DO

PRINT i

i ← i + 1

END WHILE

This while loop achieves the same result as the for loop but checks the condition before each iteration.

Recursion is a technique where a function calls itself to solve a smaller instance of the same problem. This is particularly useful for problems that can be broken down into similar sub-problems.

**Factorial Example**:

`FUNCTION factorial(n)`

IF (n = 0) THEN

RETURN 1

ELSE

RETURN n * factorial(n - 1)

END IF

END FUNCTION

In this example, the factorial function calculates the factorial of a number n by calling itself with n - 1 until it reaches the base case (n = 0).

Understanding these constructs allows you to develop and implement algorithms for various tasks. Here are a few practical implementations using these constructs:

`FUNCTION findMax(array)`

max ← array[0]

FOR i ← 1 TO length(array) - 1 DO

IF (array[i] > max) THEN

max ← array[i]

END IF

END FOR

RETURN max

END FUNCTION

This function iterates through the array to find the maximum value.

`FUNCTION sum(n)`

IF (n = 0) THEN

RETURN 0

ELSE

RETURN n + sum(n - 1)

END IF

END FUNCTION

This recursive function calculates the sum of the first n natural numbers.

Linear data structures are collections of elements arranged sequentially, where each element has a unique successor except the last element. These structures are fundamental in computer science for organizing data and facilitating various operations such as searching, sorting, and modifying elements. Here are the main types of linear data structures:

An array is a collection of elements, each identified by an index. Arrays store elements of the same type in a contiguous memory location.

**Declaration**:

`// ARRAY numbers[5] OF INTEGER`

Initialization:

numbers[0] ← 10

numbers[1] ← 20

numbers[2] ← 30

numbers[3] ← 40

numbers[4] ← 50

**Accessing Elements**:

`PRINT numbers[2] // Outputs 30`

**Advantages**:Simple to use and implement.

Efficient access to elements via indexing (O(1) time complexity).

**Disadvantages**:Fixed size; cannot dynamically grow or shrink.

Insertion and deletion operations can be expensive (O(n) time complexity) if elements need to be shifted.

A linked list is a collection of elements called nodes, where each node contains a data part and a reference (or link) to the next node in the sequence.

**Types**:**Singly Linked List**: Each node points to the next node.**Doubly Linked List**: Each node points to both the next and the previous nodes.**Circular Linked List**: The last node points back to the first node.

**Node Structure (Singly Linked List)**:

`NODE`

data: INTEGER

next: NODE

**Example Operations**:

**Insertion at the Beginning**:

`newNode ← CREATE_NODE(data)`

newNode.next ← head

head ← newNode

**Deletion from the Beginning**:

`IF head ≠ NULL THEN`

temp ← head

head ← head.next

DELETE temp

END IF

**Advantages**:Dynamic size; can grow and shrink as needed.

Efficient insertion and deletion operations (O(1) time complexity) if done at the beginning.

**Disadvantages**:Inefficient access to elements (O(n) time complexity) since nodes must be traversed sequentially.

Extra memory is required for storing references.

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. Elements can be added and removed only from the top of the stack.

**Basic Operations**:**Push**: Add an element to the top of the stack.**Pop**: Remove and return the top element of the stack.**Peek/Top**: Return the top element without removing it.**isEmpty**: Check if the stack is empty.

**Example (Using an Array)**:

`STACK stack[SIZE]`

top ← -1

FUNCTION push(element)

IF top = SIZE - 1 THEN

PRINT "Stack Overflow"

ELSE

top ← top + 1

stack[top] ← element

END IF

END FUNCTION

`FUNCTION pop()`

IF top = -1 THEN

PRINT "Stack Underflow"

ELSE

element ← stack[top]

top ← top - 1

RETURN element

END IF

END FUNCTION

**Advantages**:Simple and easy to implement.

Useful in scenarios requiring backtracking (e.g., function calls, undo mechanisms).

**Disadvantages**:Limited access to elements; only the top element can be accessed directly.

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Elements are added at the rear and removed from the front.

**Basic Operations**:**Enqueue**: Add an element to the rear of the queue.**Dequeue**: Remove and return the front element of the queue.**Front/Peek**: Return the front element without removing it.**isEmpty**: Check if the queue is empty.

**Example (Using an Array)**:

`QUEUE queue[SIZE]`

front ← -1

rear ← -1

`FUNCTION enqueue(element)`

IF rear = SIZE - 1 THEN

PRINT "Queue Overflow"

ELSE

IF front = -1 THEN

front ← 0

END IF

rear ← rear + 1

queue[rear] ← element

END IF

END FUNCTION

`FUNCTION dequeue()`

IF front = -1 OR front > rear THEN

PRINT "Queue Underflow"

ELSE

element ← queue[front]

front ← front + 1

RETURN element

END IF

END FUNCTION

**Advantages**:Useful for scheduling and managing tasks in order (e.g., CPU scheduling, print queue).

**Disadvantages**:Limited access to elements; only the front and rear elements can be accessed directly.

Non-linear data structures are used to represent data that does not follow a sequential order. Unlike linear data structures (arrays, linked lists, stacks, queues), elements in non-linear data structures are connected in a hierarchical or networked manner. This allows for more complex relationships between data elements. The most common non-linear data structures are trees and graphs.

A tree is a hierarchical data structure consisting of nodes connected by edges. Each tree has a root node, and each node has zero or more child nodes. Trees are used to represent hierarchical data, such as file systems, organizational structures, and more.

**Basic Terminology**:**Root**: The topmost node in a tree.**Parent**: A node that has one or more child nodes.**Child**: A node that has a parent node.**Leaf**: A node with no children.**Sibling**: Nodes that share the same parent.**Subtree**: A tree consisting of a node and its descendants.

**Binary Trees**: A type of tree where each node has at most two children, referred to as the left child and the right child.**Binary Search Trees (BST)**: A binary tree where for each node, the left child's value is less than the node's value, and the right child's value is greater than the node's value. This property makes searching, insertion, and deletion efficient.**Tree Traversal Methods**:**In-order Traversal**: Visit the left subtree, the node, and then the right subtree.**Pre-order Traversal**: Visit the node, the left subtree, and then the right subtree.**Post-order Traversal**: Visit the left subtree, the right subtree, and then the node.**Level-order Traversal**: Visit nodes level by level from top to bottom.

**Example (Binary Search Tree)**:

`CLASS Node`

VALUE: INTEGER

LEFT: Node

RIGHT: Node

FUNCTION insert(root, value)

IF root = NULL THEN

RETURN new Node(value)

END IF

IF value < root.VALUE THEN

root.LEFT = insert(root.LEFT, value)

ELSE

root.RIGHT = insert(root.RIGHT, value)

END IF

RETURN root

END FUNCTION

A graph is a non-linear data structure consisting of vertices (nodes) and edges (connections between nodes). Graphs are used to represent networks, such as social networks, transportation systems, and the Internet.

**Basic Terminology**:**Vertex (Node)**: A fundamental part of a graph, representing an entity.**Edge**: A connection between two vertices.**Adjacent Vertices**: Vertices connected by an edge.**Degree**: The number of edges connected to a vertex.**Path**: A sequence of vertices connected by edges.**Cycle**: A path that starts and ends at the same vertex.

**Types of Graphs**:**Directed Graph (Digraph)**: A graph where edges have a direction, going from one vertex to another.**Undirected Graph**: A graph where edges do not have a direction.**Weighted Graph**: A graph where edges have weights (values) representing cost, distance, or other metrics.**Unweighted Graph**: A graph where edges do not have weights.

**Graph Representations**:

**Adjacency Matrix**: A 2D array where the element at row i and column j indicates the presence (and possibly weight) of an edge between vertices i and j.

`AdjacencyMatrix[3][3] = {`

{0, 1, 0},

{1, 0, 1},

{0, 1, 0}

}

**Adjacency List**: An array of lists where each list contains the adjacent vertices of a particular vertex.

`AdjacencyList = {`

0: [1],

1: [0, 2],

2: [1]

}

**Graph Traversal Algorithms**:**Breadth-First Search (BFS)**: Explores vertices level by level, using a queue.

`FUNCTION BFS(graph, startVertex)`

CREATE queue

ENQUEUE startVertex

MARK startVertex as visited

WHILE queue IS NOT EMPTY

vertex = DEQUEUE

FOR EACH adjacentVertex IN graph[vertex]

IF adjacentVertex NOT visited THEN

ENQUEUE adjacentVertex

MARK adjacentVertex as visited

END IF

END FOR

END WHILE

END FUNCTION**Depth-First Search (DFS)**: Explores as far as possible along each branch before backtracking, using a stack or recursion.

`FUNCTION DFS(graph, vertex, visited)`

MARK vertex as visited

FOR EACH adjacentVertex IN graph[vertex]

IF adjacentVertex NOT visited THEN

DFS(graph, adjacentVertex, visited)

END IF

END FOR

END FUNCTION

Searching algorithms are fundamental in computer science, used to retrieve information stored within a data structure or database. They are categorized based on the type of data structure they operate on and their efficiency. Here are the main types of searching algorithms covered in the IB Computer Science HL syllabus:

Linear search is the simplest searching algorithm. It works by sequentially checking each element of a list until a match is found or the whole list has been searched.

**Algorithm**:Start from the first element.

Compare each element with the target value.

If a match is found, return the index.

If the end of the list is reached without finding the target, return an indication that the target is not found.

**Time Complexity**: O(n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION linearSearch(array, target)`

FOR i ← 0 TO length(array) - 1 DO

IF array[i] = target THEN

RETURN i

END IF

END FOR

RETURN -1 // target not found

END FUNCTION

Binary search is a more efficient algorithm than linear search but requires that the list be sorted. It works by repeatedly dividing the search interval in half.

**Algorithm**:Start with the middle element of the sorted list.

If the middle element is the target, return its index.

If the target is less than the middle element, narrow the search to the lower half.

If the target is greater than the middle element, narrow the search to the upper half.

Repeat the process until the target is found or the interval is empty.

**Time Complexity**: O(log n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION binarySearch(array, target)`

left ← 0

right ← length(array) - 1

WHILE left ≤ right DO

middle ← (left + right) / 2

IF array[middle] = target THEN

RETURN middle

ELSE IF array[middle] < target THEN

left ← middle + 1

ELSE

right ← middle - 1

END IF

END WHILE

RETURN -1 // target not found

END FUNCTION

Depth-first search is used primarily on graphs and trees. It explores as far as possible along each branch before backtracking.

**Algorithm**:Start at the root (or any arbitrary node for a graph).

Mark the node as visited.

Recursively visit all the adjacent unvisited nodes.

Backtrack when no unvisited nodes are adjacent.

**Time Complexity**: O(V + E), where V is the number of vertices and E is the number of edges.

**Example (Pseudocode)**:

`FUNCTION DFS(graph, startVertex, visited)`

visited[startVertex] ← TRUE

FOR EACH vertex IN graph[startVertex] DO

IF NOT visited[vertex] THEN

DFS(graph, vertex, visited)

END IF

END FOR

END FUNCTION

Breadth-First Search is also used on graphs and trees. It explores all the nodes at the present depth level before moving on to nodes at the next depth level.

**Algorithm**:Start at the root (or any arbitrary node for a graph).

Mark the node as visited.

Add the node to a queue.

While the queue is not empty:

Dequeue a node.

Visit all its adjacent unvisited nodes.

Mark them as visited and enqueue them.

**Time Complexity**: O(V + E), where V is the number of vertices and E is the number of edges.

**Example (Pseudocode)**:

`FUNCTION BFS(graph, startVertex)`

CREATE queue

visited[startVertex] ← TRUE

ENQUEUE startVertex

WHILE queue IS NOT EMPTY DO

vertex ← DEQUEUE

FOR EACH adjVertex IN graph[vertex] DO

IF NOT visited[adjVertex] THEN

visited[adjVertex] ← TRUE

ENQUEUE adjVertex

END IF

END FOR

END WHILE

END FUNCTION

Sorting algorithms are fundamental in computer science, used to arrange data in a specific order, typically ascending or descending. Efficient sorting is crucial for optimizing the performance of other algorithms that require sorted data as input. Here's an in-depth look at some common sorting algorithms covered in the IB Computer Science HL syllabus:

Bubble Sort is a simple comparison-based sorting algorithm. It repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.

**Algorithm**:Compare each pair of adjacent elements.

Swap them if they are in the wrong order.

Repeat the process until no swaps are needed.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION bubbleSort(array)`

n ← length(array)

FOR i ← 0 TO n-1 DO

FOR j ← 0 TO n-i-2 DO

IF array[j] > array[j+1] THEN

SWAP array[j] WITH array[j+1]

END IF

END FOR

END FOR

END FUNCTION

Selection Sort divides the list into two parts: the sorted part at the left end and the unsorted part at the right end. It repeatedly selects the smallest (or largest) element from the unsorted part and moves it to the sorted part.

**Algorithm**:Find the minimum element in the unsorted part.

Swap it with the first element of the unsorted part.

Move the boundary between the sorted and unsorted parts one step to the right.

Repeat until the entire list is sorted.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION selectionSort(array)`

n ← length(array)

FOR i ← 0 TO n-1 DO

minIndex ← i

FOR j ← i+1 TO n-1 DO

IF array[j] < array[minIndex] THEN

minIndex ← j

END IF

END FOR

SWAP array[i] WITH array[minIndex]

END FOR

END FUNCTION

Insertion Sort builds the sorted array one item at a time. It picks each element from the unsorted part and inserts it into the correct position in the sorted part.

**Algorithm**:Start with the second element.

Compare it with the elements in the sorted part.

Insert it into the correct position.

Repeat for all elements.

**Time Complexity**: O(n^2), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION insertionSort(array)`

n ← length(array)

FOR i ← 1 TO n-1 DO

key ← array[i]

j ← i-1

WHILE j ≥ 0 AND array[j] > key DO

array[j+1] ← array[j]

j ← j-1

END WHILE

array[j+1] ← key

END FOR

END FUNCTION

Merge Sort is a divide-and-conquer algorithm. It divides the list into halves, recursively sorts each half, and then merges the sorted halves to produce the sorted list.

**Algorithm**:Divide the list into two halves.

Recursively sort each half.

Merge the sorted halves.

**Time Complexity**: O(n log n), where n is the number of elements in the list.

**Example (Pseudocode)**:

`FUNCTION mergeSort(array)`

IF length(array) > 1 THEN

mid ← length(array) / 2

leftHalf ← array[0 TO mid-1]

rightHalf ← array[mid TO length(array)-1]

mergeSort(leftHalf)

mergeSort(rightHalf)

i ← 0

j ← 0

k ← 0

WHILE i < length(leftHalf) AND j < length(rightHalf) DO

IF leftHalf[i] < rightHalf[j] THEN

array[k] ← leftHalf[i]

i ← i + 1

ELSE

array[k] ← rightHalf[j]

j ← j + 1

END IF

k ← k + 1

END WHILE

WHILE i < length(leftHalf) DO

array[k] ← leftHalf[i]

i ← i + 1

k ← k + 1

END WHILE

WHILE j < length(rightHalf) DO

array[k] ← rightHalf[j]

j ← j + 1

k ← k + 1

END WHILE

END IF

END FUNCTION

Quick Sort is another divide-and-conquer algorithm. It picks a "pivot" element, partitions the array into elements less than the pivot and elements greater than the pivot, and then recursively sorts the partitions.

**Algorithm**:Choose a pivot element.

Partition the array around the pivot.

Recursively apply the above steps to the sub-arrays.

**Time Complexity**:Average case: O(n log n)

Worst case: O(n^2) (if the smallest or largest element is always chosen as the pivot)

**Example (Pseudocode)**:

`FUNCTION quickSort(array, low, high)`

IF low < high THEN

pivotIndex ← partition(array, low, high)

quickSort(array, low, pivotIndex - 1)

quickSort(array, pivotIndex + 1, high)

END IF

END FUNCTION

`FUNCTION partition(array, low, high)`

pivot ← array[high]

i ← low - 1

FOR j ← low TO high - 1 DO

IF array[j] < pivot THEN

i ← i + 1

SWAP array[i] WITH array[j]

END IF

END FOR

SWAP array[i + 1] WITH array[high]

RETURN i + 1

END FUNCTION