Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
32 views

Data Structure and Algorithm

The document discusses different types of data structures and algorithms. It defines data structures as how data is organized in computer memory and lists some common types like arrays, linked lists, queues, trees and graphs. It also defines algorithms as sets of instructions to solve problems and gives examples like sorting and searching. The document further explains linear and non-linear data structures as well as static and dynamic data structures.

Uploaded by

x21e0day
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views

Data Structure and Algorithm

The document discusses different types of data structures and algorithms. It defines data structures as how data is organized in computer memory and lists some common types like arrays, linked lists, queues, trees and graphs. It also defines algorithms as sets of instructions to solve problems and gives examples like sorting and searching. The document further explains linear and non-linear data structures as well as static and dynamic data structures.

Uploaded by

x21e0day
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 51

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.

Static and Dynamic data structures


Traversing :- This operation involves accessing
data elements with in a data structure. This is
A static data structure is one whose size and
the most frequently used operation associated
structure associated memory location is fixed at
with data structures for example , when
compile time. An array is an example of a static
traversing an array , each element in the array is
data structure , where the size of the array is accessed sequentially.
fixed at a compile time. Static data structures are
useful when the size of the data is known in
Searching :- This operation involves finding the salary, department, and address. These attributes
location of a data element with a given value or define the data that can be stored in the ADT.
finding the locations of all elements that satisfy
one or more conditions in the data structure. For The ADT also specifies the operations that can be
example , when searching for an element in a performed on the employee data, such as hiring,
binary search tree , the tree is traversed to find firing, retiring, updating employee information,
the element with the given value. and retrieving employee details. These operations
define how the data can be manipulated and
Sorting :- This operations involves arranging data
accessed.
elements in some logical order , such as
ascending order or descending order of student The ADT abstracts away non-relevant attributes,
names. For example , when sorting an array , the
such as weight, color, and height, as they are not
elements are rearranged in a specific order based
necessary for the specific purpose of managing
on a comparison function.
employees in the organization.
Merging :-This operations involves combining the It's important to note that the ADT is
data elements in two different sorted sets in to a
independent of any specific programming
single sorted set. For example , when merging
two sorted arrays , the elements are combined language or implementation. It provides a
and rearranged in a specific order. conceptual model for organizing and working
with data, and it can be implemented in various
Destroying :- This operation involves de allocating programming languages using different data
memory associated with the data structure when structures and algorithms.
it is no longer needed. For example , when
destroying a linked list , each node in the list is By defining and using ADTs, programmers can
de allocated , and the memory is freed. create reusable and modular code, as the ADT
provides a clear interface for interacting with the
data and hides the implementation details. This
Abstract Data Type (ADT) promotes code maintainability, code reuse, and
overall software design.
Abstract Data Types (ADTs) are a way of
organizing and defining data structures in
computer science. An ADT consists of two main Abstraction
components: the data to be stored and the
operations that can be performed on that data. It In the context of algorithms, abstraction refers to
provides a high-level specification of the data and the process of simplifying complex details and
operations, without specifying the implementation focusing on the essential aspects of a problem or
details or the programming language used. task. It involves hiding unnecessary details and
exposing only the relevant information that is
ADTs focus on the essential properties of the data
needed to understand and solve the problem.
and the operations, abstracting away unnecessary
details. This allows programmers to work with Abstraction allows us to think about problems
the data and perform operations on it without and algorithms at a higher level of
having to worry about the underlying understanding, without getting caught up in the
implementation. It promotes modular design and specific implementation details. It helps in
encapsulation, as the ADT provides a well-defined managing the complexity of algorithms and
interface for interacting with the data. making them more manageable and easier to
comprehend.
For example, let's consider the ADT "employees
of an organization". The relevant attributes of an When we abstract an algorithm, we focus on its
employee may include their name, ID, sex, age, overall purpose, functionality, and behavior,
rather than the specific steps or low-level
implementation details. We define the inputs, ensure that there is no confusion or
outputs, and operations of the algorithm, without multiple interpretations regarding the
getting into the specific algorithms or data intended operations.
structures used.
4. Finiteness: An algorithm is expected to
Abstraction enables us to design algorithms and terminate after a finite number of steps
analyze their efficiency without being tied to a for all possible inputs. In other words, if
specific programming language or platform. It we were to follow the instructions of an
allows us to generalize and create algorithms that algorithm, it should eventually reach a
can be applied to different scenarios or problem stopping point, ensuring that the process
domains. does not continue indefinitely.

By abstracting away unnecessary details, we can 5. Effectiveness: Every instruction in an


create more modular and reusable algorithms. We algorithm must be feasible and
can think about algorithms as black boxes, where achievable. It means that each step should
we know what the algorithm does and how to be practical and provide a meaningful
interact with it, without needing to understand contribution towards solving the problem
the internal workings. at hand.

6. Sequence: An algorithm consists of a


series of steps, and each step should have
CHAPTER TWO a clearly defined preceding and
succeeding step. The order in which
Algorithm and Algorithm Analysis instructions are executed matters, and the
algorithm should specify the correct
An algorithm can be defined as a finite set of
sequence to achieve the desired outcome.
instructions or a step-by-step procedure that takes
The first step, known as the start step,
raw data as input and processes it to produce
and the last step, known as the halt step,
refined data. It is a fundamental tool used to
must be explicitly identified.
solve well-specified computational problems.
Algorithms are utilized in various fields, including 7. Correctness: An algorithm should compute
computer science, mathematics, engineering, and the correct answer or solution for all
many other domains. possible valid inputs. It is crucial for an
algorithm to produce accurate and reliable
Properties of an Algorithm:
results, meeting the requirements and
1. Input: Every algorithm receives zero or expectations defined by the problem
more data items as input. These inputs statement.
can be provided externally, and they
8. Language Independence: An algorithm
serve as the initial information on which
should be independent of any specific
the algorithm operates.
programming language. It should be
2. Output: An algorithm should produce at defined in a way that focuses on the logic
least one output data item as a result of and operations rather than the syntax of a
its computations. The output represents particular programming language. This
the refined or processed data that is property allows the algorithm to be
obtained after executing the algorithm. implemented in different programming
languages as needed.
3. Definiteness: Each instruction or step
within an algorithm must be clear, 9. Completeness: An algorithm should
unambiguous, and have a precise provide a complete solution to the given
meaning. Ambiguity should be avoided to problem. It should consider all relevant
aspects and requirements of the problem results during its execution. Analyzing
domain, ensuring that the solution memory usage helps understand the
addresses all necessary components and algorithm's space complexity and its
constraints. impact on the overall performance and
efficiency.
10.Efficiency: An algorithm should aim to
solve the problem using the least amount 3. Communication-Bandwidth:Communication
of computational resources such as time bandwidth refers to the amount of data
and space. Efficiency is concerned with that needs to be transferred or
optimizing the algorithm's performance communicated between different
and minimizing the resources required for components of an algorithm or between
its execution. This involves reducing different processes in a distributed system.
unnecessary operations, optimizing data This resource is particularly relevant in
structures, and improving time complexity parallel and distributed computing
to achieve faster and more economical scenarios.
solutions.
Using actual clock-time as a consistent measure
These properties collectively define the of an algorithm's efficiency can be challenging
characteristics that an algorithm should possess in because it can vary based on various factors,
order to be effective, reliable, and practical in including specific processor speed, current
solving computational problems. processor load, and specific data used in a
particular run of the program. The input size and
properties, as well as the operating environment,
Analysis of Algorithm
can also influence the clock-time measurement.

Algorithm analysis involves determining the Instead of relying on absolute clock-time,

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

resource requirements of algorithms in a given they perform. By quantifying the number of

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

algorithm using scientific methods. efficiency relative to the input size.

To classify data structures and algorithms as good This approach allows for a more meaningful

or efficient, precise analysis is needed to evaluate understanding of how an algorithm's efficiency

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

most valuable resource in problem-solving complexities, researchers and developers can

domains. make informed decisions about algorithm


