0% found this document useful (0 votes)
35 views121 pages

Emp Skills-2 Notes

The document outlines the course objectives, outcomes, units, and textbooks for the Employability Skills-II course offered in the 5th semester of the B.Voc Cyber Security and Software Development programs at St. Vincent Pallotti College of Engineering & Technology. The course aims to develop proficiency in key aptitude areas and cover topics like data structures, algorithms, linked lists, searching techniques, sorting, and graph and tree data structures over 4 units spanning 50 hours. The recommended textbooks include introductions to algorithms, data structures made easy, and a textbook on data structures and algorithms.

Uploaded by

Vanshika Jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views121 pages

Emp Skills-2 Notes

The document outlines the course objectives, outcomes, units, and textbooks for the Employability Skills-II course offered in the 5th semester of the B.Voc Cyber Security and Software Development programs at St. Vincent Pallotti College of Engineering & Technology. The course aims to develop proficiency in key aptitude areas and cover topics like data structures, algorithms, linked lists, searching techniques, sorting, and graph and tree data structures over 4 units spanning 50 hours. The recommended textbooks include introductions to algorithms, data structures made easy, and a textbook on data structures and algorithms.

Uploaded by

Vanshika Jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 121

ST.

VINCENT PALLOTTI COLLEGE OF ENGINEERING & TECHNOLOGY, NAGPUR


(An autonomous institution affiliated to Rashtrasant Tukadoji Maharaj Nagpur University)
B.VOC. Scheme of Examination & Syllabus 2022-23
CYBER SECURITY,SOFTWARE DEVELOPMENT

FIFTH SEMESTER

COURSE CODE COURSE NAME TH PR CREDITS EVALUATION


CA ESE TOTAL
CS502T,SD502T Employability Skills-II 3 3
30 70 100
Space
COURSE OBJECTIVES COURSE
OUTCOMES
 To understand Data Structures & Algorithms At the end of the course,
students will be able to:
 Develop proficiency in key aptitude areas, such as quantitative aptitude, logical reasoning,
and verbal ability. CO1. Understand Data
Structures & Algorithms
CO2. Understand Linked
Lists & searching
techniques
CO3. Understand
Structures sorting
CO4: Understand Graph
and Tree Data Structure

UNIT I: Data Structures & Algorithms [10 HRS]

Overview, Algorithms Basics, Programming, Data Structures & Algorithm Basic Concepts, Data Structures and
Types, Data Structures and Algorithms – Arrays.

UNIT II: Linked Lists & searching techniques [15 HRS]

Introduction to stack & Queue, Linked List basics, Doubly linked list, Circular Linked list, Stack & Queue, Searching
Techniques, Linear search, Binary Search, Interpolation Search, Hash table

UNIT III: Data Structure Sorting [10 HRS]

Sorting Techniques, Algorithms ,Bubble Sort, Insertion sort, Selection Sort,Merge Sort, Shell Sort, Quick Sort.

UNIT IV: Graph and Tree Data Structure [15 HRS]


Depth First Traversal, Breadth first Traversal, Tree Data Structure ,Tree Traversal, Binary Search tree, AVL tree, B
tree, Spanning tree, Tries, Heap

TEXT/ REFERENCE BOOKS

S.N Title Authors Edition Publisher

1 Introduction to Algorithms Thomas H. Cormen, Charles E. Leiserson, 3rd PHI


Ronald L. Rivest and Clifford Stein.

2. Data strctures & Algorithms made Narsimha Karumanchi 1st Career monk
easy

3. Data Structure & algorithms Prof. Dippanita Mondal 1st Everest publishing house
space

1
UNIT I: Data Structures & Algorithms [10 HRS]

Overview, Algorithms Basics, Programming, Data Structures & Algorithm Basic Concepts, Data Structures and
Types, Data Structures and Algorithms – Arrays.

1.1 Data Structures & Algorithms- Overview


Data Structures are the programmatic way of storing data so that data can be used efficiently. Almost every enterprise
application uses various types of data structures in one or the other way. This tutorial will give you a great
understanding on Data Structures needed to understand the complexity of enterprise level applications and need of
algorithms, and data structures.
Why to Learn Data Structure and Algorithms?
As applications are getting complex and data rich, there are three common problems that applications face now-a-
days.

 Data Search − Consider an inventory of 1 million(106) items of a store. If the application is to search an item,
it has to search an item in 1 million(106) items every time slowing down the search. As data grows, search
will become slower.

 Processor speed − Processor speed although being very high, falls limited if the data grows to billion records.

 Multiple requests − As thousands of users can search data simultaneously on a web server, even the fast server
fails while searching the data.
To solve the above-mentioned problems, data structures come to rescue. Data can be organized in a data structure in
such a way that all items may not be required to be searched, and the required data can be searched almost instantly.
Applications of Data Structure and Algorithms
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the
desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be
implemented in more than one programming language.
From the data structure point of view, following are some important categories of algorithms −

 Search − Algorithm to search an item in a data structure.

 Sort − Algorithm to sort items in a certain order.

 Insert − Algorithm to insert item in a data structure.

 Update − Algorithm to update an existing item in a data structure.

 Delete − Algorithm to delete an existing item from a data structure.


The following computer problems can be solved using Data Structures −

 Fibonacci number series

 Knapsack problem

 Tower of Hanoi

 All pair shortest path by Floyd-Warshall

 Shortest path by Dijkstra

 Project scheduling

2
Data Structure is a systematic way to organize data in order to use it efficiently. Following terms are the
foundation terms of a data structure.

 Interface − Each data structure has an interface. Interface represents the set of operations that a data
structure supports. An interface only provides the list of supported operations, type of parameters they can
accept and return type of these operations.

 Implementation − Implementation provides the internal representation of a data structure. Implementation


also provides the definition of the algorithms used in the operations of the data structure.

Characteristics of a Data Structure


 Correctness − Data structure implementation should implement its interface correctly.

 Time Complexity − Running time or the execution time of operations of data structure must be as small as
possible.

 Space Complexity − Memory usage of a data structure operation should be as little as possible.
Need for Data Structure
As applications are getting complex and data rich, there are three common problems that applications face now-a-
days.

 Data Search − Consider an inventory of 1 million(106) items of a store. If the application is to search an item,
it has to search an item in 1 million(106) items every time slowing down the search. As data grows, search
will become slower.

 Processor speed − Processor speed although being very high, falls limited if the data grows to billion records.

 Multiple requests − As thousands of users can search data simultaneously on a web server, even the fast server
fails while searching the data.
Basic Terminology
 Data − Data are values or set of values.

 Data Item − Data item refers to single unit of values.

 Group Items − Data items that are divided into sub items are called as Group Items.

 Elementary Items − Data items that cannot be divided are called as Elementary Items.

 Attribute and Entity − An entity is that which contains certain attributes or properties, which may be assigned
values.

 Entity Set − Entities of similar attributes form an entity set.

 Field − Field is a single elementary unit of information representing an attribute of an entity.

 Record − Record is a collection of field values of a given entity.

 File − File is a collection of records of the entities in a given entity set.

What is algorithm?
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the
desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be
implemented in more than one programming language.
From the data structure point of view, following are some important categories of algorithms −

 Search − Algorithm to search an item in a data structure.

 Sort − Algorithm to sort items in a certain order.

 Insert − Algorithm to insert item in a data structure.


3
 Update − Algorithm to update an existing item in a data structure.

 Delete − Algorithm to delete an existing item from a data structure.

Characteristics of an Algorithm
Not all procedures can be called an algorithm. An algorithm should have the following characteristics –

 Unambiguous − Algorithm should be clear and unambiguous. Each of its steps (or phases), and their
inputs/outputs should be clear and must lead to only one meaning.

 Input − An algorithm should have 0 or more well-defined inputs.

 Output − An algorithm should have 1 or more well-defined outputs, and should match the desired output.

 Finiteness − Algorithms must terminate after a finite number of steps.

 Feasibility − Should be feasible with the available resources.

 Independent − An algorithm should have step-by-step directions, which should be independent of any
programming code.

How to Write an Algorithm?


There are no well-defined standards for writing algorithms. Rather, it is problem and resource dependent. Algorithms
are never written to support a particular programming code.
As we know that all programming languages share basic code constructs like loops (do, for, while), flow-control (if-
else), etc. These common constructs can be used to write an algorithm.
We write algorithms in a step-by-step manner, but it is not always the case. Algorithm writing is a process and is
executed after the problem domain is well-defined. That is, we should know the problem domain, for which we are
designing a solution.

Problem − Design an algorithm to add two numbers and display the result.

 Step 1 − START

 Step 2 − declare three integers a, b & c

 Step 3 − define values of a & b

 Step 4 − add values of a & b

 Step 5 − store output of step 4 to c

 Step 6 − print c

 Step 7 – STOP
Algorithms tell the programmers how to code the program. Alternatively, the algorithm can be written as −

 Step 1 − START ADD

 Step 2 − get values of a & b

 Step 3 − c ← a + b

 Step 4 − display c

 Step 5 – STOP

In design and analysis of algorithms, usually the second method is used to describe an algorithm. It makes it easy for
the analyst to analyze the algorithm ignoring all unwanted definitions. He can observe what operations are being used
and how the process is flowing.

4
We design an algorithm to get a solution of a given problem. A problem can be solved in more than one ways.

Hence, many solution algorithms can be derived for a given problem. The next step is to analyze those proposed
solution algorithms and implement the best suitable solution.

Algorithm Analysis

Efficiency of an algorithm can be analyzed at two different stages, before implementation and after implementation.
They are the following −

 A Priori Analysis − This is a theoretical analysis of an algorithm. Efficiency of an algorithm is measured by


assuming that all other factors, for example, processor speed, are constant and have no effect on the
implementation.
 A Posterior Analysis − This is an empirical analysis of an algorithm. The selected algorithm is implemented
using programming language. This is then executed on target computer machine. In this analysis, actual
statistics like running time and space required, are collected.

We shall learn about a priori algorithm analysis. Algorithm analysis deals with the execution or running time of
various operations involved. The running time of an operation can be defined as the number of computer instructions
executed per operation.

Algorithm Complexity

Suppose X is an algorithm and n is the size of input data, the time and space used by the algorithm X are the two main
factors, which decide the efficiency of X.

 Time Factor − Time is measured by counting the number of key operations such as comparisons in the
sorting algorithm.
 Space Factor − Space is measured by counting the maximum memory space required by the algorithm.

The complexity of an algorithm f(n) gives the running time and/or the storage space required by the algorithm in terms
of n as the size of input data.

Space Complexity

Space complexity of an algorithm represents the amount of memory space required by the algorithm in its life cycle.
The space required by an algorithm is equal to the sum of the following two components −

 A fixed part that is a space required to store certain data and variables, that are independent of the size of the
problem. For example, simple variables and constants used, program size, etc.
 A variable part is a space required by variables, whose size depends on the size of the problem. For example,
dynamic memory allocation, recursion stack space, etc.

5
Space complexity S(P) of any algorithm P is S(P) = C + SP(I), where C is the fixed part and S(I) is the variable part of
the algorithm, which depends on instance characteristic I. Following is a simple example that tries to explain the
concept −

Algorithm: SUM(A, B)
Step 1 - START
Step 2 - C ← A + B + 10
Step 3 - Stop

Here we have three variables A, B, and C and one constant. Hence S(P) = 1 + 3. Now, space depends on data types of
given variables and constant types and it will be multiplied accordingly.

Time Complexity

Time complexity of an algorithm represents the amount of time required by the algorithm to run to completion. Time
requirements can be defined as a numerical function T(n), where T(n) can be measured as the number of steps,
provided each step consumes constant time.

For example, addition of two n-bit integers takes n steps. Consequently, the total computational time is T(n) = c ∗ n,
where c is the time taken for the addition of two bits. Here, we observe that T(n) grows linearly as the input size
increases.

Data Structures and Types

Data structures are introduced in order to store, organize and manipulate data in programming languages. They are
designed in a way that makes accessing and processing of the data a little easier and simpler. These data structures are
not confined to one particular programming language; they are just pieces of code that structure data in the memory.
Data types are often confused as a type of data structures, but it is not precisely correct even though they are referred
to as Abstract Data Types. Data types represent the nature of the data while data structures are just a collection of
similar or different data types in one.

There are usually just two types of data structures −

 Linear
 Non-Linear

Linear Data Structures

The data is stored in linear data structures sequentially. These are rudimentary structures since the elements are stored
one after the other without applying any mathematical operations.

6
Linear data structures are usually easy to implement but since the memory allocation might become complicated, time
and space complexities increase. Few examples of linear data structures include −

 Arrays
 Linked Lists
 Stacks
 Queues

Based on the data storage methods, these linear data structures are divided into two sub-types. They
are− static and dynamic data structures.

Static Linear Data Structures

