Introduction To Data Structure
Introduction To Data Structure
The name "Data Structure" is a combination of two words: "Data" and "Structure". Let's
go over each word individually:
Data: Data is information that must be processed and stored in a computer. Data
can be anything from numbers to characters to dates to photos to videos.
Structure: Structure refers to how data is structured. It specifies the relationship
between various data elements and how they are kept in memory. The structure
has an impact on the efficiency of data operations.
A data structure is a systematic way of storing and managing data in a computer so that
it can be retrieved and used efficiently.
Problem Solving: Data structures are an important tool for handling many real-
world problems. Understanding data structures can help you build more efficient
and effective programmes.
Algorithm Design: To store and modify data, many algorithms rely on data
structures. A solid understanding of data structures is required for the
development of effective algorithms.
Requirements for the Job: Data structure knowledge is frequently required for
software engineering and computer science employment. Candidates with a
thorough understanding of data structures and algorithms are highly valued by
employers.
Competitive Programming: Data structure knowledge is essential for
competitive programming and coding competitions.
Better Understanding of Computer Science: Data structures are a
fundamental concept in computer science and understanding them can deepen
your overall understanding of the field.
In summary, data structures play a crucial role in solving problems related to data
management and analysis by providing efficient solutions for organizing, retrieving, and
manipulating data.
In summary, these are some of the key characteristics of data structures that affect their
efficiency, suitability, and versatility for different types of problems and applications.
==============================================================
1. Basic: Primitive data structures are the most basic and fundamental data
structures that are directly built into a programming language.
2. Simple Representation: They have a simple and straightforward representation,
such as integers, floating-point numbers, and Booleans.
3. Limited Functionality: Primitive data structures are limited in their functionality
and can only store and manipulate simple data.
4. Built-in Types: Primitive data structures are typically built-in types in most
programming languages and do not require any special implementation.
5. Fast Access: Primitive data structures are stored in contiguous memory
locations, which allows for fast access to their elements.
6. Efficient Storage: Since primitive data structures have a simple representation,
they use less memory compared to non-primitive data structures.
7. Commonly used: Primitive data structures are commonly used in programming
to store basic information and are used as building blocks for more complex data
structures.
Non-Primitive Data Structures are more complex data structures built using primitive
data structures. Examples of Non-Primitive Data Structures can be divided into two
categories: Linear and Non-Linear.
Arrays
An Array is a collection of elements of the same data type stored in contiguous memory
locations. It is a linear data structure that provides efficient access to its elements based
on their indices. Each element in an array is referred to by its index, which is an integer
that starts from 0. The elements are stored in a contiguous block of memory, which
allows for fast access to elements based on their indices.
Arrays can be used to store a variety of data types, including numbers, strings, and
objects. Arrays can have a single dimension (also called a one-dimensional array) or
multiple dimensions (e.g. two-dimensional arrays, three-dimensional arrays, etc.).
Arrays are widely used in programming to store and manipulate data. They are used as
building blocks for more complex data structures and are used in a variety of algorithms
and applications.
Linked List
A linked list is a data structure consisting of a sequence of nodes, where each node
holds data and a reference (link) to the next node in the sequence. The reference is
stored in a field called "next". The last node in the list contains a null reference
indicating the end of the list.
There are two main types of linked lists: singly linked lists and doubly linked lists. In
a singly linked list, each node only has a reference to the next node in the list, while in a
doubly linked list, each node has a reference to both the next node and the previous
node.
1. Dynamic size: Linked lists can grow or shrink in size during execution of a
program, while the size of an array needs to be fixed when it is declared.
2. Ease of insertion and deletion: Inserting or deleting an element in a linked list
is easier compared to arrays, as elements in linked lists do not need to be
shifted.
3. No wastage of space: In arrays, unused space is wasted, while in linked lists,
space is used only as needed.
On the other hand, linked lists have some disadvantages, such as slow random access
time and higher overhead compared to arrays due to the extra memory required to store
the links.
Linked lists are commonly used for dynamic data structures such as stacks, queues,
and associative arrays. They can also be used for implementing trees, graphs, and
other complex data structures.
Queue
A queue is a type of linear data structure that operates on the First In First Out (FIFO)
principle. That is, the first item put to the queue will be the first to be removed. The last
item added to the queue will be removed at last.
An array or a linked list can be used to implement a queue. The implementation used is
determined by the specific use case and needs.
Queues are widely used in a variety of algorithms and real-world applications, including:
Stack
A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. It
means the last element added to the stack will be the first one to be removed.
A stack can be thought of as a list or a collection of elements, where elements can only
be added or removed from the top of the stack. The top of the stack is the element that
was added most recently and the bottom of the stack is the element that was added
first in the stack.
Stack of plates or books, where you can only remove the top plate or book.
The back button on a web browser, where you can only go back to the previously
visited page.
In computer science, a stack can be implemented using an array or linked list data
structure. Some of the common operations performed on a stack include push (add an
element to the top of the stack), pop (remove the top element from the stack), and peek
(return the top element of the stack without removing it).
Trees
A tree has a root node, which is the topmost node and has no parent. Each node in the
tree can have zero or more child nodes. A node with no children is called a leaf node.
The depth of a node is the number of edges from the root node to that node. The height
of the tree is the maximum depth of any node in the tree.
Trees have several applications in computer science, including:
In computer science, a tree can be implemented using an array or linked list data
structure. Some of the common operations performed on a tree include insertion,
deletion, searching, and traversal (visiting all nodes in the tree).
Graph
Note that graphs can be either directed or undirected, depending on whether the
relationships represented by the edges have a direction. In a directed graph, the edges
have a direction and are called arcs, while in an undirected graph, the edges have no
direction and are simply called edges.
Undirected Graph
Directed Graph
Graphs can be used to model many different types of relationships, including social
networks, transportation networks, and relationships between web pages.
Examples of graphs:
================================================================
1. Basic Operations: These are the fundamental operations that can be performed
on a data structure. Examples include insertion, deletion, search, and access.
2. Advanced Operations: These are more complex operations that are built on top
of basic operations. Examples include sorting, merging, splitting, and traversal.
It's important to note that the specific operations that can be performed on a data
structure will depend on the type of data structure being used. For example, operations
like "pop" (removal of an item) and "push" (addition of an item) are specific to stack data
structures.
2. Deletion: Removing an element from the data structure. For example, removing
an element from a array or a linked list.
3. Search: Finding an element in the data structure. For example, searching for a
specific key or value in array or a linked list..
4. Access: Retrieving an element from the data structure. For example, accessing
the first element of a queue.
5. Traversal: Visiting every element in the data structure. For example, printing all
elements of an array or a linked list.
7. Merging: Combining two data structures into one. For example, merging two
sorted linked lists into a single sorted linked list.
8. Splitting: Breaking a data structure into smaller parts. For example, splitting a
linked list into two equal halves.
9. Reversal: Reversing the order of elements in the data structure. For example,
reversing the elements of a stack.
10. Indexing: Accessing an element of the data structure using an index or a key.
For example, accessing the element at index i in an array.
===========================================================
Here is a list of some common applications of Data structures, along with examples:
3. Graphical User Interfaces - Data structures are used to store and display
hierarchical data, such as the file system in a computer's file explorer. For
example, a tree data structure can be used to represent the file hierarchy in a file
explorer.
5. Compiler Design - Data structures are used to store and process the syntax and
semantics of programming languages. For example, a parse tree data structure
can be used to represent the structure of a program and to generate intermediate
code.
8. Bioinformatics - Data structures are used to store and analyze large amounts of
biological data, such as DNA sequences and protein structures. For example, a
suffix tree data structure can be used to efficiently store and search for patterns
in DNA sequences.
9. Web Search Engines - Data structures are used to store and retrieve large
amounts of web pages and to rank them based on relevance. For example, an
inverted index data structure can be used to store keywords and their
corresponding web pages, allowing for fast searching and retrieval.
10. Computer Algorithms and Optimization - Data structures are used to design
and analyze algorithms for solving complex problems. For example, a priority
queue data structure can be used in Dijkstra's algorithm for finding the shortest
path in a graph.
11. Geographic Information Systems (GIS) - Data structures are used to store and
analyze geographical data, such as maps, satellite images, and census data. For
example, a quadtree data structure can be used to efficiently store and search for
geographical features, such as cities or roads.
12. Video Games - Data structures are used to store and manipulate game objects
and their interactions. For example, a spatial partitioning data structure can be
used to efficiently search for and detect collisions between game objects.
13. Financial Systems - Data structures are used to store and manage financial
data, such as stock prices and transaction history. For example, a balanced
binary search tree data structure can be used to efficiently store and retrieve
stock prices.
14. Cryptography - Data structures are used to store and process encrypted data
and to implement cryptographic algorithms. For example, a hash table data
structure can be used to store and look up values for encryption and decryption.
15. Natural Language Processing (NLP) - Data structures are used to store and
process large amounts of text data, such as for text classification, sentiment
analysis, and text generation. For example, a trie data structure can be used to
efficiently store and search for words in a large text corpus.
==============================================================
Algorithms can be designed using various computational paradigms, such as divide and
conquer, greedy, dynamic programming, and others. They can be expressed using
pseudocode or a high-level programming language. The efficiency and effectiveness of
an algorithm are determined by its time complexity and space complexity, which
describe the amount of time and memory required to execute the algorithm,
respectively.
Imagine you want to sort a large list of names in alphabetical order. You could do this
manually, but it would be a time-consuming and error-prone process. Instead, you can
use a sorting algorithm such as quicksort or mergesort, which are designed to sort data
in an efficient and reliable manner. By using a well-established algorithm, you can be
confident that the results will be accurate and the process will be much faster than
manual sorting.
Importance of Algorithms
Algorithms play a critical role in computer science and other fields, and their importance
can be seen in several ways:
7. Job creation: The increasing demand for algorithms in various industries has
created job opportunities in areas such as software development, data analysis,
and machine learning.
In conclusion, algorithms are a fundamental tool in computer science and play a crucial
role in solving complex problems, improving efficiency, and driving innovation.
Characteristics of an Algorithm
An algorithm is a set of well-defined instructions used to solve a specific computational
problem. The following are the important characteristics of an algorithm:
4. Defined Outputs: An algorithm must have well-defined outputs, which are the
results of the calculation or problem-solving process.
Dataflow of an Algorithm
The data flow of an algorithm refers to the sequence in which data is processed,
transformed and produced as output. It encompasses the input of data, the operations
performed on the data, and the resulting output. A well-designed data flow should be
efficient, understandable, and maintain the integrity of the data. Some points about the
dataflow of an algorithm are as follows:
1. Input of Data: The first step in the data flow of an algorithm is the input of data.
This can be from a user, a file, or any other source.
2. Data Processing: The next step is to process the data, which includes
performing various operations such as arithmetic calculations, string
manipulations, or other transformations.
3. Decision Making: The algorithm may need to make decisions based on the data
and execute different operations accordingly. This can be represented as a
branching structure in the flow chart.
4. Data Storage: The intermediate results of the processing may need to be stored
temporarily for later use.
5. Data Output: The final step is to produce the output, which can be a result, a
visual representation of the data, or any other form of representation.
6. Data Validation: Before processing the data, it is important to validate the data
to ensure that it is in the correct format and meets the requirements of the
algorithm.
7. Error Handling: The data flow should also include error handling mechanisms to
handle unexpected or incorrect input, or to handle other types of exceptions that
may arise during the processing.
Overall, the data flow of an algorithm should be well-designed and efficient to produce
accurate and meaningful results.
2. Input: Take the principal amount (P), the rate of interest (R) and the time period
(T) as inputs.
3. Calculate the interest: Multiply the principal amount (P) by the rate of interest (R)
and the time period (T) to calculate the interest (I = P * R * T).
5. End.
This algorithm uses the simple interest formula, which is I = P * R * T, where P is the
principal amount, R is the rate of interest, T is the time period, and I is the interest. The
algorithm takes these values as input and calculates the interest, which is then
displayed as output.
Factors of an Algorithm
The factors that contribute to the effectiveness and efficiency of an algorithm can be
broadly categorized into three categories:
4. Overhead: Some algorithms may have a high overhead, meaning that they
consume a large amount of resources, such as time and memory, even when the
data size is small.
These issues highlight the importance of careful selection of algorithms when working
with data structures. The right algorithm can help to solve problems efficiently and
effectively, while the wrong algorithm can lead to inefficiency, waste of resources, and
unintended consequences.
1. Brute Force: This approach involves trying all possible solutions to a problem
and selecting the best one. This approach can be used for small problems, but is
often too slow for larger problems.
2. Divide and Conquer: This approach involves breaking a problem into smaller
sub-problems and solving each sub-problem separately. The results from the
sub-problems are then combined to find the solution to the larger problem.
==================================================================
Algorithm Analysis
Algorithm analysis is the process of evaluating the performance of an algorithm and
measuring its computational efficiency. The goal of algorithm analysis is to determine
the time and/or space complexity of an algorithm and to compare it to other algorithms
for the same problem.
There are two main aspects of algorithm analysis: Time complexity and Space
complexity. Time complexity is a measure of the amount of time an algorithm takes to
run as a function of the size of the input data. Space complexity is a measure of the
amount of memory an algorithm requires to run as a function of the size of the input
data.
There are several methods for analyzing the performance of algorithms, including:
===========================================================
An algorithm or software in this situation trades off more space usage for less time.
Thus, space refers to the amount of data storage used (RAM, HDD, etc.) and time
refers to the amount of time used to complete a certain activity (computation time or
response time)
Asymptotic Analysis
Asymptotic notations are mathematical notations for analysing the runtime of a given
algorithm for a large input. It allows us to compare the runtimes of various algorithms
without having to manually calculate their runtimes. We must determine how long our
algorithm takes to complete each step in order to evaluate how well it performs for
various inputs. This time can easily be calculated by manually. Sometimes it's not
possible for humans to record such a small runtime, so manually measuring an
algorithm's runtime is not a smart idea.
Think about the f(n) and g(n) functions, where f and g are defined on an unbounded set
of positive real numbers. For every big value of n, g(n) is always strictly positive.
The function f is said to be O(g) (read as big- oh of g), if, for a constant c>0 and a
natural number n0, f (n) ≤ CG(n) for all n >= n0. This can bw written as below
O(g(n)) = { f(n) : There exist positive constant c and n0 such that 0 ≤ f(n) ≤ c g(n), for all
n ≥ n0 }
Ω(g(n)) = { f(n) : There exist positive constant c and n0 such that 0 ≤ c g(n) ≤ f(n), for all
n ≥ n0 }
3. Big-Theta(Θ) Notation
The formal notation for expressing the lower and upper bounds of an algorithm's
execution time is Θ(n). It express the average case of an algorithm. It is displayed as
follows:
We write f(n) = Θ(g(n)), If there are positive constants n0 and c1 and c2 such that, to
the right of n0 the f(n) always lies between c1*g(n) and c2*g(n) inclusive.
Θ(g(n)) = {f(n) : There exist positive constant c1, c2 and n0 such that 0 ≤ c1 g(n) ≤ f(n)
≤ c2 g(n), for all n ≥ n0 }
=============================================================
Definition of Array
An array is a collection of elements of the same data type, stored in contiguous
memory locations. Arrays in C are useful when you need to store and manage a large
amount of data of the same data type. They enable you to store multiple values in a
single variable and simply access and manipulate them using a single variable name
and an index.
Types of Arrays
An array in C is a collection of elements of the same data type, stored in contiguous
memory locations. There are two types of arrays:
The most common way to represent an array in memory is to store the elements
sequentially in a block of memory. For example, if we have an array of integers a with 5
elements, the memory representation would look like this:
The choice of memory representation can have an impact on the performance of array
operations. For example, if a program needs to access all the elements of a row in an
array, it is more efficient to use row-major order because the elements of the row are
stored contiguously in memory. Similarly, if a program needs to access all the elements
of a column in an array, it is more efficient to use column-major order.
For example, let's say you have an array of integers with 5 elements and the base
address of the array is 100. The size of each integer element is 4 bytes. To access the
third element a[2] of the array, you would use the following formula:
It's important to note that array indices start at 0, so the first element in the array has an
index of 0, the second element has an index of 1, and so on.
Where:
For example, let's say you have a 2D array of integers with 3 rows and 4 columns, and
the base address of the array is 100. The size of each integer element is 4 bytes. To
access the element at row 1, column 2 which is a[1][2] you would use the following
formula:
So the memory address of the element at row 1, column 2 in the 2D array is 116.
===============================================================
4. Search: Search is the process of finding a specific element within an array. This
is done by comparing the target element with each element in the array until a
match is found. There are various search algorithms that can be used, such as
linear search and binary search.
These are just a few of the common operations that can be performed on arrays in data
structures. Depending on the application, there may be other operations that are
required to effectively manipulate and process array data.
Traversal Operation
Array traversal refers to the process of accessing each element of an array in a
sequential order. In data structures, an array is a collection of similar data items that are
stored in contiguous memory locations. Traversing an array involves visiting each
element of the array in a specified order, typically from the beginning to the end or from
the end to the beginning.
Here data is array name and LB is Lower Bound (start) index of the first element of an
array. UB is Upper Bound (End) is the index of the last element
Step 1: Start
Step 2: [Initialize variable. ] Set i = LB
Step 3: Repeat steps 4 and 5 While i <= UB
Step 4: Apply process (Print ) to data[i].
Step 5: Increment i=i+1
Step 6: End of loop
Step 7: Stop
#include <stdio.h>
#include<conio.h>
void main()
{
// Declare and initialize an array
int data[10], i=0,ub;
print("Enter the Size of Array");
scanf("%d",&ub);
for(i=0;i<ub;i++)
{
scanf("%d",&data[i]);
}
getch();
}
Insertion Operation
Insertion in an array refers to the process of adding a new element to a specific position
within the array while shifting the existing elements to make room for the new element.
Step 1: Start
Step 2: [Initialize variable ] Set i = size - 1
Step 3: Repeat Step 4 and 5 While i >= pos-1
Step 4: [Move ith element forward. ] set arr[i+1] = arr[i]
Step 5: [Decrease counter. ] Set i = i - 1
Step 6: [End of step 3 loop. ]
Step 7: [Insert element. ] Set arr[pos-1] = item
Step 8: Stop
#include <stdio.h>
#include <conio.h>
void main()
{
int arr[50], n, pos, item,i;
Deletion Operation
Deletion in an array means removing an element from an array and shifting the
remaining elements to fill the empty space.
Step 1: Start
Step 2: [Initialize variable] Set i = pos - 1
Step 3: Repeat Step 4 and 5 for i = pos - 1 to i < size
Step 4: [Move ith element left side ] set a[i] = a[i+1]
Step 5: [Incerement ] Set i = i + 1
Step 6: [End of step 03 loop. ]
Step 7: [Update Array Size] set size = size - 1
Step 8: Stop
#include <stdio.h>
#include <conio.h>
void main()
{
int array[100], position, i, n;
==================================================================
1. row_index: It stores the row indices of the corresponding elements in the array.
2. col_index:It stores the column indices of the corresponding elements in the array.
3. value : It stores the non-zero elements of the matrix in row-major order.
The size of these arrays depends on the number of non-zero elements in the matrix.
Here's an example of how we can represent a sparse matrix using the array
representation:
if (sm[i][j] != 0)
{
// Increment the number of non-zero elements
num_non_zero++;
}
}
}
}
getch();
}
The benefit of using a linked list instead of an array to represent the sparse matrix is
that it is simpler to add or remove nodes from a linked list than from an array.
The first node in the list is known as the head node, and the final node in the list is
known as the tail node. If a node's reference is null, it denotes the end of the list.
The head is the first node in a linked list. It is the starting point for the linked list and
contains a reference to the first element in the list. The data for the first element in the
list is stored in the head node, along with a reference to the next node in the list.
1. Dynamic size: Linked lists are dynamic data structures, which means that its
size can be changed at run time. This makes it easier to manage data sets when
the number of items changes over time.
4. Memory efficiency: When the number of items is small or the size of the
elements varies, linked lists can be more memory efficient than arrays. This is
due to the fact that linked lists only consume the memory necessary for the data
and pointers to the next node, but arrays allocate a set amount of memory for
each element regardless of whether it is used or not.
5. Flexibility: Linked lists can be singly or doubly linked, and they can be used to
build a variety of data structures such as queues, stacks, and hash tables.
Overall, linked lists offer several advantages over other data structures, including
efficient insertions and deletions, dynamic size, and flexibility. However, there are also
some disadvantages, such as slower access times and the need for more memory due
to the additional pointers.
2. Greater Memory Overhead: When compared to arrays, linked lists use more
memory. Linked lists must hold pointers to the following nodes in addition to the
data components. This may result in the linked list using more memory,
particularly if the list is lengthy.
5. No built-in Support for Sorting: Linked lists do not have built-in functionality for
sorting. It can take longer to sort a linked list than an array using built-in sorting
algorithms. In Linked List we have to arrange all the pointers for sorting. It is very
difficult as compared to arrays.
Above image shows a memory representation of a linked list data structure. It consists
of several nodes, each containing two parts: data and a pointer to the next node in the
list. The first node is called the head and the last node points to null. The data in each
node can be of any data type, and the pointer points to the memory address of the next
node. The linked list data structure allows for efficient insertion and deletion of nodes,
but requires traversal of the list to access a specific node.
===============================================================
Insertion: To add an element to a linked list, create a new node with the specified value,
then place it at the proper location on the list.
Deletion : Remove the node from the list that has the specified value to delete the
element from the linked list.
Traversal : Traversing a linked list requires iterating over each element in the list and
performing some operation on each element.
Searching : A linked list can be searched by locating the node that holds the specified
value, returning that value if it exists, or returning null if it does not.
Concatenation : Concatenation is the process of joining the two linked lists to form a
single, longer linked list.
Splitting : Splitting involves separating a linked list into two separate lists at a specific
position.
Reversal : If you want to reverse the order of elements in the Linked list then it involves
changing its elements' positions so that the last element becomes the first element and
first element becomes last element..
Sorting : Sorting a linked list means placing the elements in the list in some given order,
such as ascending or descending order.
Merging : Merging two sorted linked lists means combining the two lists into a single,
sorted linked list.
Counting : Counting the number of elements in a linked list involves iterating over each
element in the list and incrementing a counter variable for each element found.
1. Insertion at the beginning of the linked list: In this case, the new node is
inserted at the beginning of the linked list, which becomes the new head node.
This can be done by setting the next pointer of the new node to point to the
current head node and updating the head pointer to point to the new node.
2. Insertion at the end of the linked list: In this case, the new node is inserted at
the end of the linked list, which becomes the new tail node. This can be done by
traversing the linked list till the last node and setting the next pointer of the last
node to point to the new node.
3. Insertion at a specific position in the linked list: In this case, the new node is
inserted at a specific position in the linked list, which involves traversing the
linked list till the desired position and inserting the new node there. This requires
keeping track of the previous node and updating its next pointer to point to the
new node, while setting the next pointer of the new node to point to the next
node.
Write OVERFLOW
Go to Step 5
[END OF IF]
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
};
int main ()
{
struct node *head, *newnode;
int item;
newnode = (struct node *) malloc(sizeof(struct node *));
if(newnode == NULL)
{
printf("OVERFLOW");
}
else
{
printf("Enter New Node value");
scanf("%d",&item);
newnode->data = item;
newnode->next = head;
head = newnode;
printf("New Node inserted");
}
return 0;
}
Program in C to insert multiple nodes in the Linked List at the Beginning.
Insertion at the end of the linked list
Inserting a new node at the end of a linked list involves creating a new node with the
given data and setting the next pointer of the current last node to point to the new node.
#include <stdio.h>
#include <stdlib.h>
int main()
{
// Initialize an empty linked list
struct Node *head = NULL,*temp;
int n;
printf("Enter Number of Nodes: ");
scanf("%d",&n);
int arr[n];
for(int i=0;i<n;i++)
{
printf("Enter the Node %d Value: ",i+1);
scanf("%d",&arr[i]);
}
if(newNode == NULL)
{
printf("OVERFLOW");
}
else
{
newNode->data = arr[i];
}
else
{
// Traverse all nodes and find the Last Node
temp = head;
while (temp -> next != NULL)
{
temp = temp -> next;
}
temp->next = newNode;
newNode->next = NULL;
}
}
}
if(currentNode == NULL)
{
printf("NULL");
}
return 0;
}
1. Traverse the linked list until we reach the node before the desired position.
2. Create a new node with the value to be inserted.
3. Set the next reference of the new node to the node that is currently at the desired
position.
4. Set the next reference of the node before the desired position to the new node.
#include <stdio.h>
#include <stdlib.h>
// Main function
int main() {
// Create a linked list with three nodes
struct Node* head = NULL;
struct Node* node1 = (struct Node*) malloc(sizeof(struct Node));
struct Node* node2 = (struct Node*) malloc(sizeof(struct Node));
struct Node* node3 = (struct Node*) malloc(sizeof(struct Node));
node1->data = 1;
node1->next = node2;
node2->data = 2;
node2->next = node3;
node3->data = 3;
node3->next = NULL;
head = node1;
return 0;
}
=========================================================
A stack has one end, typically called the top, and it can be visualized as a collection of
objects stacked one on top of another. The top of the stack is the element that was most
recently added, and the bottom of the stack is the element that was added first or the
earliest.
The top of the stack is represented using a pointer, called the top pointer, which points
to the last element inserted. When a new element is added to the stack, it is placed on
the top of the stack, and the top pointer is updated to point to the new element.
In a stack, elements are added and removed from the top of the stack using push and
pop operations, respectively. The top pointer points to the last element inserted, and
when an element is added to the stack, the top pointer is updated to point to the new
element. When an element is removed from the stack, the top pointer is moved to the
next element below the top, which becomes the new top of the stack.
Working of Stack
A stack is a data structure that stores elements with a Last-In-First-Out (LIFO) ordering.
It works by using a stack pointer to manage the insertion and removal of elements from
the top of the stack.
Pushing 10, 20, and 30 onto the stack will result in the stack [10, 20, 30] with the top
pointer pointing to the element 30. Performing pop operations on the stack will remove
elements from the top of the stack and decrement the top pointer accordingly,
eventually leaving an empty stack with a null top pointer.
Basic Operations in Stack
The basic operations of a stack are given below:
1. Push: The push operation adds an element to the top of the stack. For example,
if we have a stack containing [10, 20], and we perform the push operation with
the value 30, the resulting stack will be [10, 20, 30], with the top element being
30.
2. Pop: The pop operation removes the topmost element from the stack. For
example, if we have a stack containing [10, 20, 30], and we perform the pop
operation, the resulting stack will be [10, 20], with the top element being 20.
3. Peek: The peek operation allows you to view the topmost element of the stack
without removing it. For example, if we have a stack containing [10, 20, 30], and
we perform the peek operation, the top element will be returned, which is 30, but
the stack will remain unchanged.
4. isEmpty: The isEmpty operation checks whether the stack is empty or not. It
returns true if the stack is empty and false if it contains one or more elements.
For example, if we have an empty stack, and we perform the isEmpty operation,
it will return true.
5. Size: The size operation returns the number of elements in the stack. For
example, if we have a stack containing [10, 20, 30], and we perform the size
operation, it will return 3.
6. Search: The search operation allows you to search for a specific element in the
stack and returns its position (counting from the top of the stack). If the element
is not found in the stack, it returns -1. For example, if we have a stack containing
[10, 20, 30], and we perform the search operation with the value 20, it will return
2 (since 20 is the second element counting from the top of the stack).
7. Updation: The update operation allows you to modify the value of an existing
element in the stack. This operation involves first searching for the element in the
stack, and then replacing its value with a new value.
8. Clear: The clear operation removes all elements from the stack, leaving it empty.
For example, if we have a stack containing [10, 20, 30], and we perform the clear
operation, the resulting stack will be empty, and the top pointer will be null or -1.
1. Check if the stack is full (if the stack has a fixed size and is already at its
maximum capacity). If the stack is full, the push operation cannot be performed.
2. Increment the top pointer of the stack to move it to the next empty position (i.e.,
the position above the current top element).
3. Add the new element to the position pointed by the top pointer.
4. Update the size of the stack to reflect the addition of the new element.
PUSH(S,TOP,N,X)
Here S is Stack, TOP is Stack Pointer, N is the Maximum Size of the Stack, X is
new element to be inserted
Step 1: If TOP==N
Write "OVERFLOW"
Return
( End of IF Statement)
Step 4 END
#include <stdio.h>
#define MAX_SIZE 100
int stack[MAX_SIZE];
int top = -1;
void push(int);
int main()
{
push(10);
push(20);
push(30);
push(40);
push(50);
printf("Elements Available in the Stack");
for(int i=top; i>=0;i--)
{
printf("|%d|",stack[i]);
}
return 0;
}
The POP operation in a stack is typically used in conjunction with the "PUSH" operation,
which adds an element to the top of the stack. Together, these operations allow you to
implement a Last-In-First-Out (LIFO) data structure.
1. Check if the stack is empty. If the stack is empty, return an error , as popping from an
empty stack is not allowed.
2. Otherwise, retrieve the topmost element from the stack.
3. Decrement the stack pointer to point to the next element in the stack.
4. Remove the topmost element from the stack.
5. Return the removed element if necessary.
Algorithm of POP Operation in Stack
POP(S,TOP,N,X)
Here S is Stack, TOP is Stack Pointer, N is the Maximum Size of the Stack, X is
element to be deleted
Write "Underflow"
Return
( End of IF Statement)
Step 4 END
#include <stdio.h>
#define MAX_SIZE 100
int stack[MAX_SIZE];
int top = -1;
void push(int);
void pop(int);
int main()
{
push(10);
push(20);
push(30);
push(40);
push(50);
printf("Elements Available in the Stack");
pop(50);
pop(40);
printf("Stack After Pop Operation");
return 0;
}
===============================================================
5. Browser History: Stacks are used in web browsers to keep track of visited web
pages. Each time a new page is visited, its URL is added to the stack. When the
back button is clicked, the most recent URL is popped off the stack and the
browser navigates to the previous page.
6. Compiler Design: Stacks are used in compiler design to check the validity of a
program's syntax. The stack is used to keep track of opening and closing
brackets, braces, and parentheses. For example, if a program contains an
opening brace {, it is pushed onto the stack. When a closing brace } is
encountered, the stack is popped to check if the opening brace matches the
closing brace.
7. Depth First Search: Stacks are used in graph algorithms such as Depth First
Search (DFS). The DFS algorithm visits all vertices in a graph by exploring as far
as possible along each branch before backtracking. The stack is used to keep
track of vertices that have been visited but not fully explored. Each time a vertex
is visited, it is pushed onto the stack. When all adjacent vertices have been
visited, the vertex is popped off the stack and the algorithm backtracks.
In Step 3 If the stack is empty, then the right parenthesis does not have a
matching left parentheisis in the STACK. This shows that this expression is
Invalid.
End of If Statement
a) If the stack is empty, then the right parenthesis does not have a
matching left parentheisis, stop scanning, print and error and goto Step 5
else
End of If Statement
Step 5: EXIT
+ addition, -
Left to right lowest
subtraction
Infix notation
Prefix notation (also known as Polish notation)
Postfix notation (also known as Reverse Polish notation)
1. Infix notation: In infix notation, the operator is placed between the operands. For
example, 2 + 3. In this notation, the order of operations is determined by the use of
parentheses, as well as by the precedence rules of the operators.
Example: (2 + 3) * 4 - 5 / 6
2. Prefix notation: In prefix notation, the operator is placed before the operands. For
example, + 2 3. In this notation, the order of operations is determined by the order of the
operands and the operator.
Example: - * + 2 3 4 / 5 6
3. Postfix notation: In postfix notation, the operator is placed after the operands. For
example, 2 3 +. In this notation, the order of operations is determined by the order of the
operands and the operator.
Example: 2 3 + 4 * 5 6 / -
1. Infix notation: 3 + 4 * 5 = 23
2. Prefix notation: + 3 * 4 5 = 23
3. Postfix notation: 3 4 5 * + = 23
As you can see, the order of the operands and operators differs in each notation, but
the result of the expression is the same.
In general, infix notation is the most common notation used in mathematics and is the
notation most commonly used by humans to write arithmetic expressions. However,
prefix and postfix notations are more commonly used in computer programming, as they
can be more easily evaluated using a stack-based algorithm.
Step 1: Push "(" onto STACK and add ")" to the end of E.
Step 2: Scan E from left to right and repeat step 3 to 6 for each element of E until
the STACK is empty
End of If Statement
End of If Statement
a) Repeatedly POP from STACK and add to P each operator which have the
same and higher precedence than the operator.
End of If Statement
a) Repeatedly POP from the STACK and add to P each element until a left
parenthesis "(" is encountered.
End of If Statement
Step 7: Exit
Solution : By Following the above algorithm we can convert Infix notation (A-b*C)
into Postfix notation
Postfix
Element Description STACK
Expression (P)
Example 2: Convert the Infix Notation X+Y* Z^P-(X/Y+Z) into Postfix Notation
Elemen Postfix
Stack
t Expression(P)
( (
X ( X
+ (+ X
Y (+ XY
* (+* XY
z (+* XYZ
^ (+*^ XYZ
P (+*^ XYZP
(-
- XYZP^*+
- has lower precedence than ^ and * and same priority with +, then we pop
all the operators having high and equal priority to current operator and
PUSH - into STACK
( (-( XYZP^*+
X (-( XYZP^*+X
/ (-(/ XYZP^*+X
Y (-(/ XYZP^*+XY
+ (-(+ XYZP^*+XY/
Z (-(+ XYZP^*+XY/Z
(-
) XYZP^*+XY/Z+
Removes all the operators and add to expression until "(" is encountered
) XYZP^*+XY/Z+-
Click Here for Program in C to Convert Infix Expression into Postfix Expression
Solution:
Elemen
Description Stack
t
8 PUSH(8) 8
7 PUSH(7) 87
20 PUSH(20) 8 7 20
POP(20)
POP(7)
* 8 140
Perform Multipliation (*) on 20 x7 = 140
PUSH(140)
+ POP(140) 148
Elemen
Description Stack
t
POP(8)
PUSH(148)
15 PUSH(15) 148 15
3 PUSH(3) 148 15 3
POP(3)
POP(15)
/ 148 5
Perform Division (/) on 15/3=5
PUSH(5)
POP(5)
POP(148)
- 143
Perform Subtraction (-) on 148-5=143
PUSH(143)
Here the result of the expression =143
For example, the prefix notation for the expression "5 + 6" would be "+ 5 6". In this
notation, the plus sign (+) comes before the operands 5 and 6.
Here's another example: the prefix notation for the expression "3 * (4 + 5)" would be "* 3
+ 4 5". In this case, the multiplication operator (*) comes before the operand 3, and the
addition operator (+) comes before the operands 4 and 5.
Prefix notation is also known as Polish notation, after the Polish logician Jan
Åukasiewicz who introduced it in the 1920s. It is often used in computer programming
and in some types of calculators.
Step 2: Scan E1 from left to right and repeat step 3 to 7 for each element of E1
until the STACK is empty.
End of If Statement
End of If Statement
a) Repeatedly pop from STACK and add to P each operator which have
the higher precedence than current operator.
End of If Statement
a) Repeatedly pop from STACK and add to P each element until a right
parenthesis ")" is encountered.
End of If Statement
Step 7: If there is no more element in E1, POP from STACK the remaining
elements and add them to P
Step 9: Exit
Example 2: Convert the Infix Notation E= (A/B+C) - (D+E*F) into Prefix Notation
Solution : Reverse the above Infix Notation first E1= )F*E+D(-)C+B/A( . Now
Convert the Reversed expression into Postfix then again Reverse the Final
Postfix expression
Elemen Postfix
Stack
t Expression(P)
) )
F ) F
* )* F
E )* FE
+ ) + FE*
D ) + FE *D
) FE*D+
-)
C -) FE*D+C
+ -)+ FE*D+C
B -)+ FE*D+CB
/ -)+/ FE*D+CB
A -)+/ FE*D+CBA
-
FE*D+CBA/+-
Expression completed POP element from STACK and add
to P
Now Reverse the Postfix Expression FE*D+CBA/+- . We will get expression after
reversal -+/ABC+D*EF. This is our final Prefix Notation Tesult
===============================================================
Introduction to Queues
A queue is a data structure in computer science that stores elements in a linear
sequence, similar to a stack. However, unlike a stack, elements in a queue are added to
the back and removed from the front. This ordering principle is often referred to as "first-
in, first-out" (FIFO).
Examples of Queue
FIFO stands for "first-in, first-out", which is a principle used in various areas of
computer science and everyday life. The principle is simple - the first item that enters a
queue is the first one to be removed from it. It means that items in a queue are
processed or served in the order that they arrive.
The array representation of a queue is simple and efficient, but it has a fixed size and
may require resizing if the queue exceeds its capacity.
2. Linked list representation of a queue: In this representation, a queue is
implemented using a linked list. Each node in the linked list contains an element and a
pointer to the next node. The front and rear of the queue are represented by two
pointers, front and rear respectively, which point to the first and last nodes of the linked
list. When a new element is added to the queue, it is added to the rear end of the linked
list, and the rear pointer is updated to point to the new node.
Similarly, when an element is removed from the queue, it is removed from the front end
of the linked list, and the front pointer is updated to point to the next node. The linked list
representation of a queue is flexible and dynamic, but it requires more memory
allocation and deallocation than the array representation.
Overall, both array and linked list representations of a queue have their advantages and
disadvantages. The choice between them depends on the specific requirements of the
application, such as memory usage, speed, and flexibility.
Here QUEUE is the name of the queue and MAX is Maximum Size of the queue and
FRONT pointer variable , REAR pointer variable, ITEM is the element to be inserted.
Write "OVERFLOW"
End of If Statement
Set FRONT = 0
End of If Statement
Step 5: END
Program in C to Perform Enqueue Operation using Arrays
( Check this Program on Replit )
#include <stdio.h>
#define MAX_SIZE 10
void enqueue(int);
int queue[MAX_SIZE];
int front = -1, rear = -1;
int main()
{
enqueue(10);
enqueue(20);
enqueue(30);
printf("Printing QUEUE Elements");
for(int i=front;i<=rear;i++)
{
printf("|%d|",queue[i]);
}
return 0;
}
Write "UNDERFLOW"
End of If Statement
ELSE Step 4
End of If Statement
Step 5: END
void dequeue()
{
if (front == -1 )
{
printf("Queue underflow!");
return;
}
if (front==rear)
{
front = -1;
rear = -1;
}
else
{
printf("%d dequeued from queue.", queue[front]);
front++;
}
Applications of Queue
Some of the applications of queues include:
1. Operating systems: Operating systems use queues to manage resources such
as CPU time, memory, and input/output devices. For example, when multiple
processes are competing for CPU time, the operating system uses a queue to
schedule each process in a fair and efficient manner.
2. Network routing: In computer networks, packets of data are sent from one
device to another. Queues are used to manage the flow of these packets,
ensuring that they are delivered in the correct order and without delay. For
example, when packets arrive at a router, they are placed in a queue and
processed in the order they were received.
3. Customer service: Many businesses use queues to manage customer service
requests. For example, when customers call a support hotline, their requests are
placed in a queue and processed in the order they were received. This ensures
that all customers are served fairly and efficiently.
4. Print spooling: When multiple users want to print a document, they are placed
in a queue and processed in the order they were received. This ensures that
each user's document is printed in turn and that no user monopolizes the printer.
5. Call centers: When customers call a call center, their calls are placed in a queue
and handled by an available agent. This ensures that all customers are served in
the order they called and that no calls are lost or ignored.
6. Task scheduling: In software development, queues are used to schedule tasks
such as database updates, report generation, and file processing. For example, a
web application might use a queue to handle requests for generating reports.
Each request is placed in the queue and processed in the order it was received.
=============================================================
Circular Queue
In this tutorial,you will learn the followings
The key advantage of a circular queue over a regular queue is that it can reuse the
empty space created by removing elements from the front of the queue.The circular
queue can be implemented using an array or a linked list data structure, with a front and
rear pointer to keep track of the start and end of the queue.
Basic Operations of Circular Queue
Enqueue: Adding an element to the back of the circular queue. This operation is
also called "Insert" or "Push".
Dequeue: Removing an element from the front of the circular queue. This
operation is also called "Delete" or "Pop".
Front: Return the element at the front of the circular queue without removing it.
Rear: Return the element at the back of the circular queue without removing it.
isEmpty: Check if the circular queue is empty or not.
isFull: Check if the circular queue is full or not.
Size: Return the number of elements present in the circular queue.
Clear: Empty the entire circular queue by removing all the elements.
Write "Overflow"
End of If Statement
Step 2 : If FRONT == -1 then (Is Queue Empty)
End of If Statement
Set REAR = 0
Else
Step 5 : END
Write "UNDERFLOW"
End of If Statement
End of If Statement
Set FRONT = 0
Else
End of If Statement
Step 5 : End
void dequeue()
{
if (front == -1)
{
printf("Queue is empty");
}
int data = queue[front];
if (front == rear)
{
front = rear = -1;
printf("Element Removed from the Queue");
void display()
{
if (front == -1)
{
printf("Queue is empty");
}
printf("Queue elements are: ");
if (rear >= front)
{
for (int i = front; i <= rear; i++)
{
printf("%d ", queue[i]);
}
}
else
{
for (int i = front; i < MAX_SIZE; i++)
{
printf("%d ", queue[i]);
}
for (int i = 0; i <= rear; i++)
{
printf("%d ", queue[i]);
}
}
======================================================
6. Depth or Level: The depth(or level) of a node is equal to the number of edges
from tree's root node.
7. Height: The height of the node is the length of the path from that node to the
deepest node in the tree.
The depth and height of a tree are two important concepts in the study of trees.
The depth of a node is the number of edges from the root node to that node. The
depth(or level) of a node is equal to the number of edges from tree's root node. The
depth of the root node is 0.
The height of a node is the number of edges from that node to the furthest leaf node. In
other words, it is the length of the longest path from the node to a leaf node. The height
of a leaf node is 0.
The root node 'A' is stored at index 0, its left child 'B' is stored at index 1 (using 2i+1) ,
and its right child 'C' is stored at index 2 (using 2i+2). The left child of 'B', 'D', is stored
at index 3, and the right child of 'B', 'E', is stored at index 4. The left child of 'C', None,
indicates that it has no left child, and its right child 'F' is stored at index 6.
==============================================================
Binary Tree
In this tutorial, you will learn the followings:
In a binary tree, the left child node always has a value less than or equal to its parent
node, while the right child node always has a value greater than or equal to its parent
node. This property makes binary trees useful for searching and sorting data efficiently.
To satisfy the definition of a binary tree, a tree must meet the following conditions:
1. Each node can have at most two children: A binary tree is a tree data structure in
which each node has at most two child nodes. This means that a node can have
either zero, one, or two children.
2. The left child is less than the parent, and the right child is greater than the parent:
In a binary search tree, the values of the nodes in the left subtree are less than
the value of the parent node, while the values of the nodes in the right subtree
are greater than the value of the parent node.
3. The tree is a connected graph: A binary tree is a connected graph, which means
that there is a path from any node to any other node in the tree.
4. The tree has a unique root node: A binary tree has a unique root node, which is
the topmost node in the tree.
5. Each child node has its own subtree: Each node in a binary tree has its own
subtree, which is a subtree that includes all the nodes that are descendants of
the node.
6. The tree is acyclic: A binary tree is acyclic, which means that there are no cycles
in the tree.
7. The tree may be empty: A binary tree may be empty, which means that it has no
nodes.
In the above examples Tree-1 , Tree-2 and Tree-3 are Binary Tree.
Not a Binary Tree
In the above example Tree is not a Binary Tree, because root node having three childs.
Binary trees can be implemented in many different ways, including arrays and linked
lists. One common way to implement a binary tree is to use a linked list, where each
node in the tree points to its left and right child nodes.
2. Full Binary Tree: A full binary tree is a binary tree in which every node has
either 0 or 2 children. In other words, every node in the tree has either two child
nodes or no child nodes.
3. Perfect Binary Tree: A perfect binary tree is a type of binary tree in which all the
leaf nodes are at the same level, and all the internal nodes have exactly two child
nodes. This means that each level of the tree is completely filled, and the total
number of nodes in the tree is a power of two.
1. Height: The height of a binary tree is the number of edges from the root node to
the deepest leaf node.
2. Depth: The depth of a node is the number of edges from the root node to that
node.
1. Inorder traversal: In inorder traversal, we first visit the left subtree, then the root
node, and finally the right subtree. This traversal results in the nodes being
visited in ascending order.
2. Preorder traversal: In preorder traversal, we first visit the root node, then the left
subtree, and finally the right subtree.
3. Postorder traversal: In postorder traversal, we first visit the left subtree, then
the right subtree, and finally the root node.
Inorder Traversal
Inorder traversal is a method of visiting every node in a binary tree in a specific order. In
inorder traversal, we first visit the left subtree, then the root node, and finally the right
subtree. This traversal results in the nodes being visited in ascending order.
Left Subtree
Root
Right Subtree
Here's an example:
In inorder traversal, we visit the nodes in the order: 2, 4, 5, 7, 8, 9, 10.
In this example, we first visit the left subtree of node 7, which consists of the nodes 2, 4,
and 5. We then visit the root node 7, followed by the right subtree, which consists of the
nodes 8, 9, and 10.
INORDER(R)
Exit
Preorder Traversal
Preorder traversal is a method of visiting every node in a binary tree in a specific order.
In preorder traversal, we first visit the root node, then the left subtree, and finally the
right subtree.
Root Node
left Subtree
Right Subtree
Here's an example:
In this example, we first visit the root node 7, followed by the left subtree of node 7,
which consists of the nodes 4, 2, and 5. We then visit the right subtree of node 4, which
consists of the nodes 9, 8, and 10.
PREORDER(R)
Step 5 : Exit
Postorder Traversal
Postorder traversal is a method of visiting every node in a binary tree in a specific order.
In postorder traversal, we first visit the left subtree, then the right subtree, and finally the
root node.
Here's an example:
In postorder traversal, we visit the nodes in the order: 2, 5, 4, 8, 10, 9, 7.
In this example, we first visit the left subtree of node 7, which consists of the nodes 2, 4,
and 5. We then visit the right subtree of node 4, which consists of the nodes 8, 9, and
10, before finally visiting the root node 7.
Postorder traversal is often used to delete a binary tree, as it visits the children before
the parent nodes. It is also a fundamental operation in working with binary trees, as it
allows us to process all the nodes in the tree in a specific order.
POSTORDER(R)
Exit
Step 5 : Exit
struct Node {
int data;
struct Node* left;
struct Node* right;
};
int main() {
// Create a sample binary tree
struct Node* root = newNode(1);
root->left = newNode(2);
root->right = newNode(3);
root->left->left = newNode(4);
root->left->right = newNode(5);
return 0;
}
================================================================
BSTs are commonly used in computer science for applications such as searching and
sorting algorithms, and are also used in databases for indexing and retrieving data.
Here's an example of a simple binary search tree:
In this example, the root node is 8. The left subtree of the root node contains nodes with
values less than 8, and the right subtree contains nodes with values greater than or
equal to 8. The left subtree has a depth of 2, while the right subtree has a depth of 3.
To perform a search in a BST, we start at the root node and compare the target value
with the value stored in the node. If the target value is less than the value stored in the
node, we move to the left child node and repeat the process. If the target value is
greater than the value stored in the node, we move to the right child node and repeat
the process. We continue this process until we find the target value or reach a null
node, indicating that the target value is not in the tree.
For example, if we wanted to search for the value 7 in the tree above, we would start at
the root node 8. Since 7 is less than 8, we move to the left child node 3. Since 7 is
greater than 3, we move to the right child node 6. Since 7 is greater than 6, we move to
the right child node 7, which is the target value.
1. Unique values: A BST can only have unique values in each node. This is
because if there were two nodes with the same value, it would violate the binary
search property.
2. Sorted order: The values in a BST are stored in a sorted order, making it easy
to search for specific values. In-order traversal of the tree will visit the nodes in
ascending order.
3. Balanced tree: A balanced tree is one in which the height of the left and right
subtrees of every node differs by at most one. A balanced BST ensures that
search, insertion, and deletion operations are performed in O(log n) time
complexity on average.
4. Maximum and minimum values: The maximum value in a BST is the rightmost
node, and the minimum value is the leftmost node.
1. Efficient searching: BSTs allow for efficient searching of data. Due to the sorted
order of the tree, search operations can be performed in O(log n) time complexity
on average. This makes BSTs an excellent choice for applications that require
frequent searching, such as database indexing and retrieval.
2. Efficient insertion and deletion: BSTs also allow for efficient insertion and
deletion of data. When adding or removing a node from a BST, the tree is
automatically rebalanced to maintain the binary search property. This ensures
that the tree remains efficient even after many insertions and deletions.
3. Memory efficiency: BSTs can be implemented using pointers or references,
which allows for efficient use of memory. Compared to other data structures,
such as arrays or linked lists, BSTs require less memory to store the same
amount of data.
4. Sorted order: The sorted order of a BST makes it easy to perform operations
that require the data to be sorted, such as finding the maximum or minimum
value, or performing in-order traversal.
5. Flexibility: BSTs are highly flexible and can be adapted to suit a wide range of
applications. For example, a BST can be used to implement a priority queue, a
symbol table, or a balanced binary tree.
In summary, BSTs offer several advantages over other data structures, including
efficient searching, efficient insertion and deletion, memory efficiency, sorted order, and
flexibility. These properties make BSTs a popular choice for a wide range of
applications in computer science and beyond.
1. Search: Searching for a value in a BST involves comparing the value with the
value at the root node. If the value is less than the root, the search continues in
the left subtree. If the value is greater than the root, the search continues in the
right subtree. The search continues recursively until the value is found or it is
determined that the value does not exist in the tree. The time complexity of a
search operation in a balanced BST is O(log n), where n is the number of nodes
in the tree.
2. Insertion: Inserting a value into a BST involves finding the correct position for
the value in the tree. The value is inserted as a new leaf node in the correct
position according to the binary search property. The time complexity of an
insertion operation in a balanced BST is also O(log n).
3. Deletion: Deleting a value from a BST involves first searching for the value in the
tree. If the value is found, there are three cases to consider:
b. If the node has one child, the child takes the place of the deleted node.
c. If the node has two children, then, find the inorder successor of the node to be
deleted. then replace the deleted node with the inorder successor node and
rebalance the tree.
4. Traversal: Traversing a BST involves visiting all the nodes in the tree in a
specific order. There are three main methods of traversing a BST:
a. In-order traversal: In-order traversal visits the left subtree, then the root node,
then the right subtree. In a BST, this results in the nodes being visited in
ascending order of value.
b. Pre-order traversal: Pre-order traversal visits the root node, then the left
subtree, then the right subtree.
c. Post-order traversal: Post-order traversal visits the left subtree, then the right
subtree, then the root node.
Algorithm to Search for a Node with Given Value in a Binary Search Tree
Step 1: Start at the root node of the BST.
Step 2: Compare the value of the root node with the value being searched for.
Step 3: If the value being searched for is equal to the value of the root node,
return the root node as the node being searched for.
Step 4 : If the value being searched for is less than the value of the root node,
search the left subtree of the root node.
Step 5 : If the value being searched for is greater than the value of the root node,
search the right subtree of the root node.
Step 6 : Repeat steps 2-5 recursively until the value being searched for is found
or it is determined that the value does not exist in the BST.
Step 7 : If the value being searched for is not found in the BST, return null to
indicate that the node does not exist in the tree.
For Example,Consider the Binary Search Tree given below and Search 5 in the Tree
Insertion of New Node in Binary Search Tree
Inserting a value into a BST involves finding the correct position for the value in the tree.
The value is inserted as a new leaf node in the correct position according to the binary
search property. The time complexity of an insertion operation in a balanced BST is also
O(log n).
Algorithm to Insert New Node with Given Value into Binary Search Tree
Step 1 : Start at the root node of the BST.
Step 2: Compare the value of the new node with the value of the root node.
Step 3 : If the value of the new node is less than the value of the root node, go to
the left subtree of the root node.
Step 4 : If the value of the new node is greater than the value of the root node, go
to the right subtree of the root node.
Step 6 : Insert the new node as a leaf node at the appropriate position in the BST.
For Example, Insert the 11 as New Node in the Binary Search Tree Given Below
Deletion in Binary Search Tree
Deleting a value from a BST involves first searching for the value in the tree. If the value
is found, there are three cases to consider:
b. If the node has one child, the child takes the place of the deleted node.
c. If the node has two children, then, find the inorder successor of the node to be
deleted. then replace the deleted node with the inorder successor node and rebalance
the tree.
Step 2 : Compare the value of the node to be deleted with the value of the root node.
Step 3 : If the value of the node to be deleted is less than the value of the root node, go
to the left subtree of the root node.
Step 4 : If the value of the node to be deleted is greater than the value of the root node,
go to the right subtree of the root node.
Step 5 : If the value of the node to be deleted is equal to the value of the root node,
perform the deletion operation.
Step 6 : If the node to be deleted is a leaf node or has only one child, perform the
deletion operation directly.
Step 7 : If the node to be deleted has two children, find the minimum value in the right
subtree to replace it with and perform the deletion operation.
Case 1 : If the node has no childern, it is simply removed from the tree.
Case 2 : If the node has no childern, it is simply removed from the tree.
Case 3 : If the node has two children, then, find the inorder successor of the node to be
deleted. then replace the deleted node with the inorder successor node and rebalance
the tree. Follow the following Steps to delete node havig 2 childerns.
struct Node {
int data;
struct Node* left;
struct Node* right;
};
int main()
{
struct Node* root = NULL;
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 70);
root = insert(root, 60);
root = insert(root, 80);
return 0;
}