selection and design. This helps in creating
2. Memory Usage: Memory usage refers to efficient solutions, optimizing performance, and
the amount of storage space an algorithm understanding the trade-offs between different
requires to store data and intermediate algorithms when solving computational problems.
Complexity Analysis 2. Order of Magnitude Analysis: In the order
of magnitude analysis phase, the function
Complexity Analysis is a systematic study of the T(n) derived from the algorithm analysis
cost of computation, whether measured in time is further analyzed to determine the
units, operations performed, or storage space general complexity category to which it
required. It aims to provide a meaningful belongs. This analysis involves classifying
measure that allows for the comparison of the function into a complexity class such
algorithms independent of the underlying as constant time (O(1)), logarithmic time
operating platform. (O(log n)), linear time (O(n)), quadratic
time (O(n^2)), or other categories. The
There are two main aspects to consider in
order of magnitude analysis provides a
complexity analysis:
concise way to describe the algorithm's
1. Time Complexity: Time complexity focuses complexity without focusing on precise
on determining the approximate number constants or lower-order terms.
of operations or steps required to solve a
problem of a given size, typically denoted
as "n." It provides an estimate of the WHY DO WE NEED ORDER OF MAGNITUDE
computational time or running time ANALYSIS
required by an algorithm as the input size
Order of Magnitude Analysis is a crucial step in
increases. Time complexity analysis helps
complexity analysis that aims to classify an
understand how the algorithm's
algorithm's time or space complexity into a
performance scales with larger inputs.
general complexity category. Instead of focusing
2. Space Complexity: Space complexity on precise constants or lower-order terms, this
involves determining the approximate analysis provides a concise representation of an
amount of memory or storage space algorithm's complexity.
required to solve a problem of a given
The need for Order of Magnitude Analysis arises
size "n." It measures the memory usage
due to the following reasons:
or storage requirements of an algorithm
as the input size grows. Space complexity 1. Concise Complexity Description: Order of
analysis helps evaluate the efficiency and Magnitude Analysis allows for a compact
memory usage characteristics of an and high-level representation of an
algorithm. algorithm's complexity. Rather than
specifying exact counts or measurements,
Complexity analysis generally involves two
it provides a general description that
distinct phases:
captures the growth rate or behavior of
1. Algorithm Analysis: In the algorithm the algorithm as the input size increases.
analysis phase, the algorithm or data
2. Comparison of Algorithms: By classifying
structure is analyzed to produce a
algorithms into complexity categories,
function T(n). This function describes the
such as constant time (O(1)), logarithmic
algorithm's complexity by quantifying the
time (O(log n)), linear time (O(n)),
number of operations or memory accesses
quadratic time (O(n^2)), and others, it
performed as a function of the input size.
becomes easier to compare and contrast
The goal is to derive an equation or
different algorithms. It enables developers
formula that captures the algorithm's
and researchers to understand the relative
behavior in terms of its operations or
efficiency and performance characteristics
memory usage.
of algorithms without getting lost in
specific details.
3. Complexity Classes: Complexity classes for a comparative analysis between
provide a standardized framework for different algorithms, focusing on the
categorizing algorithms based on their relative number of operations performed.
growth rates. Each complexity class
2. Operations with Time 1: The execution of
represents a specific order of magnitude,
certain basic operations, such as
capturing the rate at which an algorithm's
assignment operations, single input/output
performance changes with respect to the
operations, boolean operations, arithmetic
input size. This classification aids in
operations, and function returns, is
understanding the scalability and
considered to take time 1. This rule
efficiency characteristics of algorithms and
simplifies the analysis by treating these
facilitates algorithm selection based on the
operations as equal units of time.
problem requirements.
3. Selection Statements: The running time of
Order of Magnitude Analysis is essential because
a selection statement (if, switch) is the
it allows for a simplified yet meaningful
sum of the time required for the
representation of an algorithm's complexity. It
condition evaluation and the maximum
enables us to compare algorithms, identify
running time among the individual clauses
dominant factors, and make informed decisions
within the selection. This rule accounts
regarding algorithm selection and optimization
for the control flow branching and the
strategies. By focusing on the general behavior of
potential execution of different code
an algorithm rather than specific details, this
paths.
analysis provides a higher-level perspective on the
efficiency and performance characteristics of 4. Loops: The running time of a loop is
algorithms. determined by multiplying the running
time of the statements inside the loop by
By performing complexity analysis, developers
the number of iterations. For nested
and researchers can gain insights into how
loops, the analysis is performed from the
algorithms scale with larger inputs and make
innermost loop to the outermost loop. It
informed decisions about algorithm selection. It
is assumed that each loop executes the
helps identify the most efficient algorithms for
maximum number of iterations possible,
specific problem sizes and enables comparisons
providing an upper bound estimation.
between different algorithms based on their time
and space requirements. Complexity analysis also 5. Function Calls: Running time of a function
provides a theoretical foundation for optimizing call is 1 for setup + the time for any
algorithmic performance and understanding trade- parameter calculations + the time required
offs between time and space complexities for the execution of the function body.

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
}

T(n) = 1 + 1 + 1 + 1 + 1 + [n+1 + n + int findSumOfPairs()


n ] + [n + n-1] {
int sum = 0;
int n = nums.size();
T(n) = 5n+5
for (int i = 0; i < n; i++) {
Example :- for (int j = 1; j < n; j++) {
sum += nums[i] + nums[j];
}
int sum(int n) }
{
int partial_sum = 0; return sum;
for (int i = 1; i <= n; i++) }
partial_sum = partial_sum + (i
* i * i); T(n) = 1 + 1 + [1 + n + 1 + n ] + n [1 + n + n
return partial_sum; + 3n-1 ]
}
Example :-
T(N) = 1 + [ 1 + n + 1 + n + 4n ] + 1
T(n) = 6n + 4
MEASURE OF TIMES
Example :- When we talk about algorithms, we want to
know how long they will take to run and how
int total(int n) efficient they are. To measure this, we use
{ different ways of looking at their performance.
int sum = 0;
for (int i = 1; i <= n; i++) • Average Case: This is like looking at how
sum = sum + 1; an algorithm performs on average or in
return sum; typical situations. It considers a range of
} different inputs and gives us an idea of
what to expect most of the time.
T(n) = 1 + [ 1 + n+ 1+ n + 2n] + 1 • Worst Case: This is the longest amount of
T(n) = 4 + 4n time an algorithm could possibly take. It
looks at the most challenging or difficult
Example :- inputs and gives us an upper limit on
how long the algorithm will run.
int count()
{ • Best Case: This is the shortest amount of
int k = 0; time an algorithm could take. It looks at
cout << "Enter an integer: ";
cin >> n;
the easiest or simplest inputs and shows one will take longer than n^2 time to
us the best possible scenario. finish.

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.

By analyzing the running time using Big-O


notation, we can understand how the algorithm
performs as the input size grows and make
informed decisions about algorithm selection.

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).

Step 2: Let's try a value for c. We'll choose c =


 1<=n for all n>=1 15. Now we need to check if 10n + 5 is less than
 n<=n2 for all n>=1 or equal to 15n for all values of n that are
 2n <=n! for all n>=4 greater than or equal to some number k.
 log2n<=n for all n>=2
 n<=nlog2n for all n>=2 Step 3: To find the value of k, we need to solve
the inequality 10n + 5 ≤ 15n. This inequality
Describing the above helps us determine the smallest value of n for
which the inequality is true. By solving the
 For any value of n greater than or equal inequality, we can find the value of k.
to 1, we can say that 1 is less than or
equal to n. This is true for all positive 10n + 5 ≤ 15n Subtracting 10n from both sides:
integers. 5 ≤ 5n Dividing both sides by 5: 1 ≤ 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

We want to show that the function f(n) = 10n +


The constants "k" and "c" are used to establish
5 is "smaller" or "equal to" the function g(n) = n
the relationship between two functions when
when n gets bigger. We'll do this by finding some
analyzing their growth rates using Big O notation.
numbers that help us compare the two functions.
Here's what each constant represents:
Step 1: To show that f(n) is O(g(n)), we need to
"k": The constant "k" represents the starting point
find some special numbers called constants c and
or threshold value from which the relationship
k. These numbers will help us prove that for any
between the functions holds true. It signifies the
value of n that is greater than or equal to k, the
input size from which we can say that one growth of the function. This term will determine
function's growth rate is no greater than the the upper limit of the growth rate.
other.
Choose a value for c: Once you have determined
"c": The constant "c" represents a scaling factor the upper bound, you can choose a suitable value
or a value that helps determine the upper bound for "c" that satisfies the inequality. This value
of the growth rate. It allows us to establish an should be greater than or equal to the upper
upper limit on the resources (time or space) bound to ensure that the inequality holds for all
required by an algorithm. values of n greater than or equal to k.

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:

Simplifying the inequality, we get: 5n + 6 ≤ 5n + n for all n ≥ 6.

3n^2 + 4n + 1 ≤ 8n^2 for all n ≥ 1. Simplifying the inequality, we get:

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.