In Static Linear Data Structures, the memory allocation is not scalable. Once the entire memory is used, no more space
can be retrieved to store more data. Hence, the memory is required to be reserved based on the size of the program.
This will also act as a drawback since reserving more memory than required can cause a wastage of memory blocks.

The best example for static linear data structures is an array.

Dynamic Linear Data Structures

In Dynamic linear data structures, the memory allocation can be done dynamically when required. These data
structures are efficient considering the space complexity of the program.

Few examples of dynamic linear data structures include: linked lists, stacks and queues.

Non-Linear Data Structures

Non-Linear data structures store the data in the form of a hierarchy. Therefore, in contrast to the linear data structures,
the data can be found in multiple levels and are difficult to traverse through.

However, they are designed to overcome the issues and limitations of linear data structures. For instance, the main
disadvantage of linear data structures is the memory allocation. Since the data is allocated sequentially in linear data
structures, each element in these data structures uses one whole memory block. However, if the data uses less memory
than the assigned block can hold, the extra memory space in the block is wasted. Therefore, non-linear data structures
are introduced. They decrease the space complexity and use the memory optimally

Few types of non-linear data structures are −

 Graphs
 Trees
 Tries
 Maps

7
Data Structures and Algorithms – Arrays

Array is a type of linear data structure that is defined as a collection of elements with same or different data types.
They exist in both single dimension and multiple dimensions. These data structures come into picture when there is a
necessity to store multiple elements of similar nature together at one place.

The difference between an array index and a memory address is that the array index acts like a key value to label the
elements in the array. However, a memory address is the starting address of free memory available.

Following are the important terms to understand the concept of Array.

 Element − Each item stored in an array is called an element.


 Index − Each location of an element in an array has a numerical index, which is used to identify the element.

Syntax

Creating an array in C and C++ programming languages –

data_type array_name[array_size] = {elements separated using commas}

or,
data_type array_name[array_size];

Creating an array in JAVA programming language –

data_type[] array_name = {elements separated by commas}

or,
data_type array_name = new data_type[array_size];

Need for Arrays

Arrays are used as solutions to many problems from the small sorting problems to more complex problems like
travelling salesperson problem. There are many data structures other than arrays that provide efficient time and space
complexity for these problems, so what makes using arrays better? The answer lies in the random access lookup time.

Arrays provide O(1) random access lookup time. That means, accessing the 1st index of the array and the 1000th index
of the array will both take the same time. This is due to the fact that array comes with a pointer and an offset value.
The pointer points to the right location of the memory and the offset value shows how far to look in the said memory.

8
array_name[index]
| |
Pointer Offset

Therefore, in an array with 6 elements, to access the 1st element, array is pointed towards the 0th index. Similarly, to
access the 6th element, array is pointed towards the 5th index.

Array Representation
Arrays are represented as a collection of buckets where each bucket stores one element. These buckets are indexed
from ‘ 0’ to ‘ n-1’ , where n is the size of that particular array. For example, an array with size 10 will have buckets
indexed from 0 to 9.

This indexing will be similar for the multidimensional arrays as well. If it is a 2-dimensional array, it will have sub-
buckets in each bucket. Then it will be indexed as array_name[m][n], where m and n are the sizes of each level in the
array.

As per the above illustration, following are the important points to be considered.

 Index starts with 0.


 Array length is 9 which means it can store 9 elements.
 Each element can be accessed via its index. For example, we can fetch an element at index 6 as 23.

Advantages of array data structure:


 Efficient access to elements: Arrays provide direct and efficient access to any element in the collection.
Accessing an element in an array is an O(1) operation, meaning that the time required to access an element
is constant and does not depend on the size of the array.
 Fast data retrieval: Arrays allow for fast data retrieval because the data is stored in contiguous memory
locations. This means that the data can be accessed quickly and efficiently without the need for complex
data structures or algorithms.
 Memory efficiency: Arrays are a memory-efficient way of storing data. Because the elements of an array
are stored in contiguous memory locations, the size of the array is known at compile time. This means that
memory can be allocated for the entire array in one block, reducing memory fragmentation.
 Versatility: Arrays can be used to store a wide range of data types, including integers, floating-point
numbers, characters, and even complex data structures such as objects and pointers.
 Easy to implement: Arrays are easy to implement and understand, making them an ideal choice for
beginners learning computer programming.

9
 Compatibility with hardware: The array data structure is compatible with most hardware architectures,
making it a versatile tool for programming in a wide range of environments.

Disadvantages of array data structure:


 Fixed size: Arrays have a fixed size that is determined at the time of creation. This means that if the size of
the array needs to be increased, a new array must be created and the data must be copied from the old array to
the new array, which can be time-consuming and memory-intensive.
 Memory allocation issues: Allocating a large array can be problematic, particularly in systems with
limited memory. If the size of the array is too large, the system may run out of memory, which can cause the
program to crash.
 Insertion and deletion issues: Inserting or deleting an element from an array can be inefficient and time-
consuming because all the elements after the insertion or deletion point must be shifted to accommodate the
change.
 Wasted space: If an array is not fully populated, there can be wasted space in the memory allocated for the
array. This can be a concern if memory is limited.
 Limited data type support: Arrays have limited support for complex data types such as objects and
structures, as the elements of an array must all be of the same data type.
 Lack of flexibility: The fixed size and limited support for complex data types can make arrays inflexible
compared to other data structures such as linked lists and trees.

Basic Operations in the Arrays

The basic operations in the Arrays are insertion, deletion, searching, display, traverse, and update. These operations
are usually performed to either modify the data in the array or to report the status of the array.

Following are the basic operations supported by an array.

 Traverse − print all the array elements one by one.


 Insertion − Adds an element at the given index.
 Deletion − Deletes an element at the given index.
 Search − Searches an element using the given index or by the value.
 Update − Updates an element at the given index.
 Display − Displays the contents of the array.

In C, when an array is initialized with size, then it assigns defaults values to its elements in following order.

Data Type Default Value

bool false

char 0

int 0

float 0.0

double 0.0f

void

wchar_t 0

10
Insertion Operation

In the insertion operation, we are adding one or more elements to the array. Based on the requirement, a new element
can be added at the beginning, end, or any given index of array. This is done using input statements of the
programming languages.

Algorithm

Following is an algorithm to insert elements into a Linear Array until we reach the end of the array −

1. Start
2. Create an Array of a desired datatype and size.
3. Initialize a variable ‘i’ as 0.
4. Enter the element at ith index of the array.
5. Increment i by 1.
6. Repeat Steps 4 & 5 until the end of the array.
7. Stop

Here, we see a practical implementation of insertion operation, where we add data at the end of the array −

Example

Implementations of this operation in C language −


#include <stdio.h>
int main(){
int LA[3], i;
printf("Array Before Insertion:\n");
for(i = 0; i < 3; i++)
printf("LA[%d] = %d \n", i, LA[i]);
printf("Inserting Elements.. ");
printf("The array elements after insertion :\n"); // prints array values
for(i = 0; i < 3; i++) {
LA[i] = i + 2;
printf("LA[%d] = %d \n", i, LA[i]);
}
return 0;
}
Output
Array Before Insertion:
LA[0] = 587297216
LA[1] = 32767 LA[2] = 0
Inserting Elements.. The array elements after insertion :
LA[0] = 2
LA[1] = 3
LA[2] = 4

For other variations of array insertion operation

Deletion Operation

In this array operation, we delete an element from the particular index of an array. This deletion operation takes place
as we assign the value in the consequent index to the current index.

11
Algorithm

Consider LA is a linear array with N elements and K is a positive integer such that K<=N. Following is the algorithm
to delete an element available at the Kth position of LA.

1. Start
2. Set J = K
3. Repeat steps 4 and 5 while J < N
4. Set LA[J] = LA[J + 1]
5. Set J = J+1
6. Set N = N-1
7. Stop

Example

Implementations of this operation in C language –


#include <stdio.h>

