Learning outcome 2: Apply Data Structure
Definition: What is a Data Structure?
A Data Structure is a way of organizing and storing data so that it can be used efficiently.
It determines:
• How data is stored
• How data is accessed
• How data is manipulated
Classifications of Data Structures
Data structures are mainly classified into two types:
1. Linear Data Structures
Data elements are arranged sequentially, one after another.
Examples:
Structure Description
Array Fixed-size list of elements
Linked List Elements connected by pointers
Stack LIFO (Last In, First Out) structure
Queue FIFO (First In, First Out) structure
Visual Example:
Array: [1] → [2] → [3] → [4]
Stack: Top → [A] → [B] → [C]
Queue: Front → [X] → [Y] → [Z] → Rear
1|P age
2. Non-linear Data Structures
Elements are not in sequence; instead, they form a hierarchy or complex connections.
Examples:
Structure Description
Tree Hierarchical structure with nodes (e.g., binary tree)
Graph Set of nodes connected by edges
Heap Specialized tree with priority-based structure
Visual Example:
Tree:
2|P age
Graph:
Summary Table
Category Data Structures Access Type
Linear Array, Linked List, Stack, Queue Sequential
Non-linear Tree, Graph, Heap Hierarchical/Graph
3|P age
JavaScript Lists
In JavaScript, a list is typically represented by an Array — an ordered collection of items.
1. List Representation
let fruits = ["apple", "banana", "cherry"];
This list holds 3 string elements.
2. List Operations
Here are common operations you can do with lists (arrays):
Operation Code Example Description
Access fruits[0] Access element at index
Update fruits[1] = "orange" Change item
Add (end) fruits.push("mango") Add at the end
Add (start) fruits.unshift("grape") Add at the beginning
Remove (end) fruits.pop() Remove last item
Remove (start) fruits.shift() Remove first item
Length fruits.length Get number of elements
4|P age
Operation Code Example Description
Loop through for (let f of fruits) { console.log(f); } Traverse all elements
Find Index fruits.indexOf("banana") Get index of an element
Remove by index fruits.splice(1, 1) Remove 1 item at index 1
3. List Structure (Behind the Scenes)
JavaScript arrays are actually dynamic objects that can:
• Grow or shrink in size
• Hold any type of data (numbers, strings, objects, even other arrays)
Example of mixed-type list:
let mixedList = ["hello", 42, true, {name: "Ali"}, [1, 2]];
They work like this internally:
Index → 0 1 2 3 4
Value → "hello", 42, true, {name: "Ali"}, [1, 2]
Bonus: Loop Example
let list = ["pen", "pencil", "book"];
for (let i = 0; i < list.length; i++) {
console.log("Item " + i + ": " + list[i]);
5|P age
Summary
Concept Example
List Representation let arr = [1, 2, 3]
List Operations push(), pop(), splice()
Structure Arrays hold index-value pairs
Time Complexity
Time complexity is a measure of how fast an algorithm runs depending on the size of the input n.
Common Notations:
Notation Meaning Example
O(1) Constant time Accessing an array index
O(n) Linear time Looping through an array
O(n²) Quadratic time Nested loops
O(log n) Logarithmic time Binary search
Space Complexity
Space complexity is a measure of how much memory an algorithm uses as the input size grows.
• Includes: variables, data structures, function calls, etc.
• Example:
let arr = [1, 2, 3]; // O(n) space
let sum = 0; // O(1) space
6|P age
Classification of Sorting Algorithms
Sorting algorithms are ways to rearrange data into a desired order (usually ascending or descending).
Let’s classify them:
1. By Number of Comparisons
• Efficient: Merge Sort, Quick Sort (O(n log n))
• Inefficient: Bubble Sort, Selection Sort (O(n²))
2. By Number of Swaps
• Few swaps: Selection Sort
• Many swaps: Bubble Sort
3. By Memory Usage
• In-place (uses constant space): Bubble Sort, Insertion Sort
• Out-of-place (uses extra memory): Merge Sort
4. By Recursion
• Recursive: Merge Sort, Quick Sort
• Non-recursive: Selection Sort, Bubble Sort (can be written iteratively)
5. By Stability
• Stable (preserves equal elements' order): Merge Sort, Insertion Sort, Bubble Sort
• Unstable: Quick Sort, Selection Sort
7|P age
6. By Adaptability
• Adaptive (faster for nearly-sorted data): Insertion Sort, Bubble Sort (with optimization)
• Non-adaptive: Selection Sort
7. Internal Sorting
• Sorting done in main memory
• Example: Insertion Sort, Merge Sort
8. External Sorting
• Sorting very large data stored on disk
• Example: External Merge Sort (used in databases)
Summary Table
Criteria Example
Comparisons Merge Sort (few), Bubble Sort (many)
Swaps Selection Sort (few)
Memory Usage Merge Sort (high), Bubble Sort (low)
Recursion Merge Sort (recursive), Bubble Sort (non-recursive)
Stability Insertion Sort (stable), Quick Sort (unstable)
Adaptability Insertion Sort (adaptive)
Internal Sorting Done in memory
External Sorting Done with disk storage
8|P age
Sorting Techniques
1️ Selection Sort
9|P age
• Idea: Repeatedly find the minimum element and move it to the front.
• Time: O(n²)
• In-place:
• Stable:
function selectionSort(arr) {
for (let i = 0; i < arr.length; i++) {
let min = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) min = j;
[arr[i], arr[min]] = [arr[min], arr[i]];
return arr;
2️ Bubble Sort
• Idea: Repeatedly swap adjacent elements if they are in the wrong order.
• Time: O(n²)
• In-place:
• Stable:
10 | P a g e
11 | P a g e
function bubbleSort(arr) {
let swapped;
do {
swapped = false;
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
swapped = true;
} while (swapped);
return arr;
12 | P a g e
3️ Insertion Sort
• Idea: Build the sorted array one element at a time.
• Time: O(n²), but fast for small/mostly sorted arrays.
• In-place:
• Stable:
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let key = arr[i], j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
arr[j + 1] = key;
return arr; }
13 | P a g e
4️ Merge Sort
• Idea: Divide array into halves, sort each half, then merge them.
• Time: O(n log n)
• In-place: (uses extra space)
• Stable:
function mergeSort(arr) {
if (arr.length < 2) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
14 | P a g e
function merge(left, right) {
const result = [];
while (left.length && right.length) {
result.push(left[0] < right[0] ? left.shift() : right.shift());
return result.concat(left, right);
5️ Quick Sort
• Idea: Pick a pivot, partition array around it, and sort partitions.
• Time: O(n log n) average, O(n²) worst
• In-place:
• Stable:
15 | P a g e
function quickSort(arr) {
if (arr.length < 2) return arr;
const pivot = arr[0];
const left = arr.slice(1).filter(x => x <= pivot);
const right = arr.slice(1).filter(x => x > pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
6️ Shell Sort
• Idea: Generalization of insertion sort, using gaps.
• Time: O(n log n) (depends on gap sequence)
• In-place:
• Stable:
function shellSort(arr) {
let gap = Math.floor(arr.length / 2);
while (gap > 0) {
for (let i = gap; i < arr.length; i++) {
let temp = arr[i];
let j = i;
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
arr[j] = temp;
16 | P a g e
gap = Math.floor(gap / 2);
return arr;
7️ Heap Sort
• Idea: Convert to heap, then extract max/min repeatedly.
• Time: O(n log n)
• In-place:
• Stable:
(Slightly more advanced; implementation requires a heapify function.)
8️ Radix Sort
• Idea: Sort numbers by individual digits from least to most significant.
• Time: O(nk) (n = elements, k = number of digits)
• In-place:
• Stable:
(Works only for integers, and best when digit count is small.)
9️ Counting Sort
• Idea: Count occurrences of each number and rebuild array.
• Time: O(n + k)
• In-place:
17 | P a g e
• Stable:
(Only works on small range of integers.)
Bucket Sort
• Idea: Distribute elements into buckets, sort each, then merge.
• Time: O(n + k) (best case)
• In-place:
• Stable:
(Works well with uniformly distributed floating-point numbers.)
Summary Table
Sort Time In-place Stable Best Use Case
Selection O(n²) Simple, small datasets
Bubble O(n²) Teaching, small input
Insertion O(n²) Nearly sorted data
Merge O(n log n) Large, stable sort needed
Quick O(n log n) Fastest general-purpose sort
Shell O(n log n) Medium datasets, in-place
Heap O(n log n) Fast, memory-efficient
Radix O(nk) Large integers
Counting O(n + k) Small range integers
Bucket O(n + k) Uniformly distributed floats
18 | P a g e
Application of Linear Data Structures
1️ Linked List
Use Cases:
• Dynamic memory allocation
• Implementing stacks, queues, and graphs
• Undo/Redo functionality in editors
Basic Operations:
• Insert
• Delete
• Traverse
Procedure (Insert at Beginning):
Procedure InsertAtBeginning(value)
Create new_node
new_node.data ← value
new_node.next ← head
head ← new_node
End Procedure
2️ Arrays
Use Cases:
• Store data in fixed-size lists
• Lookup tables
• Multidimensional data (e.g., matrices)
19 | P a g e
Operations:
• Access
• Insert
• Delete
• Traverse
Procedure (Traverse Array):
Procedure TraverseArray(arr, n)
For i ← 0 to n - 1
Print arr[i]
End For
End Procedure
3️ Queue (FIFO)
Use Cases:
• Job scheduling
• Printer queue
• BFS traversal in graphs
Operations:
• Enqueue
• Dequeue
• Peek
Procedure (Enqueue):
Procedure Enqueue(value)
If rear == MAX - 1 Then
Print "Queue is full"
20 | P a g e
Else
rear ← rear + 1
queue[rear] ← value
End If
End Procedure
4️ Stack (LIFO)
Use Cases:
• Function call stack
• Expression evaluation (infix to postfix)
• Undo mechanisms
Operations:
• Push
• Pop
• Peek
Procedure (Push):
Procedure Push(value)
If top == MAX - 1 Then
Print "Stack Overflow"
Else
top ← top + 1
stack[top] ← value
End If
End Procedure
21 | P a g e
Application of Non-Linear Data Structures
1️ Tree
Use Cases:
• File systems
• Binary Search Trees (searching, inserting)
• Hierarchical data (e.g., menus)
Operations:
• Insert
• Search
• Traversal (Inorder, Preorder, Postorder)
Procedure (Inorder Traversal):
Procedure InOrder(node)
If node is not NULL Then
InOrder(node.left)
Print node.data
InOrder(node.right)
End If
End Procedure
2️ Graph
Use Cases:
• Social networks
• Maps and navigation
• Network routing
22 | P a g e
Operations:
• Add vertex
• Add edge
• Search (DFS/BFS)
Procedure (BFS Traversal):
Procedure BFS(start_node)
Create queue
Mark start_node as visited
Enqueue start_node
While queue is not empty
current ← Dequeue
For each neighbor of current
If neighbor is not visited
Mark neighbor as visited
Enqueue neighbor
End If
End For
End While
End Procedure
3️ Tables (Hash Tables)
Use Cases:
• Fast lookup (key-value pairs)
• Databases
• Caching
23 | P a g e
Operations:
• Insert
• Delete
• Search
Procedure (Insert in Hash Table):
Procedure HashInsert(key, value)
index ← hash_function(key)
table[index] ← value
End Procedure
Summary Table
Structure Type Applications
Linked List Linear Undo operations, dynamic memory
Array Linear Indexing, static data storage
Queue Linear Scheduling, real-time systems
Stack Linear Backtracking, recursion, parsing
Tree Non-Linear File system, search trees
Graph Non-Linear Routing, social networks
Table (Hash) Non-Linear Fast key-based lookup
24 | P a g e
Learning outcome 3: Implement Algorithm using
JavaScript
Preparation of JavaScript Running Environment
To run JavaScript in the browser, you can follow these steps:
1. Browser Console: Open Chrome (or any modern browser), press F12 or Ctrl+Shift+I to open
Developer Tools, and go to the Console tab.
2. HTML + JS Files:
o Create an .html file and link your JavaScript code.
o Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Code</title>
</head>
<body>
<script src="script.js"></script> <!-- Link to your JavaScript file -->
</body>
</html>
25 | P a g e
3. Text Editor: Use editors like VS Code, Sublime Text, or Notepad++ to write your JavaScript.
Writing JavaScript Source Code
Let's implement the basic data structures and algorithms.
1. Linked List
A Linked List is a collection of nodes, where each node has data and a reference to the next node.
class Node {
constructor(data) {
this.data = data;
this.next = null;
class LinkedList {
constructor() {
this.head = null;
insertAtBeginning(value) {
let newNode = new Node(value);
newNode.next = this.head;
this.head = newNode;
26 | P a g e
traverse() {
let current = this.head;
while (current) {
console.log(current.data);
current = current.next;
let list = new LinkedList();
list.insertAtBeginning(10);
list.insertAtBeginning(20);
list.traverse(); // Output: 20 10
2. Arrays
JavaScript arrays are quite versatile, allowing you to store multiple types of data.
let arr = [10, 20, 30, 40];
arr.push(50); // Adds to the end
arr.unshift(5); // Adds to the beginning
console.log(arr); // Output: [5, 10, 20, 30, 40, 50]
27 | P a g e
3. Queue
A Queue is a FIFO (First In, First Out) structure.
class Queue {
constructor() {
this.items = [];
enqueue(value) {
this.items.push(value);
dequeue() {
return this.items.shift();
peek() {
return this.items[0];
let queue = new Queue();
queue.enqueue(10);
queue.enqueue(20);
console.log(queue.dequeue()); // Output: 10
console.log(queue.peek()); // Output: 20
28 | P a g e
4. Stack
A Stack is a LIFO (Last In, First Out) structure.
class Stack {
constructor() {
this.items = [];
push(value) {
this.items.push(value);
pop() {
return this.items.pop();
peek() {
return this.items[this.items.length - 1];
let stack = new Stack();
stack.push(10);
stack.push(20);
console.log(stack.pop()); // Output: 20
console.log(stack.peek()); // Output: 10
29 | P a g e
5. Tree (Binary Search Tree)
A Binary Search Tree (BST) allows for fast searching, insertion, and deletion.
class TreeNode {
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
class BinarySearchTree {
constructor() {
this.root = null;
insert(data) {
let newNode = new TreeNode(data);
if (this.root === null) {
this.root = newNode;
return;
this.insertNode(this.root, newNode);
30 | P a g e
insertNode(node, newNode) {
if (newNode.data < node.data) {
if (node.left === null) node.left = newNode;
else this.insertNode(node.left, newNode);
} else {
if (node.right === null) node.right = newNode;
else this.insertNode(node.right, newNode);
traverseInOrder(node = this.root) {
if (node !== null) {
this.traverseInOrder(node.left);
console.log(node.data);
this.traverseInOrder(node.right);
let tree = new BinarySearchTree();
tree.insert(20);
tree.insert(10);
tree.insert(30);
tree.traverseInOrder(); // Output: 10 20 30
31 | P a g e
6. Graph
Graphs represent nodes and their connections (edges). Here's a basic implementation of an undirected
graph.
class Graph {
constructor() {
this.adjacencyList = {};
addVertex(vertex) {
if (!this.adjacencyList[vertex]) {
this.adjacencyList[vertex] = [];
addEdge(v1, v2) {
this.adjacencyList[v1].push(v2);
this.adjacencyList[v2].push(v1);
display() {
for (let vertex in this.adjacencyList) {
console.log(vertex + " -> " + this.adjacencyList[vertex].join(', '));
32 | P a g e
let graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addEdge("A", "B");
graph.display(); // Output: A -> B, B -> A
7. Tables (Hash Table)
A Hash Table stores key-value pairs and uses a hash function for fast retrieval.
class HashTable {
constructor() {
this.table = new Array(10);
hash(key) {
return key % this.table.length;
insert(key, value) {
let index = this.hash(key);
this.table[index] = value;
get(key) {
let index = this.hash(key);
return this.table[index];
33 | P a g e
let hashTable = new HashTable();
hashTable.insert(10, "Apple");
console.log(hashTable.get(10)); // Output: Apple
Sorting Operations
1. Bubble Sort
function bubbleSort(arr) {
let swapped;
do {
swapped = false;
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
swapped = true;
} while (swapped);
return arr;
let arr = [5, 2, 9, 1, 5, 6];
console.log(bubbleSort(arr)); // Output: [1, 2, 5, 5, 6, 9]
34 | P a g e
2. Quick Sort
function quickSort(arr) {
if (arr.length < 2) return arr;
let pivot = arr[0];
let left = arr.slice(1).filter(x => x <= pivot);
let right = arr.slice(1).filter(x => x > pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
let arr = [5, 2, 9, 1, 5, 6];
console.log(quickSort(arr)); // Output: [1, 2, 5, 5, 6, 9]
Searching Operations
1. Binary Search
Note: Binary search works on sorted arrays.
35 | P a g e
function binarySearch(arr, target) {
let low = 0, high = arr.length - 1;
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
return -1; // Not found
let arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(binarySearch(arr, 5)); // Output: 4
2. Linear Search
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i;
return -1; // Not found
let arr = [10, 20, 30, 40];
console.log(linearSearch(arr, 30)); // Output: 2
36 | P a g e
Running JavaScript Source Code
1. Using Browser Embedded Tools
a. Rendering Engine
• A rendering engine is responsible for interpreting HTML, CSS, and JavaScript to display content
on the screen. Every browser has a rendering engine:
o Chrome uses Blink.
o Firefox uses Gecko.
o Safari uses WebKit.
When you run JavaScript in a browser, the engine interprets and executes the JavaScript, dynamically
updating the DOM and CSS.
b. Web Development Tools (DevTools)
• Browser DevTools allow you to run JavaScript directly in the console, inspect elements, debug
code, and view performance metrics.
• Steps to access DevTools in browsers:
o Chrome: Ctrl+Shift+I or F12
o Firefox: Ctrl+Shift+I or F12
o Edge: Ctrl+Shift+I or F12
Using the Console in DevTools:
• Open DevTools and go to the Console tab to execute JavaScript directly. You can write code, call
functions, and view outputs in real time.
Example:
console.log("Hello, World!");
• In the Sources tab, you can debug JavaScript by setting breakpoints, stepping through the code,
and inspecting variables.
37 | P a g e
Using the Performance Tab:
• The Performance tab in DevTools allows you to measure the runtime of your JavaScript code.
You can record performance, capture the JavaScript execution timeline, and inspect memory
usage.
2. Using IDE Terminal
Most Integrated Development Environments (IDEs) like VS Code, WebStorm, or Sublime Text come with
terminal support to run JavaScript code.
Running JavaScript in VS Code:
1. Install Node.js: First, ensure you have Node.js installed, as it allows you to run JavaScript
outside the browser. You can download it here.
2. Run JavaScript File in Terminal:
o Open VS Code and create a JavaScript file (.js).
o Open the Terminal in VS Code (Ctrl+ or through the menu: Terminal -> New Terminal).
o Run your JavaScript code using:
node filename.js
Example:
node script.js
Test Time and Space Complexity
Key Concepts of Measuring Time and Space Complexity
• Time Complexity: Measures the number of operations (or steps) required for an algorithm to
complete as a function of its input size.
o Common notations include:
▪ O(1): Constant time
▪ O(n): Linear time
▪ O(n^2): Quadratic time, etc.
38 | P a g e
• Space Complexity: Measures the amount of memory used by an algorithm as a function of its
input size.
o Common notations include:
▪ O(1): Constant space
▪ O(n): Linear space, etc.
The goal is to assess whether the algorithm’s time and space usage grows efficiently as the input size
increases.
Time and Space Measurement Tools
1. Profiling Tools
Profiling tools help track the time and space used by your JavaScript code and pinpoint performance
bottlenecks.
a. Browser Profiling (DevTools)
• In Chrome DevTools, you can use the Performance tab to record and analyze your JavaScript
code’s performance.
• You can start profiling by clicking the Record button and interacting with your app.
• The timeline will show where your JavaScript spends most of its time (e.g., rendering, layout,
script execution), helping you understand which parts of your code are taking the longest.
b. Node.js Profiler
For Node.js applications, you can use the built-in profiler.
• Use the --inspect flag to start the profiler:
node --inspect script.js
• You can view the profiling information directly in DevTools.
2. Benchmarking Tools
a. Benchmark.js
Benchmark.js is a library for benchmarking JavaScript code performance. It helps compare different
pieces of code and measure their execution time.
39 | P a g e
Steps:
1. Install Benchmark.js using npm:
npm install benchmark
2. Example code to compare two functions:
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;
const functionA = () => { return 1 + 1; };
const functionB = () => { return 2 + 2; };
suite.add('Function A', functionA)
.add('Function B', functionB)
.on('cycle', function(event) {
console.log(String(event.target));
})
.run();
This will compare the performance of functionA and functionB and print the results.
b. Benchmarkify
Benchmarkify is another tool for benchmarking JavaScript functions. It’s more lightweight and easy to
integrate.
npm install benchmarkify
const benchmarkify = require('benchmarkify');
const suite = benchmarkify('My Benchmark');
suite
.add('Function 1', () => { /* function code */ })
.add('Function 2', () => { /* another function code */ })
.run();
40 | P a g e
c. jsPerf
jsPerf is an online tool for testing the performance of JavaScript code. You can create test cases for
different code snippets, share them, and see how they perform across different browsers.
• Go to jsPerf, write your test cases, and run them to compare performance across different
implementations.
Example: Measuring Time Complexity with console.time
You can measure the time complexity of a function directly in JavaScript using console.time and
console.timeEnd:
console.time('Sort Time');
let arr = [5, 2, 9, 1, 5, 6];
arr.sort((a, b) => a - b);
console.timeEnd('Sort Time'); // Output: Sort Time: <time>ms
This approach will give you the time it takes for the sorting operation to complete.
Summary of Tools
Tool Purpose Example Use Case
Profiling and performance Inspect and profile your JavaScript code in the
DevTools
measurement browser
Node.js
Profiling Node.js applications Profile server-side JavaScript code with --inspect
Profiler
Compare performance of two or more code
Benchmark.js Benchmarking JavaScript functions
snippets
Benchmarkify Lightweight benchmarking tool Quick benchmarking for small test cases
Compare performance of different code
jsPerf Online benchmarking tool
implementations
41 | P a g e
These tools and techniques will help you run JavaScript, measure its performance, and test algorithms
for time and space complexity efficiently.
THANK YOU!
42 | P a g e