In simpler terms, we have demonstrated that as n Algorithmic Problem-Solving: Sorting and


gets larger, the function f(n) grows no faster than searching algorithms serve as fundamental
g(n). This comparison helps us understand the building blocks for algorithmic problem-solving.
growth rates of the two functions and how they Many complex problems can be solved by
relate to each other. utilizing sorting and searching techniques as
subroutines or components. For example, tasks
such as finding the maximum or minimum value,
Chapter 3
identifying duplicate elements, or grouping
similar items often involve sorting and searching
Simple Sorting and Searching Algorithms
operations.

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:

1. Start at the top (beginning) of the list.


2. Compare the element at the current
position with the target key.
3. If the current element matches the target
key, the search terminates, and the index
of the element is returned.
4. If the current element does not match the
target key, move to the next element in
the list.
5. Repeat steps 2-4 until a match is found or
the end of the list is reached. In this code, we have implemented the Linear
6. If the entire list has been traversed Search algorithm. Here's how it works:
without finding a match, return -1 to
1. We have an array of numbers called list,
indicate that the key is not present in the
and we want to search for a specific
list.
value called k (in this case, 4).
Linear search is considered the most natural and
straightforward way of searching because it
2. The main function calls the Linear Search
function and passes the list and k as
follows a step-by-step approach. It is easy to
arguments. It stores the returned index in
understand and implement, making it suitable for
the variable i.
small lists or unsorted data.

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).

2. The main function calls the BinarySearch


function and passes the list and k as
arguments. It stores the returned index in
the variable i.

3. The BinarySearch function takes the list


and the key as inputs and performs the
binary search algorithm.

4. Binary search works by repeatedly


dividing the sorted list in half and
comparing the middle element with the
target value. Based on this comparison, it
either continues searching in the left or
right half of the list.

5. The function uses variables like top and


bottom to keep track of the boundaries of
the current search range.

6. It calculates the middle index as the


average of top and bottom and compares
the value at that index with the target
value.
7. If the target value is found, the variable process. Keep doing this until you reach
found is set to 1. If not, it adjusts the the end of the list.
search range by updating top or bottom
4. Now, the biggest number in the list will
accordingly.
have "bubbled" up to the end. It's like
8. The loop continues until the target value the biggest bubble floating to the top.
is found or the search range is exhausted 5. Repeat steps 2 to 4 for the remaining
(when top becomes less than bottom). unsorted numbers, but this time excluding
9. Finally, the function returns the index of the last number you already sorted (since
the target value. If the value is not found, it's already in the correct position).
it returns -1. 6. Keep repeating steps 2 to 5 until the
10.In the main function, we check the value entire list is sorted, meaning there are no
of i returned by the BinarySearch more swaps needed.
function. If it's -1, we print a message By repeating the process of comparing and
indicating that the value is not found. swapping adjacent numbers, Bubble Sort
Otherwise, we display the index position gradually moves the bigger numbers towards the
where the value is found. end of the list. This sorting technique is called
Binary Search is an efficient search algorithm for "Bubble Sort" because the larger numbers bubble
sorted lists. It divides the search range in half up to their correct positions, just like bubbles
with each comparison, making it much faster rising to the surface of water.
than linear search for large sorted lists. Bubble Sort is a simple and easy-to-understand
sorting algorithm. However, it may not be the
most efficient for large lists because it requires
Sorting Algorithms
multiple passes and comparisons. There are other
sorting algorithms, like Merge Sort or Quick Sort,
Sorting is the process of arranging a list of items
or elements in a specific order, either increasing that are more efficient for bigger sets of numbers.
or decreasing. Sorting is a fundamental problem But for smaller lists or learning purposes, Bubble
in computer science because it helps us find and Sort is a good starting point to understand the
access data more efficiently. concept of sorting.

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.

Here's how it works:

1. Start with an unsorted list of numbers.


Imagine you have a group of numbers lined up.
2. Compare the first two numbers in the list. When we do the Bubble Sort, we look at two
If the first number is bigger than the numbers at a time. If the first number is bigger
second number, you swap their positions. than the second number, we swap their positions,
If not, you leave them as they are. just like swapping places in a game. We keep
doing this for all the numbers in the line.
3. Move to the next pair of numbers and
repeat the comparison and swapping Each time we do a swap, it's like the bigger
number is floating up to the top of the line, just
like a big bubble rising to the surface. This the algorithm grows as the size of the input (the
happens again and again until the biggest number array) increases.
reaches the very end of the line.
Bubble sort works by repeatedly comparing
So, the idea is that as we go through the adjacent elements and swapping them if they are
numbers and do these swaps, the biggest number in the wrong order. In the worst case scenario,
keeps moving up, just like bubbles floating to the where the input array is in reverse order, bubble
top. By the end, all the numbers will be sorted sort needs to perform n-1 passes to fully sort the
from smallest to largest. array. On each pass, it compares adjacent
elements and swaps them if necessary.
Bubble Sort is like playing a game where you
compare two numbers, and if they're in the To determine the time complexity, we consider
wrong order, you switch their places. This the number of comparisons and swaps that occur.
process repeats until all the numbers are sorted, On each pass, bubble sort compares n-1 pairs of
with the biggest one "bubbling" up to the end. adjacent elements. Therefore, in the worst case, it
performs (n-1) + (n-2) + ... + 1 comparisons,
which can be simplified to (n*(n-1))/2 or
approximately (n^2)/2 comparisons.

Since the number of comparisons is proportional


to n^2, we can say that the time complexity of
bubble sort is O(n^2). This means that as the
size of the input array doubles, the number of
operations required increases by a factor of four.

Bubble sort is generally considered inefficient for


large arrays or datasets, as its time complexity
grows significantly with the input size. Other
sorting algorithms, such as merge sort or
quicksort, offer better time complexity and
performance in most cases.

Selection Sorting

Selection sort is a simple sorting algorithm that


works by repeatedly finding the minimum
element from the unsorted part of the list and
placing it at the beginning of the sorted part. It
divides the list into two subarrays: the sorted
subarray and the unsorted subarray.

To understand selection sort, let's consider a list


of numbers that we want to sort in ascending
order :-
TIME COMPLEXITY OF BUBBLE SORTING
[5, 2, 9, 1, 3]
The time complexity of bubble sort is O(n^2),
where n is the number of elements in the array Here's how the algorithm works step by step :-
being sorted. In simpler terms, the time 1. Start with the initial list [5, 2, 9, 1, 3].
complexity represents how the execution time of 2. Find the minimum element in the
unsorted subarray (2 in this case).
3. Swap the minimum element with the first finding the tiniest toy in a collection and
element of the unsorted subarray, putting it at the beginning.
resulting in [2, 5, 9, 1, 3]. Now, the first
3. Now, look for the second smallest number
element is part of the sorted subarray,
from the remaining unsorted numbers and
and the rest are in the unsorted subarray.
put it in the second position. It's like
4. Move to the next position of the unsorted
finding the next tiniest toy and placing it
subarray and repeat steps 2 and 3.
right after the first one.
5. Find the minimum element in the
remaining unsorted subarray (1 in this 4. Keep repeating this process, finding the
case). next smallest number and putting it in its
6. Swap the minimum element with the first correct position, until the entire list is
element of the unsorted subarray, sorted.
resulting in [1, 5, 9, 2, 3]. Now, the first
5. By the end, the numbers will be arranged
two elements are part of the sorted
in order from smallest to largest, just like
subarray.
a line of toys sorted from tiniest to
7. Repeat steps 4-6 until the entire list is
largest.
sorted.
Selection Sort works by repeatedly finding the
Selection sort works by continuously finding the
smallest number from the unsorted part of the
minimum element from the unsorted subarray
list and swapping it with the element in the
and placing it at the beginning of the sorted
correct position. It selects the smallest number
subarray. This process continues until the entire
and puts it in its rightful place, hence the name
list is sorted.
"Selection Sort."
Selection sort has a time complexity of O(n^2),
Selection Sort is a simple sorting algorithm that
where n is the number of elements in the list. It
is easy to understand. However, it may not be
requires n-1 passes through the list, each time
the most efficient for large lists because it
finding the minimum element from the remaining
requires multiple comparisons and swaps. There
unsorted sub array. However, unlike other sorting
are other sorting algorithms, like Merge Sort or
algorithms, selection sort always performs the
Quick Sort, that are more efficient for larger sets
same number of comparisons regardless of the
of numbers. But for smaller lists or learning
initial order of the elements.
purposes, Selection Sort is a good starting point
While selection sort is not the most efficient to understand the concept of sorting.
sorting algorithm for large lists, it has the
advantage of simplicity and ease of
implementation. It is suitable for small lists or
situations where the number of elements is
limited.

Defining it simply

Imagine you have a group of numbers that you


want to arrange in order from smallest to largest.
Selection Sort helps you do that.

Here's how it works:

1. Start with an unsorted list of numbers.

2. Find the smallest number in the list and


put it in the first position. This is like
Insertion sorting

Imagine you have a bunch of playing cards that


are all mixed up, and you want to put them in
order from smallest to largest. Insertion Sort
helps you do that.

Here's how it works:

1. Start with an empty hand and spread out


the cards facing down on the table.

2. Pick up one card with your empty hand.


This card represents the first element of
the sorted part of the cards.

3. Look at the next card on the table. If it's


smaller than the card in your hand, place
it to the left of that card. If it's bigger,
put it to the right.

4. Now, pick up the third card and compare


it with the two cards in your hand. If it's
smaller than the left card, move the left
card to the right, creating space for the
new card. If it's between the two cards,
insert it in between. If it's larger than
both cards, put it to the right.

5. Keep doing this process for each new


card, comparing it with the cards in your
hand and finding its correct spot. If a
card is smaller than the left card, keep
moving the left card to the right until
you find the right position.

6. Repeat this step for all the remaining


cards on the table, one by one.

7. By the end, you will have sorted all the


cards from smallest to largest, just like a
neat deck of cards.

Insertion Sort works by building a sorted part of


the cards gradually. You start with one card and
compare each new card with the sorted cards in
your hand. By finding the right spot for each
new card, you create a sorted portion of the
cards.
Insertion Sort is like organizing cards on a table,
moving them around and finding the correct
place for each card until they are all in order.
It's a simple and intuitive sorting algorithm, but
it might take more time for larger sets of cards.

CHAPTER 4

LINKED LISTS

A linked list is a data structure used in


algorithms to store and organize data. It consists
of a sequence of nodes, where each node
contains two components: a data element and a
reference (or link) to the next node in the
sequence.

Unlike arrays, linked lists do not require


contiguous memory allocation. Each node can be
allocated separately, and the references between
nodes allow us to traverse the list efficiently.

Here are some key points about linked lists:

1. Structure: Each node in a linked list


contains two parts: the data element,
which stores the actual value or
information, and a reference (or link) to
the next node in the sequence.

2. Head: The first node in the linked list is


called the head. It serves as the starting
point for accessing the list.

3. Tail: The last node in the linked list is


called the tail. It marks the end of the
list and typically contains a reference to
null or a special value indicating the end.
4. Connections: Nodes in a linked list are information, and a reference to the next
connected through their references. The node in the list.
link in each node points to the next node • Head: The first node in the linked list is
in the sequence, forming a chain-like called the head. It serves as the starting
structure. point for accessing the list.
• Tail: The last node in the linked list is
5. Dynamic Size: Linked lists can grow or
called the tail. It marks the end of the
shrink dynamically by adding or removing
list and typically contains a reference to
nodes. Unlike arrays, which have a fixed
null or a special value indicating the end.
size, linked lists can be modified more
• Connections: Nodes in a singly linked list
easily.
are connected through their next
6. Types of Linked Lists: There are different references. The next reference in each
types of linked lists, such as singly linked node points to the next node in the
lists, doubly linked lists, and circular sequence.
linked lists. Each type has its own • Traversal: To traverse a singly linked list,
variations in terms of node connections we start at the head and follow the next
and traversal. references until we reach the end (tail) or
the reference becomes null.
7. Advantages and Disadvantages: Linked
• Dynamic Size: Singly linked lists can grow
lists offer advantages such as efficient
or shrink dynamically by adding or
insertion and deletion at any position,
removing nodes. They do not require
flexibility in size, and dynamic memory
contiguous memory allocation like arrays,
allocation. However, they have slower
allowing for efficient insertion and
access times compared to arrays and
deletion at any position.
require additional memory for storing the
links.

In algorithms, linked lists are commonly used in


situations where frequent insertions or deletions
are required, or when the size of the data is
unknown or can change over time. They provide
a flexible and efficient way to store and
manipulate data elements in a sequence.

Operation of single linked list


TYPES OF LINKED LIST
1) Adding a node to the end of a singly linked
A singly linked list is a type of linked list where
list:
each node contains a data element and a
reference (or link) to the next node in the • Start from the head of the linked list and
sequence. It is called "singly" because it only traverse through the nodes until you
allows traversal in one direction, from the head reach the last node (the node with the
to the tail. next reference as NULL).
• Create a new node with the desired data.
Key Points about Singly Linked List:
• Set the next reference of the last node to
• Structure: Each node in a singly linked point to the newly created node.
list has two components: the data • Update the tail pointer to point to the
element, which holds the value or newly added node if necessary.
2. Adding a node to the left of a specific data in • Start from the head of the linked list and
a singly linked list: traverse through the nodes until you find
the node with the desired data.
• Start from the head of the linked list and
• Update the next reference of the previous
traverse through the nodes until you find
node to point to the next node of the
the node with the desired data.
found node, effectively removing the
• Create a new node with the desired data.
found node from the list.
• Set the next reference of the newly
created node to point to the node found
7. Displaying the nodes from the singly linked list
in the previous step.
in a forward manner:
• Update the next reference of the node
before the found node to point to the • Start from the head of the linked list and
newly created node. traverse through the nodes one by one.
• Print or process the data of each node as
3. Adding a node to the right of a specific data you go.
in a singly linked list:
These operations allow you to manipulate and
• Start from the head of the linked list and
work with the elements of a singly linked list,
traverse through the nodes until you find
such as adding or removing nodes at different
the node with the desired data.
positions or displaying the list in a forward
• Create a new node with the desired data.
manner. Each operation involves updating the
• Set the next reference of the newly
next references of the nodes to maintain the
created node to point to the next node of
integrity and connectivity of the linked list.
the found node.
• Set the next reference of the found node
to point to the newly created node. Double Linked List :- A doubly linked list is a
type of linked list where each node contains not
only a reference to the next node (successor), but
4. Deleting a node from the end of a singly
also a reference to the previous node
linked list: (predecessor). This additional reference allows for
• Start from the head of the linked list and easy traversal in both forward and backward
directions.
traverse through the nodes until you
reach the second-to-last node.
• Update the next reference of the second-
to-last node to NULL, effectively removing
the last node from the list.
• Update the tail pointer to point to the
new last node if necessary.

