Data Structure and Algorithm
Data Structure and Algorithm
A data structure is a way of organizing data in a The sequence of computational steps to solve a
computer’s memory or on a disk so that it can problem is known as algorithm. An algorithm is a
be accessed and manipulated efficiently. It is a set of instructions that is designed to solve a
collection of data elements that are organized in specific problem efficiently. Algorithms can be
a particular way , depending on the type of data designed to perform a variety of tasks , such as
being stored and the operations that will be searching for data elements , sorting data
performed on the data. elements or performing mathematical calculations.
There are several different types of structures , For example , a sorting algorithm can be used to
including arrays , linked lists , queues , trees and sort a collection of data elements in ascending or
graphs. Each data structure has its own unique descending order. A searching algorithm can be
way of organizing data and provides a different used to find a specific data element in a
set of operations that can be performed on the collection of data elements.
data.
Linear and Non Linear Data structures
For example , an array is a data structure that
stores a collection of data elements of the same In computer science , data structures are
type in contiguous memory locations (contiguous classified in to two main categories , linear and
refers a block of records that are logically non linear data structures , linear data structures
adjacent to one another in to physically adjacent are those in which data elements are arranged in
sectors). The elements of an array can be a linear sequence , such as an array or a linked
accessed using an index , which represents the list. In contrast , non-linear data structures are
position of the element in the array. those in which data elements are not arranged in
a linear sequence , such as trees or graphs.
On the other hand , a linked list is a data
structure that stores a collection of data elements Some common types of linear data structures
that are connected by links or pointers. Each include :-
element in the list contains a data field and a
pointer to the next element in the list. 1) Arrays :- An array is a collection of data
elements of the same type that are stored in
A program is a set of instructions that Is written contiguous memory locations. The elements of an
in a programming language to solve a specific array can be accessed using an index , which
problem. A solution to a problem can be broken represents the position of the element in the
down In two parts : the organization of the data array. Arrays are efficient for storing and
and the sequence of computational steps to solve accessing data when the size of the array is fixed
the problem. and the elements are accessed sequentially.
The way data is organized in computer’s memory 2) Linked Lists :- A linked list is a data structure
is known as a data structure. A data structure is that stores a collection of data elements that are
a collection of data elements that are organized connected by links or pointers. Each element in
in a specific manner to allow for efficient storage the list contains a data field and a pointer to the
, retrieval and manipulation of individual data next element in the list.
elements. The choice of data structures depends
on the type of data being stored and the 3) Stacks :- A stack is a data structure that stores
operations that will be performed on the data. a collection of data elements in a last in , first
For example , if the data is organized in an array out (LIFO) manner. The elements can be added to
, the data elements are stored in contiguous or removed from the stack only at one end,
memory locations and accessed using an index. If known as the top of the stack.
the data is organized in a linked list , each data
element is connected to the next using pointers 4) Queues :- A queue is a data structure that
or links. stores a collection of data elements in a first in
first out (FIFO) manner. The elements can be
added to the read of the queue and removed advance and the data elements are accessed
from the front of the queues. sequentially.
Non-linear data structures are those in which the Dynamic data structures is one that can expand
data elements are not arranged in a linear or shrink as required during program execution ,
sequence. In contrast to linear data structures , and their associated memory locations change
insertion and deletion of elements in non linear accordingly. A linked list is an example of a
data structures are not necessarily performed in a dynamic data structure , where new elements can
sequential manner. Some common types of non- be added or removed from the list during
linear data structures include trees and graphs. program execution. Dynamic data structures are
useful when the size of the data is not known in
1) Trees :- A tree is a non linear data structure advance or when the data elements are accessed
that consists of a collection of nodes connected randomly.
by edges. Each node in a tree contains a data
element and pointers to its child nodes. Trees are Data structure Operations
hierarchical data structures that are used to
represent relationships between data elements. Algorithms are a set of operations that
The top most node in a tree Is called the root manipulate the data represented by different data
node , and each node in the tree has a parent structures using various operations. These
node except for the root node. operations play a significant role in the
processing of data and are as follows :-
2) Graphs :- A graph is a non-linear data
structure that consists of a collection of vertices Creating :- Creating a data structure is the first
or nodes connected by edges. Each edge in a operation , where the data structure is declared ,
graph connects two vertices , and each vertex in initialized , and memory locations are reserved
a graph contains a data element. Graphs are used for data elements. This operation prepares the
to represent relationships between data elements data structures for future operations.
that are not hierarchical.
Inserting :- Inserting is the operation of adding a
Homogeneous and non-homogeneous data structures
new data element to the data structure. It
involves finding the appropriate location in the
Homogeneous data structures is one in which all
data structure and adding the element in that
the data elements are of the same type. An array
location
is a common example of homogeneous data
structure , where all elements are of the same
Updating :- This operation involves changing the
data types and the size of the array is fixed.
value of data elements with in the data structure.
For example , when updating an element in an
Non-homogeneous data structures is one in which
array , the value of the element at a specific
data elements may not be of the same type. A
index is changed.
structure in the C programming language is an
example of a non-homogeneous data structure ,
Deleting :- This operation involves removing a
where data elements of different data types can
data element from the data structure. For
be grouped together. Non-homogeneous data
example , when deleting an element from a
structures are useful when we need to store
queue the element at the front of the queue is
different types of data elements together.
removed.
computing time and storage space required by algorithm analysis often focuses on analyzing
different algorithms. Its purpose is to predict the algorithms based on the number of operations
environment. When solving a problem, there are operations, such as comparisons, assignments, or
multiple possible algorithms to choose from, and iterations, required to solve a problem, it
algorithm analysis helps in selecting the best becomes possible to evaluate the algorithm's
To classify data structures and algorithms as good This approach allows for a more meaningful
their resource requirements. The main resources changes with different input sizes. It provides
considered in algorithm analysis are: insights into how the algorithm scales and
whether it exhibits desirable characteristics, such
1. Running Time: Running time refers to the as sub-linear, linear, or polynomial time
amount of time an algorithm takes to complexity, as the input size increases.
execute and provide the output. It is often
treated as the most important resource By analyzing algorithms based on operation
since computational time is typically the counts and studying their time and space
ANALYSIS RULES
Examples
The provided analysis rules outline common
1) void func()
guidelines for analyzing the running time of
{
algorithms. These rules help in estimating the int x = 0;
time complexity of an algorithm by considering int i = 1;
the count of operations involved. Here's a more int j = 1;
detailed explanation of each rule: cout << "Enter an Integer value:
";
1. Arbitrary Time Unit: The analysis assumes cin >> n;
an arbitrary time unit. It provides a
relative measure of time without while (i <= n)
{
specifying the exact duration. This allows
x++; for (i = 1; i <= n; i++)
i++; k = k + 1;
} return 0;
}
while (j < n)
{
j++; T(n) = 1 + 1 + 1 + [ 1 + n + 1 + n + 2n ] + 1
} T(n) = 6 + 4n
}
Usually, we are most interested in the worst-case • Big-Omega Notation (Ω): It tells us the
scenario because it tells us how the algorithm lower limit of the algorithm's running
will perform no matter what inputs we give it. time. For example, if an algorithm has a
To describe this, we use something called "Big-O" time complexity of Ω(n), it means the
notation, which helps us understand how the running time will grow at least linearly
running time of an algorithm grows as the size of with the input size. It gives us a best-case
the input gets bigger. We use Big-O notation to estimate of the running time.
express the upper limit of the algorithm's time
• Now, let's think about the fastest time
complexity.
someone can finish the race. Big-Omega
So, when we say an algorithm has a Big-O of notation tells you the shortest possible
O(n^2), it means that the running time of the time. If someone says the race will take
algorithm will not grow faster than the square of Ω(n) time, it means no one will finish in
the input size. less than n time.
ASYMPTOTIC ANALYSIS
Big-Oh Notation (O) is a way to analyze and
describe the upper limit or worst-case running
Imagine you have two algorithms, and you want
time of an algorithm. It helps us understand how
to compare how fast they are. To do that, you
the running time of an algorithm grows as the
need a way to measure their performance as the
input size increases. By using Big-O notation, we
input size gets larger and larger.
can estimate the efficiency of an algorithm and
Asymptotic analysis helps us understand how the compare it with other algorithms.
running time of an algorithm changes as the
In Big-O notation, we express the running time of
input size increases towards infinity. It focuses on
an algorithm as a function of the input size
the growth rate of the running time, which
(usually denoted as "n"). The notation O(f(n))
means how fast the running time increases as the
represents the upper bound of the running time,
input gets bigger.
meaning the running time will not exceed a
To describe this growth rate, we use different certain multiple of the function f(n) as n becomes
notations: larger.
• Big-Oh Notation (O): It tells us the upper Here are some typical orders (growth rates) that
limit of the algorithm's running time. For are commonly encountered:
example, if an algorithm has a time
• O(1): Constant time complexity. The
complexity of O(n^2), it means the
running time of the algorithm remains the
running time won't grow faster than the
same, regardless of the input size. It is
square of the input size. It gives us a
considered the best-case scenario.
worst-case estimate of the running time.
• O(log n): Logarithmic time complexity.
• Imagine you have a race, and you want
The running time grows logarithmically
to know the maximum time it will take
with the input size. Algorithms with this
for someone to finish. Big-Oh notation
complexity often divide the problem into
tells you the longest possible time it could
smaller subproblems and solve them
take. For example, if someone says the
recursively.
race will take O(n^2) time, it means no
• O(n log n): Log-linear time complexity. complexity will be higher compared to an
The running time grows slightly faster algorithm with O(n) complexity.
than linearly. Algorithms with this
To understand why O(n log n) is faster, we need
complexity are commonly seen in sorting
to consider the growth rates of these functions. In
and searching algorithms like merge sort
linear time complexity (O(n)), the running time
and quicksort.
increases linearly with the input size. For
• O(n): Linear time complexity. The running example, if the input size doubles, the running
time grows linearly with the input size. time also doubles.
This means that as the input size doubles,
However, in log-linear time complexity (O(n log
the running time also doubles. It
n)), the running time increases at a slower rate.
represents a proportional relationship
It grows in a proportional relationship to the
between the input size and the running
input size multiplied by the logarithm of the
time.
input size. As the input size grows, the running
• O(n^2): Quadratic time complexity. The time increases, but not as quickly as in linear
running time grows quadratically with the time complexity.
input size. Algorithms with nested loops
Therefore, for larger input sizes, an algorithm
often have this complexity.
with O(n log n) complexity will generally be
• O(2^n): Exponential time complexity. The faster than an algorithm with O(n) complexity.
running time grows exponentially with the
These are just a few examples, and there are
input size. Algorithms with this
many more complexities possible. The goal is to
complexity tend to become very slow as
choose an algorithm with the best possible Big-O
the input size increases.
notation for a given problem to ensure efficiency.
ASYMPTOTIC NOTATIONS
Asymptotic notation is a mathematical tool used
to describe and analyze the efficiency or
performance of an algorithm or function as the
input size increases. It helps us understand how
the behavior of an algorithm changes when we
have more data or a larger problem to solve.
Who is faster O(nlogn) or o(n) ?
When we talk about the behavior or growth rate
In terms of asymptotic analysis, we compare the
of an algorithm, we're interested in how the
growth rates of functions to determine which one
algorithm's time or space requirements change
grows faster as the input size increases.
relative to the size of the input. Asymptotic
In this case, we are comparing O(n) (linear time notation allows us to focus on the most
complexity) and O(n log n) (log-linear time significant factors that affect the algorithm's
complexity). performance and ignore less significant details.
Asymptotically, O(n log n) grows faster than Three commonly used symbols in asymptotic
O(n). This means that as the input size increases, notation are:
the running time of an algorithm with O(n log n)
Big O notation (O):- This symbol represents the least as fast as the function g(n) as the
upper bound or worst-case scenario of an input size increases.
algorithm's time or space complexity. It tells us • It provides a lower limit on the growth
how the algorithm's performance scales as the rate of the algorithm.
input size increases. For example, if we say an
Big Theta notation (Θ):- This symbol represents
algorithm has a time complexity of O(n^2), it
both the upper and lower bounds, providing a
means that the algorithm's running time will not
tight range of possible behaviors for an
exceed a quadratic growth rate as the input size
algorithm's time or space complexity. It describes
increases. The "O" notation allows us to describe
the average-case scenario when the best and
the maximum amount of resources an algorithm
worst cases are similar. For example, if we say
may require.
an algorithm has a time complexity of Θ(n), it
means that the algorithm's running time grows
linearly with the input size, neither faster nor
• Represents the upper bound or worst-case
slower. The "Θ" notation allows us to describe
scenario of an algorithm's time or space
the exact growth rate or efficiency of an
complexity.
algorithm within a specific range.
• It describes the maximum rate at which
• Represents both the upper and lower
an algorithm's performance grows as the
bounds, providing a tight range of
input size increases.
possible growth rates.
• When we say an algorithm has a time
complexity of O(f(n)), it means the • It describes the average-case scenario
algorithm's running time will not exceed a when the best and worst cases are
certain multiple of the function f(n) as the similar.
input size grows. • When we say an algorithm has a time
• It provides an upper limit on the growth complexity of Θ(h(n)), it means the
rate of the algorithm. algorithm's running time grows at the
same rate as the function h(n) as the
Big Omega notation (Ω):- This symbol represents
input size increases.
the lower bound or best-case scenario of an
• It provides an exact description of the
algorithm's time or space complexity. It gives us
growth rate of the algorithm within a
an idea of the minimum amount of resources an
specific range.
algorithm will require to solve a problem as the
input size increases. For example, if an algorithm
has a time complexity of Ω(n), it means the BIG – O THEOREMS
algorithm's running time will grow at least
linearly with the input size. The "Ω" notation The Big-O theorems and properties help us
provides information about the minimum understand the behavior and relationships
efficiency of an algorithm. between different functions in terms of their
growth rates. They provide guidelines and rules
• Represents the lower bound or best-case for analyzing and comparing functions using Big-
scenario of an algorithm's time or space O notation.
complexity.
Detailed Explanation:
• It describes the minimum rate at which
an algorithm's performance grows as the Theorem 1: The theorem states that a constant,
input size increases. represented by k, is O(1). It means that
• When we say an algorithm has a time regardless of the value of k, it is considered to
complexity of Ω(g(n)), it means the have a constant complexity or growth rate. In
algorithm's running time will grow at Big-O notation, constants can be ignored because
they do not affect the scalability or efficiency of will not exceed the resources required by
an algorithm. h(n).
Theorem 2: This theorem focuses on polynomials The conclusion from these two statements is that:
and states that the growth rate of a polynomial is
3. Function f(n) is O(h(n)): Combining the
determined by the term with the highest power
information from statements 1 and 2, we
of n. For example, if we have a polynomial f(n)
can say that the growth rate of function
of degree d, then it is O(n^d). The dominant
f(n) is no greater than the growth rate of
term in the polynomial determines the overall
function h(n). It implies that as the input
growth rate of the function.
size increases, the resources required by
Theorem 3: The theorem states that a constant f(n) will not exceed the resources required
factor multiplied by a function does not change by h(n).
its Big-O complexity. The constant factor can be
This theorem allows us to chain together multiple
ignored when analyzing the growth rate of the
comparisons of functions' growth rates using Big-
function. For example, if we have a function f(n)
O notation. If we have evidence that one function
= 7n^4 + 3n^2 + 5n + 1000, it is O(n^4). The
grows no faster than another, and that other
constant factor 7 can be disregarded.
function grows no faster than a third function,
Theorem 4 (Transitivity): This theorem states that we can confidently say that the first function also
if function f(n) is O(g(n)) and g(n) is O(h(n)), then grows no faster than the third function.
f(n) is O(h(n)). It means that if one function has
The transitivity property is helpful when
a certain growth rate and another function has a
comparing and analyzing the efficiency of
higher growth rate, the first function is also
algorithms. It allows us to make logical
guaranteed to have a growth rate no higher than
deductions about the scalability and resource
the second function.
requirements of algorithms based on their growth
This theorem states that if function f(n) is O(g(n)) rates.
and g(n) is O(h(n)), then f(n) is O(h(n)). In
Theorem 5: The theorem states that for any base
simpler terms, it means that if one function
b, logarithm base b of n (logb(n)) is O(log(n)). It
grows no faster than another function, and that
means that logarithmic functions with different
other function grows no faster than a third
bases grow at the same rate. The base of the
function, then the first function also grows no
logarithm does not significantly affect the growth
faster than the third function.
rate.
To understand this theorem, let's break it down
Theorem 6: This theorem provides a hierarchy of
further:
growth rates for various types of functions. It
1. Function f(n) is O(g(n)): This means that states that each function in the list is O of its
the growth rate of function f(n) is no successor. Functions such as constant (k),
greater than the growth rate of function logarithmic (logn), linear (n), linearithmic (nlogn),
g(n). It indicates that as the input size quadratic (n^2), exponential (2^n, 3^n), larger
increases, the resources (time or space) constants to the nth power, factorial (n!), and n
required by f(n) will not exceed the to higher powers all have different growth rates.
resources required by g(n). As we move up the list, the growth rate
increases.
2. Function g(n) is O(h(n)): This means that
the growth rate of function g(n) is no PROPERTIES OF BIG-O
greater than the growth rate of function
h(n). It indicates that as the input size Higher powers grow faster: When we compare
increases, the resources required by g(n) functions with different powers of n, the one
with a higher power grows faster. For example, if where b is greater than 1, grow more slowly
we have a function n^r, where r is a positive than functions with powers of n. As the input
number, and another function n^s, where s is a size increases, the growth rate of logarithmic
positive number and greater than r, then the functions is lower compared to functions with
function n^s grows faster as the input size powers of n.
increases.
Example: Let's compare two functions, f(n) =
Example: Consider two functions, f(n) = n^2 and log_2(n) and g(n) = n^2. As the input size
g(n) = n^3. As the input size increases, the increases, the function f(n) with a logarithmic
function g(n) grows faster than f(n) because it has growth rate grows much more slowly than g(n)
a higher power of n. For instance, when n = 10, with a quadratic growth rate. For instance, when
f(n) = 100 and g(n) = 1000. Thus, g(n) grows n = 100, f(n) = 6.64 (approximately) and g(n) =
faster than f(n). 10,000. The logarithmic growth of f(n) is
significantly lower compared to the quadratic
Fastest growing term dominates a sum: When we
growth of g(n) as the input size increases.
have a sum of functions, the one with the fastest
growing term determines the overall growth rate. These properties help us understand and compare
For example, if we have a sum f(n) + g(n), and the growth rates of functions using Big-O
the growth rate of g(n) is faster than that of f(n), notation. They provide insights into how different
then the sum is dominated by g(n) in terms of its types of functions behave as the input size
growth rate. increases. By analyzing and comparing functions
based on their growth rates, we can make
Example: Let's say we have two functions, f(n) =
predictions about the efficiency and scalability of
n^2 and g(n) = n^3 + 1000. In this case, the
algorithms.
fastest growing term in g(n) is n^3, which
dominates the overall growth rate. Even though FORMAL DEFINITION OF BIG-O NOTATION
f(n) has a lower power term and an additional
constant, as the input size increases, the term Big O notation is a way to compare and analyze
with the highest power, n^3, will dominate the algorithms based on their efficiency and
sum. Thus, the overall growth rate is determined performance. It helps us understand how the
by the fastest growing term. runtime or resource requirements of an algorithm
change as the input size increases. Big O notation
Exponential functions grow faster than powers: focuses on the largest or most significant term in
Exponential functions, which are represented as the algorithm's expression, as it becomes the
b^n, where b is greater than 1, grow faster than dominant factor for larger input sizes.
functions with powers of n. This means that as
the input size increases, the growth rate of an Detailed Explanation: Big O notation provides a
exponential function outpaces the growth rate of standardized way to express the complexity or
a function with a power of n. growth rate of an algorithm. It allows us to make
comparisons and predictions about how the
Example: Consider two functions, f(n) = 2^n and algorithm will perform for larger values of input.
g(n) = n^3. As the input size increases, the
function f(n) with an exponential growth rate Formal Definition: In Big O notation, we say that
grows significantly faster than g(n) with a f(n) = O(g(n)) if there exist positive constants "c"
polynomial growth rate. For example, when n = and "k" such that for all values of n greater than
10, f(n) = 1024 and g(n) = 1000. The exponential or equal to "k", the function f(n) is less than or
growth of f(n) surpasses the polynomial growth of equal to "c" multiplied by the function g(n). This
g(n) as the input size increases definition helps us establish an upper bound on
the growth rate of f(n) relative to g(n).
Logarithms grow more slowly than powers:
Logarithmic functions, represented as log_b(n),
Examples: The following points are facts that you function f(n) is less than or equal to c multiplied
can use for Big-Oh problems: by g(n).
Similarly, for any value of n greater than From this, we can see that for any value of n
or equal to 1, we can say that n is less that is greater than or equal to 1, the inequality
than or equal to n^2. This inequality 10n + 5 ≤ 15n holds true.
holds for all positive integers. So, in this case, we have found that k = 1. This
As the value of n increases, the growth means that for all values of n that are 1 or larger
rate of 2^n becomes larger than the (n ≥ 1), the function f(n) = 10n + 5 is less than
growth rate of n! (n factorial). This is or equal to 15 multiplied by g(n) = n.
true for all values of n greater than or Step 4: Since the inequality is true for n = 1, we
equal to 4. can say that for all values of n that are 1 or
For values of n greater than or equal to larger (n >= 1), the function f(n) = 10n + 5 is
2, we can observe that the logarithm base less than or equal to 15 multiplied by g(n).
2 of n is less than or equal to n. The Conclusion: So we have found our constants: c =
logarithmic growth rate is smaller 15 and k = 1. This means that for all values of n
compared to linear growth (n). that are 1 or larger, the function f(n) = 10n + 5
Additionally, for values of n greater than is "smaller" or "equal to" 15 multiplied by g(n) =
or equal to 2, we can see that n is less n.
than or equal to n log base 2 of n. The In simpler terms, we have shown that as n gets
linear growth rate is smaller compared to bigger, the function f(n) = 10n + 5 is not much
linearithmic growth (n log n). larger than g(n) = n. This comparison helps us
understand how the two functions grow and how
Example :- f(n)=10n+5 and g(n)=n. Show that f(n) their values relate to each other.
is O(g(n)).
THE CONSTANTS K AND C
When using Big O notation to compare functions, Test the inequality: Substitute the chosen value of
we want to find values for "k" and "c" that "c" back into the inequality and verify that it
satisfy the condition f(n) ≤ c * g(n) for all n ≥ holds true for all values of n greater than or
k. In other words, for any input size n greater equal to k. If the inequality is satisfied, you have
than or equal to "k", the function f(n) will be found a valid value for "c".
less than or equal to "c" multiplied by the
Remember that finding the exact value of "c" is
function g(n).
not always necessary in Big O notation. The focus
By finding suitable values for "k" and "c" and is on establishing an upper bound and showing
proving this inequality, we can determine the that a constant "c" exists for which the inequality
relationship between the growth rates of the two holds.
functions and analyze their efficiency or
Example :- f(n) = 3n2 +4n+1. Show that
scalability. These constants help us quantify and f(n)=O(n^2).
compare the performance of algorithms or
functions based on their growth rates. Introduction: We want to show that the function
f(n) = 3n^2 + 4n + 1 is "smaller" or "equal to"
HOW TO FIND THE VALUE OF C ?
the function g(n) = n^2 when n gets bigger.
To find the value of the constant "c" in Big O We'll do this by finding some numbers that help
notation, you typically need to analyze the us compare the two functions.
behavior of the functions and determine an upper
Step 1: To show that f(n) is O(n^2), we need to
bound on the growth rate. Here are a few
establish that for any value of n greater than or
general steps to help find the value of "c":
equal to a certain number k, the function f(n) is
Identify the relevant functions: Determine which less than or equal to a constant c multiplied by
functions you want to compare and analyze in g(n).
terms of their growth rates.
Step 2: Let's break down the inequality and
Write down the inequality: Write the inequality simplify it step by step. We start by comparing
f(n) ≤ c * g(n) for all n ≥ k, where f(n) and the individual terms and inequalities involved.
g(n) represent the functions you are comparing,
4n ≤ 4n^2 for all n ≥ 1: This step shows that
and k is a threshold value.
for any value of n greater than or equal to 1, the
Simplify the inequality: Simplify the inequality to inequality 4n ≤ 4n^2 holds true. In other
isolate the terms involving the functions. This words, the term 4n is always smaller or equal to
step usually involves canceling out common 4n^2 when n is 1 or larger.
factors or rearranging terms to make the
1 ≤ n^2 for all n ≥ 1: This step establishes
inequality easier to work with.
that for any value of n greater than or equal to
Determine an upper bound: Look for an upper 1, the inequality 1 ≤ n^2 holds true. This
bound on the growth rate by finding the highest inequality tells us that n^2 is always greater
power of n or the term that dominates the than or equal to 1 when n is 1 or larger.
Step 3: Now that we have these two inequalities, that once n becomes 6 or larger, the value of 6
we can combine them to form a new inequality: is always smaller than or equal to n.
3n^2 + 4n + 1 ≤ 3n^2 + 4n^2 + n^2 for all Step 3: Combining these inequalities, we can
n ≥ 1. form the following inequality:
This shows that for any value of n greater than 5n + 6 ≤ 6n for all n ≥ 6.
or equal to 1, the function f(n) = 3n^2 + 4n + 1
This shows that for any value of n greater than
is less than or equal to 8 multiplied by g(n) =
or equal to 6, the function f(n) = 5n + 6 is less
n^2.
than or equal to 6 multiplied by g(n) = n.
Step 4: By choosing c = 8 and k = 1, we have
Step 4: By choosing c = 6 and k = 6, we have
shown that for all values of n greater than or
shown that for all values of n greater than or
equal to 1, the function f(n) = 3n^2 + 4n + 1 is
equal to 6, the function f(n) = 5n + 6 is
"smaller" or "equal to" 8 multiplied by g(n) =
"smaller" or "equal to" 6 multiplied by g(n) = n.
n^2.
In simpler terms, we have demonstrated that as n
In simpler terms, we have demonstrated that as n
gets larger, the function f(n) grows no faster than
gets larger, the function f(n) grows no faster than
g(n). This comparison helps us understand the
g(n). This comparison helps us understand the
growth rates of the two functions and how they
growth rates of the two functions and how they
relate to each other.
relate to each other.
BUT WE CAN ALSO DO THIS
Example :- F(n) = 5n + 6 and g(n) = n , show
that f(n) is O(g(n)) , to show that f(n) is
Introduction: We want to show that the function
O(g(n)) ?
f(n) = 5n + 6 is "smaller" or "equal to" the
Introduction: We want to show that the function function g(n) = n when n gets larger. In other
f(n) = 5n + 6 is "smaller" or "equal to" the words, we want to demonstrate that the growth
function g(n) = n when n gets larger. We'll do rate of f(n) is no faster than the growth rate of
this by finding some numbers that help us g(n) for sufficiently large values of n.
compare the two functions. Detailed Explanation:
Step 1: To show that f(n) is O(g(n)), we need to Step 1: To show that f(n) is O(g(n)), we need to
establish that for any value of n greater than or establish that for any value of n greater than or
equal to a certain number k, the function f(n) is equal to a certain number k, the function f(n) is
less than or equal to a constant c multiplied by less than or equal to a constant c multiplied by
g(n). g(n).
Step 2: Let's compare the individual terms and Step 2: Let's start by comparing the individual
inequalities involved. terms and inequalities involved.
5n ≤ 5n for all n ≥ 1: This inequality tells us 5n + 6 ≤ 11n for all n ≥ 1: This inequality tells
that for any value of n greater than or equal to us that for any value of n greater than or equal
1, the term 5n is always equal to 5n. It does not to 1, the term 5n + 6 is less than or equal to
change in relation to the value of n. 11n. We want to find a value of n (which we
6 ≤ n for all n ≥ 6: This inequality states that will call k) from which this inequality holds true.
for any value of n greater than or equal to 6, the Step 3: Simplifying the inequality, we get:
term 6 is less than or equal to n. This means
6 ≤ 6n for all n ≥ 1. Time Efficiency: The efficiency of sorting and
searching algorithms has a direct impact on the
This inequality indicates that for any value of n
time required to perform these operations. In
greater than or equal to 1, the constant term 6 is
scenarios involving large datasets or time-critical
less than or equal to 6n.
applications, the speed of sorting and searching
Step 4: By examining this inequality, we can see becomes crucial. Efficient algorithms significantly
that it is true for n = 1. When n = 1, we have 6 reduce the time complexity and execution time,
≤ 6. Therefore, we can say that for any value of enabling faster and more responsive applications.
n greater than or equal to 1, the function f(n) =
As computers continue to handle increasingly
5n + 6 is less than or equal to 11n.
large and complex datasets, the need for efficient
Step 5: By choosing c = 11 and k = 1, we have algorithms becomes even more critical. Optimized
shown that for all values of n greater than or sorting and searching algorithms can reduce
equal to 1, the function f(n) = 5n + 6 is processing time, improve system throughput, and
"smaller" or "equal to" 11 multiplied by g(n) = n. enhance overall performance.
Sorting and searching algorithms are fundamental By studying and understanding sorting and
concepts in computer science and are widely searching algorithms, individuals develop
studied for several reasons:
problem-solving skills and gain insights into
algorithmic thinking. These skills can be applied
Common and Useful Operations: Sorting and
to a wide range of computational problems,
searching are pervasive operations in various
enabling the development of efficient solutions
computer systems and applications. They are
and algorithms.
essential for managing and organizing data
effectively. Sorting algorithms arrange data Moreover, sorting and searching algorithms
elements in a specific order, such as ascending or provide a foundation for learning and analyzing
descending, making it easier to search and more advanced algorithms, data structures, and
retrieve information. Searching algorithms help optimization techniques. They form the basis for
locate specific items or determine their absence in exploring topics like graph algorithms, dynamic
a collection of data. These operations are widely programming, and algorithmic analysis.
used in applications such as data processing,
Sorting and searching algorithms are fundamental
database management, information retrieval, e-
concepts in computer science due to their
commerce, networking, and more.
widespread use, impact on time efficiency, and
Efficient sorting and searching algorithms play a contribution to algorithmic problem-solving skills.
crucial role in optimizing the performance of They are essential tools for organizing data,
these systems. They allow for faster access to improving system performance, and developing
information, improved data organization, and efficient solutions to computational problems.
enhanced user experience.
Simple Searching Algorithms algorithms should be used to reduce the search
time.
a) Linear Search: Sequential search, also known
as linear search, is a simple searching algorithm
that scans through a list of elements one by one
until the desired element is found or the entire
list is traversed. It starts from the beginning of
the list and compares each element with the
target key until a match is found. If a match is
found, the algorithm returns the index of the
element. If the entire list is traversed without
finding a match, the algorithm returns -1 to
indicate that the key is not present in the list.
Algorithm Steps:
Time Complexity: In the worst-case scenario, 3. The Linear Search function takes the list
when the target element is not present or located and the key as inputs and performs the
at the end of the list, linear search needs to linear search algorithm.
traverse the entire list, resulting in a time 4. Linear search works by checking each
complexity of O(n), where n is the number of element in the list one by one, starting
elements in the list. The time complexity grows from the first element, until the target
linearly with the size of the list. value is found or the end of the list is
Linear search is efficient for small lists or when reached.
the list is not sorted. However, for larger lists or 5. The function uses a loop to iterate
when the list is sorted, more efficient search through each element in the list.
algorithms like binary search or hash-based
6. It compares each element with the target groundwork for developing more efficient
value using the condition list[i] == key. searching techniques.
7. If a match is found, the index of that Binary search is a searching algorithm that is
element is stored in the variable index efficient for searching elements in a sorted list or
and the loop is exited using the break array. It follows a divide and conquer strategy to
statement. repeatedly divide the search space in half and
narrow down the search range until the desired
8. If the loop completes without finding a element is found or the search space is
match, the value of index remains -1, exhausted.
indicating that the target value is not
found in the list. Algorithm Steps:
9. Finally, the function returns the index 1. The binary search algorithm assumes that
value, either the index of the found the input list is sorted in ascending order.
element or -1 if the element is not found. If the list is not sorted, a pre-processing
step may be required to sort the data.
10.In the main function, we check the value 2. Start by defining the search range, which
of i returned by the Linear Search initially includes the entire sorted list.
function. If it's -1, we print a message 3. Compare the target key with the element
indicating that the value is not found. in the middle of the current search range.
Otherwise, we display the index position 4. If the middle element is equal to the
where the value is found. target key, the search terminates, and the
Linear Search is a simple and straightforward index of the element is returned.
search algorithm. It checks each element in the 5. If the target key is greater than the
list until it finds the desired value. While it middle element, eliminate the lower half
works for any type of list, it may not be the of the search range and continue
most efficient for large lists. Other search searching in the upper half.
algorithms like Binary Search are more efficient 6. If the target key is smaller than the
for sorted lists. middle element, eliminate the upper half
of the search range and continue
b) Binary Search: Binary search is a more searching in the lower half.
efficient searching algorithm applicable to sorted 7. Repeat steps 3-6 until either the target
lists. It follows a divide-and-conquer approach. It element is found or the search range is
compares the target key with the middle element reduced to a single element.
of the sorted list and recursively narrows down 8. If the search range is reduced to a single
the search range by half based on whether the element and it does not match the target
target key is smaller or larger than the middle key, the search terminates, and -1 is
element. This process continues until the target returned to indicate that the key is not
key is found or the search range is exhausted. present in the list.
Binary search is particularly efficient for large
lists as it eliminates half of the remaining Binary search leverages the fact that the list is
elements at each step, resulting in a logarithmic sorted to efficiently narrow down the search
time complexity. space by eliminating half of the remaining
elements in each iteration. This reduces the
These simple searching algorithms serve as the search time significantly compared to linear
foundation for more advanced and optimized search for large datasets.
search algorithms. By understanding and
analyzing these algorithms, we can gain insights Time Complexity: Binary search has a time
into the basic principles of searching and lay the complexity of O(log n), where n is the number of
elements in the sorted list. Since the search space After one step, you have around 500 pages
is halved in each iteration, the time complexity remaining. After two steps, you have around 250
grows logarithmically with the size of the input. pages, and so on. As you can see, the number of
This makes binary search highly efficient, remaining pages reduces quickly as you keep
particularly for large lists. dividing the search space in half. This is why
binary search is efficient. Even with a large
Binary search is like finding a word in a
dictionary of 1,000,000 pages, it would take
dictionary. Imagine you have a sorted dictionary,
around 20 steps to find the word because you are
and you want to find a specific word. In terms of
repeatedly cutting the remaining pages in half.
time complexity, it means that the time it takes
to find the word grows very slowly as the Binary search's time complexity of O(log n) means
dictionary size increases. For example, if you that the time it takes to find an element grows
have 1000 pages in the dictionary, it would take very slowly as the size of the input (number of
around 10 steps to find the word. If you have pages or items) increases. With each step, you
1,000,000 pages, it would take around 20 steps. roughly eliminate half of the remaining
The number of steps needed grows possibilities. This makes binary search a highly
logarithmically with the size of the input. With efficient algorithm for finding elements in sorted
each step, you cut the number of remaining lists or arrays, even for large datasets
pages in half. For example, if you start with 1000
In this code, we have implemented the Binary
pages, you roughly eliminate half of them with
Search algorithm. Here's how it works:
each step. In the first step, you have 1000 pages.
1. We have an array of numbers called list,
and we want to search for a specific
value called k (in this case, 54).
Bubble Sorting
Imagine you have a group of numbers that you
want to arrange in order from smallest to largest.
Bubble Sort helps you do that.
Selection Sorting
Defining it simply
CHAPTER 4
LINKED LISTS
Operations of a Circular Singly Linked List: • Start from the head of the linked
list and traverse through the nodes
1. Adding a node to the end of a Circular
until you reach the second-to-last
singly linked list:
node.
• Start from the head of the linked • Set the next reference of the
list and traverse through the nodes second-to-last node to point to the
until you reach the last node. head node, effectively removing
• Create a new node with the the last node from the list.
desired data. 5. Deleting a node from the front of a
• Set the next reference of the last Circular singly linked list:
node to point to the newly created
• Update the head pointer to point
node.
to the second node, effectively
• Set the next reference of the
removing the first node from the
newly created node to point to the
list.
head node, completing the circular
• Set the next reference of the last
loop.
node to point to the new head
2. Adding a node to the left of a specific
node.
data in a Circular singly linked list:
6. Deleting any node using the search data
• Start from the head of the linked from a Circular singly linked list:
list and traverse through the nodes
• Start from the head of the linked
until you find the node with the
list and traverse through the nodes
desired data.
until you find the node with the
• Create a new node with the
desired data.
desired data.
• Set the next reference of the
• Set the next reference of the new
previous node to point to the next
node to point to the found node.
node of the found node,
effectively removing the found • If there is room, place the new item on
node from the list. top of the stack.
7. Displaying the nodes from the Circular • If there is no room (the stack is full), give
singly linked list in a forward manner: an error message to indicate that the
stack is already at its maximum capacity.
• Start from the head node and
iterate through the nodes, printing In simple terms, pushing an item onto the stack
or processing the data of each is like adding a new book to the top of a stack
node until you reach the head of books. You can only add a book if there is
node again. space available. If the stack is full, you can't add
any more books, and an error message is
The circular nature of the linked list ensures that
displayed.
traversal can continue indefinitely in a loop,
allowing efficient access to any node in the list. Pop Operation: The pop operation removes the
item from the top of the stack and returns its
value. It follows these steps:
• Check if there is room in the stack to add • You have a stack of books placed one on
a new item. top of the other.
• When you perform the peek operation, applications. Each operation performed by the
you can take a quick glance at the book user can be stored on a stack, allowing them to
on the top of the stack. go back to previous states by undoing operations
• You can read its title, check its details, or or redoing them if needed.
simply see what it is without taking it out
Time complexity of a strack : The goal of a stack
from the stack.
is to perform all operations in constant time,
• However, the book remains in its place,
denoted as O(1). This means that regardless of
and the stack itself remains unchanged.
the number of elements in the stack, operations
In programming terms, the peek operation like push, pop, and peek should execute in
retrieves the value of the top element of the constant time, making stacks efficient and
stack without actually removing it. It allows you straightforward to implement.
to access the data stored in the top element
The goal of a stack is to make sure that all the
temporarily. This can be useful when you need to
operations you can do on a stack, like adding
examine the topmost element or check its value
elements (push), removing elements (pop), and
without altering the stack's overall structure.
looking at the top element (peek), take the same
These operations are the basic building blocks of amount of time no matter how many elements
a stack and allow you to add and remove items are in the stack. This is called constant time.
in a specific order. They provide a way to
Imagine you have a stack of books. When you
manage data in a stack efficiently and effectively.
want to add a new book to the stack, you simply
The key characteristics of a stack ADT are: place it on top. This operation is called push. It
doesn't matter if there are 10 books or 100 books
1. Elements are stored in a specific order:
in the stack; adding a new book will always take
Stacks store elements based on the order
the same amount of time.
of insertion, from the bottom to the top.
The last element inserted is always at the When you want to remove a book from the top
top of the stack. of the stack, you take the top book and remove
it. This operation is called pop. Again, whether
2. Elements are added to the top: New
you're removing the top book from a small stack
elements are added to the top of the
or a large stack, it will always take the same
stack, becoming the new top element.
amount of time.
3. Only the top element can be accessed or
Finally, when you want to peek at the top book
removed: Stacks allow access and removal
without removing it, you just take a quick look.
of only the top element. Other elements
This operation is called peek. It also takes the
in the stack remain inaccessible until the
same amount of time, regardless of how many
top element is removed.
books are in the stack.
What is the whole point of a stack
The important thing to understand is that
The main purpose of a stack is to provide a regardless of the size of the stack (the number of
simple and efficient way to manage data based books), the time it takes to perform these
on the Last-In, First-Out (LIFO) principle. Stacks operations remains the same.
are used to solve various problems and are
This constant time makes stacks efficient and
fundamental in computer science and
straightforward to work with because you don't
programming. Here are some key points about
have to worry about the size of the stack
the purpose and benefits of using a stack:
affecting the speed of the operations. It's a
Undo/Redo Operations: Stacks can be used to consistent and reliable way to manage data.
implement undo and redo functionality in
Array Implementation of a stack
In array implementation of a stack, we use a The array implementation of a stack provides a
one-dimensional array to represent the stack. The simple and efficient way to manage elements in a
array has a fixed size and each element of the Last-In, First-Out (LIFO) manner. However, it has
array represents an item in the stack. a fixed capacity, which means it can run into
overflow or underflow conditions if not managed
Here are the key components and operations
properly.
involved in the array implementation of a stack:
• Front = 1, Rear = 3
Array Implementation Of A Queue • Removed element: 10
• Queue: [_, 20, 30, 40, _]
In the array implementation of a queue, a fixed-
size array is used to store the elements of the Dequeue():
queue. The front and rear indices keep track of
• Front = 2, Rear = 3
the position of the front and rear elements,
• Removed element: 20
respectively.
• Queue: [_, _, 30, 40, _]
Example: Let's consider an example of a queue
Note that the elements are removed from the
implemented using an array with a fixed size of
front of the queue, and the front index is
5.
incremented to reflect the removal.
Initially, the queue is empty:
Enqueue and Dequeue Operations: The enqueue
Front = -1 (no elements in the queue) Rear = -1 operation adds elements to the rear of the queue,
(no elements in the queue) while the dequeue operation removes elements
from the front of the queue.
Enqueue Operation (Adding elements): When an
element is enqueued (added) to the queue, it is
inserted at the rear position and the rear index is
Enqueue Operation:
incremented.
• When an element is enqueued, it is
Let's enqueue the elements 10, 20, 30, and 40 in
inserted at the rear position (Rear index is
sequence:
incremented).
Enqueue(10): • If the queue is full (Rear index reaches
the maximum capacity), it means there is
• Front = 0, Rear = 0
no room to add more elements.
• Queue: [10, _, _, _, _]
Enqueue(20):
Dequeue Operation: effectively reusing the positions of dequeued
elements. This makes it a more efficient data
• When an element is dequeued, it is
structure in terms of space utilization.
removed from the front position (Front
index is incremented). Let's illustrate this with an example. Suppose we
• If the queue is empty (Front index equals have a circular queue with a capacity of 5,
Rear index), it means there are no represented as an array of size 5.
elements to remove.
Enqueue 10:
● front: 0
● rear: 0
Enqueue 20:
● front: 0
● rear: 1
● front: 0
Array Implementation of a Circular Queue ● rear: 3
A circular queue or ring buffer is an advanced Now, let's dequeue two elements
data structure that overcomes the space
Dequeue:
inefficiency problem in the basic array
implementation of a queue. Circular Queue: [_, 20, 30, 40, _]
1 2 3 4 5 6 7 8 9 10
The queue is not yet ordered by priority. Many
priority queue implementations ensure that the Now, we want to de-merge this queue into two
highest priority element is always at the front of separate queues: one containing the even numbers
the queue and the other containing the odd numbers.
1. Key Property:
3. Recursive Structure:
Properties and Operations of a Binary Search Insertion of data into a BST involves adding a
Tree: new node with a key (representing a value) to
the tree while maintaining the ordering property By following these steps, new nodes are inserted
of the BST. into the BST in a way that maintains the correct
order of elements. This allows for efficient
1. Starting at the root node:
searching and retrieval of data based on their
• If the tree is empty, the new node keys.
becomes the root.
• If the tree is not empty, compare
the key of the new node with the Traversing (visiting) in to BST
key of the current node.
• If the new node's key is
Traversing a binary search tree (BST) refers to the
less than the current
process of visiting each node in the tree in a
node's key, move to the
specific order. It allows us to explore and access
left sub tree.
the elements of the BST.
• If the new node's key is
greater than the current There are three common methods of traversing a
node's key, move to the BST :-
right sub tree.
1. Pre-order Traversal:
2. Repeat Step 1 until reaching an
appropriate position: • In this traversal, we visit the
nodes in a pre-defined order.
• If the current node has no left (or
• The process involves visiting the
right) child, insert the new node
parent node first, followed by
as its left (or right) child, based
recursively traversing the left
on the comparison made in the
subtree and then the right subtree.
previous step.
• This traversal can be useful for
• If the current node has a left (or
creating a copy of the tree or for
right) child, move to that child
performing prefix expression
and repeat Step 1.
evaluations.
3. Once the new node is inserted, the
ordering property of the BST is preserved:
2. Inorder Traversal:
• All keys in the left subtree of a
• In this traversal, we visit the
node are smaller than the key in
nodes in ascending order of their
that node.
keys.
• All keys in the right subtree of a
• The process involves recursively
node are larger than the key in
traversing the left subtree, visiting
that node.
the parent node, and then
recursively traversing the right sub
tree.
• This traversal is often used to
retrieve the elements in sorted
order from the BST.
3. Postorder Traversal:
The steps for deleting a node from a BST in • Let's delete the node with key 4.
simpler terms: • Since 4 is a leaf node, we can simply
remove it from the tree.
1. Start at the root node and search for the
• After deletion:
node to be deleted by comparing its key
with the keys of the nodes in the tree.
8
/ \
2 10 Directed and Undirected Graph
/ \
6 14
A directed graph, also known as a digraph, is a
/ \ \
4 7 15 type of graph where the edges have a specific
direction associated with them. Each edge
connects two vertices, with one vertex being the
Scenario 3 another option : Deleting a node with
source or starting point, and the other being the
two children
destination or endpoint. The direction of the
• Let's delete the node with key 3. edges indicates a one-way relationship or flow
• Node 3 has two children (2 and 6). between the vertices.
• We find the replacement node, which is
For example, in a directed graph representing a
the minimum element in its right subtree
social network, the vertices can represent
(4).
individuals, and the directed edges can represent
the connections or relationships between them. words, it has relatively few connections between
The direction of the edges would indicate the its vertices. In a sparse graph, the density of
direction of influence, friendship, or edges is low. The opposite of a sparse graph is a
communication between the individuals. dense graph.
In contrast, an undirected graph is a type of Sparse graphs are often encountered in real-world
graph where the edges do not have any specific scenarios where connections between entities are
direction. The edges in an undirected graph not extensive. For example, in a social network,
represent bidirectional relationships between where each user represents a vertex, a sparse
vertices. This means that the relationship between graph would indicate that users have fewer
two vertices is symmetric, and there is no connections or interactions with each other.
distinction between a source and a destination. Similarly, in a transportation network, a sparse
graph would imply that there are fewer direct
For example, in an undirected graph representing
routes between locations.
a road network, the vertices can represent
locations, and the undirected edges can represent One of the key advantages of sparse graphs is
the roads connecting the locations. The absence that they require less memory to store and less
of direction in the edges signifies that the roads computational effort to process, as there are
allow traffic in both directions. fewer edges to consider. Many graph algorithms
and data structures are designed with the
Directed and undirected graphs have different
assumption of a sparse graph.
properties and applications. Directed graphs are
useful for modeling systems with one-way Dense Graph: A dense graph is a type of graph
relationships, such as flows, dependencies, or where the number of edges is relatively larger
directed communication. Undirected graphs are compared to the number of vertices. In other
suitable for modeling symmetric relationships, words, it has a significant number of connections
such as connections, friendships, or undirected between its vertices. In a dense graph, the
communication. density of edges is high.