Algorithms Simplified - A Minimalist Approach To Problem-Solving by Rohith B. V.
Algorithms Simplified - A Minimalist Approach To Problem-Solving by Rohith B. V.
Although every precaution has been taken in the preparation of this book, the
publisher and author assume no responsibility for errors or omissions. Neither is
any liability assumed for damages resulting from the use of information
contained herein.
ISBN 978-1-0687284-1-9
Special thanks to my dad for his invaluable assistance with the diagrams.
Preface
Over the years, I've had the privilege of working at some of the most
innovative tech companies in the world. These experiences have provided
me with a deep understanding of algorithms and their practical
applications.
However, my journey has not been without challenges. I've faced many
situations where I was humbled by how much more there was to learn. But
each experience was a learning opportunity, a chance to try again, and
improve.
Rohith B V
This page intentionally left blank
Introduction 1
Fundamentals of Problem-Solving 4
1.8 Conclusion 27
2.6 Conclusion 61
3.1 Introduction 64
3.7 Arrays 74
3.8 Dictionaries 79
3.9 Sets 84
3.10 Conclusion 87
Books 127
Afterword 131
Glossary 133
Index 137
Introduction
This journey through these pages will arm you with not just the what but
the how and why behind each concept, ensuring that you gain a deeper
understanding of the material presented. The aim is to transform not just
your approach to problem-solving, but to empower you with a new lens to
view the world — one filled with patterns, efficiency and logical
progression.
Care has been taken to keep the book as succinct as possible and to use
diagrams where they help explain the ideas faster or serve to supplement
the text. By stripping away the extraneous, what remains are the essentials
— clearly presented and richly illustrated. Hopefully, this should make for
an engaging book that keeps you stimulated as you read.
Moreover, this book can also serve as a pre-read for seasoned Software
Engineers who are preparing for interviews or other scenarios where
algorithmic thinking is required. Whether you're a veteran coder or a
novice, I believe that within these pages, you'll encounter new ways to look
at old concepts — refreshing your perspective and sharpening your skills.
1
This page intentionally left blank
Chapter 1
Fundamentals of Problem-Solving
4
• Prediction Problems: Predicting the weather involves analyzing
patterns in historical data to forecast future conditions.
Imagine drawing two identical squares on the paper. They can be:
• Partially touching
• Not touching
• Touching fully
• Overlapping
To maintain the integrity of distinct data units, we'll dismiss the fourth
scenario, as it implies a fusion of data that defies our requirement for
discrete units.
5
Figure 1.1: Four types of square relationships
If we continue under the premise that our data squares must not touch, the
paper will be filled with non-intersecting squares. However, data in
isolation is often meaningless. Thus, we introduce lines that bridge these
squares, connecting one piece of data to another. This network of squares
and connecting lines forms the basic structure known as a graph. This
structure enables us to navigate from one data point to another, a principle
that underpins much of data organization and algorithm design.
6
When squares are placed adjacently, they naturally create a sequence. This
proximity can symbolize a related series of data units. We can extend this
sequence indefinitely, not just linearly, but also bi-dimensionally on our
two-dimensional paper, crafting rows and columns.
Figure 1.3: Squares form table-like structures. The 4th scenario can be seen as a
combination of the others
• Draw a boundary
• Name/label it
7
Figure 1.4: Two labelled squares “L1” and “L2”
In the end, our understanding of data, with all its intricacies, boils down to
two fundamental structures: the interconnected nodes of graphs and the
8
orderly rows and columns of tables. This insight is as straightforward as it is
deep, revealing the core frameworks that underpin all forms of data
organization.
In the context of state space, "space" refers to the set of all possible
configurations or conditions (states) that a system can occupy. It
encompasses every potential state the system can transition to based on its
variables and boundaries. The state space can be represented as a graph as
follows:
9
Figure 1.6: State and State Space
S = (v1, v2, v3 . . . vn )
• Choices (Ci): From each state Si, there is a set of possible actions
or choices (Ci) that can be applied to transition to other states:
• Transition Function (T): Each choice cij in the set Ci leads to a new
state. Applying a choice cij to the current state Si results in a new
10
state Sj. This can be represented by a transition function T such
that:
T (Si, cij ) = Sj
Reaching the Final State: To determine if a final state has been reached,
specific conditions are checked. These could include:
The specific conditions checked depend on the nature of the problem and
the desired solution characteristics.
11
1.4.2 Universal Problem Structure
• Choices (Ci): The possible moves (up, down, left, right) from the
current position.
12
• Transition Function (T): Applying a move to the current position
results in a new position.
The solution involves navigating from the initial state to the final state by
making a series of choices that transition through the state space of the
maze.
1.4.3 Summary
13
application of various algorithms and techniques to solve a wide array of
problems.
By viewing the state space as a graph, we can apply various graph traversal
techniques to explore and find solutions to the problem.
• Dijkstra's Algorithm
14
1.5.2 Advantages of Graph Traversal for State Space
Navigation
1.5.3 Summary
15
manageable subproblems. This decomposition can be effectively
represented as a Directed Acyclic Graph (DAG), which helps visualize the
relationships between different components of a problem.
16
• Identify independent subproblems that can be solved in parallel
17
1.6.2 Implications of Collapsing into a DAG
18
results of its predecessors, aligning with the concept of solving
smaller problems to build up to the solution of the larger problem.
19
Figure 1.9: Dependency Tree of Fibonacci Numbers. Can you see the DAG?
1.6.5 Summary
The DAG structure provides a clear order for solving subproblems, ensuring
that each subproblem's solution is available when needed by other
dependent subproblems. This equivalence highlights the powerful synergy
between graph theory and algorithm design in optimizing complex
problem-solving processes.
20
1.7 What is a solution?
A solution is a satisfactory answer or resolution to a problem. It bridges the
gap between the current state and the desired state while adhering to the
given constraints. In the context of computational problems, a solution
typically has the following characteristics:
Often, there might be multiple valid solutions to a problem, each with its
trade-offs in terms of these characteristics. The choice of the best solution
typically depends on the specific context and requirements of the situation.
21
the following chapters, we'll see how these fundamental concepts of
problems and solutions form the backbone of computational thinking.
22
Set of Final States (F)
Sequence of States
23
Figure 1.12: “Sequence of states” solution
Optimal Path
• Definition: The path that not only reaches the final state but does
so in the most efficient way according to a given metric (e.g.,
shortest path, least cost).
• Definition: Every possible path from the initial state to the final
state(s), providing a complete set of solutions.
24
• Graph Representation: The solution set includes all paths from S0
to the final states.
Figure 1.14: An example state space graph with starting node 3 and its solution
encoded as a tree of all paths to ending node 9
In the above example, the solution paths (assuming 3 is the starting state
and 9 is the final state) are:
25
• Graph Representation: The solution involves traversing the entire
graph or a significant portion of it.
The type of solution required depends on the problem's context and the
specific goals:
• Single Final State: Finding a route that visits all cities and returns to
the start, considering the final state as the completed tour.
26
• Optimal Path: The shortest possible route that visits all cities.
• Sequence of States: The steps or moves made to fill the grid from
the initial to the final state.
1.8 Conclusion
In this chapter, we explored the fundamentals of problem-solving, starting
with the definition and types of problems and delving into the foundational
elements of data structures. Understanding these core concepts allows us to
approach problem-solving systematically and efficiently.
27
using Directed Acyclic Graphs (DAGs), facilitating efficient problem-solving
through dynamic programming and other techniques.
28
This page intentionally left blank
This page intentionally left blank
Chapter 2
Computation and Complexity
Greedy algorithms take a local optimum choice at each step with the hope
of finding a global optimum. They traverse the problem space graph by
always choosing the most promising node that can be reached directly from
the current node.
Divide and conquer algorithms break the problem down into independent
subproblems, solve each subproblem recursively and then combine the
solutions to solve the original problem. This can be visualized as splitting
the graph into disjoint subgraphs, each representing a fraction of the
original problem.
32
2.1.3 Memoization and Caching
Imagine you're in the kitchen, embarking on a baking spree. Your menu for
the day includes two items: a delicious cinnamon cake and scrumptious
cinnamon buns. Both recipes require cinnamon, a key ingredient for that
warm, spicy flavour we all love.
As you begin with the cinnamon cake, you reach for the cinnamon, only to
find it's missing. So, you make a trip to the store and buy a packet of
33
cinnamon. The cake turns out wonderfully and it's now time to start on the
buns. You need cinnamon again. Would you head back to the store without
a second thought? Of course not! You'd remember that you already have
cinnamon from your earlier trip and use it, saving time and effort.
34
mirroring the way you wouldn't make unnecessary trips to the store for
ingredients you already have.
35
Figure 2.1.b: The corresponding decomposition into subproblem trees. With DP,
nodes can be evaluated just once in reverse topological order. In this tree, a valid
DP ordering could be 9, 7, 8, 5, 6, 4, 2, 3, 1
also finds the optimal order of traversing the state space so as to minimize
time and space usage. This will be explored in detail in the last chapter.
36
This book is not aimed at teaching programming, but we will cover the
basics using Python as it is relatively easy to understand. At its core, a
program consists of several fundamental elements:
2.2.1 Branches
If-else statements
These allow for binary decisions. If a condition is true, one block of code is
executed; otherwise, another block is executed.
if x == 0:
print("x is 0")
elif x == 1:
print("x is 1")
else:
print("x is some other value")
Switch-case/match-case statements
37
match x:
case 0:
print("x is 0")
case 1:
print("x is 1")
case _:
print("x is some other value")
“case 0” refers to the case where x is 0, “case 1” refers to the case where x
is 1 and so on. This can be seen as a sort of shorthand for the if-else blocks
we saw earlier.
2.2.2 Loops
Loops are fundamental for repetitive tasks in programming. They allow a set
of instructions to be executed multiple times.
For loops
0
1
2
3
4
5
6
7
8
9
38
While loops
x = 0
while x < 10:
print(x)
x = x+1
0
1
2
3
4
5
6
7
8
9
Do-while loops
Similar to while loops, but guarantees at least one execution of the loop
body.
Python doesn’t provide the option of using a do-while loop, so this example
simulates one:
x = 0
print(x)
x = x+1
while x < 10:
print(x)
x = x+1
39
This code also prints:
0
1
2
3
4
5
6
7
8
9
2.2.3 Variables
Variables are containers for storing data in a program. They are fundamental
to manipulating and processing information.
x = 0
if y == 0:
x = 1
I/O operations are crucial for a program to interact with the external world,
including users and other systems.
Here is a program that prints to the screen and requests the user for a
number and if the number is equal to 42, prints a statement:
40
print("Hello World!")
data = input("Please enter a number:")
if data == 42:
print("Congratulations! You have entered the winning
number!")
2.2.5 Operations
Arithmetic operations
x = 3
y = 2
print(x+y) #prints 5
print(x-y) #prints 1
print(x*y) #prints 6
print(x/y) #prints 1.5
Logical operations
x = True
y = False
print(x or y) #prints True because either one of x or y is
True
print(x and y) #prints False because both x and y have to be
True
print(not x) #prints False because x is True
41
Bitwise operations
These operations are for manipulating individual bits in data. Useful for
low-level programming and optimizations. We won’t bother about these in
this book.
a = 5
b = 4
print(sum(a, b)) #prints 9
42
2.2.7 Recursion
n! = n × (n − 1) × (n − 2) × (n − 3) . . . × 2 × 1
For example:
5! = 5 × 4 × 3 × 2 × 1 = 120
def factorial(n):
if n <= 0:
return 1 #base case
return n*factorial(n-1) #recursion
Advantages
43
Limitations
You, as the human cook, are like one CPU or executing agent. You can
actively perform tasks, make decisions and process information. In this
case, you can cut the carrots.
But there's another "agent" at work - the universe itself, or more specifically,
gravity and the water system. This agent can also "execute tasks" for you.
When you turn on the tap, gravity and the water system work to fill the pot
without your constant attention.
44
Figure 2.2: Serial and parallel (simultaneous) ordering
• While the pot is filling, you cut the carrots (you, the "human
agent", working)
This parallel approach is more efficient because two tasks are being
completed simultaneously by different "executing agents".
45
In computing terms:
It is called a “stack” because it works just like a stack in real life. Consider a
stack of plates. Plates are both removed from and added to the top. Stacks
are usually implemented using arrays, a type of data structure discussed in
the next chapter.
When a function is called, a stack frame is created and pushed onto the
stack. This frame contains the function's local variables, arguments and
return address. Once the function execution is complete, the frame is
popped off the stack.
46
Stack Frames and Local Variables
A stack frame is a data structure that contains all the information needed to
manage a single execution of a function. Each frame includes the function's
parameters, local variables and the address to return to after the function
finishes. This isolation allows for recursive function calls and proper
management of nested function executions.
47
2.2.10 Program as a Graph
def a(x):
return 2*x
def b(x):
return 3*x
def g(x):
return a(x)+b(x) #equivalent to returning 5*x
def h(x):
return b(x) #equivalent to returning 3*x
def f(x):
return g(x)+h(x) #equivalent to returning 8*x
print(f(2)) #prints 16
48
representation helps in visualizing the flow of control within the program,
making it easier to understand complex call hierarchies and dependencies.
49
along with common program data and memory blocks. In the example we
saw earlier, “filling water” and “cutting carrots” are two separate processes.
Figure 2.4: Types of execution. P1 and P2 are processes. Notice the similarities to
the “carrot soup” diagram
50
2.3.3 Multiprocessing and Its Advantages
51
to the quantity or amount of resources required by an algorithm to solve a
problem. It measures the efficiency of an algorithm in terms of time and
space. It helps in understanding the scalability and performance of
algorithms under varying input sizes.
For example, if we say an algorithm has O(n) time complexity, it means the
algorithm's running time grows linearly with the input size n in the worst
case. This notation allows us to compare algorithms abstractly, without
getting bogged down in implementation details or specific hardware
considerations.
52
2.4.3 Time Complexity
Dominating Terms
53
So
O(1) < O(log(n)) < O(n) < O(n log(n)) < O(n 2 ) < O(2n )
log10 (100) = 2
log2 (8) = 3
because
23 = 8
Using Limits
For more rigorous analysis, we can use limits to determine the Big O
complexity. The idea is to compare the growth of our function to a standard
complexity class.
If we have a function f(n) and we want to know if it's O(g(n)) where g(n) is
some other function, we can check the limit:
f (n)
lim
n→∞ g (n)
54
As a refresher, in mathematics, a limit helps us understand the behaviour of
a function as its input approaches a certain value.
lim f (x) = L
x→a
( n2 )
3n 2 + 20n + 100 20 100
lim = lim 3 + + =3
n→∞ n 2 n→∞ n
This is because the “n”s in the denominator tend to infinity and one over
infinity is zero.
Since this limit is finite, we confirm that the function is indeed O(n 2 ).
This method is particularly useful for more complex functions where the
dominating term isn't immediately obvious.
55
2. O(log n) - Logarithmic Time:
The running time grows linearly with input size. Examples include
traversing an array or performing a simple search in an unsorted
list.
These algorithms have rapidly growing run times and are typically
only feasible for small inputs. The naive recursive solution to the
Fibonacci sequence (without optimization) is an example.
56
Analyzing Algorithms for Their Time Complexity
def sum_and_product(arr):
sum = 0
product = 1
for num in arr:
sum += num
product *= num
return sum, product
57
2.4.4 Space Complexity
We can use the same Big O notation for reasoning about space complexity
as well.
58
Understanding these trade-offs is crucial for choosing the right algorithm for
a given problem and hardware constraints.
• Tape: The tape is an infinite, linear strip divided into cells, each
capable of holding a symbol from a finite alphabet. It acts as the
machine's memory.
59
• Head: The head is a read/write device that scans the tape. It can
read the symbol in the current cell, write a new symbol and move
the tape left or right one cell at a time.
• State Register: The state register holds the state of the Turing
Machine. At any given time, the machine is in one of a finite
number of states.
60
idealized model that helps computer scientists understand the essential
properties of computation.
2.6 Conclusion
In this chapter, we've explored the fundamental building blocks of
computation and complexity. We began by defining algorithms and
examining various algorithmic approaches such as greedy algorithms,
divide and conquer and dynamic programming. We then delved into the
structure of programs, discussing key elements like branches, loops,
variables and functions.
61
This page intentionally left blank
Chapter 3
Fundamental Data Structures and
their Algorithms
3.1 Introduction
In computer science, mastering the core structures of graphs and tables is
essential for efficient problem-solving.
Graphs are versatile tools for representing and analyzing relationships and
processes, from social networks to navigation systems. By leveraging graph
algorithms, we can find optimal solutions to complex problems across
various domains. Imagine a social network where people are represented
by dots (nodes) and their friendships by lines connecting these dots (edges)
or road networks where intersections are nodes and roads are edges, or
computer networks where devices are nodes and connections are edges.
These are simple examples of graphs.
Tables, including arrays and hash tables, are fundamental for fast data
access and manipulation. Arrays excel in indexed operations and are
crucial for tasks such as sorting large datasets in database management,
binary searching in search engines, and managing sequences in multimedia
applications. Hash tables, on the other hand, offer near-instantaneous key-
value management, making them ideal for use in implementing caches for
web browsers, managing dictionaries in language processing, and handling
routing tables in networking.
64
3.2 Undirected Graphs
In an undirected graph, relationships go both ways. Think of Facebook
friendships: if Alice is friends with Bob, Bob is also friends with Alice. This
mutual relationship is represented by a line without arrows between two
nodes.
Imagine you're exploring a maze. You start down a path and keep going as
far as you can. When you hit a dead end, you backtrack to the last
intersection and try a different path. This is essentially how Depth-First
Search works on a graph. It's great for tasks like finding a path between two
points or checking if a path exists.
Figure 3.1: Depth-First Search (DFS). The arrows represent the order that the
nodes are visited in starting from node 0. Green represents a forward movement
and red represents backtracking.
65
Real-world application: In computer game AI, a depth-limited version of
DFS can be used to explore possible moves in games like chess, looking
several moves ahead to determine the best strategy.
Now imagine you're in a dark cave and you light a torch. The light spreads
out evenly in all directions, illuminating nearby areas before farther ones.
This is how BFS explores a graph - it checks all nearby nodes before moving
outward.
Figure 3.2: Breadth-First Search (BFS). Each colour represents a new “layer” of
traversal starting from node 0.
66
3.2.3 Trees
Trees are special types of graphs that have a hierarchical structure, like a
family tree or an organization chart. They start with a root node (like the
CEO in a company structure) and branch out, with each node having child
nodes (like department heads, then team leaders, then team members etc.).
Tree Traversals
Traversing a tree means visiting every node in a specific order. There are
several ways to do this:
• Pre-order traversal: Visit a node, then its left subtree, then its right
subtree. This is like reading a book chapter by chapter, where you
read the chapter title, then the first section, then the second section
and so on.
• In-order traversal: Visit the left subtree, then the node, then the
right subtree. In a Binary Search Tree (BST), this gives you the
elements in sorted order. A Binary Search Tree is a data structure
where each node's left child has values less than the node, and the
right child has values greater, enabling efficient searches.
• Post-order traversal: Visit the left subtree, then the right subtree,
then the node. This is useful when you need to delete a tree, as
you'd want to delete the children before the parent.
67
Figure 3.3: Different types of tree traversals on a BST starting from root node 4.
Try to analyze them yourself!
68
Directed graphs can model many real-world scenarios:
Figure 3.4: A general directed graph. Can you find the cycle?
All of the algorithms we discussed for undirected graphs are also applicable
for directed graphs - we just have to ensure that the algorithm respects the
directionality of the edges.
69
3.4.1 Topological Sorting
70
3.5 Linked Lists
Linked lists are simple structures where each element points to the next
and/or previous one, like a chain. They're useful when you need to
frequently add or remove elements from the beginning or end of a list.
Real-world applications:
Figure 3.6: A singly linked list. Each node points only to the next one and not the
previous one as in the case of a doubly linked list.
71
Figure 3.7: A weighted graph
Dijkstra's shortest path algorithm finds the shortest path between two points
in a weighted graph. Imagine you're planning a road trip and want to find
the quickest route between two cities. Dijkstra's algorithm can help you
find this, taking into account the distances (or travel times) between each
city.
Figure 3.8: The result of running Dijkstra’s shortest path algorithm between two
nodes A and D. The length of the shortest path is 7. For “free”, the algorithm also
gives us the shortest path between A and every other node.
72
Real-world application:
Prim's algorithm finds the minimum spanning tree of a weighted graph. The
minimal spanning tree is a tree extracted out of the graph such that the sum
of edge weights is minimum. Imagine you're designing a railway network to
connect several cities using the least total track length while ensuring all
cities are connected. Prim's algorithm solves this type of problem.
Figure 3.9: The minimum spanning tree of the graph from figure 3.7. The total
weight is 9
73
Real-world application: In network design, Prim's algorithm can be used to
design efficient computer networks, minimizing the total length of cable
needed to connect all computers.
3.7 Arrays
Arrays are fundamental data structures that store elements in contiguous
memory locations. They provide fast, constant-time (O(1)) access to
elements using indices. Think of an array like a locker with many numbered
locker compartments. In an array this compartment is called a cell and the
number represents the index of the cell.
Arrays can be used for implementing stacks (LIFO) or queues (FIFO) as well.
LIFO stands for “last in first out” like a stack of plates and FIFO stands for
“first in first out” like a queue at a concert. They are also used to implement
heaps which are relatively advanced data structures. Arrays can be
extended into any number of dimensions, including the standard two
dimensions of a table that we might encounter in a book or spreadsheet.
The power of arrays lies in their direct mapping to computer memory. The
CPU can quickly access any array element using its memory address,
making array operations extremely efficient. This is possible because:
74
For example, if an integer array starts at memory address 10 and each
integer occupies 4 bytes, the address of the ith element would be:
10 + (i × 4)
Figure 3.10: An array. v1, v2, v3 are some integer values stored in the array. The rest
of the cells are empty.
75
3.7.3 Algorithms
Sorting
Several sorting algorithms are particularly well-suited for arrays. Two of the
more efficient comparison based algorithms are discussed here.
Quicksort
76
• Average time complexity: O(n log n)
• In-place sorting
Mergesort
77
• Stable sort
Comparison:
Binary Search
Prerequisites:
Applications
78
• Implementing efficient lookup in databases
3.8 Dictionaries
Dictionaries, also known as associative arrays, hash maps, or hash tables,
are abstract data types that store key-value pairs. They work by allowing
you to associate a unique key with a specific value, much like looking up a
word in a dictionary to find its definition. This makes them incredibly useful
for quickly finding and updating data. For example, a dictionary can store a
phone book where each person’s name is the key and their phone number
79
is the value. Because dictionaries use a special method called hashing to
organize the data, they can retrieve information almost instantly, even if the
dictionary contains a large amount of data. Dictionaries are implemented
using arrays under the hood.
3.8.1 Hashing
Imagine you have a pile of potatoes, each potato being unique in size,
shape and weight. You decide to make hash browns, which involves
shredding the potatoes into small, uniform pieces. After shredding, each
batch of hash browns looks similar, even though they originally came from
different potatoes. The process of shredding can be thought of as a hash
function and the hash browns represent the fixed-size hash values.
In this analogy:
• Hash Browns are the output hash values (multiple) of fixed size.
80
• Irreversibility: Just as you cannot reconstruct the original potatoes
from the hash browns, it is computationally infeasible to reverse
the hash value to obtain the original input data.
Hash functions take input data (e.g., strings, files) and convert them into a
fixed-size string of characters, which is typically a sequence of numbers
and letters. Here's a simplified breakdown of the hashing process:
81
• Hash Function: A function that processes the input data to produce
a hash value. Common hash functions include MD5, SHA-1 and
SHA-256.
• Output Hash: The fixed-size string that is the result of the hash
function.
Example:
• Input: "password123"
• Output Hash:
"ef92b778bafe771e89245b89ecbc5c308c20e040ef2d59fbf0145ef
4ff9f3b67"
When a user creates a password, the system applies a hash function to the
password. The resulting hash value is stored in the database, not the plain
text password.
Example:
• Password: "mypassword"
82
When the user logs in, they enter their password.
The system hashes the entered password using the same hash function. It
then compares the resulting hash with the stored hash value. If the hashes
match, the password is correct; otherwise, it is incorrect.
Example:
In practice, there is more nuance than the above simple method due to
techniques like salting and peppering, but the general principle applies.
83
3.8.2 Hashing and Array Indexing
• Take the remainder of that hash when divided by the array size.
• Use this remainder as the index to place the object in the array.
3.9 Sets
Sets are unordered collections of unique elements. They're often
implemented using hash tables for efficient operations. They can be thought
of as a special case of Dictionaries where the key is same as the value
stored.
These operations are typically very efficient due to the use of hash tables,
making sets powerful for managing unique collections of data.
* Modular arithmetic is a system of arithmetic for integers, where numbers "wrap around"
upon reaching a certain value, called the modulus. For example, in modular arithmetic with
a modulus of 5, the expression 7%5 equals 2, because when 7 is divided by 5, the
remainder is 2.
84
Adding elements
Process: Hash the element and add it to the appropriate bucket if not
already present
Removing elements
Process: Hash the element, locate and remove it from the appropriate
bucket
Set union
Time complexity: O(m + n), where m and n are the sizes of the two sets
85
Process: Create a new set and add all elements from both sets
Set intersection
Time complexity: O(min(m , n)), where m and n are the sizes of the two
sets
Process: Iterate through the smaller set and check each element's presence
in the larger set
Set difference
Figure 3.17: Set difference of the first set minus the second
86
Process: Create a new set, add elements from the first set that are not in the
second set
3.10 Conclusion
Graphs and their algorithms are powerful tools for modelling and solving
real-world problems. From social networks to GPS navigation, from project
scheduling to computer networking, graph theory provides a framework for
understanding and optimizing complex relationships and processes. By
representing problems as graphs, we can apply these algorithms to find
efficient solutions in various fields.
Tables, particularly arrays and hash tables, form the backbone of many
efficient algorithms and data structures. Arrays offer unparalleled speed for
indexed access and are fundamental to many sorting and searching
algorithms. Hash tables, used in dictionaries and sets, provide near-constant
time operations for key-value pair management and set operations.
In mastering the concepts of graphs and tables, you are equipped with the
foundational tools to innovate and optimize. These structures and their
algorithms not only enhance your problem-solving capabilities but also
open up opportunities to address complex challenges across various
industries.
87
This page intentionally left blank
Chapter 4
From Theory to Practice
In our daily lives, we often use algorithmic thinking without realizing it.
This chapter explores common scenarios where computational concepts
come into play, demonstrating how the algorithms we've learned about
manifest in everyday situations.
For instance, when you're getting dressed in the morning, you might use a
process similar to a decision tree: if it's cold, you choose warm clothes; if
it's raining, you grab an umbrella or a raincoat. This is akin to the if-then-
else structures used in programming.
90
• Solve complex problems: Breaking down big problems into
smaller, manageable steps is a key aspect of both algorithmic
thinking and effective problem-solving.
I encourage you to try these examples out in real life or bring out a pencil
and paper and try to draw along to the examples in order to gain a better
understanding. Refer to the earlier chapters to refresh your memory if
required.
91
4.1.1 Flipping through a book to find a specific page
Imagine you have a book and you don’t know how many pages are there
beforehand and you need to find page 753:
2. 753 is greater than 500, so you know it's in the second half.
This intuitive process is like binary search in action. Instead of checking all
1000 pages, you've narrowed it down quickly by halving the search space
each time. We tend to do this intuitively without following the step by step
procedure above.
92
Key points:
• You can find any page in about 10 steps or fewer (log₂(1000) ≈ 10).
4. You'll find "Smith, John" (or determine it's not there) in about 20
steps or fewer.
Efficiency:
93
2. It uses indexed databases and efficient search algorithms to quickly
narrow down the possibilities.
3. While not pure binary search, these systems use similar divide-and-
conquer principles to provide near-instantaneous results.
This method is much faster than copying the initial row 1023 times
individually.
94
Figure 4.1: Efficient copying in a spreadsheet
Efficiency:
† A hi-hat is a pair of cymbals mounted on a stand, played with a pedal and drumsticks to
create rhythmic patterns in music.
95
3. Copy the 2-bar pattern to make a 4-bar pattern
This concept also explains how information can spread rapidly on social
networks:
2. Each of those 5 friends shares with 4 more friends (they don’t share
it back with you)
96
4.2.4 Key Takeaways
97
4.3.1 The Travelling Salesperson Problem: Finding the
Most Efficient Route
The Travelling Salesman Problem (TSP) involves finding the shortest possible
route that visits each given location exactly once and returns to the starting
point.
• London
• Paris
• Berlin
98
• Rome
• Madrid
• Amsterdam
The challenge is to find the most cost-efficient route through all cities,
minimizing the total travel cost.
Solution Approach:
• Identify all possible direct flights between the cities and their
respective costs.
• Use available data to determine the flight costs between each pair
of cities.
• For smaller problems, use exact algorithms like branch and bound
or dynamic programming.
Complexities:
99
Real-world applications:
Items:
100
• Shoes (8 lbs, importance: 7/10)
Solution approach:
Real-world applications:
101
solution becomes computationally intensive. In practice, we often use
approximation algorithms or heuristics to find good (but not necessarily
perfect) solutions in reasonable time.
102
Figure 4.3: Two connected components (the subgraphs inside the dotted circles)
in a graph
space graph into connected components and then combining the results of
solving smaller subproblems.
• Divide: Split the large pile into smaller piles (e.g., 4-5 smaller
piles)
• Combine: Merge the sorted small piles into larger category piles
Benefits:
103
• Can parallelize the work (family members can sort different piles)
This process mirrors the Mergesort algorithm, which is highly efficient for
sorting large datasets.
Divide:
• Data migration
• Software installation
• Staff training
Conquer:
104
• Allocate resources appropriately
Combine:
Benefits:
105
4.5 Greedy Algorithms: Making the Best
Immediate Choice
Greedy algorithms are problem-solving methods that make the locally
optimal choice at each step with the hope of finding a global optimum.
While they don't always lead to the best overall solution, they are often
used for their simplicity and efficiency in many real-world scenarios.
106
• At each step, choose the laptop that offers the best value for a
particular feature.
• Continue until you find a laptop that satisfies most of your criteria
within the budget.
This approach is "greedy" because at each step, you're making the best
choice available without reconsidering previous decisions.
Benefits:
• Quick decision-making
Limitations:
At each intersection, select the direction that seems to lead most directly to
your destination.
This may not always result in the absolute shortest route, but it's often
efficient.
107
4.5.3 Task Scheduling
Always choose the task with the nearest deadline or shortest completion
time.
Efficient for many scenarios but may not be optimal if tasks have different
priorities or dependencies.
108
Sorting is a fundamental concept in both computer science and everyday
life. It involves arranging items in a specific order, which makes finding and
managing information much easier.
Organizing a Closet
109
4.6.2 Key Concepts in Sorting
Comparison-Based Sorting
Stability in Sorting
Maintaining the relative order of equal items, mostly from a previous cycle
of sorting.
Example: Sorting clothes first by type, then by colour within each type
• Improves Decision Making: Easier to see what you have and what
you need
110
• Facilitates Sharing: Others can easily find things in an organized
system
Imagine you're planning activities for an entire quarter of a year. You have a
list of potential activities, each with an associated cost and an "enjoyment
score” on a scale of 1-10. Your goal is to maximize your total enjoyment
while staying within your budget. You may repeat activities as much as you
like.
Activities:
111
Budget: $160
Figure 4.4: Dynamic Programming solution. Dinner + Movie + Spa gives total
enjoyment of 25 with cost 160
I encourage you to try to reason about how to solve the problem before
diving into the next couple of sections. The hint is that the solution is
described by the recurrence relation:
Memoization Version
112
• If the activity's cost fits within the budget, recursively call
max_enjoyment(remaining_budget)
Break down the problem into subproblems. Notice that the costs are in
multiples of 10 which is crucial for this method to work.
Build a table:
• 1 Row
• For each cell, consider all activities that fit the budget
113
• Start from the full budget
Imagine you need to make a certain amount of change using the fewest
number of coins possible. You have a list of coin denominations available,
and your goal is to find the minimum number of coins needed to make the
exact change.
Figure 4.5: The decision tree of the coin change problem. A possible solution is
highlighted in green (there can be multiple).
Coin Denominations:
Coin A (2 units)
Coin B (3 units)
Coin C (5 units)
114
Amount: 16 units
115
4.7.4 Benefits in Daily Life
Topological sorting ensures that each task is completed before any tasks
dependent on it are started. Additionally, topological sorting helps in
identifying potential parallelism in processes, enabling the efficient
scheduling of tasks that do not depend on each other.
116
Start with a list of items to put on:
1. Shoes
2. Socks
3. Jacket
4. Dress shirt
5. Trousers
6. Inner wear
7. Vest
8. Tie
117
4. Dress shirt must be on before the tie and vest
1. Inner wear
2. Socks
3. Dress shirt
4. Trousers
5. Tie
6. Vest
7. Jacket
8. Shoes
118
• C. Develop product messaging and positioning
• I. Launch PR campaign
Determine dependencies:
1. B depends on A
2. C depends on A and B
3. D depends on C
4. E depends on C and D
5. F depends on D and E
6. G depends on D and E
7. H depends on C
8. I depends on E
9. J depends on E and F
119
3. C [Develop product messaging and positioning]
9. I [Launch PR campaign]
This ordering ensures that each marketing task is performed only after all
necessary prerequisite tasks are completed.
120
5. Task scheduling in parallel computing
Imagine a square with a side length of 1 unit and a circle inscribed within
it. The circle's diameter is equal to the square's side, so its radius is 0.5
units. Now, let's conduct a thought experiment.
The ratio of pebbles inside the circle to the total number of pebbles will
approximate the ratio of the circle's area to the square's area. We can use
this to estimate pi:
121
• Area of the square: 1 × 1 = 1 square unit
2 π
• Area of the circle: π r = π (0.5)2 = square units
4
(4)
π
π
• The ratio of these areas is =
1 4
So, if we multiply our pebble ratio by 4, we get an estimate of π. The more
pebbles we throw, the more accurate our estimate becomes.
Figure 4.6: Circle inscribed in a square. There are 47 green dots and 13 red dots.
π
47/60 is 0.78333… which is close to or 0.78539…
4
This technique simulates the path of light rays as they interact with objects
in a scene.
122
Figure 4.7: A ray traced image of a 3D model that I created in Blender, rendered
using LuxRender
• Multiple light rays are randomly cast from each pixel of the image.
• The paths of these rays are traced as they bounce off surfaces,
refract through materials, or get absorbed.
123
4.10 Conclusion
In this chapter, we explored how algorithmic thinking permeates our
everyday lives, from organizing our closets to planning our daily activities.
By recognizing and applying computational concepts, we can enhance our
problem-solving abilities, improve efficiency and make more informed
decisions.
As you continue to encounter and apply these principles, you will develop
a deeper appreciation for the pervasive role of algorithms in shaping our
world.
124
This page intentionally left blank
This page intentionally left blank
Further Reading
Books
"Introduction to Algorithms" by Thomas H. Cormen, Charles E. Leiserson,
Ronald L. Rivest, and Clifford Stein
127
Online Courses
"Algorithms" by Princeton University on Coursera
LeetCode (www.leetcode.com)
HackerRank (www.hackerrank.com)
128
A site offering coding challenges and competitions across various
domains, including algorithms, data structures, and more.
129
This page intentionally left blank
Afterword
I hope this book has equipped you with useful problem-solving tools and
sparked a curiosity to see algorithms in the world around you. Thank you
for joining me on this exploration. May it serve you well in your future
endeavours!
131
This page intentionally left blank
Glossary
133
9. Depth-First Search (DFS): A graph traversal algorithm that explores
as far as possible along each branch before backtracking.
10. Directed Acyclic Graph (DAG): A graph with directed edges and no
cycles.
18. GUI (Graphical User Interface): A visual interface that allows users
to interact with electronic devices using graphical elements like
icons, buttons and menus instead of text-based commands.
134
19. Hash Table: A data structure that implements an associative array
abstract data type, a structure that can map keys to values.
21. Linked List: A linear collection of data elements whose order is not
given by their physical placement in memory.
26. Set: A data structure that stores a collection of unique elements with
no specific order.
135
30. Time Complexity: A description of the amount of time it takes to run
an algorithm.
32. Tree: A widely used data structure that simulates a hierarchical tree
structure, with a root value and subtrees of children with a parent
node.
136
Index
Algorithm, 6, 12, 14-15, 20-21, 27, 32, 34, 43, 52-53, 55-61, 64, 69,
72-74, 76-79, 87, 90-91, 93-94, 99, 101-102, 104, 106, 108, 116, 124
Data Structure, 5, 7, 27, 46-47, 58, 64, 67, 74, 87, 91, 124
Directed Acyclic Graph (DAG), 16-17, 28, 69, 116, 18-20, 70, 118
Dynamic Programming (DP), 12, 18-20, 28, 35, 61, 99, 101, 111-113, 115,
124, 36, 78
137
E
Graph, 6, 8-9, 14-20, 22-28, 32-33, 35, 48-49, 51, 61, 64-69, 71-73, 75,
87, 90-91, 103, 116, 121-122
Hashing, 80-84
Heap, 74
138
Monte Carlo Method, 121, 123-124
Set, 84-86
Sorting, 18, 56, 58, 64, 70, 76-78, 87, 91, 103-104, 108-110, 116, 118,
120, 124
139