5. Deleting a node from the front of a singly


linked list:

• Update the head pointer to point to the


Here's a brief description of the operations you
second node, effectively removing the first
mentioned for a doubly linked list:
node from the list.
1. Adding a node to the end of a doubly
6. Deleting any node using the search data from linked list:
a singly linked list: • Start from the head of the linked
list and traverse through the nodes
until you reach the last node.
• Create a new node with the • Start from the head of the linked
desired data. list and traverse through the nodes
• Set the next reference of the last until you find the node with the
node to point to the newly created desired data.
node. • Create a new node with the
• Set the previous reference of the desired data.
newly created node to point to the • Set the next reference of the new
last node. node to point to the next node of
• Update the tail pointer to point to the found node.
the newly added node if • Set the previous reference of the
necessary. new node to point to the found
2. Adding a node to the front of a doubly node.
linked list: • Set the next reference of the
found node to point to the new
• Create a new node with the
node.
desired data.
• Set the previous reference of the
• Set the next reference of the new
next node to point to the new
node to point to the current head
node.
node.
5. Deleting a node from the end of a doubly
• Set the previous reference of the
linked list:
new node to NULL.
• Set the previous reference of the • Start from the tail of the linked
current head node to point to the list and traverse through the nodes
new node. until you reach the second-to-last
• Update the head pointer to point node.
to the new node. • Set the next reference of the
3. Adding a node to the left of a specific second-to-last node to NULL,
data in a doubly linked list: effectively removing the last node
from the list.
• Start from the head of the linked
• Update the tail pointer to point to
list and traverse through the nodes
the new last node if necessary.
until you find the node with the
6. Deleting a node from the front of a
desired data.
doubly linked list:
• Create a new node with the
desired data. • Update the head pointer to point
• Set the next reference of the new to the second node, effectively
node to point to the found node. removing the first node from the
• Set the previous reference of the list.
new node to point to the previous • Set the previous reference of the
node of the found node. new head node to NULL.
• Set the next reference of the 7. Deleting any node using the search data
previous node to point to the new from a doubly linked list:
node.
• Start from the head of the linked
• Set the previous reference of the
list and traverse through the nodes
found node to point to the new
until you find the node with the
node.
desired data.
4. Adding a node to the right of a specific
• Set the next reference of the
data in a doubly linked list:
previous node to point to the next
node of the found node, • Set the next reference of the
effectively removing the found previous node of the found node
node from the list. to point to the new node.
• Set the previous reference of the 3. Adding a node to the right of a specific
next node to point to the previous data in a Circular singly linked list:
node of the found node.
• Start from the head of the linked
list and traverse through the nodes
Circular Linked Lists :- A circular singly linked until you find the node with the
list is a type of linked list in which the last node desired data.
of the list points back to the first node, forming
• Create a new node with the
a circular loop. This allows for continuous
desired data.
traversal from any node to any other node in the
list. • Set the next reference of the new
node to point to the next node of
the found node.
• Set the next reference of the
found node to point to the new
node.
4. Deleting a node from the end of a
Circular singly linked list:

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:

CHAPTER 5 • Check if the stack is not empty (contains


at least one item).
STACKS AND QUEUES • If the stack is not empty, remove the
item from the top of the stack.
• Return the value of the removed item.
A stack is a linear data structure that follows the • If the stack is empty, give an error
Last-In, First-Out (LIFO) principle. It can be message to indicate that there are no
visualized as a stack of plates, where new plates items in the stack.
are added to the top and removed from the top.
In simpler terms, popping an item from the stack
In a stack, all insertions and deletions occur at
is like taking the top book from a stack of books.
one end, known as the top of the stack.
You can only remove a book if there are books
Basic Stack Operations: in the stack. If the stack is empty, there are no
books to remove, and an error message is
1. Push: The push operation adds an element
displayed.
to the top of the stack. It places the new
element on top of the existing elements, The push operation adds an item to the stack,
becoming the new top of the stack. while the pop operation removes and returns the
top item. Both operations ensure that the stack
2. Pop: The pop operation removes and
remains in the Last-In, First-Out (LIFO) order.
returns the element from the top of the
stack. It retrieves the topmost element Imagine you have a stack of books, where each
and removes it from the stack, exposing book represents an element in the stack. The
the element beneath it as the new top. topmost book is the one you can easily see and
access. Now, let's understand the peek operation:
3. Peek: The peek operation retrieves the top
element from the stack without removing Peek: The peek operation allows you to take a
it. It provides access to the topmost look at the topmost book in the stack without
element without modifying the stack. removing it. It gives you access to the element at
the top of the stack.
Push Operation: The push operation adds an item
to the top of the stack. It follows these steps: Think of it like this:

• 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:

1. Array: We create an array with a fixed


Application of stacks
size to hold the elements of the stack. Evaluation of Algebric Expressions ?
The size of the array determines the
maximum capacity of the stack. Infix, prefix, and postfix are different notations or
ways of representing mathematical expressions.
2. Top: We use a variable called "top" to
They determine the order of operations and the
keepOperations of a Circular Singly Linked
placement of operators and operands within the
List: track of the index of the topmost
expression.
element in the stack. Initially, when the
stack is empty, the top is set to -1. Infix Notation: This is the most common way of
writing mathematical expressions, where operators
3. Push Operation: To add an element to the
are placed between the operands. For example,
stack (push operation), we increment the
"2 + 3" is an infix expression. Infix notation can
value of the top variable and then assign
sometimes be ambiguous and requires the use of
the new element to the array at the index
parentheses to indicate the desired order of
specified by the top.
operations.
4. Pop Operation: To remove an element
Prefix Notation: In prefix notation, also known as
from the stack (pop operation), we first
Polish notation, operators are placed before their
check if the stack is empty by verifying if
operands. For example, "+ 2 3" is a prefix
the top is -1. If the stack is not empty,
expression. This notation eliminates the need for
we retrieve the element from the array at
parentheses as the order of operations is
the index specified by the top, then
determined solely by the position of the
decrement the value of the top variable.
operators.
5. Peek Operation: To get the top element of
Postfix Notation: In postfix notation, also known
the stack without removing it (peek
as Reverse Polish notation (RPN), operators are
operation), we simply return the element
placed after their operands. For example, "2 3 +"
from the array at the index specified by
is a postfix expression. Similar to prefix notation,
the top.
postfix notation eliminates the need for
6. Overflow Condition: If we attempt to push parentheses as the order of operations is
an element into a full stack (i.e., the determined by the position of the operators.
array is already at its maximum capacity),
The conversion from infix to postfix notation is
it results in an overflow condition,
done to simplify the evaluation of mathematical
indicating that the stack is full and
expressions. Postfix notation has several
cannot accept more elements.
advantages over infix notation:
7. Underflow Condition: If we attempt to
1. Elimination of Ambiguity: Postfix notation
pop an element from an empty stack (i.e.,
removes the need for parentheses to
the top is -1), it results in an underflow
indicate the order of operations. The
condition, indicating that the stack is
position of the operators inherently
empty and there are no elements to
remove.
determines the order, making the than the current operator, we
expression unambiguous. push the current operator onto the
stack.
2. Easy Evaluation: Postfix notation lends
• If the top operator has higher or
itself well to evaluation using a stack or
equal precedence to the current
other data structures. The operands are
operator, we pop the top operator
pushed onto the stack, and when an
from the stack and add it to the
operator is encountered, the necessary
postfix notation. Then we repeat
operands are popped from the stack and
this step until the top operator has
the result is pushed back. This simplifies
lower precedence or the stack
the evaluation process.
becomes empty. After that, we
3. Operator Precedence: In postfix notation, push the current operator onto the
the precedence of operators is evident stack.
from their position. There is no need to 5. If we encounter a left parenthesis '(', we
rely on operator precedence rules as push it onto the stack.
required in infix notation. 6. If we encounter a right parenthesis ')', we
repeatedly pop operators from the stack
By converting infix expressions to postfix
and add them to the postfix notation until
notation, we can easily and efficiently evaluate
we encounter the matching left
the expressions, simplify the computation, and
parenthesis '(' on top of the stack. Then
remove ambiguity. It provides a more structured
we pop and discard the left parenthesis.
and unambiguous representation of mathematical
7. After scanning the entire infix expression,
expressions, facilitating their interpretation and
we pop any remaining operators from the
calculation.
stack and add them to the postfix
notation.
Conversion from infix to postifx
The resulting postfix notation is the converted
conversion. In infix notation, the operators are form of the original infix expression. It eliminates
placed between the operands, while in postfix the need for parentheses to indicate the order of
notation, the operators come after the operands. operations, as the postfix notation inherently
Converting an infix expression to postfix notation represents the order.
helps in evaluating the expression in a more
organized way.

Here's how the conversion works:

1. We start with an empty stack and an


empty postfix notation.
2. We scan the infix expression from left to
right, one character at a time.
3. If we encounter an operand (such as A,
B, C), we directly add it to the postfix
notation.
4. If we encounter an operator, we follow
certain rules to decide whether to push it
onto the stack or add it to the postfix
notation:
• If the stack is empty or the top
operator has lower precedence
Example :- Basic Properties of a Queue:

1. Ordering: Elements are stored in the order


they were added, with new elements
being added to the end (back) of the
queue.

2. Operations: The key operations performed


on a queue are:

• Offer or Enqueue: This operation


adds an element to the back of
the queue. It inserts a new
element at the end, following the
FIFO order.

• Remove or Dequeue: This


operation removes and returns the
element at the front of the queue.
It retrieves the element that has
been waiting in the queue the
longest.
Example :-
• Peek (just for displaying purpose):
This operation returns the element
at the front of the queue without
removing it. It allows you to
examine the element that will be
removed next.

• Other Operations: Additional


operations commonly available for
queues include checking if the
queue is empty, getting the size of
the queue, and clearing all
elements from the queue.

Goal: The goal of a queue is to ensure that every


operation performed on the queue executes in
constant time, denoted as O(1). This means that
regardless of the number of elements in the
Queue Abstract Data Type queue, operations like offer, remove, peek,
isEmpty, size, and clear should be efficient and
A queue is a data structure that follows the First-
have consistent performance.
In, First-Out (FIFO) principle. It behaves like a
line of people waiting for a bank teller or Queue Features:
customers waiting in a queue. In a queue,
1. Ordering: A queue maintains the order in
elements are stored in the order they were
which elements were added. New
inserted, and the first element inserted is the first
elements are added to the end of the
one to be removed.
queue, while removal happens from the
front.
2. Operations Efficiency: All the fundamental • Front = 0, Rear = 1
operations of a queue, such as offer, • Queue: [10, 20, _, _, _]
remove, and peek, have a constant time
Enqueue(30):
complexity of O(1). This makes queues
efficient for managing and processing • Front = 0, Rear = 2
elements in a specific order. • Queue: [10, 20, 30, _, _]

The Queue Operations: When we think about a Enqueue(40):


queue, we can imagine a line of people waiting
• Front = 0, Rear = 3
for a bank teller, where the first person to arrive
• Queue: [10, 20, 30, 40, _]
is the first to be served. The queue has two main
reference points: Note that the front index remains at -1 since
there are no elements removed yet.
• Front: The front of the queue represents
the element that has been waiting in the Dequeue Operation (Removing elements): When
queue the longest. It is the element that an element is dequeued (removed) from the
will be removed next. queue, it is done from the front position, and the
front index is incremented.
• Rear: The rear of the queue represents the
end of the queue, where new elements Let's dequeue two elements from the queue:
are added. Dequeue():

• 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.

In the array implementation of a queue, the front


Circular Queue: [_, _, _, _, _]
and rear indices are used to keep track of the
positions for enqueue and dequeue operations, ● front: -1
ensuring that the elements are added to the rear
● rear: -1
and removed from the front in the correct order.
Now, let's enqueue three elements 10, 20, and
30.

Enqueue 10:

Circular Queue: [10, _, _, _, _]

● front: 0

● rear: 0

Enqueue 20:

Circular Queue: [10, 20, _, _, _]

● front: 0

● rear: 1

Note: This basic implementation of a queue using


an array does not make efficient use of space Enqueue 30:
because dequeued spaces are not reused. To
Circular Queue: [10, 20, 30, _, _]
overcome this, a more advanced data structure
called a circular queue or ring buffer can be ● front: 0
used. In a circular queue, the rear index wraps
● rear: 2
around to the beginning of the array when it
reaches the end, effectively reusing the spaces of Enqueue 40:
dequeued elements. Circular Queue: [10, 20, 30, 40, _]

● 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, _]

In a circular queue, the rear index wraps around ● front: 1


to the start of the array when it reaches the end,
● rear: 3 Overflow:
Queue overflow happens when we try to enqueue
an element into a queue that is already full. In
Dequeue:
the context of a circular queue, the queue is
Circular Queue: [_, _, 30, 40, _] considered full when the next increment of rear
(when incremented and modulus applied) is equal
● front: 2
to front.
● rear: 3
If this condition is true, it means that the queue
Now, let's enqueue one element 50. is full and any attempt to add more elements
into the queue would lead to queue overflow, as
Enqueue 50:
it would overwrite the existing elements.
Circular Queue: [_, _, 30, 40, 50]
Let's consider the following example:
● front: 2
Circular Queue: [60, 70, 30, 40, 50]
● rear: 4 ● front: 2
● rear: 1
As you can see, when rear index reached the
end of the array, it wrapped around to the
If we try to enqueue another element, say 80, in
beginning of the array. Now, if we enqueue
this case, it would lead to an overflow because
another element, say 60, it will be placed at the
the queue is already full. The new element would
first position, reusing the space of the dequeued
overwrite the current front element (30), which
elements.
would lead to loss of data and incorrect
Enqueue 60: operation of the queue.
Circular Queue: [60, _, 30, 40, 50]
Underflow:
● front: 2
Queue underflow happens when we try to
● rare : 1 dequeue an element from a queue that is already
empty. In the context of a circular queue, the
queue is considered empty when front is equal to
It's important to note that in the array rear.
implementation, when the rear index reaches the
If front == rear, it means that the queue is
end of the array, the next enqueue operation
empty and any attempt to remove an element
would overwrite the first elements of the queue if
from the queue would lead to queue underflow,
they have been dequeued. This is known as
as there are no elements to remove.
queue overflow. Similarly, if the front and rear
indices are equal, any dequeue operation would
result in underflow, indicating that the queue is
empty.
Let's consider the following example:
Queue Overflow and Underflow
Circular Queue: [_, _, _, _, _]
In the context of a circular queue or a ring ● front: -1
buffer, overflow and underflow conditions are ● rear: -1
important to manage. They're used to handle
scenarios when we try to add an element to a If we try to dequeue an element in this case, it
full queue (overflow) or try to remove an element would lead to an underflow because the queue is
from an empty queue (underflow). already empty.
To avoid these conditions, it's important to check number represents the severity of their condition
whether the queue is full before performing an (with a higher number indicating a more severe
enqueue operation and to check whether the condition) :-
queue is empty before performing a dequeue
operation.
Patient 1 Patient 2 Patient 3 Patient 4 Patient 5
3 5 1 4 2

In this priority queue, Patient2 with condition


severity 5 will be served first, followed by
Patient4 with severity 4, then Patient1 with
severity 3, and so on.

Operations on Priority Queue

1. Enqueue: This operation inserts an


element into the priority queue.

2. Dequeue: This operation removes the


highest priority element from the priority
queue.

3. Peek or Top: This operation returns the


PRIORITY QUEUE
highest priority element in the priority
queue without deleting it from the queue.
A priority queue is a special type of queue in
which each element is associated with a priority 4. IsEmpty: This operation checks if the
and is served according to its priority. If elements priority queue is empty.
with equal priorities are enqueued, they are
served according to their ordering in the queue. 5. IsFull: This operation checks if the
priority queue is full.
Generally, the value of the element itself is
considered for assigning the priority. For
Example :-
example, The element with the highest value is
considered the highest priority element. However, Let's consider a priority queue in the context of a
in other cases, we can assume the element with task management system. Each task is assigned a
the lowest value as the highest priority element priority: the higher the number, the higher the
and the element with the highest value as the priority. The tasks with higher priority need to
lowest priority element. We can also set priorities be executed before the tasks with lower priority.
according to our needs. The priority queue can
Consider the following initial queue:
be implemented using an array, a linked list, a
heap data structure, or a binary search tree.

Example of a priority queue Task 1 Task 2 Task 3 Task 4 Task 5


2 5 1 3 4
Real-world example of a priority queue could be
a hospital's emergency department. Patients with The number associated with each task represents
more severe conditions are prioritized over its priority
patients with less severe conditions.
Enqueue Operation :- Let’s enqueue a new tasks ,
Consider the following priority queue of patients “Task 6” with a priority of “3”.
waiting in the emergency department. The
Let's take an example where we de-merge a
Enqueue (Task6, 3): queue into two separate queues: one queue
containing the even numbers and the other queue
Task 1 Task 2 Task 3 Task 4 Task 5 Task 6 containing the odd numbers.
2 5 1 3 4 3 Suppose we have the following queue:

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.

We start by initializing two empty queues:


Sorting the Queue:
EvenQueue and OddQueue.
Now, let's sort the queue by priority. In a real-
world priority queue implementation, this would We then dequeue each element from the original
be taken care of automatically by the data queue:
structure (for example, a binary heap): 1. If the element is even, we enqueue it to
the EvenQueue.
Task 2 Task 5 Task 4 Task 6 Task 1 Task 3 2. If the element is odd, we enqueue it to
the OddQueue.
5 4 3 3 2 1
By following these steps, we get the following
queues:
Dequeue Operation:
The dequeue operation removes the highest Even Queue
priority task from the queue. In case of a tie (like 2 4 6 8 10
Task4 and Task6 both having a priority of 3), the
task that was enqueued first is removed. Odd Queue
1 3 5 7 9
Dequeue ()
That's how de-merging of queues works. The
Task 5 Task 4 Task 6 Task 1 Task 3 original queue can be de-merged based on any
4 3 3 2 1 condition, not just even or odd numbers. The
condition depends on the particular problem
we're trying to solve.
Task2 with priority 5 was removed from the
queue. The next dequeue operation would remove
Task5 which has the next highest priority 4, and Priority De-Merging:
so on.
Priority de-merging is the process of splitting a
priority queue into two or more priority queues
based on certain criteria. The criteria can be any
Merging and Demerging Queues
condition like splitting based on odd/even
De-merging queues is a process where a given priorities, or separating elements into separate
queue is split into two or more queues based on queues based on priority ranges, etc.
certain criteria. The criteria can be any condition For example, suppose we have a priority queue:
like splitting odd and even elements into two
separate queues or separating elements based on PriorityQueue: [(A, 5), (B, 4), (C, 3), (D, 2), (E,
1)]
their priority or any other user-defined condition.
We could de-merge this queue into two queues: If we add all elements from Queue1 before
one for elements with a priority greater than or adding all elements from Queue2, we get the
equal to 3, and one for elements with a priority following result:
less than 3:
MergedQueue: [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
PriorityQueue1: [(A, 5), (B, 4), (C, 3)]
PriorityQueue2: [(D, 2), (E, 1)] In this case, the MergedQueue does not maintain
the relative order of elements from Queue1 and
Again, the specifics of how you merge or de- Queue2.
merge priority queues can depend on the specifics
of the situation or the problem you're trying to These are simple examples, and the specifics of
solve. how you merge queues can depend on the
specifics of the situation. For example, if you're
Merging Queues merging priority queues, you might want to
maintain the priority order in the merged queue.
Merging queues is a process where two or more
queues are combined into a single queue. The
Priority Merging :-
new queue might maintain the relative order of
elements from the original queues, but the
specifics can depend on the context or use case. Priority merging is the process of combining two
or more priority queues into a single priority
Let's take an example where we merge two
queue. The elements in the new priority queue
queues into one.
will be ordered based on their priorities.
Suppose we have the following queues:
For example, suppose we have two priority
Queue 1 queues:

1 3 5 7 9 PriorityQueue1: [(A, 3), (B, 2), (C, 1)]

PriorityQueue2: [(D, 4), (E, 2), (F, 1)]


Queue 2
2 4 6 8 10 If we merge these priority queues, the resulting
queue could be:

We start by initializing an empty queue:


MergedPriorityQueue: [(D, 4), (A, 3), (B, 2), (E,
MergedQueue. 2), (C, 1), (F, 1)]
We then dequeue each element from the original
Note that the merged queue maintains the
queues and enqueue it to the MergedQueue. We
priority order of the elements.
can do this in a variety of ways, depending on
the specifics of the situation. For example, we
could alternate between the queues, or we could CHAPTER – 6
add all elements from one queue before adding
all elements from the other. TREE AND GRAPH
If we alternate between the queues, we get the
Tree: A tree is a non-linear data structure that
following result:-
consists of a set of interconnected nodes. It
MergedQueue: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] represents a hierarchical structure where each
node has a specific relationship with other nodes.
In this case, the MergedQueue maintains the
relative order of elements from Queue1 and Key Components of a Tree:
Queue2. 1. Nodes: Nodes are the fundamental
building blocks of a tree. Each node
represents a unique element or entity in example is a family tree, where individuals
the tree. In a tree, nodes are connected to (nodes) are connected based on parent-child
other nodes through edges. relationships to represent familial connections.

2. Edges: Edges are the connections or links TREE TERMS


between nodes in a tree. They represent
Root: A root is a node in a tree that does not
the relationships between the nodes. Each
have a parent. It serves as the starting point of
node (except the root) is connected to
the tree.
exactly one parent node and can have
zero or more child nodes. Internal Node: An internal node is a node in a
tree that has at least one child. It is not a leaf
Rooted Tree: A rooted tree is a type of tree in
node.
which one node is designated as the root. The
root is the topmost node in the tree and serves External (Leaf) Node: An external node, also
as the starting point for traversing the tree. Every known as a leaf node, is a node in a tree that
other node in the tree has a parent-child does not have any children.
relationship with the root or other nodes.
Ancestors of a Node: The ancestors of a node are
Key Properties of a Rooted Tree: the parent node, grandparent node, great-
grandparent node, and so on, of a particular
1. Parent-Child Relationship: In a rooted
node. They are the nodes that come before the
tree, each node (except the root) has
given node along the path to the root.
exactly one parent node. The parent node
is the node from which another node is Descendants of a Node: The descendants of a
directly connected. Conversely, the child node are the children, grandchildren, great-
nodes are the nodes connected to a grandchildren, and so on, of a particular node.
specific parent node. They are the nodes that come after the given
node along the branches of the tree.
2. Unique Path to Each Node: In a rooted
tree, there is a unique path from the root Depth of a Node: The depth of a node is the
to every other node. This means that number of edges or levels between the node and
starting from the root, we can follow a the root. It represents the length of the path from
sequence of edges to reach any node in the root to the node.
the tree.
Height of a Tree: The height of a tree is the
3. Length of a Path: The length of a path in depth of the deepest node in the tree. It
a rooted tree refers to the number of represents the maximum number of levels in the
edges along that path. It represents the tree.
distance or the number of steps required
Subtree: A subtree is a smaller tree that is part of
to traverse from one node to another.
a larger tree. It consists of a node and all its
Trees vs. Linear Data Structures: Trees are descendants.
classified as non-linear data structures because
Binary Tree: A binary tree is a tree in which
the elements (nodes) do not form a sequential or
each node can have at most two children: a left
linear order like arrays or linked lists. Instead,
child and a right child.
they exhibit a hierarchical structure, allowing for
more complex relationships and organization. Full Binary Tree: A full binary tree is a binary
tree where each node has either 0 or 2 children.
Examples of Trees: One common example of a
In other words, every node in the tree is either a
tree is a file system structure, where folders
leaf node or has two children.
(nodes) are connected to subfolders or files (child
nodes) through parent-child relationships. Another
the root to the leaves, all levels are filled with
nodes until the last level, which may have some
gaps on the right side. The nodes are always
placed as far left as possible, ensuring the tree is
as balanced as possible given the number of
nodes.

Binary Search Tree (BST)


Balanced Binary Tree :- a binary tree where each
A binary search tree, also known as an ordered
node except the leaf nodes has left and right
binary tree, is a type of binary tree data
children and all the leaves are at the same level.
structure that follows specific conditions:

1. Key Property:

• Every node in the BST has a key,


which represents a unique element
or value.
• No two nodes in the BST have the
same key. This ensures that each
key in the tree corresponds to a
distinct element.
2. Ordering Property:

• For each node in the BST, all keys


in its left subtree are smaller than
the key in that node.
Complete Binary Tree: A complete binary tree is • Similarly, all keys in its right
a binary tree in which all levels, except possibly subtree are larger than the key in
the last one, are completely filled with nodes. In that node.
the last level, all nodes are filled in from left to • This ordering property ensures
right, meaning there are no "gaps" in the that the elements in the BST are
sequence of nodes from left to right on that organized in a specific order.
level. • The left subtree contains elements
that are smaller than the current
node's key, while the right subtree
contains elements that are larger.

3. Recursive Structure:

• The left and right subtrees of any


node in the BST are themselves
binary search trees.
• This means that the ordering
property holds for every node in
the tree, not just at the root level.
In simpler terms, imagine a binary tree where all
• Each node in the BST acts as a
levels are filled with nodes, except the last level,
root for its respective left and
which may be partially filled from left to right.
right subtrees, maintaining the
This means that as you move down the tree from
ordering property throughout the 1. Searching: The BST's ordering property
entire structure. enables efficient searching. Starting from
• This recursive structure allows for the root, comparisons are made between
efficient search, insertion, and the target key and the keys in each node.
deletion operations within the Based on the comparison, the search is
BST. directed to the left or right subtree,
eliminating the need to search the entire
Example :-
tree.

2. Insertion: When inserting a new node into


a BST, it is placed based on a comparison
with the keys of existing nodes. By
following the ordering property, the new
node is inserted in the appropriate
location to maintain the tree's ordered
structure.

3. Deletion: Removing a node from a BST


involves finding the node to be deleted
and then reorganizing the tree to
maintain the ordering property. Several
Below is not a Binary Search Tree cases need to be considered, depending
on whether the node has no children, one
child, or two children.

4. Traversal: The BST can be traversed in


various ways to visit and process all
nodes. Common traversal methods include
in-order, pre-order, and post-order
traversal, each offering a different order
of visiting the nodes.

The efficiency of operations in a BST depends on


the height of the tree. A well-balanced BST,
where the heights of the left and right subtrees
are nearly equal, provides optimal performance.
In the above tree, the value of root node is 40,
However, an unbalanced BST with a skewed
which is greater than its left child 30 but smaller
structure can degrade the efficiency of operations.
than right child of 30, i.e., 55. So, the above
tree does not satisfy the property of Binary search Binary search trees are widely used in
tree. Therefore, the above tree is not a binary applications that require efficient searching and
search tree. ordered data storage. They provide a balanced
and structured way to organize data elements
The BST's structure allows for efficient searching,
based on their key values.
insertion, and deletion operations. By following
the ordering property, it is possible to perform
binary search operations to quickly locate Insertion of data In to BST
elements within the tree.

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:

• In this traversal, we visit the