void main(){

int LA[] = {1,3,5};

int n = 3;

int i;

printf("The original array elements are :\n");

for(i = 0; i<n; i++)

printf("LA[%d] = %d \n", i, LA[i]);

for(i = 1; i<n; i++) {

LA[i] = LA[i+1];

n = n – 1;

printf("The array elements after deletion :\n");

for(i = 0; i<n-1; i++)

printf("LA[%d] = %d \n", i, LA[i]);

Output
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
The array elements after deletion :
LA[0] = 1
LA[1] = 5

Search Operation
Searching an element in the array using a key; The key element sequentially compares every value in the array to
check if the key is present in the array or not.

12
Algorithm

Consider LA is a linear array with N elements and K is a positive integer such that K<=N. Following is the algorithm
to find an element with a value of ITEM using sequential search.

1. Start
2. Set J = 0
3. Repeat steps 4 and 5 while J < N
4. IF LA[J] is equal ITEM THEN GOTO STEP 6
5. Set J = J +1
6. PRINT J, ITEM
7. Stop

Example

Implementations of this operation in C language −


#include <stdio.h>
void main(){
int LA[] = {1,3,5,7,8};
int item = 5, n = 5;
int i = 0, j = 0;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
for(i = 0; i<n; i++) {
if( LA[i] == item ) {
printf("Found element %d at position %d\n", item, i+1);
}
}
}
Output
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
Found element 5 at position 3

Traversal Operation
This operation traverses through all the elements of an array. We use loop statements to carry this out.

Algorithm

Following is the algorithm to traverse through all the elements present in a Linear Array −

1 Start
2. Initialize an Array of certain size and datatype.
3. Initialize another variable ‘i’ with 0.
4. Print the ith value in the array and increment i.
5. Repeat Step 4 until the end of the array is reached.
6. End

13
Example

Implementations of this operation in C language −


#include <stdio.h>
int main(){
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}
Output
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8

Update Operation
Update operation refers to updating an existing element from the array at a given index.

Algorithm

Consider LA is a linear array with N elements and K is a positive integer such that K<=N. Following is the algorithm
to update an element available at the Kth position of LA.

1. Start
2. Set LA[K-1] = ITEM
3. Stop

Example

Implementations of this operation in C language −


#include <stdio.h>
void main(){
int LA[] = {1,3,5,7,8};
int k = 3, n = 5, item = 10;
int i, j;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
LA[k-1] = item;
printf("The array elements after updation :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}

14
Output
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5 LA[3] = 7
LA[4] = 8
The array elements after updation :
LA[0] = 1
LA[1] = 3
LA[2] = 10
LA[3] = 7
LA[4] = 8

Display Operation
This operation displays all the elements in the entire array using a print statement.

Algorithm

1. Start
2. Print all the elements in the Array
3. Stop

Example

Implementations of this operation in C language −


#include <stdio.h>
int main(){
int LA[] = {1,3,5,7,8};
int n = 5;
int i;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}
Output
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8

15
UNIT II: Linked Lists & searching techniques [15 HRS]

Introduction to stack & Queue, Linked List basics, Doubly linked list, Circular Linked list, Stack & Queue, Searching
Techniques, Linear search, Binary Search, Interpolation Search, Hash table

2.1 Linked Lists & searching techniques


A linked list is a collection of “ nodes” connected together via links. These nodes consist of the data to be stored
and a pointer to the address of the next node within the linked list. Searching techniques in C for a linked list typically
involve traversing the list to find a specific element or perform some operation on the list. Here's a basic example of
searching for a specific element in a singly linked list:
WHAT IS STACK?

A stack is an Abstract Data Type (ADT) that is popularly used in most programming languages. It is named stack
because it has the similar operations as the real-world stacks, for example – a pack of cards or a pile of plates, etc.

 A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle. Stack has one end,
whereas the Queue has two ends (front and rear).

 It contains only one pointer top pointer pointing to the topmost element of the stack. Whenever an element is
added in the stack, it is added on the top of the stack, and the element can be deleted only from the stack. In
other words, a stack can be defined as a container in which insertion and deletion can be done from the one
end known as the top of the stack .

A Stack ADT allows all data operations at one end only. At any given time, we can only access the top element of a
stack.

WHAT IS QUEUE?
Queue, like Stack, is also an abstract data structure. The thing that makes queue different from stack is that a queue
is open at both its ends. Hence, it follows FIFO (First-In-First-Out) structure, i.e. the data item inserted first will also
be accessed first. The data is inserted into the queue through one end and deleted from it using the other end.

16
A real-world example of queue can be a single-lane one-way road, where the vehicle enters first, exits first. More real-
world examples can be seen as queues at the ticket windows and bus-stops.

 Representation of Queues

Similar to the stack ADT, a queue ADT can also be implemented using arrays, linked lists, or pointers. As a small
example in this tutorial, we implement queues using a one-dimensional array.

 Basic Operations

Queue operations also include initialization of a queue, usage and permanently deleting the data from the memory.

The most fundamental operations in the queue ADT include

 Insertion operation: enqueue()


 Deletion Operation: dequeue()
 The peek () Operation
 The isFull () Operation
 The isFull() Operation

These are all built-in operations to carry out data manipulation and to check the status of the queue.

Queue uses two pointers − front and rear. The front pointer accesses the data from the front end (helping in
enqueueing) while the rear pointer accesses data from the rear end (helping in dequeuing).

17
Linked List basics
A linked list is a data structure used to store a collection of elements where each element, called a "node," contains
both the data and a reference (or pointer) to the next node in the sequence. Here are the basics of working with linked
lists in C:

 Node Structure

 Creating a Node

 Initializing the Linked List

 Adding Elements

 Traversing the Linked List

 Deleting Elements

There are three types of linked lists −

 Singly Linked List − The nodes only point to the address of the next node in the list.

 Doubly Linked List − The nodes point to the addresses of both previous and next nodes.

 Circular Linked List − The last node in the list will point to the first node in the list. It can either be singly
linked or doubly linked.

 Singly Linked Lists

Singly linked lists contain two “ buckets” in one node; one bucket holds the data and the other bucket holds the
address of the next node of the list. Traversals can be done in one direction only as there is only a single link
between two nodes of the same list.

 Doubly Linked Lists

Doubly Linked Lists contain three “ buckets” in one node; one bucket holds the data and the other buckets
hold the addresses of the previous and next nodes in the list. The list is traversed twice as the nodes in the list
are connected to each other from both sides.

 Circular Linked Lists

Circular linked lists can exist in both singly linked list and doubly linked list.

Since the last node and the first node of the circular linked list are connected, the traversal in this linked list
will go on forever until it is broken.

18
Linear Search
Linear Search is defined as a sequential search algorithm that starts at one end and goes through each element
of a list until the desired element is found, otherwise the search continues till the end of the data set . Linear search is
a very simple search algorithm. In this type of search, a sequential search is made over all items one by one. Every
item is checked and if a match is found then that particular item is returned, otherwise the search continues till the end
of the data collection.

How Does Linear Search Algorithm Work?

In Linear Search Algorithm,


 Every element is considered as a potential match for the key and checked for the same.
 If any element is found equal to the key, the search is successful and the index of that element is returned.
 If no element is found equal to the key, the search yields “ No match found” .
For example: Consider the array arr [] = {10, 50, 30, 70, 80, 20, 90, 40} and key = 30

Binary Search
Binary Search is defined as a searching algorithm used in a sorted array by repeatedly dividing the search
interval in half. The idea of binary search is to use the information that the array is sorted and reduce the time
complexity to O (log N).

19
 How does Binary Search work?

To understand the working of binary search, consider the following illustration:


Consider an array arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}, and the target = 23.

 First Step: Calculate the mid and compare the mid element with the key. If the key is less than mid
element, move to left and if it is greater than the mid then move search space to the right.

 Key is less than the current mid 56. The search space moves to the left.

20
 Second Step: If the key matches the value of the mid element, the element is found and stop
search.

Interpolation search

 Interpolation search is an improved variant of binary search. This search algorithm works on the probing
position of the required value. For this algorithm to work properly, the data collection should be in a sorted
form and equally distributed.
 Binary search has a huge advantage of time complexity over linear search. Linear search has worst-case
complexity of Ο(n) whereas binary search has Ο(log n).
 There are cases where the location of target data may be known in advance. For example, in case of a
telephone directory, if we want to search the telephone number of Morphius. Here, linear search and even
binary search will seem slow as we can directly jump to memory space where the names start from 'M' are
stored.

 Positioning in Binary Search

In binary search, if the desired data is not found then the rest of the list is divided in two parts, lower and
higher. The search is carried out in either of them.

Even when the data is sorted, binary search does not take advantage to probe the position of the desired data.

21
 Position Probing in Interpolation Search

Interpolation search finds a particular item by computing the probe position. Initially, the probe position is
the position of the middle most item of the collection.

Hash Table

 Hash Table is a data structure which stores data in an associative manner. In a hash table, data is stored in an
array format, where each data value has its own unique index value. Access of data becomes very fast if we
know the index of the desired data.
 Thus, it becomes a data structure in which insertion and search operations are very fast irrespective of the size
of the data. Hash Table uses an array as a storage medium and uses hash technique to generate an index where
an element is to be inserted or is to be located from.

 Hashing

Hashing is a technique to convert a range of key values into a range of indexes of an array. We're going to use
modulo operator to get a range of key values. Consider an example of hash table of size 20, and the following items
are to be stored. Item are in the (key,value) format.

 (1,20)
 (2,70)
 (42,80)
 (4,25)
 (12,44)
 (14,32)
 (17,11)
 (13,78)
 (37,98)

22
 Linear Probing

As we can see, it may happen that the hashing technique is used to create an already used index of the array. In such
a case, we can search the next empty location in the array by looking into the next cell until we find an empty cell.
This technique is called linear probing.

Sr.No. Key Hash Array Index After Linear Probing, Array Index

1 1 1 % 20 = 1 1 1

2 2 2 % 20 = 2 2 2

3 42 42 % 20 = 2 2 3

4 4 4 % 20 = 4 4 4

 Data Item

 Define a data item having some data and key, based on which the search is to be conducted in a hash table.

Example: struct DataItem {


int data;
int key;
};

 Hash Method

 Define a hashing method to compute the hash code of the key of the data item.

Example int hashCode(int key){


return key % SIZE;
}

Basic Operations

Following are the basic primary operations of a hash table.

 Search − Searches an element in a hash table.


 Insert − inserts an element in a hash table.
 Delete − Deletes an element from a hash table.

 Search Operation

Whenever an element is to be searched, compute the hash code of the key passed and locate the element using
that hash code as index in the array. Use linear probing to get the element ahead if the element is not found at the
computed hash code.

 Insert Operation

Whenever an element is to be inserted, compute the hash code of the key passed and locate the index using that
hash code as an index in the array. Use linear probing for empty location, if an element is found at the computed
hash code.

23
 Delete Operation

Whenever an element is to be deleted, compute the hash code of the key passed and locate the index using that
hash code as an index in the array. Use linear probing to get the element ahead if an element is not found at the
computed hash code. When found, store a dummy item there to keep the performance of the hash table intact.

UNIT III: Data Structure Sorting

Sorting Techniques, Algorithms ,Bubble Sort, Insertion sort, Selection Sort,Merge Sort, Shell Sort, Quick Sort.

3.1 Data Structures Sorting

 Introduction to Sorting Techniques -

Sorting refers to rearrangement of a given array or list of elements according to a comparison operator on the
elements. The comparison operator is used to decide the new order of elements in the respective data structure.

Types of Sorting Techniques

There are various sorting algorithms are used in data structures. The following two types of sorting algorithms can
be broadly classified:
Comparison-based: We compare the elements in a comparison-based sorting algorithm)
Non-comparison-based: We do not compare the elements in a non-comparison-based sorting algorithm)

Why Sorting Algorithms are Important

The sorting algorithm is important in Computer Science because it reduces the complexity of a problem. There is a
wide range of applications for these algorithms, including searching algorithms, database algorithms, divide and
conquer methods, and data structure algorithms.

24
In the following sections, we list some important scientific applications where sorting algorithms are used
 When you have hundreds of datasets you want to print, you might want to arrange them in some way.
 Sorting algorithm is used to arrange the elements of a list in a certain order (either ascending or
descending).
 Searching any element in a huge data set becomes easy. We can use Binary search method for search if we
have sorted data. So, Sorting become important here.
 They can be used in software and in conceptual problems to solve more advanced problems.

 Bubble Sort Algorithm

Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are
in the wrong order. This algorithm is not suitable for large data sets as its average and worst-case time complexity
is quite high

In this algorithm,
 traverse from left and compare adjacent elements and the higher one is placed at right side.
 In this way, the largest element is moved to the rightmost end at first.
 This process is then continued to find the second largest and place it and so on until the data is sorted.

How does Bubble Sort Work?


Let us understand the working of bubble sort with the help of the following illustration:
Input: arr[] = {6, 3, 0, 5}
First Pass:
The largest element is placed in its correct position, i.e., the end of the array.

Second Pass:
Place the second largest element at correct position

Third Pass:
Place the remaining two elements at their correct positions.

25
 Total no. of passes: n-1
 Total no. of comparisons: n*(n-1)/2

Implementation of Bubble Sort

// Optimized implementation of Bubble sort


#include <stdbool.h>
#include <stdio.h>

void swap(int* xp, int* yp)


{
int temp = *xp;
*xp = *yp;
*yp = temp;
}

// An optimized version of Bubble Sort


void bubbleSort(int arr[], int n)
{
int i, j;
bool swapped;
for (i = 0; i < n - 1; i++) {
swapped = false;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(&arr[j], &arr[j + 1]);
swapped = true;
}

26
}

// If no two elements were swapped by inner loop,


// then break
if (swapped == false)
break;
}
}

// Function to print an array


void printArray(int arr[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
}

// Driver program to test above functions


int main()
{
int arr[] = { 64, 34, 25, 12, 22, 11, 90 };
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}

Output
Sorted array:
11 12 22 25 34 64 90

 Insertion Sort Algorithm

To sort an array of size N in ascending order iterate over the array and compare the current element
(key) to its predecessor, if the key element is smaller than its predecessor, compare it to the
elements before. Move the greater elements one position up to make space for the swapped element.
How does Insertion Sort Work?

27
Characteristics of Insertion Sort
 This algorithm is one of the simplest algorithms with a simple implementation
 Basically, Insertion sort is efficient for small data values
 Insertion sort is adaptive in nature, i.e. it is appropriate for data sets that are already partially sorted.

Implementation of Insertion Sort Algorithm


// C program for insertion sort
#include <math.h>
#include <stdio.h>

/* Function to sort an array using insertion sort*/


void insertionSort(int arr[], int n)
{
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;

/* Move elements of arr[0..i-1], that are


greater than key, to one position ahead
of their current position */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
28
arr[j + 1] = key;
}
}

// A utility function to print an array of size n


void printArray(int arr[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
/* Driver program to test insertion sort */
int main()
{
int arr[] = { 12, 11, 13, 5, 6 };
int n = sizeof(arr) / sizeof(arr[0]);

insertionSort(arr, n);
printArray(arr, n);

return 0;
}

Output
5 6 11 12 13

When is the Insertion Sort algorithm used?


Insertion sort is used when number of elements is small. It can also be useful when the input array is almost sorted, and only a
few elements are misplaced in a complete big array.

 Selection Sort

The algorithm repeatedly selects the smallest (or largest) element from the unsorted portion of the list and swaps it
with the first element of the unsorted part. This process is repeated for the remaining unsorted portion until the
entire list is sorted.

How does Selection Sort Algorithm work?

Lets consider the following array as an example: arr[] = {64, 25, 12, 22, 11}
First pass:

29
 For the first position in the sorted array, the whole array is traversed from index 0 to 4 sequentially. The
first position where 64 is stored presently, after traversing whole array it is clear that 11 is the lowest value.
 Thus, replace 64 with 11. After one iteration 11, which happens to be the least value in the array, tends to
appear in the first position of the sorted list.

Second Pass:
 For the second position, where 25 is present, again traverse the rest of the array in a sequential manner.
 After traversing, we found that 12 is the second lowest value in the array and it should appear at the second
place in the array, thus swap these values.

Third Pass:
 Now, for third place, where 25 is present again traverse the rest of the array and find the third least value
present in the array.
 While traversing, 22 came out to be the third least value and it should appear at the third place in the array,
thus swap 22 with element present at third position.

Fourth pass:
 Similarly, for fourth position traverse the rest of the array and find the fourth least element in the array
 As 25 is the 4th lowest value hence, it will place at the fourth position.

30
Fifth Pass:
 At last the largest value present in the array automatically get placed at the last position in the array
 The resulted array is the sorted array.

Implementation of Selection Sort Algorithm

// C program for implementation of selection sort


#include <stdio.h>

void swap(int *xp, int *yp)


{
int temp = *xp;
*xp = *yp;
*yp = temp;
}

void selectionSort(int arr[], int n)


{
int i, j, min_idx;

// One by one move boundary of unsorted subarray


for (i = 0; i < n-1; i++)
{
// Find the minimum element in unsorted array
min_idx = i;
for (j = i+1; j < n; j++)
31
if (arr[j] < arr[min_idx])
min_idx = j;

// Swap the found minimum element with the first element


if(min_idx != i)
swap(&arr[min_idx], &arr[i]);
}
}

/* Function to print an array */


void printArray(int arr[], int size)
{
int i;
for (i=0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

// Driver program to test above functions


int main()
{
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr)/sizeof(arr[0]);
selectionSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}

 Merge Sort

Merge sort is defined as a sorting algorithm that works by dividing an array into smaller subarrays, sorting each
subarray, and then merging the sorted subarrays back together to form the final sorted array.

32
Let’ s see how Merge Sort uses Divide and Conquer:

The merge sort algorithm is an implementation of the divide and conquers technique. Thus, it gets completed in
three steps:
1. Divide: In this step, the array/list divides itself recursively into sub-arrays until the base case is reached.
2. Conquer: Here, the sub-arrays are sorted using recursion.
3. Combine: This step makes use of the merge( ) function to combine the sub-arrays into the final sorted
array.

How does Merge Sort work?

Merge sort is a recursive algorithm that continuously splits the array in half until it cannot be further
divided i.e., the array has only one element left (an array with one element is always sorted). Then the
sorted subarrays are merged into one sorted array.
Lets consider an array arr[] = {38, 27, 43, 10}
 Initially divide the array into two equal halves:

 These subarrays are further divided into two halves. Now they become array of unit length that can no
longer be divided and array of unit length are always sorted.

These sorted subarrays are merged together, and we get bigger sorted subarrays.

33
This merging process is continued until the sorted array is built from the smaller subarrays.

The above diagram shows the complete merge sort process for an example array {38, 27, 43, 3, 9, 82, 10}.

Implementation of Merge Sort Algorithm

// C program for Merge Sort


#include <stdio.h>
#include <stdlib.h>

// Merges two subarrays of arr[].


// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
void merge(int arr[], int l, int m, int r)
{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

// Create temp arrays


int L[n1], R[n2];

// Copy data to temp arrays L[] and R[]

34
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];

// Merge the temp arrays back into arr[l..r


i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}

// Copy the remaining elements of L[],


// if there are any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}

// Copy the remaining elements of R[],


// if there are any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}

35
}

// l is for left index and r is right index of the


// sub-array of arr to be sorted
void mergeSort(int arr[], int l, int r)
{
if (l < r) {
int m = l + (r - l) / 2;

// Sort first and second halves


mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);

merge(arr, l, m, r);
}
}