nodes in a post-defined order.
• The process involves recursively the desired element and the search
traversing the left subtree, then is successful.
the right subtree, and finally • If the target key is smaller than
visiting the parent node. the current node's key, move to
• This traversal can be helpful for the left subtree, as the desired
performing certain mathematical element should be located in the
calculations or deleting nodes from left subtree if it exists.
the BST. • If the target key is larger than the
current node's key, move to the
To understand these traversals, let's consider a
right subtree, as the desired
simple BST:
element should be located in the
4 right subtree if it exists.
/
\ 3. Repeat step 2 until one of the following
2 6
conditions is met:
/ \ / \
1 3 5 7 • The target key is found in a node,
and the search is successful.
In a preorder traversal, we would visit the nodes • The search reaches a leaf node (a
in the order: 4, 2, 1, 3, 6, 5, 7. node with no children), indicating
that the target key does not exist
In an inorder traversal, we would visit the nodes in the BST.
in ascending order: 1, 2, 3, 4, 5, 6, 7.
By following this process, you can efficiently
In a postorder traversal, we would visit the nodes search for data within a BST. The key advantage
in the order: 1, 3, 2, 5, 7, 6, 4. of a BST is that the search can be performed in
Traversing a BST allows us to process or display logarithmic time complexity (O(log n)), where n
the elements stored in the tree in a particular is the number of elements in the tree. This makes
order. The choice of traversal method depends on searching in a BST faster compared to searching
the requirements of our specific task. in an unsorted data structure.

For example, let's consider the following BST:

Searching data in BST


8
Searching for data in a binary search tree (BST) / \
involves finding a specific element within the tree 3 10
/ \ \
based on its key value. The searching process
1 6 14
utilizes the ordering property of the BST to / \ / \
efficiently locate the desired element. 4 7 13 15
Here's a brief description of the searching process
in simpler terms: If we want to search for the key value 6, we
start at the root (8) and compare it with the
1. Start at the root node of the BST. target key (6). Since 6 is smaller, we move to the
left subtree. Next, we compare the key value 3
2. Compare the key value of the current
with 6. Again, 6 is larger, so we move to the
node with the target key value that you right subtree of node 3. Finally, we find the key
want to search for. value 6 in the left subtree of node 6, and the
search is successful.
• If the current node's key matches
the target key, you have found
Deleting from a BST • Repeat this process until the node
to be deleted is found or until
reaching a leaf node (indicating
Deleting a node from a binary search tree (BST)
the node does not exist in the
involves removing a specific element from the
tree).
tree while maintaining the ordering property of
2. Once the node to be deleted is found:
the BST.
• If the node has no children,
The process of deleting a node from a BST
simply remove it from the tree.
depends on the following scenarios:
• If the node has one child, bypass
1. Node has no children (leaf node): the node by connecting its child
to its parent.
• If the node to be deleted has no
• If the node has two children, find
children, it can be simply removed
the replacement node (either the
from the tree without affecting the
minimum element in the right
other nodes.
subtree or the maximum element
2. Node has one child:
in the left subtree).
• If the node to be deleted has only • Replace the node to be deleted
one child, we can bypass the node with the replacement node, and
by connecting its child directly to then delete the replacement node
its parent. from its original position.
3. Node has two children:
By following these steps, we can safely delete a
• If the node to be deleted has two node from the BST while preserving the ordering
children, we need to find a property of the tree.
suitable replacement node to
Let's consider a simple binary search tree (BST) as
preserve the BST properties. This
an example :-
replacement node is usually the
minimum element in the right
8
subtree or the maximum element
/ \
in the left subtree.
3 10
• Once the replacement node is / \ \
found, we replace the node to be 2 6 14
deleted with the replacement / \ / \
node, and then delete the 4 7 13 15
replacement node from its original
position (using one of the above Scenario 1: Deleting a leaf node (node with no
scenarios). children)

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.

• If the key is smaller, move to the 8


/ \
left subtree.
3 10
• If the key is larger, move to the / \ \
right subtree. 2 6 14
\ / \ • We replace node 3 with the replacement
7 13 15 node 4 and delete the original position of
2.
Scenario 2: Deleting a node with one child • After deletion:

• Let's delete the node with key 10. 8


• Node 10 has only one child (right child / \
14). 4 10
/ \ \
• We bypass node 10 by connecting its
2 6 14
child (14) directly to its parent (3).
\ / \
• After deletion: 7 13 15
8
/ \ GRAPH – NON-LINEAR DATA STRUCTURE
3 14
/ \ \ A graph is a mathematical structure that consists
2 6 15 of two main components: vertices (also called
/ \ nodes) and edges. Vertices represent individual
4 7 elements, and edges represent connections or
relationships between those elements.
Scenario 3: Deleting a node with two children
Graphs can be classified as directed or undirected
• Let's delete the node with key 3. based on whether the edges have a specific
• Node 3 has two children (2 and 6). direction or not. In a directed graph, the edges
• We find the replacement node, which is have an associated direction, while in an
the maximum element in its left subtree undirected graph, the edges are bidirectional.
(2).
• We replace node 3 with the replacement Graphs are widely used to model relationships
node 2 and delete the original position of between entities in various domains, such as
2. social networks, transportation networks, and
• After deletion: computer networks. They serve as the foundation
for many graph algorithms that solve problems
related to connectivity, shortest paths, network
flows, and more.

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.

Dense graphs are often encountered in scenarios


where entities have a high degree of connectivity
or interactions. For example, in a complete social
network where every user is connected to every
other user, the graph would be dense. In a fully
connected transportation network, where there
are direct routes between every pair of locations,
the graph would also be dense.

Dense graphs require more memory to store and


Both types of graphs are fundamental in graph more computational effort to process compared to
theory and are used in various real-world sparse graphs, as there are more edges to
applications, including social networks, consider. Some graph algorithms and data
transportation networks, computer networks, and structures may have different performance
many more. characteristics when applied to dense graphs.

Sparse and Dense Graphs

Sparse Graph: A sparse graph is a type of graph


where the number of edges is much smaller
compared to the number of vertices. In other
presence or absence of connections between
vertices rather than the quantitative values
associated with the edges.

Unweighted graphs are often used in situations


where the relationships between vertices are
binary, indicating only the presence or absence of
a connection. They are simpler to work with and
require less computational effort compared to
weighted graphs.
Determining whether a graph is sparse or dense
depends on the ratio of the number of edges to
the number of vertices. There is no strict
threshold to define the boundary between sparse
and dense graphs, as it depends on the specific
context and problem being analyzed.

Understanding whether a graph is sparse or dense


is important for selecting appropriate algorithms
and data structures, as the choice may vary based
on the characteristics of the graph.

Weighted and Unweighted Graph


The choice between using a weighted or
In graph theory, a weighted graph is a type of unweighted graph depends on the specific
graph where each edge is assigned a numerical problem being addressed and the available
value called a weight. The weight represents information about the relationships between
some kind of quantitative measure or cost vertices. Weighted graphs provide more flexibility
associated with the edge. The weights can and precision in modeling real-world scenarios
represent distances, costs, capacities, or any other that involve varying costs or measures associated
relevant metric depending on the application. with the connections, while unweighted graphs
For example, in a weighted graph representing a offer simplicity and efficiency in certain
transportation network, the weights of the edges applications where the quantitative values of the
could represent the distances between locations or connections are not essential.
the travel times required to move from one
GRAPH TRAVERSALS
location to another. In a social network, the
weights could represent the strength of Breadth First Search (BFS) and Depth First Search
relationships between individuals. (DFS) are two commonly used techniques for
traversing or exploring a graph to visit all its
The presence of weights in a graph allows for
vertices. They are used to search for specific
more nuanced analysis and optimization. It
nodes, find connected components, detect cycles,
enables the consideration of the costs or distances
or find the shortest path between two vertices.
associated with traversing edges when solving
problems such as finding the shortest path or
determining the most efficient route.

On the other hand, an unweighted graph is a


type of graph where all edges are considered to
have the same uniform weight or no weight at
all. In an unweighted graph, the focus is on the
7.3.1 Breadth First Search (BFS):

In BFS, the traversal starts at a given vertex and


explores all its neighbors before moving on to the
next level of vertices. It follows the "breadth" of
the graph, visiting all the vertices at a given
level before moving to the next level.

7.3.2 Depth First Search (DFS):

In DFS, the traversal starts at a given vertex and


explores as far as possible along each branch
before backtracking. It follows the "depth" of the
graph, going deeper into each branch before
exploring other branches.

You might also like