// Function to print an array


void printArray(int A[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}

// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int arr_size = sizeof(arr) / sizeof(arr[0]);

printf("Given array is \n");


printArray(arr, arr_size);

mergeSort(arr, 0, arr_size - 1);

36
printf("\nSorted array is \n");
printArray(arr, arr_size);
return 0;
}

Output :
Given array is
12 11 13 5 6 7
Sorted array is
5 6 7 11 12 13

 QuickSort
QuickSort is a sorting algorithm based on the Divide and Conquer algorithm that picks an element as a pivot and
partitions the given array around the picked pivot by placing the pivot in its correct position in the sorted array.

Implementation of Quick sort

// C code to implement quicksort


#include <stdio.h>
// Function to swap two elements
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
// Partition the array using the last element as the pivot
int partition(int arr[], int low, int high)
{
// Choosing the pivot
int pivot = arr[high];

// Index of smaller element and indicates


// the right position of pivot found so far
int i = (low - 1);

for (int j = low; j <= high - 1; j++) {

37
// If current element is smaller than the pivot
if (arr[j] < pivot) {

// Increment index of smaller element


i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
// The main function that implements QuickSort
// arr[] --> Array to be sorted,
// low --> Starting index,
// high --> Ending index
void quickSort(int arr[], int low, int high)
{
if (low < high) {

// pi is partitioning index, arr[p]


// is now at right place
int pi = partition(arr, low, high);

// Separately sort elements before


// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// Driver code
int main()
{
int arr[] = { 10, 7, 8, 9, 1, 5 };
int N = sizeof(arr) / sizeof(arr[0]);

// Function call
quickSort(arr, 0, N - 1);
printf("Sorted array: \n");
for (int i = 0; i < N; i++)
printf("%d ", arr[i]);
return 0;
}

38
Output

Sorted array:
1 5 7 8 9 10

 Shell sort

Shell sort is mainly a variation of Insertion Sort. In insertion sort, we move elements only one position ahead. When
an element has to be moved far ahead, many movements are involved. The idea of ShellSort is to allow the
exchange of far items. In Shell sort, we make the array h-sorted for a large value of h. We keep reducing the value
of h until it becomes 1. An array is said to be h-sorted if all sublists of every h’ th element are sorted.

UNIT IV: Graph and Tree Data Structure [15 HRS]


Depth First Traversal, Breadth first Traversal, Tree Data Structure ,Tree Traversal, Binary Search tree, AVL tree, B
tree, Spanning tree, Tries, Heap

39
UNIT IV: Graph and Tree Data Structure

 Depth First Traversal – Depth First Search (DFS) algorithm traverses a graph in a depthward motion and
uses a stack to remember to get the next vertex to start a search, when a dead end occurs in any iteration.

As in the example given above, DFS algorithm traverses from S to A to D to G to E to B first, then to F and lastly to
C. It employs the following rules.

 Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Push it in a stack.
 Rule 2 − If no adjacent vertex is found, pop up a vertex from the stack. (It will pop up all the vertices from the
stack, which do not have adjacent vertices.)
 Rule 3 − Repeat Rule 1 and Rule 2 until the stack is empty.

Step Traversal Description

1 Initialize the stack.

40
Mark S as visited and put it onto the stack.
Explore any unvisited adjacent node from S.
2 We have three nodes and we can pick any of
them. For this example, we shall take the
node in an alphabetical order.

Mark A as visited and put it onto the stack.


Explore any unvisited adjacent node from A.
3
Both S and D are adjacent to A but we are
concerned for unvisited nodes only.

Visit D and mark it as visited and put onto


the stack. Here, we have B and C nodes,
4 which are adjacent to D and both are
unvisited. However, we shall again choose
in an alphabetical order.

We choose B, mark it as visited and put onto


the stack. Here B does not have any
5
unvisited adjacent node. So, we pop B from
the stack.

41
We check the stack top for return to the
previous node and check if it has any
6
unvisited nodes. Here, we find D to be on
the top of the stack.

Only unvisited adjacent node is


7 from D is C now. So we visit C, mark it as
visited and put it onto the stack.

As C does not have any unvisited adjacent node so we keep popping the stack until we find a node that has an
unvisited adjacent node. In this case, there's none and we keep popping until the stack is empty.

To know about the implementation of this algorithm in C programming language,

We shall not see the implementation of Depth First Traversal (or Depth First Search) in C programming language. For
our reference purpose, we shall follow our example and take this as our graph model −

Implementation in C

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX 5

struct Vertex {
char label;
bool visited;
};

//stack variables

42
int stack[MAX];
int top = -1;

//graph variables

//array of vertices
struct Vertex* lstVertices[MAX];

//adjacency matrix
int adjMatrix[MAX][MAX];

//vertex count
int vertexCount = 0;

//stack functions

void push(int item) {


stack[++top] = item;
}

int pop() {
return stack[top--];
}

int peek() {
return stack[top];
}

bool isStackEmpty() {
return top == -1;
}

//graph functions

//add vertex to the vertex list


void addVertex(char label) {
struct Vertex* vertex = (struct Vertex*) malloc(sizeof(struct Vertex));
vertex->label = label;
vertex->visited = false;
lstVertices[vertexCount++] = vertex;
}

//add edge to edge array


void addEdge(int start,int end) {
adjMatrix[start][end] = 1;
adjMatrix[end][start] = 1;
}

//display the vertex


void displayVertex(int vertexIndex) {
printf("%c ",lstVertices[vertexIndex]->label);
}

//get the adjacent unvisited vertex


int getAdjUnvisitedVertex(int vertexIndex) {
int i;

for(i = 0; i < vertexCount; i++) {


if(adjMatrix[vertexIndex][i] == 1 && lstVertices[i]->visited == false) {
return i;
}

43
}

return -1;
}

void depthFirstSearch() {
int i;

//mark first node as visited


lstVertices[0]->visited = true;

//display the vertex


displayVertex(0);

//push vertex index in stack


push(0);

while(!isStackEmpty()) {
//get the unvisited vertex of vertex which is at top of the stack
int unvisitedVertex = getAdjUnvisitedVertex(peek());

//no adjacent vertex found


if(unvisitedVertex == -1) {
pop();
} else {
lstVertices[unvisitedVertex]->visited = true;
displayVertex(unvisitedVertex);
push(unvisitedVertex);
}
}

//stack is empty, search is complete, reset the visited flag


for(i = 0;i < vertexCount;i++) {
lstVertices[i]->visited = false;
}
}

int main() {
int i, j;

for(i = 0; i < MAX; i++) { // set adjacency


for(j = 0; j < MAX; j++) // matrix to 0
adjMatrix[i][j] = 0;
}

addVertex('S'); // 0
addVertex('A'); // 1
addVertex('B'); // 2
addVertex('C'); // 3
addVertex('D'); // 4

addEdge(0, 1); // S - A
addEdge(0, 2); // S - B
addEdge(0, 3); // S - C
addEdge(1, 4); // A - D
addEdge(2, 4); // B - D
addEdge(3, 4); // C - D

printf("Depth First Search: ");


depthFirstSearch();

44
return 0;
}

If we compile and run the above program, it will produce the following result −

Output

Depth First Search: S A D B C

 Breadth first Traversal-Breadth First Search (BFS) algorithm traverses a graph in a breadthward motion and
uses a queue to remember to get the next vertex to start a search, when a dead end occurs in any iteration.

As in the example given above, BFS algorithm traverses from A to B to E to F first then to C and G lastly to D. It
employs the following rules.

 Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Insert it in a queue.
 Rule 2 − If no adjacent vertex is found, remove the first vertex from the queue.
 Rule 3 − Repeat Rule 1 and Rule 2 until the queue is empty.

45
Step Traversal Description

1 Initialize the queue.

We start from visiting S (starting node), and


2
mark it as visited.

We then see an unvisited adjacent node


from S. In this example, we have three
3
nodes but alphabetically we choose A, mark
it as visited and enqueue it.

Next, the unvisited adjacent node


4 from S is B. We mark it as visited and
enqueue it.

Next, the unvisited adjacent node


5 from S is C. We mark it as visited and
enqueue it.

46
Now, S is left with no unvisited adjacent
6
nodes. So, we dequeue and find A.

From A we have D as unvisited adjacent


7
node. We mark it as visited and enqueue it.

At this stage, we are left with no unmarked (unvisited) nodes. But as per the algorithm we keep on dequeuing in order
to get all unvisited nodes. When the queue gets emptied, the program is over.

The implementation of this algorithm in C programming language can be

We shall not see the implementation of Breadth First Traversal (or Breadth First Search) in C programming language.
For our reference purpose, we shall follow our example and take this as our graph model −

Implementation in C

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX 5
struct Vertex {
char label;
bool visited;
};
//queue variables

47
int queue[MAX];
int rear = -1;
int front = 0;
int queueItemCount = 0;

//graph variables

//array of vertices
struct Vertex* lstVertices[MAX];

//adjacency matrix
int adjMatrix[MAX][MAX];

//vertex count
int vertexCount = 0;

//queue functions

void insert(int data) {


queue[++rear] = data;
queueItemCount++;
}

int removeData() {
queueItemCount--;
return queue[front++];
}

bool isQueueEmpty() {
return queueItemCount == 0;
}

//graph functions

//add vertex to the vertex list


void addVertex(char label) {

48
struct Vertex* vertex = (struct Vertex*) malloc(sizeof(struct Vertex));
vertex->label = label;
vertex->visited = false;
lstVertices[vertexCount++] = vertex;
}

//add edge to edge array


void addEdge(int start,int end) {
adjMatrix[start][end] = 1;
adjMatrix[end][start] = 1;
}

//display the vertex


void displayVertex(int vertexIndex) {
printf("%c ",lstVertices[vertexIndex]->label);
}

//get the adjacent unvisited vertex


int getAdjUnvisitedVertex(int vertexIndex) {
int i;

for(i = 0; i<vertexCount; i++) {


if(adjMatrix[vertexIndex][i] == 1 && lstVertices[i]->visited == false)
return i;
}

return -1;
}

void breadthFirstSearch() {
int i;

//mark first node as visited


lstVertices[0]->visited = true;

//display the vertex

49
displayVertex(0);

//insert vertex index in queue


insert(0);
int unvisitedVertex;

while(!isQueueEmpty()) {
//get the unvisited vertex of vertex which is at front of the queue
int tempVertex = removeData();

//no adjacent vertex found


while((unvisitedVertex = getAdjUnvisitedVertex(tempVertex)) != -1) {
lstVertices[unvisitedVertex]->visited = true;
displayVertex(unvisitedVertex);
insert(unvisitedVertex);
}

//queue is empty, search is complete, reset the visited flag


for(i = 0;i<vertexCount;i++) {
lstVertices[i]->visited = false;
}
}

int main() {
int i, j;

for(i = 0; i<MAX; i++) { // set adjacency


for(j = 0; j<MAX; j++) // matrix to 0
adjMatrix[i][j] = 0;
}

addVertex('S'); // 0
addVertex('A'); // 1
addVertex('B'); // 2

50
addVertex('C'); // 3
addVertex('D'); // 4

addEdge(0, 1); // S - A
addEdge(0, 2); // S - B
addEdge(0, 3); // S - C
addEdge(1, 4); // A - D
addEdge(2, 4); // B - D
addEdge(3, 4); // C - D

printf("\nBreadth First Search: ");

breadthFirstSearch();

return 0;
}

If we compile and run the above program, it will produce the following result −

Output

Breadth First Search: S A B C D

 Tree Data Structure-A tree is a non-linear abstract data type with a hierarchy-based structure. It consists of
nodes (where the data is stored) that are connected via links. The tree data structure stems from a single node
called a root node and has subtrees connected to the root.

51
Important Terms

Following are the important terms with respect to tree.

 Path − Path refers to the sequence of nodes along the edges of a tree.
 Root − The node at the top of the tree is called root. There is only one root per tree and one path from the root
node to any node.
 Parent − Any node except the root node has one edge upward to a node called parent.
 Child − The node below a given node connected by its edge downward is called its child node.
 Leaf − The node which does not have any child node is called the leaf node.
 Subtree − Subtree represents the descendants of a node.
 Visiting − Visiting refers to checking the value of a node when control is on the node.
 Traversing − Traversing means passing through nodes in a specific order.
 Levels − Level of a node represents the generation of a node. If the root node is at level 0, then its next child
node is at level 1, its grandchild is at level 2, and so on.
 Keys − Key represents a value of a node based on which a search operation is to be carried out for a node.

Types of Trees

There are three types of trees −

 General Trees
 Binary Trees
 Binary Search Trees

General Trees

General trees are unordered tree data structures where the root node has minimum 0 or maximum ‘ n’ subtrees.

The General trees have no constraint placed on their hierarchy. The root node thus acts like the superset of all the
other subtrees.

52
Binary Trees

Binary Trees are general trees in which the root node can only hold up to maximum 2 subtrees: left subtree and right
subtree. Based on the number of children, binary trees are divided into three types.

Full Binary Tree

 A full binary tree is a binary tree type where every node has either 0 or 2 child nodes.

Complete Binary Tree

 A complete binary tree is a binary tree type where all the leaf nodes must be on the same level. However, root
and internal nodes in a complete binary tree can either have 0, 1 or 2 child nodes.

Perfect Binary Tree

 A perfect binary tree is a binary tree type where all the leaf nodes are on the same level and every node except
leaf nodes have 2 children.

Binary Search Trees

Binary Search Trees possess all the properties of Binary Trees including some extra properties of their own, based on
some constraints, making them more efficient than binary trees.

The data in the Binary Search Trees (BST) is always stored in such a way that the values in the left subtree are always
less than the values in the root node and the values in the right subtree are always greater than the values in the root
node, i.e. left subtree < root node ≤ right subtree.
53
Advantages of BST

 Binary Search Trees are more efficient than Binary Trees since time complexity for performing various
operations reduces.
 Since the order of keys is based on just the parent node, searching operation becomes simpler.
 The alignment of BST also favors Range Queries, which are executed to find values existing between two
keys. This helps in the Database Management System.

Disadvantages of BST

The main disadvantage of Binary Search Trees is that if all elements in nodes are either greater than or lesser than the
root node, the tree becomes skewed. Simply put, the tree becomes slanted to one side completely.

This skewness will make the tree a linked list rather than a BST, since the worst case time complexity for searching
operation becomes O(n).

To overcome this issue of skewness in the Binary Search Trees, the concept of Balanced Binary Search Trees was
introduced.

54
Balanced Binary Search Trees

Consider a Binary Search Tree with ‘ m’ as the height of the left subtree and ‘ n’ as the height of the right subtree.
If the value of (m-n) is equal to 0,1 or -1, the tree is said to be a Balanced Binary Search Tree.

The trees are designed in a way that they self-balance once the height difference exceeds 1. Binary Search Trees use
rotations as self-balancing algorithms. There are four different types of rotations: Left Left, Right Right, Left Right,
Right Left.

There are various types of self-balancing binary search trees −

 AVL Trees
 Red Black Trees
 B Trees
 B+ Trees
 Splay Trees
 Priority Search Trees

55
 Tree Traversal-Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all
nodes are connected via edges (links) we always start from the root (head) node. That is, we cannot randomly
access a node in a tree. There are three ways which we use to traverse a tree −

 In-order Traversal
 Pre-order Traversal
 Post-order Traversal

Generally, we traverse a tree to search or locate a given item or key in the tree or to print all the values it contains.

In-order Traversal

In this traversal method, the left subtree is visited first, then the root and later the right sub-tree. We should always
remember that every node may represent a subtree itself.

If a binary tree is traversed in-order, the output will produce sorted key values in an ascending order.

We start from A, and following in-order traversal, we move to its left subtree B.B is also traversed in-order. The
process goes on until all the nodes are visited. The output of in-order traversal of this tree will be −

D→B→E→A→F→C→G

Algorithm

Until all nodes are traversed −

Step 1 − Recursively traverse left subtree.

Step 2 − Visit root node.

Step 3 − Recursively traverse right subtree.

Example

Following are the implementations of this operation in various programming languages −

#include <stdio.h>

#include <stdlib.h>

56
struct node {

int data;

struct node *leftChild;

struct node *rightChild;

};

struct node *root = NULL;

void insert(int data){

struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

57
}//go to right of the tree

else {

current = current->rightChild;

//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

void inorder_traversal(struct node* root){

if(root != NULL) {

inorder_traversal(root->leftChild);

printf("%d ",root->data);

inorder_traversal(root->rightChild);

int main(){

int i;

int array[7] = { 27, 14, 35, 10, 19, 31, 42 };

for(i = 0; i < 7; i++)

insert(array[i]);

printf("\nInorder traversal: ");

inorder_traversal(root);

return 0;

}
Output
Inorder traversal: 10 14 19 27 31 35 42

58
Pre-order Traversal

In this traversal method, the root node is visited first, then the left subtree and finally the right subtree.

We start from A, and following pre-order traversal, we first visit A itself and then move to its left subtree B. B is also
traversed pre-order. The process goes on until all the nodes are visited. The output of pre-order traversal of this tree
will be −

A→B→D→E→C→F→G

Algorithm

Until all nodes are traversed −

Step 1 − Visit root node.

Step 2 − Recursively traverse left subtree.

Step 3 − Recursively traverse right subtree.

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int data;

struct node *leftChild;

59
struct node *rightChild;

};

struct node *root = NULL;

void insert(int data){

struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

60
}//go to right of the tree

else {

current = current->rightChild;

//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

void pre_order_traversal(struct node* root){

if(root != NULL) {

printf("%d ",root->data);

pre_order_traversal(root->leftChild);

pre_order_traversal(root->rightChild);

int main(){

int i;

int array[7] = { 27, 14, 35, 10, 19, 31, 42 };

for(i = 0; i < 7; i++)

insert(array[i]);

printf("\nPreorder traversal: ");

pre_order_traversal(root);

return 0;

61
Output
Preorder traversal: 27 14 10 19 35 31 42

Post-order Traversal

In this traversal method, the root node is visited last, hence the name. First we traverse the left subtree, then the right
subtree and finally the root node.

We start from A, and following pre-order traversal, we first visit the left subtree B. B is also traversed post-order. The
process goes on until all the nodes are visited. The output of post-order traversal of this tree will be −

D→E→B→F→G→C→A

Algorithm

Until all nodes are traversed −

Step 1 − Recursively traverse left subtree.

Step 2 − Recursively traverse right subtree.

Step 3 − Visit root node.

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int data;

struct node *leftChild;

struct node *rightChild;

62
};

struct node *root = NULL;

void insert(int data){

struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

}//go to right of the tree

63
else {

current = current->rightChild;

//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

void post_order_traversal(struct node* root){

if(root != NULL) {

post_order_traversal(root->leftChild);

post_order_traversal(root->rightChild);

printf("%d ", root->data);

int main(){

int i;

int array[7] = { 27, 14, 35, 10, 19, 31, 42 };

for(i = 0; i < 7; i++)

insert(array[i]);

printf("\nPost order traversal: ");

post_order_traversal(root);

return 0;

Output
Post order traversal: 10 19 14 31 42 35 27

64
Implementation

Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all nodes are connected
via edges (links) we always start from the root (head) node. That is, we cannot randomly access a node in a tree. There
are three ways which we use to traverse a tree −

 In-order Traversal
 Pre-order Traversal
 Post-order Traversal

We shall now see the implementation of tree traversal in C programming language here using the following binary
tree −

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int data;

struct node *leftChild;

struct node *rightChild;

};

struct node *root = NULL;

void insert(int data){

struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

65
//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

}//go to right of the tree

else {

current = current->rightChild;

//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

66
}

void pre_order_traversal(struct node* root){

if(root != NULL) {

printf("%d ",root->data);

pre_order_traversal(root->leftChild);

pre_order_traversal(root->rightChild);

void inorder_traversal(struct node* root){

if(root != NULL) {

inorder_traversal(root->leftChild);

printf("%d ",root->data);

inorder_traversal(root->rightChild);

void post_order_traversal(struct node* root){

if(root != NULL) {

post_order_traversal(root->leftChild);

post_order_traversal(root->rightChild);

printf("%d ", root->data);

int main(){

int i;

int array[7] = { 27, 14, 35, 10, 19, 31, 42 };

for(i = 0; i < 7; i++)

insert(array[i]);

67
printf("\nPreorder traversal: ");

pre_order_traversal(root);

printf("\nInorder traversal: ");

inorder_traversal(root);

printf("\nPost order traversal: ");

post_order_traversal(root);

return 0;

Output
Preorder traversal: 27 14 10 19 35 31 42
Inorder traversal: 10 14 19 27 31 35 42
Post order traversal: 10 19 14 31 42 35 27

 Binary Search tree-A Binary Search Tree (BST) is a tree in which all the nodes follow the below-mentioned
properties −

 The left sub-tree of a node has a key less than or equal to its parent node's key.
 The right sub-tree of a node has a key greater than or equal to its parent node's key.

Thus, BST divides all its sub-trees into two segments; the left sub-tree and the right sub-tree and can be defined as −

left_subtree (keys) ≤ node (key) ≤ right_subtree (keys)

Representation

BST is a collection of nodes arranged in a way where they maintain BST properties. Each node has a key and an
associated value. While searching, the desired key is compared to the keys in BST and if found, the associated value is
retrieved.

Following is a pictorial representation of BST −

We observe that the root node key (27) has all less-valued keys on the left sub-tree and the higher valued keys on the
right sub-tree.

Basic Operations

Following are the basic operations of a tree −

 Search − Searches an element in a tree.


68
 Insert − Inserts an element in a tree.
 Pre-order Traversal − Traverses a tree in a pre-order manner.
 In-order Traversal − Traverses a tree in an in-order manner.
 Post-order Traversal − Traverses a tree in a post-order manner.

Defining a Node

Define a node that stores some data, and references to its left and right child nodes.

struct node {
int data;
struct node *leftChild;
struct node *rightChild;
};

Search Operation

Whenever an element is to be searched, start searching from the root node. Then if the data is less than the key value,
search for the element in the left subtree. Otherwise, search for the element in the right subtree. Follow the same
algorithm for each node.

Algorithm
1. START
2. Check whether the tree is empty or not
3. If the tree is empty, search is not possible
4. Otherwise, first search the root of the tree.
5. If the key does not match with the value in the root, search its subtrees.
6. If the value of the key is less than the root value, search the left subtree
7. If the value of the key is greater than the root value, search the right subtree.
8. If the key is not found in the tree, return unsuccessful search.
9. END

Example

Following are the implementations of this operation in various programming languages-

#include <stdio.h>

#include <stdlib.h>

struct node {

int data;

struct node *leftChild, *rightChild;

};

struct node *root = NULL;

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->data = item;

temp->leftChild = temp->rightChild = NULL;

69
return temp;

void insert(int data){

struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

}//go to right of the tree

70
else {

current = current->rightChild;

//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

struct node* search(int data){

struct node *current = root;

printf("\nVisiting elements: ");

while(current->data != data) {

if(current != NULL) {

printf("%d ",current->data);

//go to left tree

if(current->data > data) {

current = current->leftChild;

}//else go to right tree

else {

current = current->rightChild;

//not found

if(current == NULL) {

71
return NULL;

return current;

void printTree(struct node* Node){

if(Node == NULL)

return;

printTree(Node->leftChild);

printf(" --%d", Node->data);

printTree(Node->rightChild);

int main(){

insert(55);

insert(20);

insert(90);

insert(50);

insert(35);

insert(15);

insert(65);

printf("Insertion done\n");

printTree(root);

struct node* k;

k = search(35);

if(k != NULL)

printf("\nElement %d found", k->data);

else

printf("\nElement not found");

72
return 0;

Output
Insertion done
--15 --20 --35 --50 --55 --65 --90
Visiting elements: 55 20 50
Element 35 found

Insert Operation
an elemen is to be inserted, first locate its proper location. Start searching from the root node, then if the data is less
than the key value, search f the empty location in the left subtree and insert the data. Otherwise, search for the empty
location in the right subtree and insert the data.

Aorithm
1 – START
2 – If the tree is empty, insert the first element as the root node of the tree. The following elements are added as the
leaf nodes.
3 – If an element is less than the root value, it is added into the left subtree as a leaf node.
4 – If an element is greater than the root value, it is added into the right subtree as a leaf node.
5 – The final leaf nodes of the tree point to NULL values as their child nodes.
6 – END

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int data;

struct node *leftChild, *rightChild;

};

struct node *root = NULL;

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->data = item;

temp->leftChild = temp->rightChild = NULL;

return temp;

void insert(int data){

73
struct node *tempNode = (struct node*) malloc(sizeof(struct node));

struct node *current;

struct node *parent;

tempNode->data = data;

tempNode->leftChild = NULL;

tempNode->rightChild = NULL;

//if tree is empty

if(root == NULL) {

root = tempNode;

} else {

current = root;

parent = NULL;

while(1) {

parent = current;

//go to left of the tree

if(data < parent->data) {

current = current->leftChild;

//insert to the left

if(current == NULL) {

parent->leftChild = tempNode;

return;

}//go to right of the tree

else {

current = current->rightChild;

74
//insert to the right

if(current == NULL) {

parent->rightChild = tempNode;

return;

void printTree(struct node* Node){

if(Node == NULL)

return;

printTree(Node->leftChild);

printf(" --%d", Node->data);

printTree(Node->rightChild);

int main(){

insert(55);

insert(20);

insert(90);

insert(50);

insert(35);

insert(15);

insert(65);

printf("Insertion done\n");

printTree(root);

return 0;

Output
Insertion done

75
--15 --20 --35 --50 --55 --65 --90

Inorder Traversal

The inorder traversal operation in a Binary Search Tree visits all its nodes in the following order −

 Firstly, we traverse the left child of the root node/current node, if any.
 Next, traverse the current node.
 Lastly, traverse the right child of the current node, if any.

Algorithm
1. START
2. Traverse the left subtree, recursively
3. Then, traverse the root node
4. Traverse the right subtree, recursively.
5. END

Example

Following are the implementations of this operation in various programming languages −

#include <stdio.h>

#include <stdlib.h>

struct node {

int key;

struct node *left, *right;

};

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->key = item;

temp->left = temp->right = NULL;

return temp;

// Inorder Traversal

void inorder(struct node *root){

if (root != NULL) {

76
inorder(root->left);

printf("%d -> ", root->key);

inorder(root->right);

// Insertion operation

struct node *insert(struct node *node, int key){

if (node == NULL) return newNode(key);

if (key < node->key)

node->left = insert(node->left, key);

else

node->right = insert(node->right, key);

return node;

int main(){

struct node *root = NULL;

root = insert(root, 55);

root = insert(root, 20);

root = insert(root, 90);

root = insert(root, 50);

root = insert(root, 35);

root = insert(root, 15);

root = insert(root, 65);

printf("Inorder traversal: ");

inorder(root);

Output
Inorder traversal: 15 -> 20 -> 35 -> 50 -> 55 -> 65 -> 90 ->

77
Preorder Traversal

The preorder traversal operation in a Binary Search Tree visits all its nodes. However, the root node in it is first
printed, followed by its left subtree and then its right subtree.

Algorithm
1. START
2. Traverse the root node first.
3. Then traverse the left subtree, recursively
4. Later, traverse the right subtree, recursively.
5. END

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int key;

struct node *left, *right;

};

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->key = item;

temp->left = temp->right = NULL;

return temp;

// Preorder Traversal

void preorder(struct node *root){

if (root != NULL) {

printf("%d -> ", root->key);

preorder(root->left);

preorder(root->right);

78
// Insertion operation

struct node *insert(struct node *node, int key){

if (node == NULL) return newNode(key);

if (key < node->key)

node->left = insert(node->left, key);

else

node->right = insert(node->right, key);

return node;

int main(){

struct node *root = NULL;

root = insert(root, 55);

root = insert(root, 20);

root = insert(root, 90);

root = insert(root, 50);

root = insert(root, 35);

root = insert(root, 15);

root = insert(root, 65);

printf("Preorder traversal: ");

preorder(root);

Output
Preorder traversal: 55 -> 20 -> 15 -> 50 -> 35 -> 90 -> 65 ->

Postorder Traversal

Like the other traversals, postorder traversal also visits all the nodes in a Binary Search Tree and displays them.
However, the left subtree is printed first, followed by the right subtree and lastly, the root node.

Algorithm
1. START
2. Traverse the left subtree, recursively
3. Traverse the right subtree, recursively.

79
4. Then, traverse the root node
5. END

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct node {

int key;

struct node *left, *right;

};

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->key = item;

temp->left = temp->right = NULL;

return temp;

// Postorder Traversal

void postorder(struct node *root){

if (root != NULL) {

printf("%d -> ", root->key);

postorder(root->left);

postorder(root->right);

// Insertion operation

struct node *insert(struct node *node, int key){

if (node == NULL) return newNode(key);

if (key < node->key)

node->left = insert(node->left, key);

else

80
node->right = insert(node->right, key);

return node;

int main(){

struct node *root = NULL;

root = insert(root, 55);

root = insert(root, 20);

root = insert(root, 90);

root = insert(root, 50);

root = insert(root, 35);

root = insert(root, 15);

root = insert(root, 65);

printf("Postorder traversal: ");

postorder(root);

}#include <stdio.h>

#include <stdlib.h>

struct node {

int key;

struct node *left, *right;

};

struct node *newNode(int item){

struct node *temp = (struct node *)malloc(sizeof(struct node));

temp->key = item;

temp->left = temp->right = NULL;

return temp;

// Postorder Traversal

void postorder(struct node *root){

if (root != NULL) {

81
printf("%d -> ", root->key);

postorder(root->left);

postorder(root->right);

// Insertion operation

struct node *insert(struct node *node, int key){

if (node == NULL) return newNode(key);

if (key < node->key)

node->left = insert(node->left, key);

else

node->right = insert(node->right, key);

return node;

int main(){

struct node *root = NULL;

root = insert(root, 55);

root = insert(root, 20);

root = insert(root, 90);

root = insert(root, 50);

root = insert(root, 35);

root = insert(root, 15);

root = insert(root, 65);

printf("Postorder traversal: ");

postorder(root);

Output
Postorder traversal: 55 -> 20 -> 15 -> 50 -> 35 -> 90 -> 65 ->

82
 AVL tree-The first type of self-balancing binary search tree to be invented is the AVL tree. The name AVL tree is
coined after its inventor's names − Adelson-Velsky and Landis.

In AVL trees, the difference between the heights of left and right subtrees, known as the Balance Factor, must be at
most one. Once the difference exceeds one, the tree automatically executes the balancing algorithm until the
difference becomes one again.

BALANCE FACTOR = HEIGHT(LEFT SUBTREE) – HEIGHT(RIGHT SUBTREE)

There are usually four cases of rotation in the balancing algorithm of AVL trees: LL, RR, LR, RL.

LL Rotations

LL rotation is performed when the node is inserted into the right subtree leading to an unbalanced tree. This is a single
left rotation to make the tree balanced again −

Fig : LL Rotation

The node where the unbalance occurs becomes the left child and the newly added node becomes the right child with
the middle node as the parent node.

RR rotation is performed when the node is inserted into the left subtree leading to an unbalanced tree. This is a single
right rotation to make the tree balanced again −

Fig : RR Rotation
83
The node where the unbalance occurs becomes the right child and the newly added node becomes the left child with
the middle node as the parent node.

LR Rotations

LR rotation is the extended version of the previous single rotations, also called a double rotation. It is performed when
a node is inserted into the right subtree of the left subtree. The LR rotation is a combination of the left rotation
followed by the right rotation. There are multiple steps to be followed to carry this out.

 Consider an example with “ A” as the root node, “ B” as the left child of “ A” and “ C” as the right child
of “ B” .
 Since the unbalance occurs at A, a left rotation is applied on the child nodes of A, i.e. B and C.
 After the rotation, the C node becomes the left child of A and B becomes the left child of C.
 The unbalance still persists, therefore a right rotation is applied at the root node A and the left child C.
 After the final right rotation, C becomes the root node, A becomes the right child and B is the left child.

Fig : LR Rotation

RL Rotations

RL rotation is also the extended version of the previous single rotations, hence it is called a double rotation and it is
performed if a node is inserted into the left subtree of the right subtree. The RL rotation is a combination of the right
rotation followed by the left rotation. There are multiple steps to be followed to carry this out.

 Consider an example with “ A” as the root node, “ B” as the right child of “ A” and “ C” as the left child
of “ B” .
 Since the unbalance occurs at A, a right rotation is applied on the child nodes of A, i.e. B and C.
 After the rotation, the C node becomes the right child of A and B becomes the right child of C.
 The unbalance still persists, therefore a left rotation is applied at the root node A and the right child C.
 After the final left rotation, C becomes the root node, A becomes the left child and B is the right child.

Fig : RL Rotation

84
Basic Operations of AVL Trees

The basic operations performed on the AVL Tree structures include all the operations performed on a binary search
tree, since the AVL Tree at its core is actually just a binary search tree holding all its properties. Therefore, basic
operations performed on an AVL Tree are − Insertion and Deletion.

Insertion

The data is inserted into the AVL Tree by following the Binary Search Tree property of insertion, i.e. the left subtree
must contain elements less than the root value and right subtree must contain all the greater elements. However, in
AVL Trees, after the insertion of each element, the balance factor of the tree is checked; if it does not exceed 1, the
tree is left as it is. But if the balance factor exceeds 1, a balancing algorithm is applied to readjust the tree such that
balance factor becomes less than or equal to 1 again.

Algorithm

The following steps are involved in performing the insertion operation of an AVL Tree −

Step 1 − Create a node

Step 2 − Check if the tree is empty

Step 3 − If the tree is empty, the new node created will become the root node of the AVL Tree.

Step 4 − If the tree is not empty, we perform the Binary Search Tree insertion operation and check the balancing
factor of the node in the tree.

Step 5 − Suppose the balancing factor exceeds ±1, we apply suitable rotations on the said node and resume the
insertion from Step 4.

START
if node == null then:
return new node
if key < node.key then:
node.left = insert (node.left, key)
else if (key > node.key) then:
node.right = insert (node.right, key)
else
return node
node.height = 1 + max (height (node.left), height (node.right))
balance = getBalance (node)
if balance > 1 and key < node.left.key then:
rightRotate
if balance < -1 and key > node.right.key then:
leftRotate
if balance > 1 and key > node.left.key then:
node.left = leftRotate (node.left)
rightRotate
if balance < -1 and key < node.right.key then:
node.right = rightRotate (node.right)
leftRotate (node)
return node
END

Insertion Example

Let us understand the insertion operation by constructing an example AVL tree with 1 to 7 integers.

Starting with the first element 1, we create a node and measure the balance, i.e., 0.

85
Since both the binary search property and the balance factor are satisfied, we insert another element into the tree.

The balance factor for the two nodes are calculated and is found to be -1 (Height of left subtree is 0 and height of the
right subtree is 1). Since it does not exceed 1, we add another element to the tree.

Now, after adding the third element, the balance factor exceeds 1 and becomes 2. Therefore, rotations are applied. In
this case, the RR rotation is applied since the imbalance occurs at two right nodes.

The tree is rearranged as −

86
Similarly, the next elements are inserted and rearranged using these rotations. After rearrangement, we achieve the
tree as −

Example

Following are the implementations of this operation in various programming languages

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node *leftChild;

struct Node *rightChild;

int height;

};

int max(int a, int b);

int height(struct Node *N){

if (N == NULL)

return 0;

return N->height;

87
}

int max(int a, int b){

return (a > b) ? a : b;

struct Node *newNode(int data){

struct Node *node = (struct Node *) malloc(sizeof(struct Node));

node->data = data;

node->leftChild = NULL;

node->rightChild = NULL;

node->height = 1;

return (node);

struct Node *rightRotate(struct Node *y){

struct Node *x = y->leftChild;

struct Node *T2 = x->rightChild;

x->rightChild = y;

y->leftChild = T2;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

return x;

struct Node *leftRotate(struct Node *x){

struct Node *y = x->rightChild;

struct Node *T2 = y->leftChild;

y->leftChild = x;

x->rightChild = T2;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

return y;

88
}

int getBalance(struct Node *N){

if (N == NULL)

return 0;

return height(N->leftChild) - height(N->rightChild);

struct Node *insertNode(struct Node *node, int data){

if (node == NULL)

return (newNode(data));

if (data < node->data)

node->leftChild = insertNode(node->leftChild, data);

else if (data > node->data)

node->rightChild = insertNode(node->rightChild, data);

else

return node;

node->height = 1 + max(height(node->leftChild),

height(node->rightChild));

int balance = getBalance(node);

if (balance > 1 && data < node->leftChild->data)

return rightRotate(node);

if (balance < -1 && data > node->rightChild->data)

return leftRotate(node);

if (balance > 1 && data > node->leftChild->data) {

node->leftChild = leftRotate(node->leftChild);

return rightRotate(node);

if (balance < -1 && data < node->rightChild->data) {

node->rightChild = rightRotate(node->rightChild);

return leftRotate(node);

89
}

return node;

struct Node *minValueNode(struct Node *node){

struct Node *current = node;

while (current->leftChild != NULL)

current = current->leftChild;

return current;

void printTree(struct Node *root){

if (root == NULL)

return;

if (root != NULL) {

printTree(root->leftChild);

printf("%d ", root->data);

printTree(root->rightChild);

int main(){

struct Node *root = NULL;

root = insertNode(root, 22);

root = insertNode(root, 14);

root = insertNode(root, 72);

root = insertNode(root, 44);

root = insertNode(root, 25);

root = insertNode(root, 63);

root = insertNode(root, 98);

printf("AVL Tree: ");

printTree(root);

90
return 0;

Output
AVL Tree: 14 22 25 44 63 72 98

Deletion

Deletion in the AVL Trees take place in three different scenarios −


 Scenario 1 (Deletion of a leaf node) − If the node to be deleted is a leaf node, then it is deleted without any
replacement as it does not disturb the binary search tree property. However, the balance factor may get
disturbed, so rotations are applied to restore it.
 Scenario 2 (Deletion of a node with one child) − If the node to be deleted has one child, replace the value in
that node with the value in its child node. Then delete the child node. If the balance factor is disturbed,
rotations are applied.
 Scenario 3 (Deletion of a node with two child nodes) − If the node to be deleted has two child nodes, find
the inorder successor of that node and replace its value with the inorder successor value. Then try to delete the
inorder successor node. If the balance factor exceeds 1 after deletion, apply balance algorithms.
START
if root == null: return root
if key < root.key:
root.left = delete Node
else if key > root.key:
root.right = delete Node
else:
if root.left == null or root.right == null then:
Node temp = null
if (temp == root.left)
temp = root.right
else
temp = root.left
if temp == null then:
temp = root
root = null
else
root = temp
else:
temp = minimum valued node
root.key = temp.key
root.right = delete Node
if (root == null) then:
return root
root.height = max (height (root.left), height (root.right)) + 1
balance = getBalance
if balance > 1 and getBalance (root.left) >= 0:
rightRotate
if balance > 1 and getBalance (root.left) < 0:
root.left = leftRotate (root.left);
rightRotate
if balance < -1 and getBalance (root.right) <= 0:
leftRotate
if balance < -1 and getBalance (root.right) > 0:
root.right = rightRotate (root.right);
leftRotate
return root
END

91
Deletion Example

Using the same tree given above, let us perform deletion in three scenarios −

 Deleting element 7 from the tree above −

Since the element 7 is a leaf, we normally remove the element without disturbing any other node in the tree

 Deleting element 6 from the output tree achieved −

However, element 6 is not a leaf node and has one child node attached to it. In this case, we replace node 6 with its
child node: node 5.

The balance of the tree becomes 1, and since it does not exceed 1 the tree is left as it is. If we delete the element 5
further, we would have to apply the left rotations; either LL or LR since the imbalance occurs at both 1-2-4 and 3-2-4.

92
The balance factor is disturbed after deleting the element 5, therefore we apply LL rotation (we can also apply the LR
rotation here).

Once the LL rotation is applied on path 1-2-4, the node 3 remains as it was supposed to be the right child of node 2
(which is now occupied by node 4). Hence, the node is added to the right subtree of the node 2 and as the left child of
the node 4.

 Deleting element 2 from the remaining tree −

As mentioned in scenario 3, this node has two children. Therefore, we find its inorder successor that is a leaf node
(say, 3) and replace its value with the inorder successor.

93
The balance of the tree still remains 1, therefore we leave the tree as it is without performing any rotations.

Following are the implementations of this operation in various programming languages −

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node *leftChild;

struct Node *rightChild;

int height;

};

int max(int a, int b);

int height(struct Node *N){

if (N == NULL)

return 0;

return N->height;

int max(int a, int b){

return (a > b) ? a : b;

struct Node *newNode(int data){

struct Node *node = (struct Node *) malloc(sizeof(struct Node));

node->data = data;
94
node->leftChild = NULL;

node->rightChild = NULL;

node->height = 1;

return (node);

struct Node *rightRotate(struct Node *y){

struct Node *x = y->leftChild;

struct Node *T2 = x->rightChild;

x->rightChild = y;

y->leftChild = T2;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

return x;

struct Node *leftRotate(struct Node *x){

struct Node *y = x->rightChild;

struct Node *T2 = y->leftChild;

y->leftChild = x;p

x->rightChild = T2;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

return y;

int getBalance(struct Node *N){

if (N == NULL)

return 0;

return height(N->leftChild) - height(N->rightChild);

struct Node *insertNode(struct Node *node, int data){

95
if (node == NULL)

return (newNode(data));

if (data < node->data)

node->leftChild = insertNode(node->leftChild, data);

else if (data > node->data)

node->rightChild = insertNode(node->rightChild, data);

else

return node;

node->height = 1 + max(height(node->leftChild),

height(node->rightChild));

int balance = getBalance(node);

if (balance > 1 && data < node->leftChild->data)

return rightRotate(node);

if (balance < -1 && data > node->rightChild->data)

return leftRotate(node);

if (balance > 1 && data > node->leftChild->data) {

node->leftChild = leftRotate(node->leftChild);

return rightRotate(node);

if (balance < -1 && data < node->rightChild->data) {

node->rightChild = rightRotate(node->rightChild);

return leftRotate(node);

return node;

struct Node *minValueNode(struct Node *node){

struct Node *current = node;

while (current->leftChild != NULL)

current = current->leftChild;

96
return current;

struct Node *deleteNode(struct Node *root, int data){

if (root == NULL)

return root;

if (data < root->data)

root->leftChild = deleteNode(root->leftChild, data);

else if (data > root->data)

root->rightChild = deleteNode(root->rightChild, data);

else {

if ((root->leftChild == NULL) || (root->rightChild == NULL)) {

struct Node *temp = root->leftChild ? root->leftChild : root->rightChild;

if (temp == NULL) {

temp = root;

root = NULL;

} else

*root = *temp;

free(temp);

} else {

struct Node *temp = minValueNode(root->rightChild);

root->data = temp->data;

root->rightChild = deleteNode(root->rightChild, temp->data);

if (root == NULL)

return root;

root->height = 1 + max(height(root->leftChild),

height(root->rightChild));

int balance = getBalance(root);

97
if (balance > 1 && getBalance(root->leftChild) >= 0)

return rightRotate(root);

if (balance > 1 && getBalance(root->leftChild) < 0) {

root->leftChild = leftRotate(root->leftChild);

return rightRotate(root);

if (balance < -1 && getBalance(root->rightChild) <= 0)

return leftRotate(root);

if (balance < -1 && getBalance(root->rightChild) > 0) {

root->rightChild = rightRotate(root->rightChild);

return leftRotate(root);

return root;

// Print the tree

void printTree(struct Node *root){

if (root != NULL) {

printTree(root->leftChild);

printf("%d ", root->data);

printTree(root->rightChild);

int main(){

struct Node *root = NULL;

root = insertNode(root, 22);

root = insertNode(root, 14);

root = insertNode(root, 72);

root = insertNode(root, 44);

98
root = insertNode(root, 25);

root = insertNode(root, 63);

root = insertNode(root, 98);

printf("AVL Tree: ");

printTree(root);

root = deleteNode(root, 25);

printf("\nAfter deletion: ");

printTree(root);

return 0;

Output
AVL Tree: 14 22 25 44 63 72 98
After deletion: 14 22 44 63 72 98

Implementation of AVL Trees

In the following implementation, we consider the inputs in ascending order and store them in AVL Trees by
calculating the balance factor and applying rotations.

Example

Following are the implementations of this operation in various programming languages –

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node *leftChild;

struct Node *rightChild;

int height;

};

int max(int a, int b);

int height(struct Node *N){

if (N == NULL)

return 0;

return N->height;

99
}

int max(int a, int b){

return (a > b) ? a : b;

struct Node *newNode(int data){

struct Node *node = (struct Node *) malloc(sizeof(struct Node));

node->data = data;

node->leftChild = NULL;

node->rightChild = NULL;

node->height = 1;

return (node);

struct Node *rightRotate(struct Node *y){

struct Node *x = y->leftChild;

struct Node *T2 = x->rightChild;

x->rightChild = y;

y->leftChild = T2;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

return x;

struct Node *leftRotate(struct Node *x){

struct Node *y = x->rightChild;

struct Node *T2 = y->leftChild;

y->leftChild = x;

x->rightChild = T2;

x->height = max(height(x->leftChild), height(x->rightChild)) + 1;

y->height = max(height(y->leftChild), height(y->rightChild)) + 1;

return y;

100
}

int getBalance(struct Node *N){

if (N == NULL)

return 0;

return height(N->leftChild) - height(N->rightChild);

struct Node *insertNode(struct Node *node, int data){

if (node == NULL)

return (newNode(data));

if (data < node->data)

node->leftChild = insertNode(node->leftChild, data);

else if (data > node->data)

node->rightChild = insertNode(node->rightChild, data);

else

return node;

node->height = 1 + max(height(node->leftChild),

height(node->rightChild));

int balance = getBalance(node);

if (balance > 1 && data < node->leftChild->data)

return rightRotate(node);

if (balance < -1 && data > node->rightChild->data)

return leftRotate(node);

if (balance > 1 && data > node->leftChild->data) {

node->leftChild = leftRotate(node->leftChild);

return rightRotate(node);

if (balance < -1 && data < node->rightChild->data) {

node->rightChild = rightRotate(node->rightChild);

return leftRotate(node);

101
}

return node;

struct Node *minValueNode(struct Node *node){

struct Node *current = node;

while (current->leftChild != NULL)

current = current->leftChild;

return current;

struct Node *deleteNode(struct Node *root, int data){

if (root == NULL)

return root;

if (data < root->data)

root->leftChild = deleteNode(root->leftChild, data);

else if (data > root->data)

root->rightChild = deleteNode(root->rightChild, data);

else {

if ((root->leftChild == NULL) || (root->rightChild == NULL)) {

struct Node *temp = root->leftChild ? root->leftChild : root->rightChild;

if (temp == NULL) {

temp = root;

root = NULL;

} else

*root = *temp;

free(temp);

} else {

struct Node *temp = minValueNode(root->rightChild);

root->data = temp->data;

root->rightChild = deleteNode(root->rightChild, temp->data);

102
}

if (root == NULL)

return root;

root->height = 1 + max(height(root->leftChild),

height(root->rightChild));

int balance = getBalance(root);

if (balance > 1 && getBalance(root->leftChild) >= 0)

return rightRotate(root);

if (balance > 1 && getBalance(root->leftChild) < 0) {

root->leftChild = leftRotate(root->leftChild);

return rightRotate(root);

if (balance < -1 && getBalance(root->rightChild) <= 0)

return leftRotate(root);

if (balance < -1 && getBalance(root->rightChild) > 0) {

root->rightChild = rightRotate(root->rightChild);

return leftRotate(root);

return root;

// Print the tree

void printTree(struct Node *root){

if (root != NULL) {

printTree(root->leftChild);

printf("%d ", root->data);

printTree(root->rightChild);

103
}

int main(){

struct Node *root = NULL;

root = insertNode(root, 22);

root = insertNode(root, 14);

root = insertNode(root, 72);

root = insertNode(root, 44);

root = insertNode(root, 25);

root = insertNode(root, 63);

root = insertNode(root, 98);

printf("AVL Tree: ");

printTree(root);

root = deleteNode(root, 25);

printf("\nAfter deletion: ");

printTree(root);

return 0;

Output
AVL Tree: 14 22 25 44 63 72 98
After deletion: 14 22 44 63 72 98

B tree-B trees are extended binary search trees that are specialized in m-way searching, since the order of B trees is
‘ m’ . Order of a tree is defined as the maximum number of children a node can accommodate. Therefore, the height
of a b tree is relatively smaller than the height of AVL tree and RB tree.

They are general form of a Binary Search Tree as it holds more than one key and two children.

The various properties of B trees include −

 Every node in a B Tree will hold a maximum of m children and (m-1) keys, since the order of the tree is m.
 Every node in a B tree, except root and leaf, can hold at least m/2 children
 The root node must have no less than two children.
 All the paths in a B tree must end at the same level, i.e. the leaf nodes must be at the same level.
 A B tree always maintains sorted data.

104
B trees are also widely used in disk access, minimizing the disk access time since the height of a b tree is low.

Note − A disk access is the memory access to the computer disk where the information is stored and disk access time
is the time taken by the system to access the disk memory.

Basic Operations of B Trees

The operations supported in B trees are Insertion, deletion and searching with the time complexity of O(log n) for
every operation.

Insertion

The insertion operation for a B Tree is done similar to the Binary Search Tree but the elements are inserted into the
same node until the maximum keys are reached. The insertion is done using the following procedure −

Step 1 − Calculate the maximum $\mathrm{\left ( m-1 \right )}$ and minimum $\mathrm{\left ( \left \lceil
\frac{m}{2}\right \rceil-1 \right )}$ number of keys a node can hold, where m is denoted by the order of the B Tree.

Step 2 − The data is inserted into the tree using the binary search insertion and once the keys reach the maximum
number, the node is split into half and the median key becomes the internal node while the left and right keys become
its children.

105
Step 3 − All the leaf nodes must be on the same level.

The keys, 5, 3, 21, 9, 13 are all added into the node according to the binary search property but if we add the key 22, it
will violate the maximum key property. Hence, the node is split in half, the median key is shifted to the parent node
and the insertion is then continued.

106
Another hiccup occurs during the insertion of 11, so the node is split and median is shifted to the parent.

While inserting 16, even if the node is split in two parts, the parent node also overflows as it reached the maximum
keys. Hence, the parent node is split first and the median key becomes the root. Then, the leaf node is split in half the
median of leaf node is shifted to its parent.

107
The final B tree after inserting all the elements is achieved.

// Insert the value


void insertion(int item) {
int flag, i;
struct btreeNode *child;
flag = setNodeValue(item, &i, root, &child);
if (flag)
root = createNode(i, child);
}

Deletion

The deletion operation in a B tree is slightly different from the deletion operation of a Binary Search Tree. The
procedure to delete a node from a B tree is as follows −

Case 1 − If the key to be deleted is in a leaf node and the deletion does not violate the minimum key property, just
delete the node.

Case 2 − If the key to be deleted is in a leaf node but the deletion violates the minimum key property, borrow a key
from either its left sibling or right sibling. In case if both siblings have exact minimum number of keys, merge the
node in either of them.

108
Case 3 − If the key to be deleted is in an internal node, it is replaced by a key in either left child or right child based
on which child has more keys. But if both child nodes have minimum number of keys, they’re merged together.

Case 4 − If the key to be deleted is in an internal node violating the minimum keys property, and both its children and
sibling have minimum number of keys, merge the children. Then merge its sibling with its parent.

109
Following is functional C++ code snippet of the deletion operation in B Trees –

// Deletion operation

void deletion(int key){

int index = searchkey(key);

if (index < n && keys[index] == key) {

if (leaf)

deletion_at_leaf(index);

else

deletion_at_nonleaf(index);

} else {

if (leaf) {

cout << "key " << key << " does not exist in the tree\n";

return;

bool flag = ((index == n) ? true : false);

if (C[index]->n < t)

fill(index);

if (flag && index > n)

C[index - 1]->deletion(key);

else

C[index]->deletion(key);

return;

110
// Deletion at the leaf nodes

void deletion_at_leaf(int index){

for (int i = index + 1; i < n; ++i)

keys[i - 1] = keys[i];

n--;

return;

// Deletion at the non leaf node

void deletion_at_nonleaf(int index){

int key = keys[index];

if (C[index]->n >= t) {

int pred = get_Predecessor(index);

keys[index] = pred;

C[index]->deletion(pred);

} else if (C[index + 1]->n >= t) {

int successor = copysuccessoressor(index);

keys[index] = successor;

C[index + 1]->deletion(successor);

} else {

merge(index);

C[index]->deletion(key);

return;

111
C++ Implementation

Following is the complete implementation of B Trees in C++ −

#include<iostream>

using namespace std;

struct BTree {//node declaration

int *d;

BTree **child_ptr;

bool l;

int n;

}*r = NULL, *np = NULL, *x = NULL;

BTree* init() {//creation of node

int i;

np = new BTree;

np->d = new int[6];//order 6

np->child_ptr = new BTree *[7];

np->l = true;

np->n = 0;

for (i = 0; i < 7; i++) {

np->child_ptr[i] = NULL;

return np;

void traverse(BTree *p) { //traverse the tree

cout<<endl;

int i;

112
for (i = 0; i < p->n; i++) {

if (p->l == false) {

traverse(p->child_ptr[i]);

cout << " " << p->d[i];

if (p->l == false) {

traverse(p->child_ptr[i]);

cout<<endl;

void sort(int *p, int n){ //sort the tree

int i, j, t;

for (i = 0; i < n; i++) {

for (j = i; j <= n; j++) {

if (p[i] >p[j]) {

t = p[i];

p[i] = p[j];

p[j] = t;

int split_child(BTree *x, int i) {

int j, mid;

BTree *np1, *np3, *y;

np3 = init();//create new node

113
np3->l = true;

if (i == -1) {

mid = x->d[2];//find mid

x->d[2] = 0;

x->n--;

np1 = init();

np1->l= false;

x->l= true;

for (j = 3; j < 6; j++) {

np3->d[j - 3] = x->d[j];

np3->child_ptr[j - 3] = x->child_ptr[j];

np3->n++;

x->d[j] = 0;

x->n--;

for (j = 0; j < 6; j++) {

x->child_ptr[j] = NULL;

np1->d[0] = mid;

np1->child_ptr[np1->n] = x;

np1->child_ptr[np1->n + 1] = np3;

np1->n++;

r = np1;

} else {

y = x->child_ptr[i];

mid = y->d[2];

y->d[2] = 0;

y->n--;

for (j = 3; j <6 ; j++) {

114
np3->d[j - 3] = y->d[j];

np3->n++;

y->d[j] = 0;

y->n--;

x->child_ptr[i + 1] = y;

x->child_ptr[i + 1] = np3;

return mid;

void insert(int a) {

int i, t;

x = r;

if (x == NULL) {

r = init();

x = r;

} else {

if (x->l== true && x->n == 6) {

t = split_child(x, -1);

x = r;

for (i = 0; i < (x->n); i++) {

if ((a >x->d[i]) && (a < x->d[i + 1])) {

i++;

break;

} else if (a < x->d[0]) {

break;

} else {

continue;

115
}

x = x->child_ptr[i];

} else {

while (x->l == false) {

for (i = 0; i < (x->n); i++) {

if ((a >x->d[i]) && (a < x->d[i + 1])) {

i++;

break;

} else if (a < x->d[0]) {

break;

} else {

continue;

if ((x->child_ptr[i])->n == 6) {

t = split_child(x, i);

x->d[x->n] = t;

x->n++;

continue;

} else {

x = x->child_ptr[i];

x->d[x->n] = a;

sort(x->d, x->n);

x->n++;

116
}

int main() {

int i, n, t;

insert(10);

insert(20);

insert(30);

insert(40);

insert(50);

cout<<"B tree:\n";

traverse(r);

Output
B tree:
10 20 30 40 50

 Spanning tree-A spanning tree is a subset of Graph G, which has all the vertices covered with minimum
possible number of edges. Hence, a spanning tree does not have cycles and it cannot be disconnected..

By this definition, we can draw a conclusion that every connected and undirected Graph G has at least one spanning
tree. A disconnected graph does not have any spanning tree, as it cannot be spanned to all its vertices.

We found three spanning trees off one complete graph. A complete undirected graph can have maximum nn-2 number
of spanning trees, where n is the number of nodes. In the above addressed example, n is 3, hence 33−2 = 3 spanning
trees are possible.

General Properties of Spanning Tree

117
We now understand that one graph can have more than one spanning tree. Following are a few properties of the
spanning tree connected to graph G −

 A connected graph G can have more than one spanning tree.


 All possible spanning trees of graph G, have the same number of edges and vertices.
 The spanning tree does not have any cycle (loops).
 Removing one edge from the spanning tree will make the graph disconnected, i.e. the spanning tree
is minimally connected.
 Adding one edge to the spanning tree will create a circuit or loop, i.e. the spanning tree is maximally acyclic.

Mathematical Properties of Spanning Tree

 Spanning tree has n-1 edges, where n is the number of nodes (vertices).
 From a complete graph, by removing maximum e - n + 1 edges, we can construct a spanning tree.
 A complete graph can have maximum nn-2 number of spanning trees.

Thus, we can conclude that spanning trees are a subset of connected Graph G and disconnected graphs do not have
spanning tree.

Application of Spanning Tree

Spanning tree is basically used to find a minimum path to connect all nodes in a graph. Common application of
spanning trees are −

 Civil Network Planning


 Computer Network Routing Protocol
 Cluster Analysis

Let us understand this through a small example. Consider, city network as a huge graph and now plans to deploy
telephone lines in such a way that in minimum lines we can connect to all city nodes. This is where the spanning tree
comes into picture.

Minimum Spanning Tree (MST)

In a weighted graph, a minimum spanning tree is a spanning tree that has minimum weight than all other spanning
trees of the same graph. In real-world situations, this weight can be measured as distance, congestion, traffic load or
any arbitrary value denoted to the edges.

Minimum Spanning-Tree Algorithm

We shall learn about two most important spanning tree algorithms here −

 Kruskal's Algorithm
 Prim's Algorithm

 Tries-A trie is a type of a multi-way search tree, which is fundamentally used to retrieve specific keys from a
string or a set of strings. It stores the data in an ordered efficient way since it uses pointers to every letter within
the alphabet.

The trie data structure works based on the common prefixes of strings. The root node can have any number of nodes
considering the amount of strings present in the set. The root of a trie does not contain any value except the pointers to
its child nodes.

There are three types of trie data structures −

 Standard Tries
 Compressed Tries
 Suffix Tries

118
The real-world applications of trie include − autocorrect, text prediction, sentiment analysis and data sciences.

Basic Operations in Tries

The trie data structures also perform the same operations that tree data structures perform. They are −

 Insertion
 Deletion
 Search

Insertion

The insertion operation in a trie is a simple approach. The root in a trie does not hold any value and the insertion starts
from the immediate child nodes of the root, which act like a key to their child nodes. However, we observe that each
node in a trie represents a singlecharacter in the input string. Hence the characters are added into the tries one by one
while the links in the trie act as pointers to the next level nodes.

Deletion

The deletion operation in a trie is performed using the bottom-up approach. The element is searched for in a trie and
deleted, if found. However, there are some special scenarios that need to be kept in mind while performing the
deletion operation.

Case 1 − The key is unique − in this case, the entire key path is deleted from the node. (Unique key suggests that there
is no other path that branches out from one path).

Case 2 − The key is not unique − the leaf nodes are updated. For example, if the key to be deleted is see but it is a
prefix of another key seethe; we delete the see and change the Boolean values of t, h and e as false.

Case 3 − The key to be deleted already has a prefix − the values until the prefix are deleted and the prefix remains in
the tree. For example, if the key to be deleted is heart but there is another key present he; so we delete a, r, and t until
only he remains.

119
Search

Searching in a trie is a rather straightforward approach. We can only move down the levels of trie based on the key
node (the nodes where insertion operation starts at). Searching is done until the end of the path is reached. If the
element is found, search is successful; otherwise, search is prompted unsuccessful.

 Heap- Heap is a special case of balanced binary tree data structure where the root-node key is compared with its
children and arranged accordingly. If α has child node β then −

key(α) ≥ key(β)

As the value of parent is greater than that of child, this property generates Max Heap. Based on this criteria, a heap
can be of two types −

For Input → 35 33 42 10 14 19 27 44 26 31

Min-Heap − Where the value of the root node is less than or equal to either of its children.

Max-Heap − Where the value of the root node is greater than or equal to either of its children.

Both trees are constructed using the same input and order of arrival.

Max Heap Construction Algorithm

We shall use the same example to demonstrate how a Max Heap is created. The procedure to create Min Heap is
similar but we go for min values instead of max values.

We are going to derive an algorithm for max heap by inserting one element at a time. At any point of time, heap must
maintain its property. While insertion, we also assume that we are inserting a node in an already heapified tree.

120
Step 1 − Create a new node at the end of heap.
Step 2 − Assign new value to the node.
Step 3 − Compare the value of this child node with its parent.
Step 4 − If value of parent is less than child, then swap them.
Step 5 − Repeat step 3 & 4 until Heap property holds.

Note − In Min Heap construction algorithm, we expect the value of the parent node to be less than that of the child
node.

Let's understand Max Heap construction by an animated illustration. We consider the same input sample that we used
earlier

Input 35 33 42 10 14 19 27 44 26 31.

Max Heap Deletion Algorithm

Let us derive an algorithm to delete from max heap. Deletion in Max (or Min) Heap always happens at the root to
remove the Maximum (or minimum) value.

Step 1 − Remove root node.


Step 2 − Move the last element of last level to root.
Step 3 − Compare the value of this child node with its parent.
Step 4 − If value of parent is less than child, then swap them.
Step 5 − Repeat step 3 & 4 until Heap property holds.

121

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy