0% found this document useful (0 votes)
4 views

MDU BCA- Data Structures

The document is a comprehensive guide on data structures, covering their definitions, types, operations, and applications in computer science. It discusses linear and non-linear data structures, including arrays, linked lists, stacks, queues, trees, and graphs, along with their respective operations like traversal, insertion, deletion, and searching. Additionally, it highlights the importance of selecting appropriate data structures for efficient algorithm design and software performance.

Uploaded by

amanverma5144
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)
4 views

MDU BCA- Data Structures

The document is a comprehensive guide on data structures, covering their definitions, types, operations, and applications in computer science. It discusses linear and non-linear data structures, including arrays, linked lists, stacks, queues, trees, and graphs, along with their respective operations like traversal, insertion, deletion, and searching. Additionally, it highlights the importance of selecting appropriate data structures for efficient algorithm design and software performance.

Uploaded by

amanverma5144
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/ 474

1

MAHARSHI DAYANAND UNIVERSITY

Notes in Easy Language

CREATED BY: ROHIT RATHOUR

CONTACT NO-7827646303
2

MDU BCA-3rd SEMESTER


DATA STRUCTURE
INDEX
SECTION-I
Introduction:
 Elementary Data Organization
 Data Structure Definations
 Data type vs. Data Structure
 Categories of Data Structure
 Data Structure Operation
 Applications of Data Structure
 Algorithms Complexity and time-space
tradeoff
 Big-O notation
Strings:
 Introduction
 Storing String
 String Operations
 Pattern Matching Algorithm
SECTION-II
Arrays:
 Introduction
 Linear Arrays
3

 Representation of Linear Array in Memory


 Address Calculations
 Traversal
 Insertions
 Deletion in array
 Multidimensional Arrays
 Parallel Arrays
 Sparse Arrays
Linked Lists:
 Introduction
 Array vs Linked Lists
 Representation of Linked lists in memory
 Traversal
 Insertion
 Deletion
 Searching in a Linked List
 Header Linked List
 Circular Linked List
 Two way Linked Lists
 Threaded Lists
 Garbage Collection
 Applications of Linked Lists
SECTION-III
Stack:
 Introduction
 Array and Linked Representation of Stacks
 Operation on Stacks
4

 Application of Stacks:Polish
Notation,Recursion
Queues:
 Introduction
 Array and Linked List Representation of
Queues
 Operation on Queues
 Deques
 Priority Queues
 Application of Queues

SECTION-IV
Trees:
 Introduction
 Defination
 Representing Binary Tree in Memory
 Traversing Binary Tree
 Traversal Algorithm Using Stacks
Graph:
 Introduction
 Graph Theory Terminology
 Sequential and Linked Representation of
Graphs
5

SECTION-I
INTRODUCTION
Elementary Data Organization:
Data Structure can be defined as the group of data elements
which provides an efficient way of storing and organising data
in the computer so that it can be used efficiently. Some
examples of Data Structures are arrays, Linked List, Stack,
Queue, etc. Data Structures are widely used in almost every
aspect of Computer Science i.e. Operating System, Compiler
Design, Artifical intelligence, Graphics and many more.
Data Structures are the main part of many computer science
algorithms as they enable the programmers to handle the data
in an efficient way. It plays a vital role in enhancing the
performance of a software or a program as the main function of
the software is to store and retrieve the user's data as fast as
possible.
Basic Terminology
Data structures are the building blocks of any program or the
software. Choosing the appropriate data structure for a program
is the most difficult task for a programmer. Following
terminology is used as far as data structures are concerned.
Data: Data can be defined as an elementary value or the
collection of values, for example, student's name and its id are
the data about the student.
6

Group Items: Data items which have subordinate data items


are called Group item, for example, name of a student can have
first name and the last name.
Record: Record can be defined as the collection of various data
items, for example, if we talk about the student entity, then its
name, address, course and marks can be grouped together to
form the record for the student.
File: A File is a collection of various records of one type of
entity, for example, if there are 60 employees in the class, then
there will be 20 records in the related file where each record
contains the data about each employee.
Attribute and Entity: An entity represents the class of certain
objects. it contains various attributes. Each attribute represents
the particular property of that entity.
Field: Field is a single elementary unit of information
representing the attribute of an entity.
Need of Data Structures
As applications are getting complexed and amount of data is
increasing day by day, there may arrise the following problems:
Processor speed: To handle very large amout of data, high
speed processing is required, but as the data is growing day by
day to the billions of files per entity, processor may fail to deal
with that much amount of data.
Data Search: Consider an inventory size of 106 items in a
store, If our application needs to search for a particular item, it
needs to traverse 106 items every time, results in slowing down
the search process.
7

Multiple requests: If thousands of users are searching the data


simultaneously on a web server, then there are the chances that
a very large server can be failed during that process
in order to solve the above problems, data structures are used.
Data is organized to form a data structure in such a way that all
items are not required to be searched and required data can be
searched instantly.
Advantages of Data Structures
Efficiency: Efficiency of a program depends upon the choice
of data structures. For example: suppose, we have some data
and we need to perform the search for a perticular record. In
that case, if we organize our data in an array, we will have to
search sequentially element by element. hence, using array may
not be very efficient here. There are better data structures which
can make the search process efficient like ordered array, binary
search tree or hash tables.
Reusability: Data structures are reusable, i.e. once we have
implemented a particular data structure, we can use it at any
other place. Implementation of data structures can be compiled
into libraries which can be used by different clients.
Abstraction: Data structure is specified by the ADT which
provides a level of abstraction. The client program uses the data
structure through interface only, without getting into the
implementation details.
8

Data Structure Classification

Linear Data Structures: A data structure is called linear if all


of its elements are arranged in the linear order. In linear data
structures, the elements are stored in non-hierarchical way
where each element has the successors and predecessors except
the first and last element.
Types of Linear Data Structures are given below:
Arrays: An array is a collection of similar type of data items
and each data item is called an element of the array. The data
type of the element may be any valid data type like char, int,
float or double.
The elements of array share the same variable name but each
one carries a different index number known as subscript. The
array can be one dimensional, two dimensional or
multidimensional.
The individual elements of the array age are:
age[0], age[1], age[2], age[3],......... age[98], age[99].
9

Linked List: Linked list is a linear data structure which is used


to maintain a list in the memory. It can be seen as the collection
of nodes stored at non-contiguous memory locations. Each
node of the list contains a pointer to its adjacent node.
Stack: Stack is a linear list in which insertion and deletions are
allowed only at one end, called top.
A stack is an abstract data type (ADT), can be implemented in
most of the programming languages. It is named as stack
because it behaves like a real-world stack, for example: - piles
of plates or deck of cards etc.
Queue: Queue is a linear list in which elements can be inserted
only at one end called rear and deleted only at the other end
called front.
It is an abstract data structure, similar to stack. Queue is opened
at both end therefore it follows First-In-First-Out (FIFO)
methodology for storing the data items.
Non Linear Data Structures: This data structure does not
form a sequence i.e. each item or element is connected with two
or more other items in a non-linear arrangement. The data
elements are not arranged in sequential structure.
Types of Non Linear Data Structures are given below:
Trees: Trees are multilevel data structures with a hierarchical
relationship among its elements known as nodes. The
bottommost nodes in the herierchy are called leaf node while
the topmost node is called root node. Each node contains
pointers to point adjacent nodes.
Tree data structure is based on the parent-child relationship
among the nodes. Each node in the tree can have more than one
10

children except the leaf nodes whereas each node can have
atmost one parent except the root node. Trees can be classfied
into many categories which will be discussed later in this
tutorial.
Graphs: Graphs can be defined as the pictorial representation
of the set of elements (represented by vertices) connected by
the links known as edges. A graph is different from tree in the
sense that a graph can have cycle while the tree can not have
the one.
Operations on data structure
1) Traversing: Every data structure contains the set of data
elements. Traversing the data structure means visiting each
element of the data structure in order to perform some specific
operation like searching or sorting.
Example: If we need to calculate the average of the marks
obtained by a student in 6 different subject, we need to traverse
the complete array of marks and calculate the total sum, then
we will devide that sum by the number of subjects i.e. 6, in
order to find the average.
2) Insertion: Insertion can be defined as the process of adding
the elements to the data structure at any location.
If the size of data structure is n then we can only insert n-1 data
elements into it.
3) Deletion:The process of removing an element from the data
structure is called Deletion. We can delete an element from the
data structure at any random location.
If we try to delete an element from an empty data structure
then underflow occurs.
11

4) Searching: The process of finding the location of an


element within the data structure is called Searching. There are
two algorithms to perform searching, Linear Search and Binary
Search. We will discuss each one of them later in this tutorial.
5) Sorting: The process of arranging the data structure in a
specific order is known as Sorting. There are many algorithms
that can be used to perform sorting, for example, insertion sort,
selection sort, bubble sort, etc.
6) Merging: When two lists List A and List B of size M and N
respectively, of similar type of elements, clubbed or joined to
produce the third list, List C of size (M+N), then this process is
called merging

Data Structure definition:

A data structure is a specialized format for organizing,


processing, retrieving and storing data. There are several basic
and advanced types of data structures, all designed to arrange
data to suit a specific purpose. Data structures make it easy for
users to access and work with the data they need in appropriate
ways. Most importantly, data structures frame the organization
of information so that machines and humans can better
understand it.

In computer science and computer programming, a data


structure may be selected or designed to store data for the
purpose of using it with various algorithms. In some cases, the
algorithm's basic operations are tightly coupled to the data
structure's design. Each data structure contains information
about the data values, relationships between the data and -- in
some cases -- functions that can be applied to the data.
12

For instance, in an object-oriented programming language, the


data structure and its associated methods are bound together as
part of a class definition. In non-object-oriented languages,
there may be functions defined to work with the data structure,
but they are not technically part of the data structure.

Why are data structures important?


Typical base data types, such as integers or floating-point
values, that are available in most computer programming
languages are generally insufficient to capture the logical intent
for data processing and use. Yet applications that ingest,
manipulate and produce information must understand how data
should be organized to simplify processing. Data structures
bring together the data elements in a logical way and facilitate
the effective use, persistence and sharing of data. They provide
a formal model that describes the way the data elements are
organized.

Data structures are the building blocks for more sophisticated


applications. They are designed by composing data elements
into a logical unit representing an abstract data type that has
relevance to the algorithm or application. An example of an
abstract data type is a "customer name" that is composed of the
character strings for "first name," "middle name" and "last
name."

It is not only important to use data structures, but it is also


important to choose the proper data structure for each task.
Choosing an ill-suited data structure could result in
slow runtimes or unresponsive code. Five factors to consider
when picking a data structure include the following:

1. What kind of information will be stored?


13

2. How will that information be used?


3. Where should data persist, or be kept, after it is created?
4. What is the best way to organize the data?
5. What aspects of memory and storage reservation
management should be considered?
How are data structures used?
In general, data structures are used to implement the physical
forms of abstract data types. Data structures are a crucial part
of designing efficient software. They also play a critical role in
algorithm design and how those algorithms are used within
computer programs.

Early programming languages -- such as Fortran, C and C++ -


- enabled programmers to define their own data structures.
Today, many programming languages include an extensive
collection of built-in data structures to organize code and
information. For example, Python lists and dictionaries,
and JavaScript arrays and objects are common coding
structures used for storing and retrieving information.

Software engineers use algorithms that are tightly coupled with


the data structures -- such as lists, queues and mappings from
one set of values to another. This approach can be fused in a
variety of applications, including managing collections of
records in a relational database and creating an index of those
records using a data structure called a binary tree.

Some examples of how data structures are used include the


following:

 Storing data. Data structures are used for efficient data


persistence, such as specifying the collection of attributes
14

and corresponding structures used to store records in a


database management system.
 Managing resources and services. Core operating system
(OS) resources and services are enabled through the use of
data structures such as linked lists for memory allocation,
file directory management and file structure trees, as well as
process scheduling queues.
 Data exchange. Data structures define the organization of
information shared between applications, such as TCP/IP
packets.
 Ordering and sorting. Data structures such as binary search
trees -- also known as an ordered or sorted binary tree --
provide efficient methods of sorting objects, such as
character strings used as tags. With data structures such as
priority queues, programmers can manage items organized
according to a specific priority.
 Indexing. Even more sophisticated data structures such as
B-trees are used to index objects, such as those stored in a
database.
 Searching. Indexes created using binary search trees, B-
trees or hash tables speed the ability to find a specific sought-
after item.
 Scalability. Big data applications use data structures for
allocating and managing data storage across distributed
storage locations, ensuring scalability and performance.
Certain big data programming environments -- such
as Apache Spark -- provide data structures that mirror the
underlying structure of database records to simplify
querying.
15

Characteristics of data structures


Data structures are often classified by their characteristics. The
following three characteristics are examples:

1. Linear or non-linear. This characteristic describes whether


the data items are arranged in sequential order, such as with
an array, or in an unordered sequence, such as with a graph.
2. Homogeneous or heterogeneous. This characteristic
describes whether all data items in a given repository are of
the same type. One example is a collection of elements in an
array, or of various types, such as an abstract data type
defined as a structure in C or a class specification in Java.
3. Static or dynamic. This characteristic describes how the
data structures are compiled. Static data structures have fixed
sizes, structures and memory locations at compile time.
Dynamic data structures have sizes, structures and memory
locations that can shrink or expand, depending on the use.
16

Data type vs. data structure:

Difference between data type and data structure:

Data Types Data Structures

Data Type is the kind or Data Structure is the


form of a variable which is collection of different kinds
being used throughout the of data. That entire data can
program. It defines that the be represented using an
particular variable will object and can be used
assign the values of the given throughout the entire
data type only program.

Implementation through Data Implementation through


Types is a form of abstract Data Structures is called
implementation concrete implementation

Can hold different kind and


Can hold values and not data, types of data within one
so it is data less single object

The data is assigned to the


data structure object using
Values can directly be some set of algorithms and
assigned to the data type operations like push, pop
variables and so on.

Time complexity comes


No problem of time into play when working
complexity with data structures
17

Data Types Data Structures

Examples: stacks, queues,


Examples: int, float, double tree

Categories of data structures:

Data structure has many different uses in our daily life. There
are many different data structures that are used to solve
different mathematical and logical problems. By using data
structure, one can organize and process a very large amount of
data in a relatively short period. Let’s look at different data
structures that are used in different situations.
18

Classification of Data Structure

 Linear data structure: Data structure in which data


elements are arranged sequentially or linearly, where each
element is attached to its previous and next adjacent
elements, is called a linear data structure.
Examples of linear data structures are array, stack, queue,
linked list, etc.
 Static data structure: Static data structure has a

fixed memory size. It is easier to access the elements


in a static data structure.
An example of this data structure is an array.
 Dynamic data structure: In dynamic data
structure, the size is not fixed. It can be randomly
updated during the runtime which may be
considered efficient concerning the memory (space)
complexity of the code.
Examples of this data structure are queue, stack, etc.
 Non-linear data structure: Data structures where data

elements are not placed sequentially or linearly are called


non-linear data structures. In a non-linear data structure, we
can’t traverse all the elements in a single run only.
Examples of non-linear data structures are trees and
graphs.
Arrays:
An array is a linear data structure and it is a collection of items
stored at contiguous memory locations. The idea is to store
multiple items of the same type together in one place. It allows
the processing of a large amount of data in a relatively short
period. The first element of the array is indexed by a subscript
of 0. There are different operations possible in an array, like
Searching, Sorting, Inserting, Traversing, Reversing, and
Deleting.
19

Array

Characteristics of an Array:
An array has various characteristics which are as follows:
 Arrays use an index-based data structure which helps to
identify each of the elements in an array easily using the
index.
 If a user wants to store multiple values of the same data type,

then the array can be utilized efficiently.


 An array can also handle complex data structures by storing

data in a two-dimensional array.


 An array is also used to implement other data structures like

Stacks, Queues, Heaps, Hash tables, etc.


 The search process in an array can be done very easily.

Applications of Array:
Different applications of an array are as follows:
 An array is used in solving matrix problems.
 Database records are also implemented by an array.
 It helps in implementing a sorting algorithm.
 It is also used to implement other data structures like Stacks,
Queues, Heaps, Hash tables, etc.
 An array can be used for CPU scheduling.
 Can be applied as a lookup table in computers.
20

 Arrays can be used in speech processing where every speech


signal is an array.
 The screen of the computer is also displayed by an array .
Here we use a multidimensional array.
 The array is used in many management systems like a
library, students, parliament, etc.
 The array is used in the online ticket booking system.
Contacts on a cell phone are displayed by this array.
 In games like online chess, where the player can store his
past moves as well as current moves. It indicates a hint of
position.
 To save images in a specific dimension in the android Like
360*1200

Real-Life Applications of Array:


 An array is frequently used to store data for mathematical

computations.
 It is used in image processing.

 It is also used in record management.

 Book pages are also real-life examples of an array.

 It is used in ordering boxes as well.

Linked list:
A linked list is a linear data structure in which elements are
not stored at contiguous memory locations. The elements in a
linked list are linked using pointers as shown in the below
image:
Types of linked list:
 Singly-linked list
 Doubly linked list
 Circular linked list
 Doubly circular linked list
21

Linked List

Characteristics of a Linked list:


A linked list has various characteristics which are as follows:
 A linked list uses extra memory to store links.
 During initialization of linked list, there is no need to know

the size of the elements.


 Linked lists are used to implement stacks, queues, graphs,

etc.
 The first node of the linked list is called the Head.

 The next pointer of the last node always points to NULL.

 In linked list, insertion and deletion is possible easily.

 Each node of the linked list consists of a pointer/link which

is the address of the next node.


 Linked list can shrink or grow at any point in time easily.

Applications of the Linked list:


Different applications of linked list are as follows:
 Linked lists are used to implement stacks, queues, graphs,
etc.
 Linked lists are used to perform arithmetic operations on
long integers.
 It is used for the representation of sparse matrices.
 It is used in linked allocation of files.
 It helps in memory management.
22

 It is used in the representation of Polynomial Manipulation


where each polynomial term represents a node in the linked
list.
 Linked lists are used to display image containers. Users can
visit past, current, and next images.
 They are used to store the history of the visited page.
 They are used to perform undo operations.
 Linked are used in software development where they
indicate the correct syntax of a tag.
 Linked lists are used to display social media feeds.

Real-Life Applications of a Linked list:


 A linked list is used in Round-Robin scheduling to keep

track of the turn in multi-player games.


 It is used in image viewer. The previous and next images

are linked, hence can be accessed by the previous and next


buttons.
 In a music playlist, songs are linked to the previous and next

songs.

Stack:
Stack is a linear data structure that follows a particular order
in which the operations are performed. The order is LIFO(Last
in first out). Entering and retrieving data is possible from only
one end. The entering and retrieving of data is also called push
and pop operation in a stack. There are different operations
possible in a stack like reversing a stack using recursion,
Sorting, Deleting the middle element of a stack, etc.
23

Characteristics of a Stack:
Stack has various different characteristics which are as
follows:
 Stack is used in many different algorithms like Tower of
Hanoi, tree traversal, recursion etc.
 Stack is implemented through array or linked list.

 It follows Last In First Out operation i.e., element which is

inserted first will pop in last and vice versa.


 The insertion and deletion happens at one end i.e. from the

top of the stack.


 In stack, if allocated space for stack is full, and still anyone

attempts to add more elements, it will lead to stack


overflow.
Applications of Stack:
Different applications of Stack are as follows:
 Stack data structure is used in evaluation and conversion of
arithmetic expressions.
 Stack is used in Recursion.
 It is used for parenthesis checking.
 While reversing a string, stack is used as well.
 Stack is used in memory management.
 It is also used for processing of function calls.
 The stack is used to convert expressions from infix to
postfix .
24

 The stack is used to perform undo as well as redo operations


in word processors.
 The stack is used in virtual machines like JVM.
 The stack is used in the media players. Useful to play the
next and previous song.
 The stack is used in recursion operation.

Real Life Applications of Stack:


 Real life example of a stack is the layer of eating plates

arranged one above the other. When you remove a plate


from the pile, you can take the plate on the top of the pile.
But this is exactly the plate that was added most recently to
the pile. If you want the plate at the bottom of the pile, you
must remove all the plates on top of it to reach it.
 Browsers use stack data structure to keep track of previously

visited sites.
 Call log in mobile also uses stack data structure.

Queue:
Queue is a linear data structure that follows a particular order
in which the operations are performed. The order is First In
First Out(FIFO) i.e. the data item stored first will be accessed
first. In this, entering and retrieving data is not done from only
one end. An example of a queue is any queue of consumers for
a resource where the consumer that came first is served first.
Different operations are performed on Queue like Reversing a
Queue (with or without using recursion), Reversing the first K
elements of a Queue, etc. Few basic operations performed In
Queue are enqueue, dequeue, front, rear, etc.
25

Characteristics of a Queue:
Queue has various different characteristics which are as
follows:
 Queue is a FIFO (First In First Out) structure.
 To remove the last element of Queue, all the elements

inserted before the new element in the queue must be


removed.
 A queue is an ordered list of elements of similar data types.

Applications of Queue:
Different applications of Queue are as follows:
 Queue is used for handling website traffic.
 It helps to maintain the playlist in media players.
 Queue is used in operating systems for handling interrupts.
 It helps in serving requests on a single shared resource, like
a printer, CPU task scheduling, etc.
 It is used in asynchronous transfer of data for e.g. pipes, file
IO, sockets.
 Queues are used for job scheduling in operating system.
 In social media to upload multiple phots or videos queue is
used.
 To send an e-mail queue data structure is used.
 To handle website traffic at a time queue are used.
 In window operating system, to switch multiple application.
26

Real-Life Applications of Queue:


 A real world example of queue is a single lane one way road,

where the vehicle that enters first will exit first.


 A more real-world example can be seen in the queue at the

ticket windows.
 Cashier line in a store is also an example of queue.

 People on an escalator

Tree:
A tree is a non-linear and hierarchal data structure where the
elements are arranged in a tree-like structure. In a tree, the
topmost node is called the root node. Each node contains some
data, and data can be of any type. It consists of a central node,
structural nodes, and sub-nodes which are connected via
edges. Different tree data structures allow quicker and easier
access to the data as it is a non-linear data structure. A tree has
various terminologies like Node, Root, Edge, Height of a tree,
Degree of a tree, etc.
There are different types of Tree like
 Binary Tree,
 Binary Search Tree,
 AVL Tree,
 B-Tree, etc.
27

Tree

Characteristics of a Tree:
Tree has various different characteristics which are as follows:
 A tree is also known as a Recursive data structure.
 In a tree, Height of the root can be defined as the longest

path from the root node to the leaf node.


 In a tree, one can also calculate the depth from the top to

any node. The root node has depth of 0.


Applications of Tree:
Different applications of Tree are as follows:
 Heap is a tree data structure that is implemented using
arrays, and used to implement priority queues.
 B-Tree and B+ Tree are used to implement indexing in

databases.
 Syntax Tree helps in scanning, parsing, generation of code

and evaluation of arithmetic expressions in Compiler


design.
 K-D Tree is a space partitioning tree used to organize points

in K-dimensional space.
 Spanning trees are used in routers in computer networks.

Real Life Applications of Tree:


 In real life, tree data structure helps in Game Development.

 It also helps in indexing in databases.

 Decision Tree is an efficient machine learning tool,

commonly used in decision analysis. It has a flowchart-like


structure that helps to understand data.
 Domain Name Server also uses tree data structure.

 The most common use case of a tree is any social

networking site.
28

Graph:
A graph is a non-linear data structure that consists of vertices
(or nodes) and edges. It consists of a finite set of vertices and
set of edges that connect a pair of nodes. Graph is used to solve
the most challenging and complex programming problems. It
has different terminologies which are Path, Degree, Adjacent
vertices, Connected components, etc.

Graph

Characteristics of Graph:
Graph has various different characteristics which are as
follows:
 The maximum distance from a vertex to all the other
vertices is considered as the Eccentricity of that vertex.
 The vertex having minimum Eccentricity is considered the

central point of the graph.


 The minimum value of Eccentricity from all vertices is

considered as the radius of a connected graph.


Applications of Graph:
Different applications of Graph are as follows:
 Graph is used to represent the flow of computation.
 It is used in modeling graphs.
 The operating system uses Resource Allocation Graph.
29

 Also used in the World Wide Web where the web pages
represent the nodes.
Real-Life Applications of Graph:
 One of the most common real-world examples of a graph is

Google Maps where cities are located as vertices and paths


connecting those vertices are located as edges of the graph.
 A social network is also one real-world example of a graph

where every person on the network is a node, and all of their


friendships on the network are the edges of the graph.
 A graph is also used to study molecules in physics and

chemistry.

Data structure operations:

Operations on data structure


1) Traversing: Every data structure contains the set of data
elements. Traversing the data structure means visiting each
element of the data structure in order to perform some specific
operation like searching or sorting.
Example: If we need to calculate the average of the marks
obtained by a student in 6 different subject, we need to traverse
the complete array of marks and calculate the total sum, then
we will devide that sum by the number of subjects i.e. 6, in
order to find the average.
2) Insertion: Insertion can be defined as the process of adding
the elements to the data structure at any location.
If the size of data structure is n then we can only insert n-1 data
elements into it.
30

3) Deletion:The process of removing an element from the data


structure is called Deletion. We can delete an element from the
data structure at any random location.
If we try to delete an element from an empty data structure
then underflow occurs.
4) Searching: The process of finding the location of an
element within the data structure is called Searching. There are
two algorithms to perform searching, Linear Search and Binary
Search. We will discuss each one of them later in this tutorial.
5) Sorting: The process of arranging the data structure in a
specific order is known as Sorting. There are many algorithms
that can be used to perform sorting, for example, insertion sort,
selection sort, bubble sort, etc.
6) Merging: When two lists List A and List B of size M and N
respectively, of similar type of elements, clubbed or joined to
produce the third list, List C of size (M+N), then this process is
called merging

Applications of data structures:

A data structure is a particular way of organizing data in a


computer so that it can be used effectively. In this article, the
real-time applications of all the data structures are discussed.
Application of Arrays:
Arrays are the simplest data structures that store items of the
same data type. A basic application of Arrays can be storing
data in tabular format. For example, if we wish to store the
contacts on our phone, then the software will simply place all
our contacts in an array.
31

Some other applications of the arrays are:


1. Arrangement of the leader-board of a game can be done
simply through arrays to store the score and arrange them
in descending order to clearly make out the rank of each
player in the game.
2. A simple question Paper is an array of numbered questions
with each of them assigned some marks.
3. 2D arrays, commonly known as, matrices, are used in
image processing.
4. It is also used in speech processing, in which each speech
signal is an array.
5. Your viewing screen is also a multidimensional array of
pixels.
6. Book titles in a Library Management Systems.
7. Online ticket booking.
8. Contacts on a cell phone.
9. For CPU scheduling in computer.
10. To store the possible moves of chess on a chessboard.
11. To store images of a specific size on an android or
laptop.

Application of Strings:

1. Spam email detection.


2. Plagiarism detection.
32

3. Search engine.
4. Digital forensic and information retrieval system
5. Spell checkers.
6. In the database to check valid information of the user

Application of Matrix:

Matrix is an ordered collection of columns and rows of


elements. It is necessary to enclose the elements of a matrix
within the brackets.

Some applications of a matrix are:


1. In geology, matrices are used for making seismic surveys.
2. Used for plotting graphs, and statistics and also to do
scientific studies and research in almost different fields.
3. Matrices are also used in representing real-world data like
the population of people, infant mortality rate, etc.
4. They are the best representation methods for plotting
surveys.
5. For refraction and reflection in science optics.
6. Electronic circuit and quantum physics.
7. Media player.
33

8. Mailing list.
9. Symbol table creation.

Application of Linked Lists:


A linked list is a sequence data structure, which connects
elements, called nodes, through links.

Some other applications of the linked list are:


1. Images are linked with each other. So, an image viewer
software uses a linked list to view the previous and the
next images using the previous and next buttons.
2. Web pages can be accessed using the previous and the next
URL links which are linked using a linked list.
3. The music players also use the same technique to switch
between music.
4. To keep the track of turns in a multi-player game,
a circular linked list is used.
5. MS-Paint drawings and shapes are connected via a linked
list on canvas.
6. Escalators — Circular linked List.
7. Each of the lines of code in an IDE internally is a record
on a doubly-linked list.
8. Left/Right swipe on Tinder uses a doubly-linked list.
9. Social media content “feeds”.
10. Used for symbol table management in a designing
compiler
11. Used in switching between applications and programs
(Alt + Tab) in the Operating system (implemented using
Circular Linked List)
12. Train coaches are connected to one another in a doubly-
linked list fashion.
34

13. It can be used to implement Stacks, Queues, Graphs,


and Trees.
14. To perform undo operation.
15. Back button.[LIFO]
16. Syntax in the coding editor.
17. History of visited pages.

Application of Stack:
A stack is a data structure that uses LIFO order.

Some Applications of a stack are:


1. Converting infix to postfix expressions.
2. Undo/Redo button/operation in word processors.
3. Syntaxes in languages are parsed using stacks.
4. It is used in many virtual machines like JVM.
5. Forward-backward surfing in the browser.
6. History of visited websites.
7. Message logs and all messages you get are arranged in a
stack.
8. Call logs, E-mails, Google photos’ any gallery, YouTube
downloads, Notifications ( latest appears first ).
9. Scratch card’s earned after Google pay transaction.
10. Wearing/Removing Bangles, Pile of Dinner Plates,
Stacked chairs.
11. Changing wearables on a cold evening, first in, comes
out at last.
12. Last Hired, First Fired - which is typically utilized when
a company reduces its workforce in an economic
recession.
13. Loading bullets into the magazine of a gun. The last one
to go in is fired first. Bam!
14. Java Virtual Machine.
35

15. Recursion.
16. Used in IDEs to check for proper parentheses matching
17. Media playlist. T o play previous and next song
Application of Queue:
A queue is a data structure that uses FIFO order.

Some applications of a queue are:


1. Operating System uses queues for job scheduling.
2. To handle congestion in the networking queue can be used.
3. Data packets in communication are arranged in queue
format.
4. Sending an e-mail, it will be queued.
5. Server while responding to request
6. Uploading and downloading photos, first kept for
uploading/downloading will be completed first (Not if
there is threading)
7. Most internet requests and processes use queue.
8. While switching multiple applications, windows use
circular queue.
9. In Escalators, Printer spooler, Car washes queue.
10. A circular queue is used to maintain the playing
sequence of multiple players in a game.
11. A queue can be implemented in - Linked List-based
Queue, Array-based Queue, Stack-based Queue.
12. Uploading and downloading photos, first kept for
uploading/downloading will be completed first (Not if
there is threading).
13. Handle website traffic
14. CPU scheduling
36

Priority Queue:
1. Process scheduling in the kernel.
2. Priority queues are used in file downloading operations in
a browser
3. Vehicle at the toll center.

Application of Sorting Algorithms

1. Order things by their value.


2. Backend Databases (Merge Sort).
3. Playing Cards with your friends (Insertion Sort).
4. sort() - uses IntroSort (a hybrid of Quicksort, Heapsort, and
Insertion Sort), Faster than qsort()
5. Contact list on the phone
6. Online shopping . To sort prize in different range .
example : flipkart and amazon.

Application of Graph:
Graph is a data structure where data is stored in a collection
of interconnected vertices (nodes) and edges (paths).

Some applications of a graph are:


1. Facebook’s Graph API uses the structure of Graphs.
2. Google’s Knowledge Graph also has to do something with
Graph.
3. Dijkstra algorithm or the shortest path first algorithm also
uses graph structure to find the smallest path between the
nodes of the graph.
4. The GPS navigation system also uses shortest path APIs.
37

5. Networking components have a huge application for graph


6. Facebook, Instagram, and all social media networking sites
every user is Node
7. Data organization
8. React’s virtual DOM uses graph data structures.
9. MS Excel uses DAG (Directed Acyclic Graphs).
10. Path Optimization Algorithms, BFS, DFS.
11. Recommendation Engines.
12. Scientific Computations, Flight Networks, Page ranking.
13. Google map to find nearest location.
14. Facebook to suggest mutual friends

Application of Tree:
Trees are hierarchical structures having a single root node.

Some applications of the trees are:


1. XML Parser uses tree algorithms.
2. The decision-based algorithm is used in machine learning
which works upon the algorithm of the tree.
3. Databases also use tree data structures for indexing.
4. Domain Name Server(DNS) also uses tree structures.
5. File explorer/my computer of mobile/any computer
6. BST used in computer Graphics
7. Posting questions on websites like Quora, the comments
are a child of questions.
8. Parsers(XML parser).
9. Code Compression(zip).
10. DOM in Html.
11. Evaluate an expression (i.e., parse).
12. Integral to compilers/automata theory.
13. To store the possible moves in a chess game.
38

14. To store the genealogy information of biological


species.
15. Used by JVM (Java Virtual Machine) to store Java
objects.

Application of Binary Search Tree:

1. D Game Engine.
2. Computer Graphics Rendering.
3. Routing table.
RED-BLACK TREE
1. Used when there is frequent Insertion/Deletion and few
searches.
2. K -mean Clustering using a red-black tree, Databases,
Simple-minded database, searching words inside
dictionaries, searching on the web.
3. Process Scheduling in Linux.
AVL TREE
1. More Search and less Insertion/Deletion.
2. Data Analysis and Data Mining and the applications which
involve more searches.
SUFFIX TREE
1. Fast full-text search, used in most word processors.
TRIE
1. Dictionary application.
2. Autocomplete feature in searching.
3. Auto-completing the text and spells checking.
Application of Hash Tables:
Hash Tables are store data in key-value pairs. It only stores
data that has a key associated with it. Inserting and Searching
operations are easily manageable while using Hash Tables.
39

Some applications of a hashtable are:


1. Data stored in databases is generally of the key-value
format which is done through hash tables.
2. Every time we type something to be searched in google
chrome or other browsers, it generates the desired output
based on the principle of hashing.
3. Message Digest, a function of cryptography also uses
hashing for creating output in such a manner that reaching
the original input from that generated output is almost next
to impossible.
4. In our computers we have various files stored in it, each
file has two very crucial pieces of information that is, the
filename and file path, in order to make a connection
between the filename to its corresponding file path hash
tables are used.
5. Social network “feeds”.
6. Password hashing.
7. Used for fast data lookup - symbol table for compilers,
database indexing, caches, Unique data representation.
8. To store a set of fixed keywords that are referenced very
frequently.
40

Application of Heap:
A Heap is a special case of a binary tree where the parent
nodes are compared to their children with their values and are
arranged accordingly.

Some applications of heaps are:


1. In heapsort Algorithm, is an algorithm for sorting elements
in either min heap(the key of the parent is less than or
equal to those of its children) or max heap(the key of the
parent is greater than or equal to those of its
children), sorting is done with the creation of heaps.
2. Heaps are used to implementing a priority queue where
priority is based on the order of heap created.
3. Systems concerned with security and embedded system
such as Linux Kernel uses Heap Sort because of the O( n
log(n) ).
4. If we are stuck in finding the Kthsmallest (or largest) value
of a number then heaps can solve the problem in an easy
and fast manner.
41

5. Used by JVM (Java Virtual Machine) to store Java objects.

Application Of Greedy Algorithm:

1. Dijkstra algorithm.
2. Shopping on a tight budget but want to buy gifts for all
family members.
3. Prim’s and Kruskal’s algorithms are used for finding the
minimum spanning trees.
Dijkstra Algorithm
1. Used in applications like Google Maps to find the shortest
path in a graph.
PRIM’S and KRUSKAL’S
1. Used for finding the minimum spanning trees.

Application Of Dynamic Programming:

A. Real-life examples
1. In Google Maps to find the shortest path between the
source and the series of destinations (one by one) out of
the various available paths.
2. In networking to transfer data from a sender to various
receivers in a sequential manner.
B. Applications in Computer science
1. Multi-stage graph
2. Traveling salesman problem
3. Largest common subsequence – to identify similar videos
used by youtube
4. Optimal search binary tree- to get optimized search results.
5. Single source shortest path- Bellman-Ford Algorithm.
6. Document Distance Algorithms- to identify the extent of
similarity between two text documents used by Search
42

engines like Google, Wikipedia, Quora, and other


websites.
7. To schedule jobs on a processor.
8. To find potential partners.
9. In theory, graphics, AI, system.

Application Of Backtracking:

1. Suppose we are coding a chess-playing algorithm and at a


certain point, the algorithm finds that a set of steps fails to
win. In this situation, the algorithm will reverse back to the
safe state and try another possible set of steps.
2. Sudoku solver
3. 2048 game
4. Computer networking.
5. To solve problem of the N Queen.
6. To solve the problem of the Maze.
7. To find the Hamiltonian Path present in a graph.
8. To the love problem of Knight’s Tour Problem.

Algorithms complexity and time-space tradeoff:

The best Algorithm is that which helps to solve a problem that


requires less space in memory and also takes less time to
generate the output. But in general, it is not always possible to
achieve both of these conditions at the same time. The most
common condition is an algorithm using a lookup table. This
means that the answers to some questions for every possible
value can be written down. One way of solving this problem
is to write down the entire lookup table, which will let you
find answers very quickly but will use a lot of space. Another
way is to calculate the answers without writing down anything,
which uses very little space, but might take a long time.
43

Therefore, the more time-efficient algorithms you have, that


would be less space-efficient.
Types of Space-Time Trade-off
 Compressed or Uncompressed data

 Re Rendering or Stored images

 Smaller code or loop unrolling

 Lookup tables or Recalculation

Compressed or Uncompressed data: A space-time trade-off


can be applied to the problem of data storage. If data stored
is uncompressed, it takes more space but less time. But if the
data is stored compressed, it takes less space but more time to
run the decompression algorithm. There are many instances
where it is possible to directly work with compressed data. In
that case of compressed bitmap indices, where it is faster to
work with compression than without compression.
Re-Rendering or Stored images: In this case, storing only
the source and rendering it as an image would take more space
but less time i.e., storing an image in the cache is faster than
re-rendering but requires more space in memory.
Smaller code or Loop Unrolling: Smaller code occupies less
space in memory but it requires high computation time that is
required for jumping back to the beginning of the loop at the
end of each iteration. Loop unrolling can optimize execution
speed at the cost of increased binary size. It occupies more
space in memory but requires less computation time.
Lookup tables or Recalculation: In a lookup table, an
implementation can include the entire table which reduces
computing time but increases the amount of memory needed.
It can recalculate i.e., compute table entries as needed,
increasing computing time but reducing memory
requirements.
For Example: In mathematical terms, the sequence Fn of
the Fibonacci Numbers is defined by the recurrence relation:
44

F n = F n – 1 + F n – 2,
where, F0 = 0 and F1 = 1.
A simple solution to find the Nth Fibonacci
term using recursion from the above recurrence relation.
Below is the implementation using recursion:

// C++ program to find Nth Fibonacci

// number using recursion

#include <iostream>

using namespace std;

// Function to find Nth Fibonacci term

int Fibonacci(int N)

// Base Case

if (N < 2)

return N;

// Recursively computing the term


45

// using recurrence relation

return Fibonacci(N - 1) + Fibonacci(N - 2);

// Driver Code

int main()

int N = 5;

// Function Call

cout << Fibonacci(N);

return 0;

Output:
5
46

Time Complexity: O(2N)


Auxiliary Space: O(1)
Explanation: The time complexity of the above
implementation is exponential due to multiple calculations of
the same subproblems again and again. The auxiliary space
used is minimum. But our goal is to reduce the time
complexity of the approach even it requires extra space. Below
is the Optimized approach discussed.
Efficient Approach: To optimize the above approach, the
idea is to use Dynamic Programming to reduce the complexity
by memoization of the overlapping subproblems as shown in
the below recursion tree:

// C++ program to find Nth Fibonacci

// number using recursion


47

#include <iostream>

using namespace std;

// Function to find Nth Fibonacci term

int Fibonacci(int N)

int f[N + 2];

int i;

// 0th and 1st number of the

// series are 0 and 1

f[0] = 0;

f[1] = 1;

// Iterate over the range [2, N]

for (i = 2; i <= N; i++) {


48

// Add the previous 2 numbers

// in the series and store it

f[i] = f[i - 1] + f[i - 2];

// Return Nth Fibonacci Number

return f[N];

// Driver Code

int main()

int N = 5;

// Function Call
49

cout << Fibonacci(N);

return 0;

Output:
5

Time Complexity: O(N)


Auxiliary Space: O(N)
Explanation: The time complexity of the above
implementation is linear by using an auxiliary space for
storing the overlapping subproblems states so that it can be
used further when required.

Big-O notataion:
Big-O Analysis of Algorithms

We can express algorithmic complexity using the big-O


notation. For a problem of size N:
 A constant-time function/method is “order 1” : O(1)
 A linear-time function/method is “order N” : O(N)

 A quadratic-time function/method is “order N squared” :

O(N 2 )
Definition: Let g and f be functions from the set of natural
numbers to itself. The function f is said to be O(g) (read big-
50

oh of g), if there is a constant c > 0 and a natural


number n0 such that f (n) ≤ cg(n) for all n >= n0 .
Note: O(g) is a set!
Abuse of notation: f = O(g) does not mean f ∈ O(g).
The Big-O Asymptotic Notation gives us the Upper Bound
Idea, mathematically described below:
f(n) = O(g(n)) if there exists a positive integer n 0 and a positive
constant c, such that f(n)≤c.g(n) ∀ n≥n0
The general step wise procedure for Big-O runtime analysis is
as follows:
1. Figure out what the input is and what n represents.
2. Express the maximum number of operations, the algorithm
performs in terms of n.
3. Eliminate all excluding the highest order terms.
4. Remove all the constant factors.
Some of the useful properties of Big-O notation analysis are
as follow:
Constant Multiplication:
If f(n) = c.g(n), then O(f(n)) = O(g(n)) ; where c is a nonzero
constant.
Polynomial Function:
If f(n) = a0 + a1.n + a2.n2 + —- + am.nm, then O(f(n)) =
O(nm).
Summation Function:
If f(n) = f1(n) + f2(n) + —- + fm(n) and fi(n)≤fi+1(n) ∀ i=1, 2,
—-, m,
then O(f(n)) = O(max(f 1(n), f2(n), —-, fm(n))).
Logarithmic Function:
If f(n) = logan and g(n)=log bn, then O(f(n))=O(g(n))
51

; all log functions grow in the same manner in terms of Big-


O.
Basically, this asymptotic notation is used to measure and
compare the worst-case scenarios of algorithms theoretically.
For any algorithm, the Big-O analysis should be
straightforward as long as we correctly identify the operations
that are dependent on n, the input size.

Runtime Analysis of Algorithms

In general cases, we mainly used to measure and compare the


worst-case theoretical running time complexities of
algorithms for the performance analysis.
The fastest possible running time for any algorithm is O(1),
commonly referred to as Constant Running Time. In this
case, the algorithm always takes the same amount of time to
execute, regardless of the input size. This is the ideal runtime
for an algorithm, but it’s rarely achievable.
In actual cases, the performance (Runtime) of an algorithm
depends on n, that is the size of the input or the number of
operations is required for each input item.
The algorithms can be classified as follows from the best-to-
worst performance (Running Time Complexity):
A logarithmic algorithm – O(logn)
Runtime grows logarithmically in proportion to n.
A linear algorithm – O(n)
Runtime grows directly in proportion to n.
A superlinear algorithm – O(nlogn)
Runtime grows in proportion to n.
A polynomial algorithm – O(nc)
Runtime grows quicker than previous all based on n.
52

A exponential algorithm – O(cn)


Runtime grows even faster than polynomial algorithm based
on n.
A factorial algorithm – O(n!)
Runtime grows the fastest and becomes quickly unusable for
even
small values of n.
Where, n is the input size and c is a positive constant.

Algorithmic Examples of Runtime Analysis:


Some of the examples of all those types of algorithms (in
worst-case scenarios) are mentioned below:
Logarithmic algorithm – O(logn) – Binary Search.
Linear algorithm – O(n) – Linear Search.
Superlinear algorithm – O(nlogn) – Heap Sort, Merge
Sort.
Polynomial algorithm – O(n^c) – Strassen’s Matrix
Multiplication, Bubble Sort, Selection Sort, Insertion Sort,
Bucket Sort.
Exponential algorithm – O(c^n) – Tower of Hanoi.
Factorial algorithm – O(n!) – Determinant Expansion by
53

Minors, Brute force Search algorithm for Traveling


Salesman Problem.
Mathematical Examples of Runtime Analysis:
The performances (Runtimes) of different orders of algorithms
separate rapidly as n (the input size) gets larger. Let’s consider
the mathematical example:
If n = 10, If n=20,
log(10) = 1; log(20) = 2.996;
10 = 10; 20 = 20;
10log(10)=10; 20log(20)=59.9;
102=100; 20 2=400;
210=1024; 2 20=1048576;
10!=3628800; 20!=2.432902e+18 18;
Memory Footprint Analysis of Algorithms
For performance analysis of an algorithm, runtime
measurement is not only relevant metric but also we need to
consider the memory usage amount of the program. This is
referred to as the Memory Footprint of the algorithm, shortly
known as Space Complexity.
Here also, we need to measure and compare the worst case
theoretical space complexities of algorithms for the
performance analysis.
It basically depends on two major aspects described below:
 Firstly, the implementation of the program is responsible for
memory usage. For example, we can assume that recursive
implementation always reserves more memory than the
corresponding iterative implementation of a particular
problem.
 And the other one is n, the input size or the amount of
storage required for each item. For example, a simple
algorithm with a high amount of input size can consume
54

more memory than a complex algorithm with less amount


of input size.
Algorithmic Examples of Memory Footprint Analysis: The
algorithms with examples are classified from the best-to-worst
performance (Space Complexity) based on the worst-case
scenarios are mentioned below:
Ideal algorithm - O(1) - Linear Search, Binary Search,
Bubble Sort, Selection Sort, Insertion Sort, Heap Sort, Shell
Sort.
Logarithmic algorithm - O(log n) - Merge Sort.
Linear algorithm - O(n) - Quick Sort.
Sub-linear algorithm - O(n+k) - Radix Sort.
Space-Time Tradeoff and Efficiency
There is usually a trade-off between optimal memory use and
runtime performance.
In general for an algorithm, space efficiency and time
efficiency reach at two opposite ends and each point in
between them has a certain time and space efficiency. So, the
more time efficiency you have, the less space efficiency you
have and vice versa.
For example, Mergesort algorithm is exceedingly fast but
requires a lot of space to do the operations. On the other side,
Bubble Sort is exceedingly slow but requires the minimum
space.
At the end of this topic, we can conclude that finding an
algorithm that works in less running time and also having
less requirement of memory space, can make a huge
difference in how well an algorithm performs.
Example of Big-oh noatation:
55

 C++

// C++ program to findtime complexity for single for loop

#include <bits/stdc++.h>

using namespace std;

// main Code

int main()

//declare variable

int a = 0, b = 0;

//declare size

int N = 5, M = 5;

// This loop runs for N time

for (int i = 0; i < N; i++) {

a = a + 5;

// This loop runs for M time


56

for (int i = 0; i < M; i++) {

b = b + 10;

//print value of a and b

cout << a << ' ' << b;

return 0;

Output
25 50
Explanation :
First Loop runs N Time whereas Second Loop runs M Time.
The calculation takes O(1)times.
So by adding them the time complexity will be O ( N + M +
1) = O( N + M).
Time Complexity : O( N + M)
57

CHAPTER-2
STRINGS
Introduction:
What is String?

Strings are defined as an array of characters. The difference


between a character array and a string is the string is terminated
with a special character ‘\0’.
Below are some examples of strings:
“geeks”, “for”, “geeks”, “GeeksforGeeks”, “Geeks for
Geeks”, “123Geeks”, “@123 Geeks”
How String is represented in Memory?

In C, a string can be referred to either using a character pointer


or as a character array. When strings are declared as character
arrays, they are stored like other types of arrays in C. For
example, if str[] is an auto variable then the string is stored in
the stack segment, if it’s a global or static variable then stored in
the data segment, etc.

Declaration of Strings
 char str[] = { ‘J’ , ’A’ , ’V’ , ’A’ , ’T’ , ’P’ , ’O’ , ’I’ , ’N’
, ’T’ , ’\0’ };
 char str[] = { “JAVATPOINT” };
o In this form of declaration, '0' will automatically

insert at the end.


58

String datatypes
A datatype string is a datatype modeled on a structured
sequence concept. Strings are a data form that is so effective
and necessary that they are introduced in almost every
computer programming language. They are accessible as
primitive data types in a specific languages and synthetic
varieties in others. The structure of so many high-level
programming languages enables an occurrence of a string
datatype to be interpreted by a string, typically referenced in
certain manner; such a meta-string is called a symbolic or string
literal.
59

String Length
While structured strings may have an absolute fixed length,
they often restrict string's size to an imaginary maximum in
specific languages. Besides, there are two kinds of string data
types: strings of a specified length that have a defined
maximum size to be calculated at the time of compilation and
which use a similar amount of storage space, whether or not
this maximum is required, and strings of variable size which do
not have arbitrary finite size and which might be using different
amounts of storage at runtime, based on the exact parameters.
Variable-length strings are the bulk of sequences in other
programming languages. Although variable-length strings are,
for example, restricted in size by the amount of memory storage
usable. The string's length can be processed as a different
integer number (which can give the size another arbitrary
barrier) or impliedly as a revocation character, usually a
character quality with all null bits, such as in the computer
language C.
Character Encoding
Traditionally, string datatypes have assigned one byte per
character. Still, while the real character set diverse by province,
character implementations were sufficiently similar to prevent
developers from disregarding this because a program's
specially prepared characters (such as time frame, storage, and
comma) were in a similar place in all of the Unicode characters
that a project would encounter. Traditionally, these character
series are based on ASCII or EBCDIC. When a message was
exhibited on a framework using distinct encryption in one
processing, a message was often disfigured, although
somewhat legible. Some internet users learned to read the
disfigured text.
60

Unicode has a kind of condensed image. Many


other programming languages now have Unicode string
datatypes. The chosen byte source model of Unicode UTF-8 is
intended not to have the earlier mentioned issues for older
multibyte Unicode characters. UTF-8, UTF-16, and UTF-32
enable the developer to realize that the application divisions of
pre-defined length are distinct from the "characters"; however,
the main problem is poorly built APIs to mask this distinction.
Implementation
Several languages, such as Ruby and C++, enable a string's
components to be updated after it has been developed; these are
called mutable strings. The value is set in other languages, such
as Java and Python, and must generate a new string if any
changes are to be made. These are called permanent sequences
(some of these languages also provide another mutable type,
such as Java and .NET StringBuilder, the thread-safe Java
String Buffer, and the Cocoa NS Mutable String). Usually,
strings are configured as sequences of bytes, characters, or code
items to allow easy access, excluding characters when they
have a finite size, to separate groups or substrings.
Alternatively, a couple of languages like Haskell incorporate
them as relational databases.
Representation of String
String representations are highly dependent on the availability
of the text catalog and the conventional cryptographic system.
Former implementations of strings were designed to operate
with ASCII-defined catalog and encryption, or more modern
enhancements such as the ISO (International Organizations for
Standardizations) 8859 sequence. The Unicode's substantial
accompaniment, together with a range of essential embedding
61

like UTF-8 and UTF-16, is often used by advanced


functionalities.
The word byte string tends to indicate a sequence of bytes for
specific purposes instead of the strings of only (readable) texts,
strings of sections, or something like that. Byte strings also
indicate that bytes may accept any input and therefore can
retain any information which implies that neither value can be
treated as a termination value.
Some strings implement some implementations to sequences of
variable lengths with inputs that store text codes for the
respective strings. The significant difference is that a single
rational text may occupy upwards of a single entry in the array
of certain encryptions.
For instance, it exists with UTF-8 since individual codes (UCS
code assists) can occupy anything between one to four bytes.
An infinite number of values can be used for unique characters.
The string sequence (number of bits) varies from the actual size
of the line in these situations (when the number of bytes is
being used). UTF-32 avoids the first portion of the issue.
62

A string can be represented in the below-mentioned forms.


They are-
o Bit and byte terminated
In both software and hardware, using a unique byte apart from
zero for ending strings has historically appeared, however often
with a value that's also a publishing character. "Several
embedded systems use $ symbol: use CDC (Control data
corporation) systems (it means the character had a zero value)
and the use ZX80 "as this was the sequence substring in the
BASIC language of the machine. "Data processing" devices
such as IBM 1401 use a new acronym sign bit to delineate
strings on the left side, in which the procedure will begin on the
right side, quite identical. In most of the other portions of the
63

sequence, this bit will have to be explicit. Although the IBM


1401 had a 7-bit code, absolutely nobody ever conceived of
using this as a function to circumvent the seventh-bit
designation (for instance) manage ASCII codes.
Initially, microcomputer software pointed out that the elevated
bit was not used in ASCII codes and configured it to signify a
sequence's termination. Leading up to output, it must return it
to 0 value.
o Null-terminated
Using a unique termination word, the duration of a sequence
can be encoded impliedly; this is always the zero-value
character (it means all bits contain zero value), a standard that
the prominent C programming language uses and perpetuates.
This formulation is also usually alluded to as a C string. The n-
character string composition consumes n + 1 storage (1 is for
the terminator) and seems to be, thus an implied data structure.
In ended strings and in any string, the ending character is not
an acceptable word. Length-field strings don't have this
restriction and can contain specific binary information as well.
For Example:
Here, we have an instance in which a null-terminated sequence
preserved as 8-bit hexadecimal digits in a 10-byte frame,
together with its ASCII (or even more standard UTF-8)
expression, is:

The string size in the above illustration, 'DAVID,' is five words,


but it takes 6 bytes in size. Characters do not appear to be an
64

element of the interpretation after the terminator; they can be


either component of any other data or only trash. (Strings of
this type are often called ASCIZ sequence after being declared
by the declaration's existing programming languages).
o String as a record
Several programming languages introduce strings via records
through some structural properties, like object-oriented models,
along with the following code:
1. Class Str
2. {
3. size_p length;
4. char *text;
5. };
Although the implementation usually is secret, it is essential to
manipulate and change the sequence via member variables. The
input text refers to a system memory that is dynamically
assigned, which can be extended as required.
o Length-Prefixed
It is also possible to directly save a string's size, such as by
appending the string only with size as a byte value. In several
Pascal accents, this pattern is used; several people call such a
series a Pascal string or a P-string as a result. Saving the size of
strings as bytes restricts the maximum length of strings to 255.
Enhanced representations of Pascal-strings use 16-, 32-, or 64-
bit terms to hold the string size to prevent certain restrictions.
When the length field reaches the physical address, only the
available storage restricts the strings.
65

If the size is limited, it can be encrypted in steady storage.


Usually, a computer term, resulting in an implied data structure,
using n+k storage, here k is the number of characters in a text
(8 on a 64-bit system representing 8-bit ASCII, one on the 32-
bit UTF-32/UCS-4 on a 32-bit system, etc.). Encrypting a size
n requires log(n) memory if the size is not constrained, so
length-prefixed sequences are a concise data structure,
encrypting a string of size n in log(n) + n memory.
There is no fixed size in the size-prefix field alone in the above
situation, but as the sequence developed, the individual string
value must be shifted so that the sequence number has to be
expanded.
Below is a Pascal string placed including its ASCII / UTF-8
expression in a 10-byte buffer:

o Other Representations
Either text elimination or length codes restrict sequences: C
language character arrays contain null values. For instance,
they cannot be explicitly treated by the C string library's
operations: strings with a size value are constrained to the
maximum sized code value.
By creative programming, both of the above drawbacks can be
resolved.
Here, we have some different representations of string in
several languages-
66

String Representation in 'C' Language


As we know, C programming does not enable a character type
parameter to hold over than a single character. In C
programming, the below-given examples are therefore
incorrect and generate syntax errors.
1. char c1 = "pq";
2. char c2 = "100";
As we have seen before, the array's idea is to save in a variable
over than single value of a particular data type. Here we have
the syntactical structure for storing and printing five digits in
an integer-type sequence.
1. # include<stdio.h>
2. # include<conio.h>
3. Main ()
4. {
5. int number [5] = {100, 200, 300, 400, 500};
6. int i = 0;
7. while (i < 5)
8. {
9. Printf("number[%d] = %d\n", i, number[i]);
10. i = i + 1;
11. }
12. }
After the successful execution of the above code syntax, we got
the below-given output.
67

Storing strings:

In C, a string can be referred to either using a character pointer


or as a character array.
Strings as character arrays
 C

char str[4] = "GfG"; /*One extra for string terminator*/

/* OR */

char str[4] = {‘G’, ‘f’, ‘G’, '\0'}; /* '\0' is string terminator */

When strings are declared as character arrays, they are stored


like other types of arrays in C. For example, if str[] is an auto
variable then string is stored in stack segment, if it’s a global
or static variable then stored in data segment, etc.
Strings using character pointers
Using character pointer strings can be stored in two ways:
1) Read only string in a shared segment.
When a string value is directly assigned to a pointer, in most
of the compilers, it’s stored in a read-only block (generally in
data segment) that is shared among functions.
68

 C

char *str = "GfG";

In the above line “GfG” is stored in a shared read-only


location, but pointer str is stored in a read-write memory. You
can change str to point something else but cannot change value
at present str. So this kind of string should only be used when
we don’t want to modify string at a later stage in the program.
2) Dynamically allocated in heap segment.
Strings are stored like other dynamically allocated things in C
and can be shared among functions.

 C

char *str;

int size = 4; /*one extra for ‘\0’*/

str = (char *)malloc(sizeof(char)*size);

*(str+0) = 'G';

*(str+1) = 'f';

*(str+2) = 'G';

*(str+3) = '\0';

Let us see some examples to better understand the above ways


to store strings.
69

Example 1 (Try to modify string)


The below program may crash (gives segmentation fault error)
because the line *(str+1) = ‘n’ tries to write a read only
memory.
 C

int main()

char *str;

str = "GfG"; /* Stored in read only part of data segment */

*(str+1) = 'n'; /* Problem: trying to modify read only memory */

getchar();

return 0;

The below program works perfectly fine as str[] is stored in


writable stack segment.

 C

int main()

{
70

char str[] = "GfG"; /* Stored in stack segment like other auto variables
*/

*(str+1) = 'n'; /* No problem: String is now GnG */

getchar();

return 0;

Below program also works perfectly fine as data at str is stored


in writable heap segment.

 C

int main()

int size = 4;

/* Stored in heap segment like other dynamically allocated things */

char *str = (char *)malloc(sizeof(char)*size);

*(str+0) = 'G';

*(str+1) = 'f';
71

*(str+2) = 'G';

*(str+3) = '\0';

*(str+1) = 'n'; /* No problem: String is now GnG */

getchar();

return 0;

Example 2 (Try to return string from a function)


The below program works perfectly fine as the string is stored
in a shared segment and data stored remains there even after
return of getString()
 C

char *getString()

char *str = "GfG"; /* Stored in read only part of shared segment */

/* No problem: remains at address str after getString() returns*/

return str;
72

int main()

printf("%s", getString());

getchar();

return 0;

The below program also works perfectly fine as the string is


stored in heap segment and data stored in heap segment
persists even after the return of getString()

 C

char *getString()

int size = 4;

char *str = (char *)malloc(sizeof(char)*size); /*Stored in heap


segment*/
73

*(str+0) = 'G';

*(str+1) = 'f';

*(str+2) = 'G';

*(str+3) = '\0';

/* No problem: string remains at str after getString() returns */

return str;

int main()

printf("%s", getString());

getchar();

return 0;

But, the below program may print some garbage data as string
is stored in stack frame of function getString() and data may
not be there after getString() returns.

 C
74

char *getString()

char str[] = "GfG"; /* Stored in stack segment */

/* Problem: string may not be present after getString() returns */

/* Problem can be solved if write static before char, i.e. static char
str[] = "GfG";*/

return str;

int main()

printf("%s", getString());

getchar();

return 0;

}
75

String operations:

There are certain predefined library functions that are created


with the specific purpose of handling strings. These are as
follows:
1. strcat
This function is used for concatenation which means it
combines two strings. Using this function, a specified source
string is appended to the end of a specified destination string.
On success, this function returns a reference to the destination
string; on failure, it returns NULL.
Syntax:
char *strcat(char *destination, const char *source) ;
The first argument is the destination string. The second
argument is the source string.
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "Scaler ";
char str2[10] = "Academy";
strcat(str1, str2);
printf("%s",str1);
return 0;
}
Output:
76

Scaler Academy
Explanation:
First, we defined str1 and then we defined str2. And then
concatenated the two. str1 had a size large enough to
accomodate the concatenated string.
2. strncat
The name of this function looks similar to the previous one
and that is because this function is also used for
concatenation. The only difference is that we use this function
when we want to combine N characters of one string into
another.
One rule associated with the use of this function is that the
length of the destination string must be more than that of the
source string. At most N characters from the source string are
appended to the end of the destination string with this
function. On success, this function returns a reference to the
destination string; on failure, it returns NULL.
Syntax:
char *strncat(char *destination, const char *source, size_t N) ;
The first argument is the destination string. The second
argument is the source string.
The third argument is the number of characters that are
appended.
Code:
#include <stdio.h>
#include <string.h>
77

int main() {
char str1[50] = "Scaler ";
char str2[10] = "Academy";
strncat(str1, str2,2);
printf("%s",str1);
return 0;
}
Output:
Scaler Ac
Explanation:
First we define string1 and then we define string2. Then we
concatenate only the first two characters
of string2 i.e. “Ac” to str 1 i.e. "Scaler" to form “Scaler Ac”.
3. strlen
The length of a string is returned by this function, excluding
the null character at the end. That is, it returns the string's
character count minus 1 for the terminator.
Syntax:
size_t strlen(const char *string);
The argument is the string whose length needs to be found.
Code:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20]="Scaler";
78

printf("%zu",strlen(str1));
return 0;
}
Output:
1
Explanation:
First, str1 is defined and then it’s length is found using strlen.
%zu is used to print variables of size_t length.
4. strcpy
strcpy copies a string from the source string to the destination
string, including the null character terminator. The return
value of this function is a reference to the destination string.
Syntax:
char *strcpy(char *destination, const char *source) ;
The first argument is the destination string The second
argument is the source string
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "Scaler Academy";
char str2[50];
strcpy(str2, str1);
printf("%s",str2);
}
79

Output:
Scaler Academy
Explanation:
First str1 and str 2 are defined. Since str2 is the string in
which str1 is copied, make sure str2 is larger than or equal
to str1’s size.
5. strncpy
Strncpy is similar to strcpy, but it allows you to
copy N characters.
Syntax:
char *strncpy(char *destination, const char *source, size_t N)
;
 The first argument is the destination string.
 The second argument is the source string.
 The third argument is the number of characters that are to
be copied.
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "Scaler Academy";
char str2[50];
strncpy(str2, str1,6);
printf("%s",str2);
return 0;
}
80

Output:
Scale
Explanation:
str1 is copied to str2 and the first 6 characters are printed.
6. strcmp
This function joins two strings together. It returns a value less
than zero if the second string is greater. It returns a number
greater than zero if the first string is greater than the second. It
returns 0 if the strings are equivalent.
Syntax:
int strcmp(const char *str1, const char *str2) ;
The first argument is the first string i.e. str1 from the image
above. The second argument is the second string i.e. str2 from
the image above.
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "a";
char str2[] = "b";
int res;
res = strcmp(str1, str2);
printf("%d\n", res);
return 0;
}
Output:
81

-1
Explanation:
a is smaller than b hence according to the image above, the
output is -1.
7. strncmp
Strncmp is similar to strcmp, but it allows you to compare the
first N characters of the respective strings.
Syntax:
int strncmp(const char *first, const char *second, size_T N) ;
The first argument is the first string i.e. str1 from the image
above. The second argument is the second string i.e. str2 from
the image above. The third argument is the number of
characters that will be compared.
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "abc";
char str2[] = "acb";
int res;
res = strncmp(str1, str2,1);
printf("%d\n", res);
res = strncmp(str1, str2,2);
printf("%d\n", res);
return 0;
}
82

Output:
0 -1
Explanation:
In the first res, only 1 character is compared and since both are
a, they’re the same and the output is 0. In the second
res, 2 characters are compared and hence the first string is
smaller than the second string and the output is -1.
8. memset
To initialise a string to all nulls or any character, use memset.
Syntax:
void *memset(const void *destination, int c, site_t N) ;
The first argument is the destination string which is the address
of memory to be filled. The second argument is the value to be
filled. The third argument is the number of bytes to be filled
starting from the destination string.
Code:
#include <stdio.h>
#include <string.h>
int main() {
char str1[50]="Hello";
char ch='.';
memset(str1+5,ch,sizeof(char));
printf("%s", str1);
return 0;
}
Output:
83

Hello.
Explanation:
str1+5 means the 5th position in the string, i.e. after ‘o’. After
memset, the character, i.e. ‘.’ is placed after o.
9. strtok
To retrieve the next token in a string, use the strtok function. A
list of possible delimiters is used to define the token.
Arrays of String
An Array of String is an array that stores a fixed number of
values that are of the String data type.
Array of String in C/C++:
Syntax:
char strarr[m][n]
Explanation:
m denotes the number of strings that can be stored in the array
and n denotes the maximum length of the String.
Code
char strarr[2][5]={“Code”,”Word”}
**Array of String in Python:**
Syntax:
strarr={“string1”,”string2”}
Explanation:
84

This is done using the lists data structure


Code:
strarr=[“Code”,”Word”]
Array of String in Java:
Syntax:
String[] strarr={“string1”,”string2”};
Explanation:
The index starts from 0.
Code:
String[] strarr = {"Code", "Word"};
Passing String to Functions
In general, to pass a string to a function we enter it as a
parameter of the function.
Passing a String to a function in C/C++:
Syntax:
functionName(string);
Code:
// Declaring a String
char str[5]="Hello";

// Passing string to Function


func(str);
85

In the above code, we first declared the string and then passed
it to a function named func.
Passing a String to a function in Python:
Syntax:
functionName(string)
Code:
//Declaring a String
s='Hello'

//Passing string to function


func(s)

In the above code, we first declared the string and then passed
it to a function named func.
Passing a String to a function in Java:
Syntax:
functionName(string);
Code:
//Declaring a String
String s="Hello";

//Passing String to function


func(s);
86

In the above code, we first declared the string and then passed
it to a function named func.

String in C/C++
A string is a collection of characters that ends with the null
character \0 in C programming. By default, the compiler
appends a null character \0 to the end of a sequence of
characters wrapped in double quotation marks.
Declaring a String:

Syntax:
char str[5];
As seen in the photo below, the index starts from 0 and goes
uptil 4 meaning 5 locations.

Initializing a String:

There are 4 ways to do this:


i) Without mentioning length and without array:
Syntax:
char c[] = "abcd";
ii) Without mentioning length and with array:
Syntax:
char c[] = {'a', 'b', 'c', 'd', '\0'};
87

iii) With mentioning length and without array:


Syntax:
char c[50] = "abcd";
iv) With mentioning length and with array:
Syntax:
char c[5] = {'a', 'b', 'c', 'd', '\0'};
Reading string from user

i) scanf() is used to read a string from the user.


Syntax:
scanf(const char *format, Object *argument(s))
Code:
#include <stdio.h>
int main()
{
char strarr[5];
scanf("%s", arritem);
return 0;
}
In the above code, we gave the format as %s which means
string and arritem as the
address where the input will be stored.
ii) fgets() is used to read a line of string from the user.
Syntax:
88

char* fgets(char* string, int num, FILE* stream);


char* string is a pointer to a string from where the characters
are copied. Num specifies the number of characters that must
be copied from the string and FILE* stream is a pointer that
points to the file stream.
Code:
#include <stdio.h>
int main()
{
char strarr[5];
fgets(arritem, sizeof(arritem), stdin); // read string
return 0;
}
In the above code, arritem is the initialized
string, sizeof(arritem) is the number of characters that will be
copied and since the input is to be taken from standard
input, stdin is supplied as the third parameter.

String Manipulation

This is done using pointers


Syntax for declaring a pointer:
char *string;
Example Code:
#include <stdio.h>
int main(void) {
char strarr[] = "Coding with Strings";
89

printf("%c", *strarr); // Output: C


printf("%c", *(strarr+1)); // Output: o
printf("%c", *(strarr+7)); // Output: w

char *strarrPtr;

strarrPtr = strarr;
printf("%c", *strarrPtr); // Output: C
printf("%c", *(strarrPtr+1)); // Output: o
printf("%c", *(strarrPtr+7)); // Output: w
}
In the above code, it is seen that when *strarr is printed the first
character from the string “Coding with Strings”, i.e. C is
printed. When *strarr+1 is printed, the character
present 1 position away from the first character is printed,
i.e. o and so on. This is how manipulation in strings is done.
String Functions
 strlen() - returns length of string
 strcpy() - copies one string to another
 strcmp() - compares two strings
 strcat() - concatenates two strings

Pattern matching algorithms:


 Pattern Searching algorithms are used to find a pattern or
substring from another bigger string. There are different
algorithms. The main goal to design these type of
algorithms to reduce the time complexity. The traditional
approach may take lots of time to complete the pattern
searching task for a longer text.
90

 Pattern matching is used to determine whether source


files of high-level languages are syntactically correct. It
is also used to find and replace a matching pattern in a
text or code with another text/code. Any application that
supports search functionality uses pattern matching in
one way or another.
91

SECTION-II

CHAPTER-3

ARRAYS

Introduction of Arrays:

An array is a collection of items stored at contiguous memory


locations. The idea is to store multiple items of the same type
together. This makes it easier to calculate the position of each
element by simply adding an offset to a base value, i.e., the
memory location of the first element of the array (generally
denoted by the name of the array).

Is the array always of fixed size?


In C language, the array has a fixed size meaning once the size
is given to it, it cannot be changed i.e. you can’t shrink it nor
can you expand it. The reason was that for expanding if we
change the size we can’t be sure ( it’s not possible every time)
that we get the next memory location to us for free. The
shrinking will not work because the array, when declared, gets
memory statically allocated, and thus compiler is the only one
that can destroy it.
92

Types of indexing in an array:


 0 (zero-based indexing): The first element of the array is

indexed by a subscript of 0.
 1 (one-based indexing): The first element of the array is

indexed by the subscript of 1.


 n (N-based indexing): The base index of an array can be

freely chosen. Usually, programming languages allowing n-


based indexing also allow negative index values, and other
scalar data types like enumerations, or characters may be
used as an array index.

How an Array is initialized?


By default the array is uninitialized, and no elements of the
array are set to any value. However, for the proper working of
the array, array initialization becomes important. Array
initialization can be done by the following methods:
1. Passing no value within the initializer: One can initialize
the array by defining the size of the array and passing no
values within the initializer.
Syntax:
int arr[ 5 ] = { };
93

2. By passing specific values within the initializer: One can


initialize the array by defining the size of the array and passing
specific values within the initializer.
Syntax:
int arr[ 5 ] = { 1 , 2 , 3 , 4 , 5 };
Note: The count of elements within the “{ }”, must be less
than the size of the array
If the count of elements within the “{ }” is less than the size
of the array, the remaining positions are considered to be ‘0’.
Syntax:
int arr[ 5 ] = { 1 , 2 , 3 } ;
3. By passing specific values within the initializer but not
declaring the size: One can initialize the array by passing
specific values within the initializer and not particularly
mentioning the size, the size is interpreted by the compiler.
Syntax:
int arr[ ] = { 1 , 2 , 3 , 4 , 5 };
4. Universal Initialization: After the adoption of universal
initialization in C++, one can avoid using the equals sign
between the declaration and the initializer.
Syntax:
int arr[ ] { 1 , 2 , 3 , 4 , 5 };
To know more about array initialization, click here.
What are the different operations on the array?
Arrays allow random access to elements. This makes
accessing elements by position faster. Hence operation like
searching, insertion, and access becomes really efficient.
Array elements can be accessed using the loops.
1. Insertion in Array:
94

We try to insert a value to a particular array index position, as


the array provides random access it can be done easily using
the assignment operator.
Pseudo Code:
// to insert a value= 10 at index position 2;
arr[ 2 ] = 10;
Time Complexity:
 O(1) to insert a single element

 O(N) to insert all the array elements [where N is the size of

the array]
2. Access elements in Array:
Accessing array elements become extremely important, in
order to perform operations on arrays.
Pseudo Code:
// to access array element at index position 2, we simply can
write
return arr[ 2 ] ;
Time Complexity: O(1)
3. Searching in Array:
We try to find a particular value in the array, in order to do that
we need to access all the array elements and look for the
particular value.
Pseudo Code:
// searching for value 2 in the array;
Loop from i = 0 to 5:
check if arr[i] = 2:
return true;
Time Complexity: O(N), where N is the size of the array.
Here is the code for working with an array:

95

#include <iostream>

using namespace std;

int main()

// Creating an integer array

// named arr of size 10.

int arr[10];

// accessing element at 0 index

// and setting its value to 5.

arr[0] = 5;

// access and print value at 0

// index we get the output as 5.

cout << arr[0];

return 0;

}
96

Output
5
Here the value 5 is printed because the first element has index
zero and at the zeroth index, we already assigned the value 5.
Types of arrays :
1. One dimensional array (1-D arrays)
2. Multidimensional array
To learn about the differences between One-dimensional and
Multidimensional arrays, click here.
Advantages of using arrays:
 Arrays allow random access to elements. This makes

accessing elements by position faster.


 Arrays have better cache locality which makes a pretty big

difference in performance.
 Arrays represent multiple data items of the same type using

a single name.
Disadvantages of using arrays:
You can’t change the size i.e. once you have declared the array
you can’t change its size because of static memory
allocation. Here Insertion(s) and deletion(s) are difficult as the
elements are stored in consecutive memory locations and the
shifting operation is costly too.
Now if take an example of the implementation of data
structure Stack using array there are some obvious flaws.
Let’s take the POP operation of the stack. The algorithm
would go something like this.
1. Check for the stack underflow
2. Decrement the top by 1
What we are doing here is, that the pointer to the topmost
element is decremented, which means we are just bounding
our view, and actually that element stays there taking up the
memory space. If you have an array (as a Stack) of any
97

primitive data type then it might be ok. But in the case of an


array of Objects, it would take a lot of memory.
Examples –
// A character array in C/C++/Java
char arr1[] = {‘g’, ‘e’, ‘e’, ‘k’, ‘s’};
// An Integer array in C/C++/Java
int arr2[] = {10, 20, 30, 40, 50};
// Item at i’th index in array is typically accessed as “arr[i]”.
For example:
arr1[0] gives us ‘g’
arr2[3] gives us 40
Usually, an array of characters is called a ‘string’, whereas an
array of ints or floats is simply called an array.
Applications on Array
 Array stores data elements of the same data type.

 Arrays are used when the size of the data set is known.

 Used in solving matrix problems.

 Applied as a lookup table in computer.

 Databases records are also implemented by the array.

 Helps in implementing sorting algorithm.

 The different variables of the same type can be saved under

one name.
 Arrays can be used for CPU scheduling.

 Used to Implement other data structures like Stacks,

Queues, Heaps, Hash tables, etc.


Properties of array
There are some of the properties of an array that are listed as
follows -
o Each element in an array is of the same data type and
carries the same size that is 4 bytes.
98

o Elements in the array are stored at contiguous memory


locations from which the first element is stored at the
smallest memory location.
o Elements of the array can be randomly accessed since we
can calculate the address of each element of the array with
the given base address and the size of the data element.
Representation of an array
We can represent an array in various ways in different
programming languages. As an illustration, let's see the
declaration of array in C language -

As per the above illustration, there are some of the following


important points -
o Index starts with 0.
o The array's length is 10, which means we can store 10
elements.
o Each element in the array can be accessed via its index.
Why are arrays required?
Arrays are useful because -
o Sorting and searching a value in an array is easier.
o Arrays are best to process multiple values quickly and
easily.
99

o Arrays are good for storing multiple values in a single


variable - In computer programming, most cases require
storing a large number of data of a similar type. To store
such an amount of data, we need to define a large number
of variables. It would be very difficult to remember the
names of all the variables while writing the programs.
Instead of naming all the variables with a different name,
it is better to define an array and store all the elements into
it.
Memory allocation of an array
As stated above, all the data elements of an array are stored at
contiguous locations in the main memory. The name of the
array represents the base address or the address of the first
element in the main memory. Each element of the array is
represented by proper indexing.
We can define the indexing of an array in the below ways -
1. 0 (zero-based indexing): The first element of the array will
be arr[0].
2. 1 (one-based indexing): The first element of the array will
be arr[1].
3. n (n - based indexing): The first element of the array can
reside at any random index number.
100

In the above image, we have shown the memory allocation of


an array arr of size 5. The array follows a 0-based indexing
approach. The base address of the array is 100 bytes. It is the
address of arr[0]. Here, the size of the data type used is 4 bytes;
therefore, each element will take 4 bytes in the memory.
How to access an element from the array?
We required the information given below to access any random
element from the array -
o Base Address of the array.
o Size of an element in bytes.
o Type of indexing, array follows.
The formula to calculate the address to access an array element
-
1. Byte address of element A[i] = base address + size * ( i - first
index)
Here, size represents the memory taken by the primitive data
types. As an instance, int takes 2 bytes, float takes 4 bytes of
memory space in C programming.
We can understand it with the help of an example -
Suppose an array, A[-10 ..... +2 ] having Base address (BA) =
999 and size of an element = 2 bytes, find the location of A[-
1].
L(A[-1]) = 999 + 2 x [(-1) - (-10)]
= 999 + 18
= 1017
101

Basic operations
Now, let's discuss the basic operations supported in the array -
o Traversal - This operation is used to print the elements of
the array.
o Insertion - It is used to add an element at a particular index.
o Deletion - It is used to delete an element from a particular
index.
o Search - It is used to search an element using the given
index or by the value.
o Update - It updates an element at a particular index.
Traversal operation
This operation is performed to traverse through the array
elements. It prints all array elements one after another. We can
understand it with the below program -
1. #include <stdio.h>
2. void main() {
3. int Arr[5] = {18, 30, 15, 70, 12};
4. int i;
5. printf("Elements of the array are:\n");
6. for(i = 0; i<5; i++) {
7. printf("Arr[%d] = %d, ", i, Arr[i]);
8. }
9. }
Output
102

Insertion operation
This operation is performed to insert one or more elements into
the array. As per the requirements, an element can be added at
the beginning, end, or at any index of the array. Now, let's see
the implementation of inserting an element into the array.
1. #include <stdio.h>
2. int main()
3. {
4. int arr[20] = { 18, 30, 15, 70, 12 };
5. int i, x, pos, n = 5;
6. printf("Array elements before insertion\n");
7. for (i = 0; i < n; i++)
8. printf("%d ", arr[i]);
9. printf("\n");
10.
11. x = 50; // element to be inserted
12. pos = 4;
13. n++;
14.
15. for (i = n-1; i >= pos; i--)
16. arr[i] = arr[i - 1];
17. arr[pos - 1] = x;
18. printf("Array elements after insertion\n");
19. for (i = 0; i < n; i++)
20. printf("%d ", arr[i]);
21. printf("\n");
22. return 0;
23. }
Output
103

Deletion operation
As the name implies, this operation removes an element from
the array and then reorganizes all of the array elements.
1. #include <stdio.h>
2.
3. void main() {
4. int arr[] = {18, 30, 15, 70, 12};
5. int k = 30, n = 5;
6. int i, j;
7.
8. printf("Given array elements are :\n");
9.
10. for(i = 0; i<n; i++) {
11. printf("arr[%d] = %d, ", i, arr[i]);
12. }
13.
14. j = k;
15.
16. while( j < n) {
17. arr[j-1] = arr[j];
18. j = j + 1;
19. }
20.
21. n = n -1;
22.
23. printf("\nElements of array after deletion:\n");
24.
25. for(i = 0; i<n; i++) {
26. printf("arr[%d] = %d, ", i, arr[i]);
104

27. }
28. }
Output

Search operation
This operation is performed to search an element in the array
based on the value or index.
1. #include <stdio.h>
2.
3. void main() {
4. int arr[5] = {18, 30, 15, 70, 12};
5. int item = 70, i, j=0 ;
6.
7. printf("Given array elements are :\n");
8.
9. for(i = 0; i<5; i++) {
10. printf("arr[%d] = %d, ", i, arr[i]);
11. }
12. printf("\nElement to be searched = %d", item);
13. while( j < 5){
14. if( arr[j] == item ) {
15. break;
16. }
17.
18. j = j + 1;
19. }
20.
105

21. printf("\nElement %d is found at %d position", item, j+


1);
22. }
Output

Update operation
This operation is performed to update an existing array element
located at the given index.
1. #include <stdio.h>
2.
3. void main() {
4. int arr[5] = {18, 30, 15, 70, 12};
5. int item = 50, i, pos = 3;
6.
7. printf("Given array elements are :\n");
8.
9. for(i = 0; i<5; i++) {
10. printf("arr[%d] = %d, ", i, arr[i]);
11. }
12.
13. arr[pos-1] = item;
14. printf("\nArray elements after updation :\n");
15.
16. for(i = 0; i<5; i++) {
17. printf("arr[%d] = %d, ", i, arr[i]);
18. }
19. }
Output
106

Complexity of Array operations


Time and space complexity of various array operations are
described in the following table.
Time Complexity

Operation Average Case Worst Case

Access O(1) O(1)

Search O(n) O(n)

Insertion O(n) O(n)

Deletion O(n) O(n)

Space Complexity
In array, space complexity for worst case is O(n).
Advantages of Array
o Array provides the single name for the group of variables
of the same type. Therefore, it is easy to remember the
name of all the elements of an array.
o Traversing an array is a very simple process; we just need
to increment the base address of the array in order to visit
each element one by one.
107

o Any element in the array can be directly accessed by using


the index.
Disadvantages of Array
o Array is homogenous. It means that the elements with
similar data type can be stored in it.
o In array, there is static memory allocation that is size of an
array cannot be altered.
o There will be wastage of memory if we store less number
of elements than the declared size.

Linear arrays:

What is the Linear data structure?


A linear data structure is a structure in which the elements are
stored sequentially, and the elements are connected to the
previous and the next element. As the elements are stored
sequentially, so they can be traversed or accessed in a single
run. The implementation of linear data structures is easier as
the elements are sequentially organized in memory. The data
elements in an array are traversed one after another and can
access only one element at a time.
The types of linear data structures are Array, Queue, Stack,
Linked List.
4

Let's discuss each linear data structure in detail.


o Array: An array consists of data elements of a same data
type. For example, if we want to store the roll numbers of
10 students, so instead of creating 10 integer type
108

variables, we will create an array having size 10.


Therefore, we can say that an array saves a lot of memory
and reduces the length of the code.
o Stack: It is linear data structure that uses the LIFO (Last
In-First Out) rule in which the data added last will be
removed first. The addition of data element in a stack is
known as a push operation, and the deletion of data
element form the list is known as pop operation.
o Queue: It is a data structure that uses the FIFO rule (First
In-First Out). In this rule, the element which is added first
will be removed first. There are two terms used in the
queue front end and rear The insertion operation
performed at the back end is known ad enqueue, and the
deletion operation performed at the front end is known as
dequeue.
o Linked list: It is a collection of nodes that are made up of
two parts, i.e., data element and reference to the next node
in the sequence.
What is a Non-linear data structure?
A non-linear data structure is also another type of data structure
in which the data elements are not arranged in a contiguous
manner. As the arrangement is nonsequential, so the data
elements cannot be traversed or accessed in a single run. In the
case of linear data structure, element is connected to two
elements (previous and the next element), whereas, in the non-
linear data structure, an element can be connected to more than
two elements.
Trees and Graphs are the types of non-linear data structure.
Let's discuss both the data structures in detail.
109

o Tree
It is a non-linear data structure that consists of various linked
nodes. It has a hierarchical tree structure that forms a parent-
child relationship. The diagrammatic representation of
a tree data structure is shown below:

For example, the posts of employees are arranged in a tree data


structure like managers, officers, clerk. In the above
figure, A represents a manager, B and C represent the officers,
and other nodes represent the clerks.
o Graph
A graph is a non-linear data structure that has a finite number
of vertices and edges, and these edges are used to connect the
vertices. The vertices are used to store the data elements, while
the edges represent the relationship between the vertices. A
graph is used in various real-world problems like telephone
110

networks, circuit networks, social networks like LinkedIn,


Facebook. In the case of facebook, a single user can be
considered as a node, and the connection of a user with others
is known as edges.

Differences between the Linear data structure and non-


linear data structure.

Linear Non-Linear Data


Data structure
structure

Basic In this In this structure, the


structure, the elements are arranged
elements are hierarchically or non-
arranged linear manner.
sequentially
or linearly
and attached
to one
another.
111

Types Arrays, Trees and graphs are the


linked list, types of a non-linear data
stack, queue structure.
are the types
of a linear
data
structure.

implementation Due to the Due to the non-linear


linear organization, they are
organization, difficult to implement.
they are easy
to
implement.

Traversal As linear The data items in a non-


data linear data structure
structure is a cannot be accessed in a
single level, single run. It requires
so it requires multiple runs to be
a single run traversed.
to traverse
each data
item.

Arrangement Each data Each item is attached to


item is many other items.
attached to
the previous
112

and next
items.

Levels This data In this, the data elements


structure are arranged in multiple
does not levels.
contain any
hierarchy,
and all the
data
elements are
organized in
a single
level.

Memory In this, the In this, memory is utilized


utilization memory in a very efficient manner.
utilization is
not efficient.

Time The time The time complexity of


complexity complexity non-linear data structure
of linear data often remains same with
structure the increase in the input
increases size.
with the
increase in
the input
size.
113

Applications Linear data Non-linear data structures


structures are used in image
are mainly processing and Artificial
used for Intelligence.
developing
the software.

Representation of linear array in memory:

Array is a container which can hold a fix number of items and


these items should be of the same type. Most of the data
structures make use of arrays to implement their algorithms.
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.
Array Representation
Arrays can be declared in various ways in different languages.
For illustration, let's take C array declaration.

Arrays can be declared in various ways in different languages.


For illustration, let's take C array declaration.
114

As per the above illustration, following are the important points


to be considered.
 Index starts with 0.
 Array length is 10 which means it can store 10 elements.
 Each element can be accessed via its index. For example,
we can fetch an element at index 6 as 9.

Address Calculations:

Calculating the address of any element In the 1-D array:


A 1-dimensional array (or single dimension array) is a type
of linear array. Accessing its elements involves a single
subscript that can either represent a row or column index.
Example:
115

2-D array

To find the address of an element in an array the


following formula is used-
Address of A[I] = B + W * (I – LB)
I = Subset of element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LB = Lower Limit/Lower Bound of subscript(If not specified
assume zero).
Example: Given the base address of an array A[1300
………… 1900] as 1020 and the size of each element is 2
bytes in the memory, find the address of A[1700].
Solution:
Given:
Base address B = 1020
Lower Limit/Lower Bound of subscript LB = 1300
Storage size of one element store in any array W = 2 Byte
Subset of element whose address to be found I = 1700
Formula used:
Address of A[I] = B + W * (I – LB)
Solution:
Address of A[1700] = 1020 + 2 * (1700 – 1300)
= 1020 + 2 * (400)
= 1020 + 800
Address of A[1700] = 1820
Calculate the address of any element in the 2-D array:
The 2-dimensional array can be defined as an array of arrays.
The 2-Dimensional arrays are organized as matrices which can
be represented as the collection of rows and columns as
array[M][N] where M is the number of rows and N is the
number of columns.
116

Example:

2-D array

To find the address of any element in a 2-Dimensional array


there are the following two ways-
1. Row Major Order
2. Column Major Order
1. Row Major Order:
Row major ordering assigns successive elements, moving
across the rows and then down the next row, to successive
memory locations. In simple language, the elements of an
array are stored in a Row-Wise fashion.
To find the address of the element using row-major order uses
the following formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in an array(in byte),
LR = Lower Limit of row/start row index of the matrix(If not
given assume it as zero),
117

LC = Lower Limit of column/start column index of the


matrix(If not given assume it as zero),
N = Number of column given in the matrix.
Example: Given an array, arr[1………10][1………15] with
base value 100 and the size of each element is 1 Byte in
memory. Find the address of arr[8][6] with the help of row-
major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J =
6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of column given in the matrix N = Upper Bound –
Lower Bound + 1
= 15 – 1 + 1
= 15
Formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
Solution:
Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))
= 100 + 1 * ((7) * 15 + (5))
= 100 + 1 * (110)
Address of A[I][J] = 210
2. Column Major Order:
If elements of an array are stored in a column-major fashion
means moving across the column and then to the next column
then it’s in column-major order. To find the address of the
element using column-major order use the following formula:
118

Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))


I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LR = Lower Limit of row/start row index of matrix(If not
given assume it as zero),
LC = Lower Limit of column/start column index of matrix(If
not given assume it as zero),
M = Number of rows given in the matrix.
Example: Given an array arr[1………10][1………15] with
a base value of 100 and the size of each element is 1 Byte in
memory find the address of arr[8][6] with the help of
column-major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J =
6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of column given in the matrix M = Upper Bound –
Lower Bound + 1
= 10 – 1 + 1
= 10
Formula: used
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))
= 100 + 1 * ((5) * 10 + (7))
= 100 + 1 * (57)
Address of A[I][J] = 157
119

From the above examples, it can be observed that for the same
position two different address locations are obtained that’s
because in row-major order movement is done across the rows
and then down to the next row, and in column-major order,
first move down to the first column and then next column. So
both the answers are right.
So it’s all based on the position of the element whose address
is to be found for some cases the same answers is also obtained
with row-major order and column-major order and for some
cases, different answers are obtained.
Calculate the address of any element in the 3-D Array:
A 3-Dimensional array is a collection of 2-Dimensional
arrays. It is specified by using three subscripts:
1. Block size
2. Row size
3. Column size
More dimensions in an array mean more data can be stored in
that array.
Example:
120

3-D array

To find the address of any element in 3-Dimensional arrays


there are the following two ways-
 Row Major Order
 Column Major Order

1. Row Major Order:


To find the address of the element using row-major order, use
the following formula:
Address of A[i][j][k] = B + W (M * N(i-x) + N *(j-y) + (k-z))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of Row
y = Lower Bound of Column
z = Lower Bound of Width
Example: Given an array, arr[1:9, -4:1, 5:10] with a base
value of 400 and the size of each element is 2 Bytes in
memory find the address of element arr[5][-1][8] with the
help of row-major order?
Solution:
Given:
Row Subset of an element whose address to be found I = 5
Column Subset of an element whose address to be found J = -
1
Block Subset of an element whose address to be found K = 8
Base address B = 400
Storage size of one element store in any array(in Byte) W = 2
121

Lower Limit of row/start row index of matrix x = 1


Lower Limit of column/start column index of matrix y = -4
Lower Limit of blocks in matrix z = 5
M = Upper Bound – Lower Bound + 1 = 1 – (-4) + 1 = 6
N = Upper Bound – Lower Bound + 1 = 10 – 5 + 1 = 6
Formula used:
Address of[I][J][K] =B + W (M * N(i-x) + N *(j-y) + (k-z))
Solution:
Address of[][][] = 400 + 2 * {[6 * 6 * (5 – 1)] + 6 * [(-1 +
4)]} + [8 – 5]
= 400 + 2 * ((4 * 6 + 3) * 6 + 3)
= 400 + 2 * (165)
= 730
2. Column Major Order:
To find the address of the element using column-major order,
use the following formula:1
Address of A[i][j][k = B + W(M * N(i – x) + M *(k – z) +
(j – y))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of Row
y = Lower Bound of Column
z = Lower Bound of Width
Example: Given an array arr[1:8, -5:5, -10:5] with a base
value of 400 and the size of each element is 4 Bytes in
memory find the address of element arr[3][3][3] with the help
of column-major order?
Solution:
122

Given:
Row Subset of an element whose address to be found I = 3
Column Subset of an element whose address to be found J =
3
Block Subset of an element whose address to be found K = 3
Base address B = 400
Storage size of one element store in any array(in Byte) W = 4
Lower Limit of row/start row index of matrix x = 1
Lower Limit of column/start column index of matrix y = -5
Lower Limit of blocks in matrix z = -10
M = Upper Bound – Lower Bound + 1 = 5 + 5 + 1 = 11
N = Upper Bound – Lower Bound + 1 = 5 + 10 + 1 = 16
Formula used:
Address of[i][j][k] = B + W(M * N(i – x) + M * (k – z) + (j –
y))
Solution:
Address of[3][3][3] = 400 + 4 * {[(3 – 1)] * 16 + [3 + 10] ]}
* 11 + [3 + 5]
= 400 + 4 * ((32 + 13) * 11 + 8)
= 400 + 4 * (503)
= 400 + 2012
= 2412
Traversal:

Visiting every element of an array once is known


as traversing the array.
Why Traversal?
For use cases like:
 Storing all elements – Using scanf()
 Printing all elements – Using printf()
 Updating elements.
123

An array can easily be traversed using a for loop in C


language.
An important note on Arrays:
If we create an array of length 100 using a[100] in C language,
we need not use all the elements. It is possible for a program
to use just 60 elements out of these 100. (But we cannot go
beyond 100 elements).

Insertions:

An element can be inserted in an array at a specific


position. For this operation to succeed, the array must have
enough capacity. Suppose we want to add an element 10 at
index 2 in the below-illustrated array, then the elements after
index 1 must get shifted to their adjacent right to make way
for a new element.
124

When no position is specified, it’s best to insert the element


at the end to avoid shifting, and this is when we achieve the
best runtime O(1).

Deletion in an array:

An element at a specified position can be deleted, creating a


void that needs to be fixed by shifting all the elements to their
adjacent left, as illustrated in the figure below.
We can also bring the last element of the array to fill the void
if the relative ordering is not important. :)
125

Multidimensional arrays:

A multi-dimensional array can be termed as an array of


arrays that stores homogeneous data in tabular form. Data in
multidimensional arrays are stored in row-major order.
The general form of declaring N-dimensional arrays is:
data_type array_name[size1][size2]....[sizeN];
 data_type: Type of data to be stored in the array.
 array_name: Name of the array

 size1, size2,… ,sizeN: Sizes of the dimension

Examples:
Two dimensional array: int two_d[10][20];

Three dimensional array: int three_d[10][20][30];


126

Size of Multidimensional Arrays:


The total number of elements that can be stored in a
multidimensional array can be calculated by multiplying the
size of all the dimensions.
For example:
 The array int x[10][20] can store total (10*20) = 200

elements.
 Similarly array int x[5][10][20] can store total (5*10*20)

= 1000 elements.

Two-Dimensional Array

Two – dimensional array is the simplest form of a


multidimensional array. We can see a two – dimensional
array as an array of one-dimensional array for easier
understanding.
The basic form of declaring a two-dimensional array of size
x, y:
Syntax:
data_type array_name[x][y];
Here, data_type is the type of data to be stored.
We can declare a two-dimensional integer array say ‘x’ of
size 10,20 as:
int x[10][20];
Elements in two-dimensional arrays are commonly referred
to by x[i][j] where i is the row number and ‘j’ is the column
number.
A two – dimensional array can be seen as a table with ‘x’
rows and ‘y’ columns where the row number ranges from 0
to (x-1) and the column number ranges from 0 to (y-1). A
two – dimensional array ‘x’ with 3 rows and 3 columns is
shown below:
127

Initializing Two – Dimensional Arrays: There are various


ways in which a Two-Dimensional array can be initialized.
First Method:
int x[3][4] = {0, 1 ,2 ,3 ,4 , 5 , 6 , 7 , 8 , 9 , 10 , 11}
The above array has 3 rows and 4 columns. The elements in
the braces from left to right are stored in the table also from
left to right. The elements will be filled in the array in order,
the first 4 elements from the left in the first row, the next 4
elements in the second row, and so on.
Second Method:
int x[3][4] = {{0,1,2,3}, {4,5,6,7}, {8,9,10,11}};
Third Method:
int x[3][4];
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
cin >> x[i][j];
}
}
Fourth Method(Dynamic Allocation):
int** x = new int*[3];
128

for(int i = 0; i < 3; i++){


x[i] = new int[4];
for(int j = 0; j < 4; j++){
cin >> x[i][j];
}
}
This type of initialization makes use of nested braces. Each
set of inner braces represents one row. In the above example,
there is a total of three rows so there are three sets of inner
braces.
Accessing Elements of Two-Dimensional

Arrays: Elements in Two-Dimensional arrays are accessed


using the row indexes and column indexes.
Example:
int x[2][1];
The above example represents the element present in the
third row and second column.
Note: In arrays, if the size of an array is N. Its index will be
from 0 to N-1. Therefore, for row index 2 row number is 2+1
= 3. To output all the elements of a Two-Dimensional array
we can use nested for loops. We will require two ‘for‘ loops.
One to traverse the rows and another to traverse columns.

Example:

 CPP
 C
129

// C++ Program to print the elements of a

// Two-Dimensional array

#include<iostream>

using namespace std;

int main()

// an array with 3 rows and 2 columns.

int x[3][2] = {{0,1}, {2,3}, {4,5}};

// output each array element's value

for (int i = 0; i < 3; i++)

for (int j = 0; j < 2; j++)

cout << "Element at x[" << i


130

<< "][" << j << "]: ";

cout << x[i][j]<<endl;

return 0;

Output:
Element at x[0][0]: 0
Element at x[0][1]: 1
Element at x[1][0]: 2
Element at x[1][1]: 3
Element at x[2][0]: 4
Element at x[2][1]: 5
131

Three-Dimensional Array

Initializing Three-Dimensional Array: Initialization in a


Three-Dimensional array is the same as that of Two-
dimensional arrays. The difference is as the number of
dimensions increases so the number of nested braces will
also increase.
Method 1:
int x[2][3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23};
Method 2(Better):
int x[2][3][4] =
{
{ {0,1,2,3}, {4,5,6,7}, {8,9,10,11} },
{ {12,13,14,15}, {16,17,18,19}, {20,21,22,23} }
};
132

Accessing elements in Three-Dimensional Arrays:


Accessing elements in Three-Dimensional Arrays is also
similar to that of Two-Dimensional Arrays. The difference is
we have to use three loops instead of two loops for one
additional dimension in Three-dimensional Arrays.
 CPP
 C

// C++ program to print elements of Three-Dimensional

// Array

#include <iostream>

using namespace std;

int main()

// initializing the 3-dimensional array

int x[2][3][2] = { { { 0, 1 }, { 2, 3 }, { 4, 5 } },

{ { 6, 7 }, { 8, 9 }, { 10, 11 } } };

// output each element's value


133

for (int i = 0; i < 2; ++i) {

for (int j = 0; j < 3; ++j) {

for (int k = 0; k < 2; ++k) {

cout << "Element at x[" << i << "][" << j

<< "][" << k << "] = " << x[i][j][k]

<< endl;

return 0;

Output:
Element at x[0][0][0] = 0
Element at x[0][0][1] = 1
Element at x[0][1][0] = 2
Element at x[0][1][1] = 3
Element at x[0][2][0] = 4
Element at x[0][2][1] = 5
Element at x[1][0][0] = 6
134

Element at x[1][0][1] = 7
Element at x[1][1][0] = 8
Element at x[1][1][1] = 9
Element at x[1][2][0] = 10
Element at x[1][2][1] = 11
In similar ways, we can create arrays with any number of
dimensions. However, the complexity also increases as the
number of dimensions increases. The most used
multidimensional array is the Two-Dimensional Array.

Parallel arrays:

Parallel Array: Also known as structure an array (SoA),


multiple arrays of the same size such that i-th element of each
array is closely related and all i-th elements together represent
an object or entity. An example parallel array is two arrays that
represent x and y co-ordinates of n points.
Below is another example where we store the first name, last
name and heights of 5 people in three different arrays.
first_name = ['Bones', 'Welma', 'Frank', 'Han', 'Jack']
last_name = ['Smith', 'Seger', 'Mathers', 'Solo', 'Jackles']
height = [169, 158, 201, 183, 172]
This way we could easily store the information and for
accessing, the first index of each array corresponds to the data
belonging to the same person.
Application:
Two of the most essential applications performs on an array or
a record are searching and sorting.
135

 Searching: Each index in a parallel array corresponds to the


data belonging to the same entity in a record. Thus, for
searching an entity based on a specific value of an attribute,
e.g. we need to find the name of a person having height >200
cms in the above example. Thus, we search for the index in
the height array having value greater than 200. Now, once
we have obtained the index, we print the values of the index
in the first_name and last_name arrays. This way searching
becomes an easy task in parallel array.
 Sorting: Now, using the same fact that each index

corresponds to data items in different arrays corresponding


to the same entity. Thus, we sort all arrays based on the same
criteria. For example, in the above-displayed example, we
need to sort the people in increasing order of their respective
heights. Thus, when we swap the two heights, we even swap
the corresponding values in other arrays using the same
index.
Searching:
Following steps are performed to search an entity based on a
specific attribute in structure of array or parallel array.
 Search for the value of the required attribute in the
respective array (e.g. search for values greater than 200 in
the above example) using either of the linear search/binary
search approaches.
 Store the index where the following values are obtained in

the array
 Print the values at the evaluated indices in other arrays

Sorting:
The arrays can be sorted in any way, numerical, alphabetical
or any other way but the elements are placed at equally spaced
addresses. Any of the sorting techniques can be used to sort
the record based on a specified criteria. Following steps are
performed to sort a record.
136

 Find the index in the array (the array according to which


sorting shall be done is based on the specified criteria)
where the sequence voids (i.e. we need to compute those
two indices where the sorting sequence fails, which is to be
corrected by swapping the values at those indices).
 Now swap the values of those two calculated indices in all

the arrays.
Thus, following the above steps, once the chosen array (the
array to be chosen on a specified criterion) is sorted, all the
arrays shall be sorted with respect to the chosen array.
Implementation:
1. The code below stores the first name, second name, and
height of 10 students.
2. Sorts them in increasing order of the height
using quicksort algorithm.
3. Searches the name of the 2nd tallest student, the 3rd shortest
student and the student having a height equal to 158 cms in
the record.

// C++ implementation of parallel arrays

// and operations on them

#include <iostream>

using namespace std;

/* This function takes the last element as


137

pivot places the pivot element at its

correct position in a sorted array, and

places all smaller (smaller than pivot)

to left of the pivot and all greater elements

to right of pivot */

int partition(string first_name[], string

last_name[],

int height[], int low, int high)

int pivot = height[high]; // pivot

int i = (low - 1); // Index of smaller element

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

// If current element is smaller than

// or equal to pivot. This means are


138

// sorting sequence condition fails if

// the condition becomes true. Thus the

// two indices which shall be obtained

// here will be i and j and therefore

// we will be swapping values of i and j

// in all the arrays.

if (height[j] <= pivot) {

// increment index of smaller element

i++;

// Swapping values of i and j in

// all the arrays

string temp = first_name[i];

first_name[i] = first_name[j];

first_name[j] = temp;
139

temp = last_name[i];

last_name[i] = last_name[j];

last_name[j] = temp;

int temp1 = height[i];

height[i] = height[j];

height[j] = temp1;

string temp = first_name[i + 1];

first_name[i + 1] = first_name[high];

first_name[high] = temp;

temp = last_name[i + 1];

last_name[i + 1] = last_name[high];

last_name[high] = temp;

int temp1 = height[i + 1];

height[i + 1] = height[high];
140

height[high] = temp1;

return (i + 1);

// Function which implements quick sort

void quickSort(string first_name[], string last_name[],

int height[], int low, int high)

if (low < high) {

/* pi is partitioning index, arr[p] is now

at right place */

int pi = partition(first_name, last_name,

height, low, high);

// Separately sort elements before

// partition and after partition


141

quickSort(first_name, last_name, height,

low, pi - 1);

quickSort(first_name, last_name, height,

pi + 1, high);

// Function which binary searches the height

// array for value 158 and if found, prints

// the corresponding value in other arrays

// at that index.

void binarySearch(string first_name[], string

last_name[],

int height[], int value, int n)

{
142

int low = 0, high = n - 1;

int index;

while (low <= high) {

index = (high + low) / 2;

if (height[index] == 158) {

// This index of height array

// corresponds to the name

// of the person in first name

// and last name array.

cout << "Person having height 158"

" cms is "

<< first_name[index]

<< " " << last_name[index] << endl;

return;
143

else if (height[index] > 158)

high = index - 1;

else

low = index + 1;

cout << "Sorry, no such person with"

" height 158 cms";

cout << "is found in the record";

// Printing same index of each array. This

// will give meaningful data as in parallel

// array indices point to values in different

// arrays belonging to the same entity

void printParallelArray(string first_name[],


144

string last_name[], int height[], int n)

cout << "Name of people in increasing";

cout << "order of their height: " << endl;

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

cout << first_name[i] << " "

<< last_name[i] << " has height "

<< height[i] << " cms\n";

cout << endl;

// Driver Function

int main()
145

// These arrays together form a set

// of arrays known as parallel array

int n = 10;

string first_name[] = { "Bones", "Welma",

"Frank", "Han", "Jack", "Jinny", "Harry",

"Emma", "Tony", "Sherlock" };

string last_name[] = { "Smith", "Seger",

"Mathers", "Solo", "Jackles", "Weasly",

"Potter", "Watson", "Stark", "Holmes" };

int height[] = { 169, 158, 201, 183, 172,

152, 160, 163, 173, 185 };

// Sorting the above arrays using quickSort

// technique based on increasing order of


146

// their heights.

quickSort(first_name, last_name, height,

0, n - 1);

printParallelArray(first_name, last_name,

height, n);

// Second tallest person in the sorted

// list will be the second person from the

// right end.

cout << "Name of the second tallest person"

" is "

<< first_name[n - 2] << " "

<< last_name[n - 2] << endl;

// Third shortest person in the sorted

// list will be the third person from the


147

// left end.

cout << "Name of the third shortest person is "

<< first_name[2] << " " << last_name[2]

<< endl;

// We binary search the height array to

// search for a person having height 158

// cms.

binarySearch(first_name, last_name, height,

158, n);

return 0;

Output
Name of people in increasingorder of their height:
Jinny Weasly has height 152 cms
Welma Seger has height 158 cms
Harry Potter has height 160 cms
Emma Watson has height 163 cms
148

Bones Smith has height 169 cms


Jack Jackles has height 172 cms
Tony Stark has height 173 cms
Han Solo has height 183 cms
Sherlock Holmes has height 185 cms
Frank Mathers has height 201 cms

Name of the second tallest person is Sherlock Holmes


Name of the third shortest person is Harry Potter
Person having height 158 cms is Welma Seger
Advantages:
 They can be used in languages which support only arrays of

primitive types and not of records (or perhaps don’t support


records at all).
 Parallel arrays are simple to understand and use, and are

often used where declaring a record is more trouble than it’s


worth.
 They can save a substantial amount of space in some cases

by avoiding alignment issues.


 If the number of items is small, array indices can occupy

significantly less space than full pointers, particularly on


architectures with large words.
 Sequentially examining a single field of each record in the

array is very fast on modern machines, since this amounts


to a linear traversal of a single array, exhibiting ideal
locality of reference and cache behavior.
Disadvantages:
 They have significantly worse locality of reference when

visiting the records non-sequentially and examining


multiple fields of each record.
149

 They have little direct language support (the language and


its syntax typically express no relationship between the
arrays in the parallel array).
 They are expensive to grow or shrink since each of several
arrays must be reallocated. Multi-level arrays can
ameliorate this problem, but impacts performance due to the
additional indirection needed to find the desired elements.

Sparse arrays:

A sparse array or sparse matrix is an array in which most of


the elements are zero.
Characteristics of Sparse array:
 The sparse array is an array in which most of the elements

have the same value(the default value is zero or null).


 Sparse matrices are those array that has the majority of their

elements equal to zero.


 A sparse array is an array in which elements do not have

contiguous indexes starting at zero.


 Sparse arrays are used over arrays when there are lesser

non-zero elements. Sparse arrays require lesser memory to


store the elements and the computation time can be saved.
Examples of Sparse array:
150

Why sparse array is required over simple arrays to store the


elements:
 Storage: When there is the maximum number of zero

elements and the minimum number of non-zero elements


then we use a sparse array over a simple array as it requires
less memory to store the elements. In the sparse array, we
only store the non-zero elements.
 Computation Time: In the sparse array we only store non-

zero elements and hence, traversing only non-zero elements


takes less computation time.
Representation of Sparse Array:
Sparse arrays can be represented in two ways:
 Array Representation
 Linked List Representation

1. Array Representation:
To represent a sparse array 2-D array is used with three rows
namely: Row, Column, and Value.
Row: Index of the row where non-zero elements are present.
Column: Index of the column where the non-zero element is
present.
Value: The non-zero value which is present in (Row, Column)
index.

Array Representation of Sparse Array


151

2. Linked List Representation:


To represent a sparse array using linked lists, each node has
four fields namely: Row, Column, Value, and Next node.
Row: Index of the row where non-zero elements are present.
Column: Index of the column where the non-zero element is
present.
Value: The non-zero value which is present in (Row, Column)
index.
Next node: It stores the address of the next node.

Linked List Representation of Sparse Array

Implementation of array representation of the sparse array:

Implementation of array representation of the sparse array

Below is the representation of the sparse array:

 C++
 Java
 C#
 Javascript
152

// Implementation of array representation

// of the sparse array

#include <iostream>

using namespace std;

int main()

int sparse[4][4] = { { 0, 0, 7, 0 },

{ 1, 0, 0, 0 },

{ 2, 0, 5, 0 },

{ 0, 8, 0, 4 } };

int s = 0;

for (int i = 0; i < 4; i++)

for (int j = 0; j < 4; j++)

if (sparse[i][j] != 0)

s++;
153

int representsparse[3][s];

int k = 0;

for (int i = 0; i < 4; i++)

for (int j = 0; j < 4; j++)

if (sparse[i][j] != 0) {

representsparse[0][k] = i;

representsparse[1][k] = j;

representsparse[2][k] = sparse[i][j];

k++;

cout << "Representation of Sparse array using arrays : "

"\n";

for (int i = 0; i < 3; i++) {

for (int j = 0; j < s; j++)

cout << " " << representsparse[i][j];

cout << "\n";


154

return 0;

Output
Representation of Sparse array using arrays :
012233
200213
712584
155

CHAPTER-4

LINKED LIST

Introduction of Linked List:

Linked List is a linear data structure. Unlike arrays, linked list


elements are not stored at a contiguous location; the elements
are linked using pointers. They include a series of connected
nodes. Here, each node stores the data and the address of the
next node.

Linked-List

Why Linked List?


Arrays can be used to store linear data of similar types, but
arrays have the following limitations:
 The size of the arrays is fixed: So we must know the upper
limit on the number of elements in advance. Also, generally,
the allocated memory is equal to the upper limit irrespective
of the usage.
 Insertion of a new element / Deletion of a existing element
in an array of elements is expensive: The room has to be
created for the new elements and to create room existing
156

elements have to be shifted but in Linked list if we have the


head node then we can traverse to any node through it and
insert new node at the required position.
Example:
In a system, if we maintain a sorted list of IDs in an array id[]
= [1000, 1010, 1050, 2000, 2040].
If we want to insert a new ID 1005, then to maintain the sorted
order, we have to move all the elements after 1000 (excluding
1000).
Deletion is also expensive with arrays until unless some
special techniques are used. For example, to delete 1010 in
id[], everything after 1010 has to be moved due to this so much
work is being done which affects the efficiency of the code.

Advantages of Linked Lists over arrays:


 Dynamic Array.

 Ease of Insertion/Deletion.

Drawbacks of Linked Lists:


 Random access is not allowed. We have to access elements

sequentially starting from the first node(head node). So we


cannot do a binary search with linked lists efficiently with
its default implementation.
 Extra memory space for a pointer is required with each

element of the list.


 Not cache friendly. Since array elements are contiguous

locations, there is locality of reference which is not there in


case of linked lists.
Types of Linked Lists:
 Simple Linked List – In this type of linked list, one can

move or traverse the linked list in only one direction


157

 Doubly Linked List – In this type of linked list, one can


move or traverse the linked list in both directions (Forward
and Backward)
 Circular Linked List – In this type of linked list, the last

node of the linked list contains the link of the first/head node
of the linked list in its next pointer and the first/head node
contains the link of the last node of the linked list in its prev
pointer
Basic operations on Linked Lists:
 Deletion

 Insertion

 Search

 Display

Representation of Linked Lists:


A linked list is represented by a pointer to the first node of the
linked list. The first node is called the head of the linked list.
If the linked list is empty, then the value of the head points to
NULL.
Each node in a list consists of at least two parts:
 A Data Item (we can store integer, strings, or any type of
data).
 Pointer (Or Reference) to the next node (connects one node

to another) or An address of another node


In C, we can represent a node using structures. Below is an
example of a linked list node with integer data.
In Java or C#, LinkedList can be represented as a class and a
Node as a separate class. The LinkedList class contains a
reference of Node class type.
158

// A linked list node

struct Node {

int data;

struct Node* next;

};

Construction of a simple linked list with 3 nodes:

// C program to implement a

// linked list

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;


159

};

// Driver's code

int main()

struct Node* head = NULL;

struct Node* second = NULL;

struct Node* third = NULL;

// allocate 3 nodes in the heap

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

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

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

/* Three blocks have been allocated dynamically.

We have pointers to these three blocks as head,


160

second and third

head second third

| | |

| | |

+---+-----+ +----+----+ +----+----+

|# |# | |# |# | | #| #|

+---+-----+ +----+----+ +----+----+

# represents any random value.

Data is random because we haven’t assigned

anything yet */

head->data = 1; // assign data in first node

head->next = second; // Link first node with

// the second node


161

/* data has been assigned to the data part of the first

block (block pointed by the head). And next

pointer of first block points to second.

So they both are linked.

head second third

| | |

| | |

+---+---+ +----+----+ +-----+----+

| 1 | o----->| # | # | | # |# |

+---+---+ +----+----+ +-----+----+

*/

// assign data to second node

second->data = 2;
162

// Link second node with the third node

second->next = third;

/* data has been assigned to the data part of the second

block (block pointed by second). And next

pointer of the second block points to the third

block. So all three blocks are linked.

head second third

| | |

| | |

+---+---+ +---+---+ +----+----+

| 1 | o----->| 2 | o-----> | # | # |

+---+---+ +---+---+ +----+----+ */

third->data = 3; // assign data to third node


163

third->next = NULL;

/* data has been assigned to data part of third

block (block pointed by third). And next pointer

of the third block is made NULL to indicate

that the linked list is terminated here.

We have the linked list ready.

head

+---+---+ +---+---+ +----+------+

| 1 | o----->| 2 | o-----> | 3 | NULL |

+---+---+ +---+---+ +----+------+


164

Note that only head is sufficient to represent

the whole list. We can traverse the complete

list by following next pointers. */

return 0;

Traversal of a Linked List


In the previous program, we created a simple linked list with
three nodes. Let us traverse the created list and print the data
of each node. For traversal, let us write a general-purpose
function printList() that prints any given list.

 C
 C++
 Java
 Python3
 C#
 Javascript

// A simple C program for

// traversal of a linked list


165

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;

};

// This function prints contents of linked list starting

// from the given node

void printList(struct Node* n)

while (n != NULL) {

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

n = n->next;
166

// Driver's code

int main()

struct Node* head = NULL;

struct Node* second = NULL;

struct Node* third = NULL;

// allocate 3 nodes in the heap

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

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

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

head->data = 1; // assign data in first node


167

head->next = second; // Link first node with second

second->data = 2; // assign data to second node

second->next = third;

third->data = 3; // assign data to third node

third->next = NULL;

// Function call

printList(head);

return 0;

Output
1 2 3
Time Complexity:
Time Complexity Worst Case Average Case
168

Search O(n) O(n)

Insert O(1) O(1)

Deletion O(1) O(1)

Auxiliary Space: O(N)

Array vs. linked list:

Major differences between array and linked-list are listed


below:
 Size: Since data can only be stored in contiguous blocks of

memory in an array, its size cannot be altered at runtime due


to the risk of overwriting other data.
However, in a linked list, each node points to the next one
such that data can exist at scattered (non-contiguous)
addresses; this allows for a dynamic size that can change at
runtime.
 Memory allocation: For arrays at compile time and at

runtime for linked lists. but, a dynamically allocated array


also allocates memory at runtime.
 Memory efficiency: For the same number of elements,

linked lists use more memory as a reference to the next node


is also stored along with the data. However, size flexibility
in linked lists may make them use less memory overall; this
is useful when there is uncertainty about size or there are
large variations in the size of data elements;
Memory equivalent to the upper limit on the size has to be
allocated (even if not all of it is being used) while using
169

arrays, whereas linked lists can increase their sizes step-by-


step proportionately to the amount of data.
 Execution time: Any element in an array can be directly
accessed with its index. However, in the case of a linked list,
all the previous elements must be traversed to reach any
element.
Also, better cache locality in arrays (due to contiguous
memory allocation) can significantly improve performance.
As a result, some operations (such as modifying a certain
element) are faster in arrays, while others (such as
inserting/deleting an element in the data) are faster in linked
lists.
 Insertion: In an array, insertion operation takes more time
but in a linked list these operations are fast. For example, if
we want to insert an element in the array at the end position
in the array and the array is full then we copy the array into
another array and then we can add an element whereas if the
linked list is full then we find the last node and make it next
to the new node
 Dependency: In an array, values are independent of each
other but
In the case of linked list nodes are dependent on each other.
one node is dependent on its previous node. If the previous
node is lost then we can’t find its next subsequent nodes.
170

Array vs Linked List

Advantages of Linked Lists:


 The size of the arrays is fixed: So we must know the upper

limit on the number of elements in advance. Also, generally,


the allocated memory is equal to the upper limit irrespective
of usage, and in practical uses, the upper limit is rarely
reached.
 Inserting a new element in an array of elements is expensive

because a room has to be created for the new elements and


to create a room existing elements have to be shifted.
Example:
suppose we maintain a sorted list of IDs in an array id[ ] =
[1000, 1010, 1050, 2000, 2040, …..].
And if we want to insert a new ID 1005, then to maintain the
sorted order, we have to move all the elements after 1000
(excluding 1000).
Deletion is also expensive with arrays unless some special
techniques are used. For example, to delete 1010 in id[],
everything after 1010 has to be moved.
171

So Linked list provides the following two advantages over


arrays:
1. Dynamic size
2. Ease of insertion/deletion
Disadvantages of Linked Lists:
 Random access is not allowed. We have to access elements

sequentially starting from the first node. So we cannot do a


binary search with linked lists.
 Extra memory space for a pointer is required for each

element of the list.


 Arrays have a better cache locality that can make a pretty

big difference in performance.


 It takes a lot of time in traversing and changing the pointers.

 It will be confusing when we work with pointers.

Advantages of Arrays:
 Arrays store multiple data of similar types with the same

name.
 It allows random access to elements.

 As the array is of fixed size and stored in contiguous

memory locations there is no memory shortage or overflow.


 It is helpful to store any type of data with a fixed size.

 Since the elements in the array are stored at contiguous

memory locations it is easy to iterate in this data structure


and unit time is required to access an element if the index is
known.
Disadvantages of Arrays:
 The array is static in nature. Once the size of the array is

declared then we can’t modify it.


 Insertion and deletion operations are difficult in an array as

elements are stored in contiguous memory locations and the


shifting operations are costly.
 The number of elements that have to be stored in an array

should be known in advance.


172

 Wastage of memory is the main problem in the array. If the


array size is big the less allocation of memory leads to
wastage of memory.

Representation of linked lists in memory:

Representation of a Linked list


Linked list can be represented as the connection of nodes in
which each node points to the next node of the list. The
representation of the linked list is shown below -

Till now, we have been using array data structure to organize


the group of elements that are to be stored individually in the
memory. However, Array has several advantages and
disadvantages that must be known to decide the data structure
that will be used throughout the program.

Traversal of Linked List:

Traversal() is used to visit each node of the list to perform an


operation. Here, we will traverse and print data present in
the list.
173

Singly LinkedList
A singly LinkedList is Uni-directional, meaning traversal is
possible in forwarding direction
only.

Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
174

16
17
18
19
20
21
22
23
24
25
26
#Traversing using singly linked list
class Node: #Creating a node
def __init__(self, data=None):
self.data = data
self.next = None
class SinglyLinkedList: #Node is attribute of linkedlist
def __init__(self):
self.head = None
def Traversal(self): #Traversal through linked List
n = self.head
while n is not None:
print (n.data)
n = n.next
175

list = SinglyLinkedList()
list.head = Node("Monday")
l2 = Node("Tuesday")
l3 = Node("Wedneday")
# Link first Node to second node
list.head.next = l2
# Link second Node to third node
l2.next = l3
list.Traversal()

Run
Traversal in a singly linked list

Explanation

 Line 1–3: We initially create a node because a collection


of nodes is a LinkedList.
 Line 3–6: A node consists of two attributes, self.data =
data and self.next = None.
 Line 7–10: Next, we create a class
named SinglyLinkedList.
 Line 10–15: We use the Traversal() function to traverse
along with the list.
 Line 17–20: We are calling the list by assigning some
values.
176

 Line 20–26: For traversing further, it is important to link


each node, which is done by list.head.next =
l2 and l2.next = l3.

Doubly LinkedList
A doubly LinkedList is Bi-directional, meaning traversal is
possible in both forward and backward
directions.

Example
1
2
3
4
5
6
7
8
9
10
11
12
177

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Node: #Creating a node
def __init__(self, data):
self.data = data
self.nref = None
self.pref=None
178

class doublyLinkedList: #Node is attribute of linkedlist


def __init__(self):
self.head = None
def insert_empty(self,data):
if self.head is None:
new_node=Node(data)
self.head=new_node
def add_begin(self,data):
new_node=Node(data)
if self.head is None:
self.head=new_node
else:
new_node.nref=self.head
self.head.pref=new_node
self.head=new_node
def add_end(self,data):
new_node=Node(data)
if self.head is None:
self.head=new_node
else:
n=self.head
while n.nref is not None:
n=n.nref
n.nref=new_node
179

new_node.pref=n

Run
Traversal in doubly linkedlist

Explanation

 Line 1–5: We create a node in doubly LinkedList. It


consist of three attributes self.data = data, self.nref =
None, and self.pref=None.
 Line 6–10: We create a class doublyLinkedList() in
which we can traverse in both directions.
 Line 9–12: We use insert_empty() to insert elements in
an empty LinkedList.
 Line 13–20: We use add_begin() to add elements in the
beginning, and change the self.head to the current node.
 Line 20–25: We use add_end() to add elements in the
end.
 Line 20–30: We
use Traversal_forward() and Traversal_backward() to
traverse the LinkedList in both the directions.
In this way, traversal operation takes place in the LinkedList.
180

Insertion:

Inserting a new element into a singly linked list at beginning is


quite simple. We just need to make a few adjustments in the
node links. There are the following steps which need to be
followed in order to inser a new node in the list at beginning.
o Allocate the space for the new node and store data into the
data part of the node. This will be done by the following
statements.
1. ptr = (struct node *) malloc(sizeof(struct node *));
2. ptr → data = item
o Make the link part of the new node pointing to the existing

first node of the list. This will be done by using the


following statement.
1. ptr->next = head;
o At the last, we need to make the new node as the first node

of the list this will be done by using the following


statement.
1. head = ptr;
Algorithm
o Step 1: IF PTR = NULL
Write OVERFLOW
Go to Step 7
[END OF IF]
o Step 2: SET NEW_NODE = PTR
o Step 3: SET PTR = PTR → NEXT
o Step 4: SET NEW_NODE → DATA = VAL
181

o Step 5: SET NEW_NODE → NEXT = HEAD


o Step 6: SET HEAD = NEW_NODE
o Step 7: EXIT

C Function
1. #include<stdio.h>
2. #include<stdlib.h>
3. void beginsert(int);
4. struct node
5. {
6. int data;
7. struct node *next;
8. };
9. struct node *head;
10. void main ()
11. {
12. int choice,item;
13. do
14. {
182

15. printf("\nEnter the item which you want to insert?\n


");
16. scanf("%d",&item);
17. beginsert(item);
18. printf("\nPress 0 to insert more ?\n");
19. scanf("%d",&choice);
20. }while(choice == 0);
21. }
22. void beginsert(int item)
23. {
24. struct node *ptr = (struct node *)malloc(sizeof(struc
t node *));
25. if(ptr == NULL)
26. {
27. printf("\nOVERFLOW\n");
28. }
29. else
30. {
31. ptr->data = item;
32. ptr->next = head;
33. head = ptr;
34. printf("\nNode inserted\n");
35. }
36.
37. }
Output
Enter the item which you want to insert?
12

Node inserted

Press 0 to insert more ?


183

Enter the item which you want to insert?


23

Node inserted

Press 0 to insert more ?


2

Deletion of LinkedList:

Deleting a node from the beginning of the list is the simplest


operation of all. It just need a few adjustments in the node
pointers. Since the first node of the list is to be deleted,
therefore, we just need to make the head, point to the next of
the head. This will be done by using the following statements.
1. ptr = head;
2. head = ptr->next;
Now, free the pointer ptr which was pointing to the head node
of the list. This will be done by using the following statement.
1. free(ptr)
Algorithm
o Step 1: IF HEAD = NULL
Write UNDERFLOW
Go to Step 5
[END OF IF]
184

o Step 2: SET PTR = HEAD


o Step 3: SET HEAD = HEAD -> NEXT
o Step 4: FREE PTR
o Step 5: EXIT

C function
1. #include<stdio.h>
2. #include<stdlib.h>
3. void create(int);
4. void begdelete();
5. struct node
6. {
7. int data;
8. struct node *next;
9. };
10. struct node *head;
11. void main ()
12. {
13. int choice,item;
185

14. do
15. {
16. printf("\n1.Append List\n2.Delete node\n3.Exit\n4.
Enter your choice?");
17. scanf("%d",&choice);
18. switch(choice)
19. {
20. case 1:
21. printf("\nEnter the item\n");
22. scanf("%d",&item);
23. create(item);
24. break;
25. case 2:
26. begdelete();
27. break;
28. case 3:
29. exit(0);
30. break;
31. default:
32. printf("\nPlease enter valid choice\n");
33. }
34.
35. }while(choice != 3);
36. }
37. void create(int item)
38. {
39. struct node *ptr = (struct node *)malloc(sizeof(struc
t node *));
40. if(ptr == NULL)
41. {
42. printf("\nOVERFLOW\n");
43. }
44. else
186

45. {
46. ptr->data = item;
47. ptr->next = head;
48. head = ptr;
49. printf("\nNode inserted\n");
50. }
51.
52. }
53. void begdelete()
54. {
55. struct node *ptr;
56. if(head == NULL)
57. {
58. printf("\nList is empty");
59. }
60. else
61. {
62. ptr = head;
63. head = ptr->next;
64. free(ptr);
65. printf("\n Node deleted from the begining ...");
66. }
67. }
Output
1.Append List
2.Delete node
3.Exit
4.Enter your choice?1

Enter the item


23
187

Node inserted

1.Append List
2.Delete node
3.Exit
4.Enter your choice?2

Node deleted from the begining ...

Searching in a linked list:

Searching is performed in order to find the location of a


particular element in the list. Searching any element in the list
needs traversing through the list and make the comparison of
every element of the list with the specified element. If the
element is matched with any of the list element then the
location of the element is returned from the function.
Algorithm
o Step 1: SET PTR = HEAD
o Step 2: Set I = 0
o STEP 3: IF PTR = NULL
WRITE "EMPTY LIST"
GOTO STEP 8
END OF IF
o STEP 4: REPEAT STEP 5 TO 7 UNTIL PTR != NULL
o STEP 5: if ptr → data = item
write i+1
End of IF
188

o STEP 6: I = I + 1
o STEP 7: PTR = PTR → NEXT
[END OF LOOP]
o STEP 8: EXIT
C function
1. #include<stdio.h>
2. #include<stdlib.h>
3. void create(int);
4. void search();
5. struct node
6. {
7. int data;
8. struct node *next;
9. };
10. struct node *head;
11. void main ()
12. {
13. int choice,item,loc;
14. do
15. {
16. printf("\n1.Create\n2.Search\n3.Exit\n4.Enter your c
hoice?");
17. scanf("%d",&choice);
18. switch(choice)
19. {
20. case 1:
21. printf("\nEnter the item\n");
22. scanf("%d",&item);
23. create(item);
24. break;
189

25. case 2:
26. search();
27. case 3:
28. exit(0);
29. break;
30. default:
31. printf("\nPlease enter valid choice\n");
32. }
33.
34. }while(choice != 3);
35. }
36. void create(int item)
37. {
38. struct node *ptr = (struct node *)malloc(sizeof(struc
t node *));
39. if(ptr == NULL)
40. {
41. printf("\nOVERFLOW\n");
42. }
43. else
44. {
45. ptr->data = item;
46. ptr->next = head;
47. head = ptr;
48. printf("\nNode inserted\n");
49. }
50.
51. }
52. void search()
53. {
54. struct node *ptr;
55. int item,i=0,flag;
56. ptr = head;
190

57. if(ptr == NULL)


58. {
59. printf("\nEmpty List\n");
60. }
61. else
62. {
63. printf("\nEnter item which you want to search?\n");

64. scanf("%d",&item);
65. while (ptr!=NULL)
66. {
67. if(ptr->data == item)
68. {
69. printf("item found at location %d ",i+1);
70. flag=0;
71. }
72. else
73. {
74. flag=1;
75. }
76. i++;
77. ptr = ptr -> next;
78. }
79. if(flag==1)
80. {
81. printf("Item not found\n");
82. }
83. }
84.
85. }
Output
1.Create
191

2.Search
3.Exit
4.Enter your choice?1

Enter the item


23

Node inserted

1.Create
2.Search
3.Exit
4.Enter your choice?1

Enter the item


34

Node inserted

1.Create
2.Search
3.Exit
4.Enter your choice?2

Enter item which you want to search?


34
item found at location 1

Header linked list:

A header node is a special node that is found at


the beginning of the list. A list that contains this type of
node, is called the header-linked list. This type of list is
192

useful when information other than that found in each node is


needed.
For example, suppose there is an application in which the
number of items in a list is often calculated. Usually, a list is
always traversed to find the length of the list. However, if the
current length is maintained in an additional header node that
information can be easily obtained.
Types of Header Linked List
1. Grounded Header Linked List
It is a list whose last node contains the NULL pointer. In
the header linked list the start pointer always points to the
header node. start -> next = NULL indicates that the
grounded header linked list is empty. The operations that
are possible on this type of linked list are Insertion,
Deletion, and Traversing.

2. Circular Header Linked List


A list in which last node points back to the header node is
193

called circular linked list. The chains do not indicate first


or last nodes. In this case, external pointers provide a
frame of reference because last node of a circular linked
list does not contain the NULL pointer. The possible
operations on this type of linked list are Insertion, Deletion
and Traversing.

// C program for a Header Linked List

#include <malloc.h>

#include <stdio.h>
194

// Structure of the list

struct link {

int info;

struct link* next;

};

// Empty List

struct link* start = NULL;

// Function to create a header linked list

struct link* create_header_list(int data)

// Create a new node

struct link *new_node, *node;

new_node = (struct link*)


195

malloc(sizeof(struct link));

new_node->info = data;

new_node->next = NULL;

// If it is the first node

if (start == NULL) {

// Initialize the start

start = (struct link*)

malloc(sizeof(struct link));

start->next = new_node;

else {

// Insert the node in the end

node = start;

while (node->next != NULL) {


196

node = node->next;

node->next = new_node;

return start;

// Function to display the

// header linked list

struct link* display()

struct link* node;

node = start;

node = node->next;

while (node != NULL) {

printf("%d ", node->info);

node = node->next;
197

printf("\n");

return start;

// Driver code

int main()

// Create the list

create_header_list(11);

create_header_list(12);

create_header_list(13);

// Print the list

display();

create_header_list(14);
198

create_header_list(15);

// Print the list

display();

return 0;

Output:
11 12 13
11 12 13 14 15

Circular linked list:

The circular linked list is a linked list where all nodes are
connected to form a circle. In a circular linked list, the first
node and the last node are connected to each other which
forms a circle. There is no NULL at the end.

There are generally two types of circular linked lists:


199

 Circular singly linked list: In a circular Singly linked list,


the last node of the list contains a pointer to the first node of
the list. We traverse the circular singly linked list until we
reach the same node where we started. The circular singly
linked list has no beginning or end. No null value is present
in the next part of any of the nodes.

Representation of Circular singly linked list

 Circular Doubly linked list: Circular Doubly Linked List


has properties of both doubly linked list and circular linked
list in which two consecutive elements are linked or
connected by the previous and next pointer and the last node
points to the first node by the next pointer and also the first
node points to the last node by the previous pointer.

Representation of circular doubly linked list

Note: We will be using the singly circular linked list to


represent the working of the circular linked list.
Representation of circular linked list:
200

Circular linked lists are similar to single Linked Lists with the
exception of connecting the last node to the first node.
Node representation of a Circular Linked List:
 C++

// Class Node, similar to the linked list

class Node{

int value;

// Points to the next node.

Node next;

Example of Circular singly linked list:

Example of circular linked list

The above Circular singly linked list can be represented as:

 C++
201

// Initialize the Nodes.

Node one = new Node(3);

Node two = new Node(5);

Node three = new Node(9);

// Connect nodes

one.next = two;

two.next = three;

three.next = one;

Explanation: In the above program one, two, and three are the
node with values 3, 5, and 9 respectively which are connected
in a circular manner as:
 For Node One: The Next pointer stores the address of Node

two.
 For Node Two: The Next stores the address of Node three

 For Node Three: The Next points to node one.

Operations on the circular linked list:


We can do some operations on the circular linked list similar
to the singly linked list which are:
1. Insertion
2. Deletion
202

1. Insertion in the circular linked list:


A node can be added in three ways:
1. Insertion at the beginning of the list
2. Insertion at the end of the list
3. Insertion in between the nodes
1) Insertion at the beginning of the list: To insert a node at
the beginning of the list, follow these steps:
 Create a node, say T.

 Make T -> next = last -> next.

 last -> next = T.

Circular linked list before insertion

And then,

Circular linked list after insertion


203

Below is the code implementation to insert a node at the


beginning of the list:

 C++

struct Node *addBegin(struct Node *last, int data)

if (last == NULL)

return addToEmpty(last, data);

// Creating a node dynamically.

struct Node *temp

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

// Assigning the data.

temp -> data = data;

// Adjusting the links.

temp -> next = last -> next;


204

last -> next = temp;

return last;

Time complexity: O(1) to insert a node at the beginning no


need to traverse list it takes constant time
Auxiliary Space: O(1)
2) Insertion at the end of the list: To insert a node at the end
of the list, follow these steps:
 Create a node, say T.

 Make T -> next = last -> next;

 last -> next = T.

 last = T.

Before insertion,

Circular linked list before insertion of node at the end

After insertion,
205

Circular linked list after insertion of node at the end

Below is the code implementation to insert a node at the


beginning of the list:

 C++

struct Node *addEnd(struct Node *last, int data)

if (last == NULL)

return addToEmpty(last, data);

// Creating a node dynamically.

struct Node *temp =

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


206

// Assigning the data.

temp -> data = data;

// Adjusting the links.

temp -> next = last -> next;

last -> next = temp;

last = temp;

return last;

Time Complexity: O(1) to insert a node at the end of the list.


No need to traverse the list as we are utilizing the last pointer,
hence it takes constant time.
Auxiliary Space: O(1)
3) Insertion in between the nodes: To insert a node in
between the two nodes, follow these steps:
 Create a node, say T.

 Search for the node after which T needs to be inserted, say

that node is P.
 Make T -> next = P -> next;
207

 P -> next = T.
Suppose 12 needs to be inserted after the node has the value
10,

Circular linked list before insertion

After searching and insertion,

Circular linked list after insertion

Below is the code to insert a node at the specified position of


the List:
 C++

struct Node *addAfter(struct Node *last, int data, int item)


208

if (last == NULL)

return NULL;

struct Node *temp, *p;

p = last -> next;

// Searching the item.

do

if (p ->data == item)

// Creating a node dynamically.

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

// Assigning the data.


209

temp -> data = data;

// Adjusting the links.

temp -> next = p -> next;

// Adding newly allocated node after p.

p -> next = temp;

// Checking for the last node.

if (p == last)

last = temp;

return last;

p = p -> next;

} while (p != last -> next);


210

cout << item << " not present in the list." << endl;

return last;

Time Complexity: O(N)


Auxiliary Space: O(1)

2. Deletion in a circular linked list:

1) Delete the node only if it is the only node in the circular


linked list:
 Free the node’s memory

 The last value should be NULLA node always points to

another node, so NULL assignment is not necessary.


Any node can be set as the starting point.
Nodes are traversed quickly from the first to the last.
2) Deletion of the last node:
 Locate the node before the last node (let it be temp)

 Keep the address of the node next to the last node in temp

 Delete the last memory

 Put temp at the end

3) Delete any node from the circular linked list: We will be


given a node and our task is to delete that node from the
circular linked list.
Algorithm:
Case 1: List is empty.
 If the list is empty we will simply return.

Case 2:List is not empty


211

 If the list is not empty then we define two


pointers curr and prev and initialize the pointer curr with
the head node.
 Traverse the list using curr to find the node to be deleted

and before moving to curr to the next node, every time set
prev = curr.
 If the node is found, check if it is the only node in the list.

If yes, set head = NULL and free(curr).


 If the list has more than one node, check if it is the first node

of the list. Condition to check this( curr == head). If yes,


then move prev until it reaches the last node. After prev
reaches the last node, set head = head -> next and prev ->
next = head. Delete curr.
 If curr is not the first node, we check if it is the last node in

the list. Condition to check this is (curr -> next == head).


 If curr is the last node. Set prev -> next = head and delete

the node curr by free(curr).


 If the node to be deleted is neither the first node nor the last

node, then set prev -> next = curr -> next and delete curr.
Below is the implementation for the above approach:

 C++

// C++ program to delete a given key from

// linked list.

#include <bits/stdc++.h>

using namespace std;


212

// Structure for a node

class Node {

public:

int data;

Node* next;

};

// Function to insert a node at the

// beginning of a Circular linked list

void push(Node** head_ref, int data)

// Create a new node and make head

// as next of it.

Node* ptr1 = new Node();

ptr1->data = data;
213

ptr1->next = *head_ref;

// If linked list is not NULL then

// set the next of last node

if (*head_ref != NULL) {

// Find the node before head and

// update next of it.

Node* temp = *head_ref;

while (temp->next != *head_ref)

temp = temp->next;

temp->next = ptr1;

else

// For the first node


214

ptr1->next = ptr1;

*head_ref = ptr1;

// Function to print nodes in a given

// circular linked list

void printList(Node* head)

Node* temp = head;

if (head != NULL) {

do {

cout << temp->data << " ";

temp = temp->next;

} while (temp != head);

}
215

cout << endl;

// Function to delete a given node

// from the list

void deleteNode(Node** head, int key)

// If linked list is empty

if (*head == NULL)

return;

// If the list contains only a

// single node

if ((*head)->data == key && (*head)->next == *head) {


216

free(*head);

*head = NULL;

return;

Node *last = *head, *d;

// If head is to be deleted

if ((*head)->data == key) {

// Find the last node of the list

while (last->next != *head)

last = last->next;

// Point last node to the next of

// head i.e. the second node


217

// of the list

last->next = (*head)->next;

free(*head);

*head = last->next;

return;

// Either the node to be deleted is

// not found or the end of list

// is not reached

while (last->next != *head && last->next->data != key) {

last = last->next;

// If node to be deleted was found

if (last->next->data == key) {
218

d = last->next;

last->next = d->next;

free(d);

else

cout << "no such keyfound";

// Driver code

int main()

// Initialize lists as empty

Node* head = NULL;

// Created linked list will be

// 2->5->7->8->10
219

push(&head, 2);

push(&head, 5);

push(&head, 7);

push(&head, 8);

push(&head, 10);

cout << "List Before Deletion: ";

printList(head);

deleteNode(&head, 7);

cout << "List After Deletion: ";

printList(head);

return 0;

}
220

Output
List Before Deletion: 10 8 7 5 2
List After Deletion: 10 8 5 2
Time Complexity: O(N), Worst case occurs when the element
to be deleted is the last element and we need to move through
the whole list.
Auxiliary Space: O(1), As constant extra space is used.
Advantages of Circular Linked Lists:
 Any node can be a starting point. We can traverse the whole

list by starting from any point. We just need to stop when


the first visited node is visited again.
 Useful for implementation of a queue.
Unlike this implementation, we don’t need to maintain two
pointers for front and rear if we use a circular linked list. We
can maintain a pointer to the last inserted node and the front
can always be obtained as next of last.

 Circular lists are useful in applications to repeatedly go


around the list. For example, when multiple applications are
running on a PC, it is common for the operating system to
put the running applications on a list and then cycle through
them, giving each of them a slice of time to execute, and
then making them wait while the CPU is given to another
application. It is convenient for the operating system to use
a circular list so that when it reaches the end of the list it can
cycle around to the front of the list.
 Circular Doubly Linked Lists are used for the
implementation of advanced data structures like
the Fibonacci Heap.
Disadvantages of circular linked list:
 Compared to singly linked lists, circular lists are more

complex.
221

 Reversing a circular list is more complicated than singly or


doubly reversing a circular list.
 It is possible for the code to go into an infinite loop if it is

not handled carefully.


 It is harder to find the end of the list and control the loop.

Applications of circular linked lists:


 Multiplayer games use this to give each player a chance to

play.
 A circular linked list can be used to organize multiple

running applications on an operating system. These


applications are iterated over by the OS.
Why circular linked list?
 A node always points to another node, so NULL assignment

is not necessary.
 Any node can be set as the starting point.
 Nodes are traversed quickly from the first to the last.

Two-way linked list:

87

Threaded lists:

A list in which additional linkage structures, called threads,


have been added to provide for traversals in special orders.
This permits bounded workspace, i.e. read-only traversals
along the direction provided by the threads. It does
presuppose that the list and any sublists are not recursive and
further that no sublist is shared.
222

Garbage collection:

Garbage collection (GC) is a dynamic technique for memory


management and heap allocation that examines and identifies
dead memory blocks before reallocating storage for reuse.
Garbage collection's primary goal is to reduce memory leaks.
Garbage collection frees the programmer from having to
deallocate and return objects to the memory system manually.
Garbage collection can account for a considerable amount of a
program's total processing time, and as a result, can have a
significant impact on performance. Stack allocation, region
inference, memory ownership, and combinations of various
techniques are examples of related techniques.
The basic principles of garbage collection are finding data
objects in a program that cannot be access
ed in the future and reclaiming the resources used by those
objects. Garbage collection does not often handle resources
other than memory, such as network sockets, database handles,
user interaction windows, files, and device descriptors.
Methods for managing such resources, especially destructors,
may be sufficient to manage memory without the requirement
for GC. Other resources can be associated with a memory
sector in some GC systems, which, when collected, causes the
task of reclaiming these resources.
Many programming languages, such as RPL, Java, C#, Go, and
most scripting languages, require garbage collection either as
part of the language specification or effectively for practical
implementation (for example, formal languages like lambda
calculus); these are referred to as garbage-collected languages.
Other languages, such as C and C++, were designed for use
with manual memory management but included garbage-
223

collected implementations. Some languages, such as Ada,


Modula-3, and C++/CLI, allow for both garbage collection and
manual memory management in the same application by using
separate heaps for collected and manually managed objects;
others, such as D, are garbage-collected but allow the user to
delete objects manually and completely disable garbage
collection when speed is required.
Garbage collection's dynamic approach to automatic heap
allocation addresses common and costly faults that, if left
undiscovered, can lead to real-world programmer problems.
Allocation errors are costly because they are difficult to detect
and correct. As a result, many programmers regard garbage
collection as an essential language feature that simplifies the
programmer's job by reducing manual heap allocation
management.
Now let us have a look at some of the most famous and
commonly implemented Garbage Collection techniques.
o Mark and Sweep
o Reference Counting
Mark and Sweep
The Mark Sweep algorithm is as straightforward as its name
suggests. It consists of two phases: a mark phase and a sweep
phase. The collector crawls across all the roots (global
variables, local variables, stack frames, virtual and hardware
registers, and so on) and marks every item it meets by setting a
bit anywhere around that object during the mark phase. It also
walks across the heap during the sweep phase, reclaiming
memory from all the unmarked items.
224

The fundamental algorithm is outlined in pseudo-code in


Python below. The collector is assumed to be single-threaded
in this example, although there might be several mutators.
While the collector is running, all mutator threads are paused.
This stop-the-world technique may appear inefficient, but it
vastly simplifies the collector implementation because
mutators cannot affect the state beneath it.
Code

1. def gc():
2. stop_all_mutators()
3. mark_roots()
4. sweep()
5. resume_all_mutators()
6.
7. def mark_roots():
8. candidates = Stack()
9. for field in Roots:
10. if field != nil && not is_marked(field):
11. set_marked(field)
12. candidates.push(field)
13. mark(candidates)
14.
15. def mark(candidates):
16. while not candidates.empty():
17. ref = candidates.pop()
18. for field in pointers(ref):
225

19. if field != nil && not is_marked(field):


20. set_marked(field)
21. candidates.push(field)
22.
23. def sweep():
24. scan = start_of_heap()
25. end = end_of_heap()
26. while scan < end:
27. if is_marked(scan):
28. unset_marked(scan)
29. else:
30. free(scan)
31. scan = next_object(scan)
32.
33. def next_object(address):
34. # Parse the heap and return the next object.
35. ...
It is evident from the pseudo-code that mark-sweep does not
immediately identify rubbish. Instead, it first recognizes all
items that aren't rubbish, such as living things, before
concluding that everything else is garbage. The process of
marking is a cyclical one. We recurse into its child fields after
detecting a live reference, and so on. Because of the time cost
and risk for stack overflow, recursive procedure calls aren't a
suitable way for marking. That's why we're utilizing a stack
that's explicitly defined. The space and time overhead of the
marking phase are both made obvious by this technique. The
size of the longest path that must be traced via the object graph
determines the maximum depth of the candidate's stack.
Theoretically, the worst case is equal to the number of nodes
on the heap. However, most real-world applications yield rather
shallow stacks. Despite this, a secure GC system must deal with
226

unusual scenarios. We use the mark() right after adding a new


object to the candidates in our implementation to keep the stack
size under control. The problem with marking is that GC is
required exactly because there is little memory, yet auxiliary
stacks demand more space. Large applications might lead the
trash collector to run out of memory.
There are a variety of approaches to detect overflow. One
advantage of using an explicit stack is that an overflow may be
immediately identified and a recovery procedure initiated.
Using an inline check-in for each push is a straightforward
approach ( ). Using a guard page and triggering recovery after
trapping the guard violation exception might be a somewhat
more efficient solution. Both techniques' tradeoffs must be
considered in the context of the underlying operating system
and hardware. The is-full test will probably cost a few
instructions (test followed by a branch) in the first technique,
but it will be performed every time we inspect an object. The
second technique necessitates catching access violation
exceptions, which are often costly but uncommon.
Sweep() is a simple function with a straightforward
implementation. It linearly traverses the heap, freeing any
objects that aren't tagged. Our heap layout does face
parseability restrictions as a result of this. The next
object(address) implementation must be able to return the
heap's next object. In most cases, the heap just has to be
parseable in one way. In most GC-enabled language runtimes,
an object's data is often tagged with an object header. The
header provides details about the item, such as type, size,
hashcode, mark bits, sync block, etc.
The header of an object is usually placed before the object's
data. As a result, the object's reference points to the middle of
227

the allocated heap cell immediately after the object header,


rather than the first byte. This makes it easier to parse the heap
from the top down. In most cases, free(address) will fill the
freed cell with a predetermined filler pattern that the heap
parsing algorithm recognizes.
Advantages of Mark and Sweep Algorithm
o The usage efficiency of hardware cache is usually the

deciding factor in the performance of most applications.


The L1-L3 caches may now be accessed in 2 to 10 CPU
cycles, whereas the RAM can take up to 100 cycles.
Caches help applications with good temporal and spatial
locality operate better. When a program accesses a
memory place that has recently been accessed, it is said to
be temporal local. If a program accesses nearby memory
regions in a scan-like pattern, it has a high spatial locality.
Unfortunately, the mark phase in the mark-sweep
algorithm fails miserably regarding the temporal and
geographical locality. The header of an object is normally
read and written just once in mark() (assuming that most
objects are popular and are referenced by only a single
pointer). We read the mark bit, and if the object hasn't been
marked yet, it won't be accessed again. Hardware
prefetching (whether speculative or via explicit prefetch
instructions) isn't ideal for such erratic pointer chasing.
Instead of making the mark bits part of the object headers,
one typical strategy for improving cache speed is to place
them in a separate bitmap. The bitmap's format, position,
and size are determined by various parameters, including
heap size, object alignment requirements, hardware cache
sizes, etc. The mark-sweep algorithm benefits from these
marking bitmaps in terms of performance. Marking, for
example, does not need object modification; numerous
228

objects can be marked with a single instruction (bit


whacking against a bitmap word). Because it alters fewer
words, it generates fewer dirty cache lines, resulting in
fewer cache flushes. Sweeping does not need to read any
active objects and may depend entirely on the bitmap for
heap scanning.
o The mark phase has an O(L) complexity, where L is the
size of living objects accessible from all roots. The sweep
phase's temporal complexity is O(H), where H is the
number of heap cells. Given that H > L, it's easy to think
that O(H) dominates O(L), but in reality, the sweep phase
has excellent cache performance owing to high spatial
locality, but the whole collection pace is dominated by
O(L) due to all the cache-unfriendly pointer chasing.
o Because marking is a costly procedure, it is only done on
a limited basis (only when required). The mark-sweep
approach uses less space and can cleanly handle cyclical
structures without any pointer manipulation complexity
compared to reference counting techniques. It, like other
tracing algorithms, demands certain heap headroom to
function. Additionally, because mark-sweep does not
compact the heap, the system may experience increased
internal fragmentation, resulting in lower heap utilization
(especially for larger allocations).
o With mutator's read and write operations, mark-sweep
adds essentially no coordination overhead. The object
allocation function is the sole way to interact with the
mutators, and even then, the overhead is small.
o In general, complicated allocators that comprehend and
support heap parsing and bitmap manipulation are
required for mark-sweep systems. Heap managers may
need to design non-trivial implementation solutions to
deal with internal fragmentation. Mark sweep, on the other
229

hand, because it does not move objects, is a good


candidate for usage in non-cooperative contexts where the
language runtime does not coordinate with the garbage
collector (it can happen if the GC was introduced as an
afterthought in the language design). Another benefit of
not moving is that object addresses do not change.
Therefore no patching is required after the sweep phase.
Reference Counting
The method of reference counting is really easy. It is based on
counting how many pointer references each allocated object
has. It's a straightforward, inherently incremental solution
because the program's memory management overhead is
distributed. Aside from memory management, reference
counting is widely used in operating systems as a resource
management tool for managing system resources such as files,
sockets, etc.
Each allocated object in the reference counting technique has a
reference count field. The memory manager is in charge of
ensuring that the reference count of each object is equal to the
number of direct pointer references to that object at all times.
Below is a simplified version of the algorithm.
Code

1. # new() allocates a new object. For brevity, we've ignored the


object types and
230

2. # assumed that all objects are of the same type and size.
3. def new():
4. obj = allocate_memory()
5. obj.set_reference_count(1)
6. return obj
7.
8. # delete() is invoked when an object is no longer required by t
he client program
9. def delete(obj):
10. obj.decrement_reference_count()
11. if obj.get_reference_count() == 0:
12. for child in children(obj):
13. delete(child)
14. release_memory(obj)
15.
16. # update() is the only blessed way to perform pointer assi
gnments in the system.
17. def update(source, target):
18. # We increment before deleting, this correctly deals w
ith source == target case.
19. target.increment_reference_count()
20. delete(source)
21. source = target
The inability to recover cyclic storage is the most significant
disadvantage of reference counting. Cyclic data structures such
as doubly-linked lists and non-basic graphs cannot be
successfully recovered using a simple reference counting
technique and will leak memory.
Advantages of Reference Counting
o Compared to tracing collectors, the memory management

cost is dispersed across the application, resulting in a


significantly smoother and responsive system. It's worth
231

noting that the processing cost is proportional to the size


of the sub-graph referenced by the final pointer, and it's
not always trivial.
o A reference counting system's spatial locality is usually no
worse than that of the actual client program, and it's
usually better than that of tracing GCs, which must trace
across all living objects.
o Unlike tracing collectors, which leave inaccessible
memory unallocated until the collector executes (usually
on heap depletion), the reference counting technique
allows the wasted memory to be reused right away.
Because of the instant reuse, caches have a greater
temporal locality, resulting in fewer page faults. It also
makes resource cleanup easier because finalizers may be
called immediately, resulting in faster system resource
release. Immediate reuse of space also allows for
improvements such as in-place data-structure
modifications.
o In terms of technical specifics, reference counting-based
collection is the simplest garbage collection approach. If
the language runtime doesn't enable pointer manipulation
and/or the programmers can't determine/manipulate the
object roots, the implementation is extremely simple.
o The programmer can have total control over the allocation
and deallocation of an object using a reference counting
technique. It may be possible for a programmer to
optimize away the reference counting cost in places where
it is judged safe. This represents difficulty in terms of
accuracy and therefore necessitates a greater level of code
discipline. Even in the absence of smart optimizations, the
interface of a client application and the reference counting
method are tightly coupled. Clients must appropriately
call operations that increase and reduce reference counts.
232

o Each item carries the space above the reference-count


field. This might theoretically equal a 50% overhead for
very tiny items. This expense must be considered against
the fact that memory cells can be reused right away and
that reference counting does not utilize heap space during
collection. Instead of utilizing a complete word for ref-
count, a reference counting system might save space by
employing a single byte. Such systems use a fall-back
tracing mechanism (like mark-sweep) to gather objects
with maxed-out reference counts and reference counting
(and circular references).
o Unlike tracing techniques, where pointer changes are free,
reference counting has a large cost since each pointer
update necessitates updating two reference counts to keep
the program valid.
o As previously stated, reference counting's major flaw is its
inability to recover cyclic storage. Cyclic data structures
such as doubly-linked lists and non-basic graphs cannot be
successfully recovered using a simple reference counting
technique and will leak memory.

Applications of linked lists:

A linked list is a linear data structure consisting of elements


called nodes where each node is composed of two parts: an
information part and a link part, also called the next pointer
part.
Linked list is used in a wide variety of applications such as
o Polynomial Manipulation representation
o Addition of long positive integers
o Representation of sparse matrices
233

o Addition of long positive integers


o Symbol table creation
o Mailing list
o Memory management
o Linked allocation of files
o Multiple precision arithmetic etc
Polynomial Manipulation
Polynomial manipulations are one of the most important
applications of linked lists. Polynomials are an important part
of mathematics not inherently supported as a data type by most
languages. A polynomial is a collection of different terms, each
comprising coefficients, and exponents. It can be represented
using a linked list. This representation makes polynomial
manipulation efficient.
Skip Ad

While representing a polynomial using a linked list, each


polynomial term represents a node in the linked list. To get
better efficiency in processing, we assume that the term of
every polynomial is stored within the linked list in the order of
decreasing exponents. Also, no two terms have the same
exponent, and no term has a zero coefficient and without
coefficients. The coefficient takes a value of 1.
Each node of a linked list representing polynomial
constitute three parts:
o The first part contains the value of the coefficient of the
term.
o The second part contains the value of the exponent.
o The third part, LINK points to the next term (next node).
234

The structure of a node of a linked list that represents a


polynomial is shown below:

Consider a polynomial P(x) = 7x2 + 15x3 - 2 x2 + 9. Here 7, 15,


-2, and 9 are the coefficients, and 4,3,2,0 are the exponents of
the terms in the polynomial. On representing this polynomial
using a linked list, we have

Observe that the number of nodes equals the number of terms


in the polynomial. So we have 4 nodes. Moreover, the terms
are stored to decrease exponents in the linked list. Such
representation of polynomial using linked lists makes the
operations like subtraction, addition, multiplication, etc., on
polynomial very easy.
Addition of Polynomials:
To add two polynomials, we traverse the list P and Q. We take
corresponding terms of the list P and Q and compare their
exponents. If the two exponents are equal, the coefficients are
added to create a new coefficient. If the new coefficient is equal
to 0, then the term is dropped, and if it is not zero, it is inserted
at the end of the new linked list containing the resulting
polynomial. If one of the exponents is larger than the other, the
235

corresponding term is immediately placed into the new linked


list, and the term with the smaller exponent is held to be
compared with the next term from the other list. If one list ends
before the other, the rest of the terms of the longer list is
inserted at the end of the new linked list containing the resulting
polynomial.
Let us consider an example an example to show how the
addition of two polynomials is performed,
P(x) = 3x4 + 2x3 - 4 x2 + 7
Q (x) = 5x3 + 4 x2 - 5
These polynomials are represented using a linked list in order
of decreasing exponents as follows:

To generate a new linked list for the resulting polynomials that


is formed on the addition of given polynomials P(x) and Q(x),
we perform the following steps,
1. Traverse the two lists P and Q and examine all the nodes.
2. We compare the exponents of the corresponding terms of
two polynomials. The first term of polynomials P and Q
236

contain exponents 4 and 3, respectively. Since the


exponent of the first term of the polynomial P is greater
than the other polynomial Q, the term having a larger
exponent is inserted into the new list. The new list initially
looks as shown below:

3. We then compare the exponent of the next term of the list


P with the exponents of the present term of list Q. Since
the two exponents are equal, so their coefficients are
added and appended to the new list as follows:

4. Then we move to the next term of P and Q lists and


compare their exponents. Since exponents of both these
terms are equal and after addition of their coefficients, we
get 0, so the term is dropped, and no node is appended to
the new list after this,
237

5. Moving to the next term of the two lists, P and Q, we find


that the corresponding terms have the same exponents
equal to 0. We add their coefficients and append them to
the new list for the resulting polynomial as shown below:

Example:
C++ program to add two polynomials
1. #include <iostream>
2. using namespace std;
3. int max(int m, int n) { return (m > n)? m: n; }
4. int *add(int A[], int B[], int m, int n)
5. {
6. int size = max(m, n);
7. int *sum = new int[size];
8. for (int i = 0; i<m; i++)
9. sum[i] = A[i];
10. for (int i=0; i<n; i++)
11. sum[i] += B[i];
12. return sum;
13. }
14. void printPoly(int poly[], int n)
15. {
16. for (int i=0; i<n; i++)
17. {
18. cout << poly[i];
19. if (i != 0)
20. cout << "x^" << i ;
238

21. if (i != n-1)
22. cout << " + ";
23. }
24. }
25. int main()
26. {
27. int A[] = { 5, 0, 10, 6 };
28. int B[] = { 1, 2, 4 };
29. int m = sizeof(A)/sizeof(A[0]);
30. int n = sizeof(B)/sizeof(B[0]);
31. cout << "First polynomial is \n";
32. printPoly(A, m);
33. cout << "\n Second polynomial is \n";
34. printPoly(B, n);
35. int *sum = add(A, B, m, n);
36. int size = max(m, n);
37. cout << "\n Sum of polynomial is \n";
38. printPoly(sum, size);
39. return 0;
40. }
Explanation:
In the above example, we have created an example of sum of
two polynomial using array.
Output:
Below is the output of this example.
239

Addition of long positive integer using linked list


Most programming languages allow restrictions on the
maximum value of integers stored. The maximum value of the
largest integers is 32767, and the largest is 2147483647.
Sometimes, applications such as security algorithms and
cryptography require storing and manipulating integers of
unlimited size. So in such a situation, it is desirable to use a
linked list for storing and manipulating integers of arbitrary
length.
Adding long positive integers can be performed effectively
using a circular linked list. As we know that when we add two
long integers, the digits of the given numbers are individually
traversed from right to left, and the corresponding digits of the
two numbers along with a carry from prior digits sum are
added. So to accomplish addition, we must need to know how
the digits of a long integer are stored in a linked list.
The digits of a long integer must be stored from right to left in
a linked list so that the first node on the list contains the least
significant digit, i.e., the rightmost digit, and the last node
contains the most significant digit, i.e., leftmost digit.
Example: An integer value 543467 can be represented using a
linked list as
240

For performing the addition of two long integers, the


following steps need to be followed:
o Traverse the two linked lists in parallel from left to right.
o During traversal, corresponding digits and a carry from
prior digits sum are added, then stored in the new node of
the resultant linked list.
The first positive long integer 543467 is represented using a
linked list whose first node is pointed by NUM1 pointer.
Similarly, the second positive long integer 48315 is represented
using the second linked list whose first node is pointed by
NUM2 pointer. These two numbers are stored in the third
linked list whose first node is pointed to by the RESULT
pointer.
241

Example:
C++ program for addition of two polynomials using Linked
Lists
1. #include <bits/stdc++.h>
2. #include <iostream.h>
3. using namespace std;
4. struct Node {
5. int coeff;
6. int pow;
7. struct Node* next;
8. };
9. void create_node(int x, int y, struct Node** temp)
10. {
11. struct Node *r, *z;
12. z = *temp;
13. if (z == NULL) {
14. r = (struct Node*)malloc(sizeof(struct Node));
15. r->coeff = x;
16. r->pow = y;
17. *temp = r;
18. r-
>next = (struct Node*)malloc(sizeof(struct Node));
19. r = r->next;
20. r->next = NULL;
21. }
22. else {
23. r->coeff = x;
24. r->pow = y;
25. r-
>next = (struct Node*)malloc(sizeof(struct Node));
26. r = r->next;
27. r->next = NULL;
242

28. }
29. }
30. void polyadd(struct Node* poly1, struct Node* poly2,
31. struct Node* poly)
32. {
33. while (poly1->next && poly2->next) {
34. if (poly1->pow > poly2->pow) {
35. poly->pow = poly1->pow;
36. poly->coeff = poly1->coeff;
37. poly1 = poly1->next;
38. }
39. else if (poly1->pow < poly2->pow) {
40. poly->pow = poly2->pow;
41. poly->coeff = poly2->coeff;
42. poly2 = poly2->next;
43. }
44. else {
45. poly->pow = poly1->pow;
46. poly->coeff = poly1->coeff + poly2->coeff;
47. poly1 = poly1->next;
48. poly2 = poly2->next;
49. }
50. poly->next
51. = (struct Node*)malloc(sizeof(struct Node));
52. poly = poly->next;
53. poly->next = NULL;
54. }
55. while (poly1->next || poly2->next) {
56. if (poly1->next) {
57. poly->pow = poly1->pow;
58. poly->coeff = poly1->coeff;
59. poly1 = poly1->next;
60. }
243

61. if (poly2->next) {
62. poly->pow = poly2->pow;
63. poly->coeff = poly2->coeff;
64. poly2 = poly2->next;
65. }
66. poly->next
67. = (struct Node*)malloc(sizeof(struct Node));
68. poly = poly->next;
69. poly->next = NULL;
70. }
71. }
72. void show(struct Node* node)
73. {
74. while (node->next != NULL) {
75. printf("%dx^%d", node->coeff, node->pow);
76. node = node->next;
77. if (node->coeff >= 0) {
78. if (node->next != NULL)
79. printf("+");
80. }
81. }
82. }
83. int main()
84. {
85. struct Node *poly1 = NULL, *poly2 = NULL, *poly
= NULL;
86. create_node(5, 2, &poly1);
87. create_node(4, 1, &poly1);
88. create_node(2, 0, &poly1);
89. create_node(-5, 1, &poly2);
90. create_node(-5, 0, &poly2);
91. printf("1st Number: ");
92. show(poly1);
244

93. printf("\n 2nd Number: ");


94. show(poly2);
95. poly = (struct Node*)malloc(sizeof(struct Node));
96. polyadd(poly1, poly2, poly);
97. printf("\n Sum of polynomial after addition: ");
98. show(poly);
99. return 0;
100. }
Explanation:
In the above example, we have created an example of sum of
two polynomial using linked list.
Output:
Below is the output of this example.

Polynomial of Multiple Variables


We can represent a polynomial with more than one variable,
i.e., it can be two or three variables. Below is a node structure
suitable for representing a polynomial with three variables X,
Y, Z using a singly linked list.
245

Consider a polynomial P(x, y, z) = 10x2y2z + 17 x2y z2 - 5


xy2 z+ 21y4z2 + 7. On represnting this polynomial using linked
list are:

Terms in such a polynomial are ordered accordingly to the


decreasing degree in x. Those with the same degree in x are
ordered according to decreasing degree in y. Those with the
same degree in x and y are ordered according to decreasing
degrees in z.
Example
Simple C++ program to multiply two polynomials
1. #include <iostream>
2. using namespace std;
3. int *multiply(int A[], int B[], int m, int n)
4. {
5. int *prod = new int[m+n-1];
6. for (int i = 0; i<m+n-1; i++)
7. prod[i] = 0;
8. for (int i=0; i<m; i++)
9. {
10. for (int j=0; j<n; j++)
11. prod[i+j] += A[i]*B[j];
12. }
13. return prod;
14. }
15. void printPoly(int poly[], int n)
16. {
17. for (int i=0; i<n; i++)
246

18. {
19. cout << poly[i];
20. if (i != 0)
21. cout << "x^" << i ;
22. if (i != n-1)
23. cout << " + ";
24. }
25. }
26. int main()
27. {
28. int A[] = { 5, 0, 10, 6 };
29. int B[] = { 1, 2, 4 };
30. int m = sizeof(A)/sizeof(A[0]);
31. int n = sizeof(B)/sizeof(B[0]);
32. cout << "First polynomial is \n";
33. printPoly(A, m);
34. cout << "\nSecond polynomial is \n";
35. printPoly(B, n);
36. int *prod = multiply(A, B, m, n);
37. cout << "\nProduct of two polynomial is \n";
38. printPoly(prod, m+n-1);
39. return 0;
40. }
Explanation:
In the above example, we have created an example of multiple
of two polynomial using arrays.
Output:
Below is the output of this example.
247

Some other applications of linked list:


o Memory Management: Memory management is one of
the operating system's key features. It decides how to
allocate and reclaim storage for processes running on the
system. We can use a linked list to keep track of portions
of memory available for allocation.
o Mailing List: Linked lists have their use in email
applications. Since it is difficult to predict multiple lists,
maybe a mailer builds a linked list of addresses before
sending a message.
o LISP: LISP is an acronym for LIST processor, an
important programming language in artificial intelligence.
This language extensively uses linked lists in performing
symbolic processing.
o Linked allocation of files: A file of large size may not be
stored in one place on a disk. So there must be some
mechanism to link all the scattered parts of the file
together. The use of a linked list allows an efficient file
allocation method in which each block of a file contains a
pointer to the file's text block. But this method is good
only for sequential access.
248

o Virtual Memory: An interesting application of linked


lists is found in the way systems support virtual memory.
o Support for other data structures: Some other data
structures like stacks, queues, hash tables, graphs can be
implemented using a linked list.

SECTION-III

STACK

Introduction of Stack:

It is a linear data structure that follows a particular order in


which the operations are performed.
LIFO( Last In First Out ):
This strategy states that the element that is inserted last will
come out first. You can take a pile of plates kept on top of each
other as a real-life example. The plate which we put last is on
the top and since we remove the plate that is at the top, we can
say that the plate that was put last comes out first.
Basic Operations on Stack
In order to make manipulations in a stack, there are certain
operations provided to us.
 push() to insert an element into the stack
 pop() to remove an element from the stack
 top() Returns the top element of the stack.
249

 isEmpty() returns true is stack is empty else false


 size() returns the size of stack

Stack

Push:
Adds an item to the stack. If the stack is full, then it is said to
be an Overflow condition.
Algorithm for push:
begin
if stack is full
return
250

endif
else
increment top
stack[top] assign value
end else
end procedure
Pop:
Removes an item from the stack. The items are popped in the
reversed order in which they are pushed. If the stack is empty,
then it is said to be an Underflow condition.
Algorithm for pop:
begin
if stack is empty
return
endif
else
store value of stack[top]
decrement top
return value
end else
end procedure
Top:
Returns the top element of the stack.
Algorithm for Top:
begin
return stack[top]
251

end procedure
isEmpty:
Returns true if the stack is empty, else false.
Algorithm for isEmpty:
begin
if top < 1
return true
else
return false
end procedure
Understanding stack practically:
There are many real-life examples of a stack. Consider the
simple example of plates stacked over one another in a
canteen. The plate which is at the top is the first one to be
removed, i.e. the plate which has been placed at the
bottommost position remains in the stack for the longest
period of time. So, it can be simply seen to follow the
LIFO/FILO order.
Complexity Analysis:
 Time Complexity

Operations Complexity

push() O(1)

pop() O(1)

isEmpty() O(1)
252

Operations Complexity

size() O(1)

Types of Stacks:
 Register Stack: This type of stack is also a memory

element present in the memory unit and can handle a small


amount of data only. The height of the register stack is
always limited as the size of the register stack is very small
compared to the memory.
 Memory Stack: This type of stack can handle a large

amount of memory data. The height of the memory stack is


flexible as it occupies a large amount of memory data.
Applications of the stack:
 Infix to Postfix /Prefix conversion

 Redo-undo features at many places like editors, photoshop.

 Forward and backward features in web browsers

 Used in many algorithms like Tower of Hanoi, tree

traversals, stock span problems, and histogram problems.


 Backtracking is one of the algorithm designing techniques.

Some examples of backtracking are the Knight-Tour


problem, N-Queen problem, find your way through a maze,
and game-like chess or checkers in all these problems we
dive into someway if that way is not efficient we come back
to the previous state and go into some another path. To get
back from a current state we need to store the previous state
for that purpose we need a stack.
 In Graph Algorithms like Topological Sorting and Strongly

Connected Components
 In Memory management, any modern computer uses a stack

as the primary management for a running purpose. Each


program that is running in a computer system has its own
memory allocations
253

 String reversal is also another application of stack. Here one


by one each character gets inserted into the stack. So the
first character of the string is on the bottom of the stack and
the last element of a string is on the top of the stack. After
Performing the pop operations on the stack we get a string
in reverse order.
Implementation of Stack:
There are two ways to implement a stack
 Using array
 Using linked list

Implementing Stack using Arrays:


Recommended Problem
Implement Stack using Linked List

/* C++ program to implement basic stack

operations */

#include <bits/stdc++.h>

using namespace std;

#define MAX 1000


254

class Stack {

int top;

public:

int a[MAX]; // Maximum size of Stack

Stack() { top = -1; }

bool push(int x);

int pop();

int peek();

bool isEmpty();

};

bool Stack::push(int x)

if (top >= (MAX - 1)) {


255

cout << "Stack Overflow";

return false;

else {

a[++top] = x;

cout << x << " pushed into stack\n";

return true;

int Stack::pop()

if (top < 0) {

cout << "Stack Underflow";

return 0;

}
256

else {

int x = a[top--];

return x;

int Stack::peek()

if (top < 0) {

cout << "Stack is Empty";

return 0;

else {

int x = a[top];

return x;

}
257

bool Stack::isEmpty()

return (top < 0);

// Driver program to test above functions

int main()

class Stack s;

s.push(10);

s.push(20);

s.push(30);

cout << s.pop() << " Popped from stack\n";

//print top element of stack after poping


258

cout << "Top element is : " << s.peek() << endl;

//print all elements in stack :

cout <<"Elements present in stack : ";

while(!s.isEmpty())

// print top element in stack

cout << s.peek() <<" ";

// remove top element in stack

s.pop();

return 0;

Output
10 pushed into stack
20 pushed into stack
30 pushed into stack
259

30 Popped from stack


Top element is : 20
Elements present in stack : 20 10
Advantages of array implementation:
 Easy to implement.

 Memory is saved as pointers are not involved.

Disadvantages of array implementation:


 It is not dynamic.

 It doesn’t grow and shrink depending on needs at runtime.

Implementing Stack using Linked List:

// C++ program for linked list implementation of stack

#include <bits/stdc++.h>

using namespace std;

// A structure to represent a stack

class StackNode {

public:

int data;

StackNode* next;
260

};

StackNode* newNode(int data)

StackNode* stackNode = new StackNode();

stackNode->data = data;

stackNode->next = NULL;

return stackNode;

int isEmpty(StackNode* root)

return !root;

void push(StackNode** root, int data)


261

StackNode* stackNode = newNode(data);

stackNode->next = *root;

*root = stackNode;

cout << data << " pushed to stack\n";

int pop(StackNode** root)

if (isEmpty(*root))

return INT_MIN;

StackNode* temp = *root;

*root = (*root)->next;

int popped = temp->data;

free(temp);
262

return popped;

int peek(StackNode* root)

if (isEmpty(root))

return INT_MIN;

return root->data;

// Driver code

int main()

StackNode* root = NULL;

push(&root, 10);
263

push(&root, 20);

push(&root, 30);

cout << pop(&root) << " popped from stack\n";

cout << "Top element is " << peek(root) << endl;

cout <<"Elements present in stack : ";

//print all elements in stack :

while(!isEmpty(root))

// print top element in stack

cout << peek(root) <<" ";

// remove top element in stack

pop(&root);

}
264

return 0;

// This is code is contributed by rathbhupendra

Output
10 pushed to stack
20 pushed to stack
30 pushed to stack
30 popped from stack
Top element is 20
Elements present in stack : 20 10
Advantages of Linked List implementation:
 The linked list implementation of a stack can grow and

shrink according to the needs at runtime.


 It is used in many virtual machines like JVM.

 Stacks are more secure and reliable as they do not get

corrupted easily.
 Stack cleans up the objects automatically.

Disadvantages of Linked List


implementation:

 Requires extra memory due to the involvement of pointers.


 Random accessing is not possible in stack.
 The total size of the stack must be defined before.
265

 If the stack falls outside the memory it can lead to abnormal


termination.
Array and linked representation of stacks

Stack: A stack is a linear data structure in which elements can


be inserted and deleted only from one side of the list, called
the top. A stack follows the LIFO (Last In First Out) principle,
i.e., the element inserted at the last is the first element to come
out. The insertion of an element into a stack is
called push operation, and the deletion of an element from the
stack is called pop operation. In stack, we always keep track
of the last element present in the list with a pointer called top.
The diagrammatic representation of the stack is given below:

Array: An array is a collection of items stored at contiguous


memory locations. The idea is to store multiple items of the
same type together. This makes it easier to calculate the
position of each element by simply adding an offset to a base
value, i.e., the memory location of the first element of the array
266

(generally denoted by the name of the array).


The diagrammatic representation of Array is given below:

Difference between Stack and Array Data Structures:


Basis of
Comparison Stacks Array

Stack is a
linear data An array is a
structure collection of
represented related data
by a values called
sequential elements
collection of each
elements in a identified by
fixed an an indexed
Definition order array

Stacks are In the array


based on the the elements
LIFO belong to
principle, indexes, i.e.,
i.e., the if you want to
element get into the
inserted at fourth
Principle the last, is the element you
267

Basis of
Comparison Stacks Array

first element have to write


to come out the variable
of the list. name with its
index or
location
within the
square
bracket eg
arr[4]

Insertion and
deletion in
stacks take Insertion and
place only deletion in
from one end the array can
of the list be done at
called the any index in
Operations top. the array.

The stack has


a dynamic The array has
Storage size. a fixed size.

The stack can The array


contain contains
elements of elements of
different data the same data
Data Types types. type.
268

Basis of
Comparison Stacks Array

We can do
We can do both linear
only a linear and Binary
Methods search search

Random Random
access to access to
elements is elements is
not allowed allowed in
Data Access in stacks arrays

We can We cannot
implement a implement an
stack using array using
Implementation the array stack

There are It is rich in


limited methods or
number of operations
operations that can be
can be perform on it
performed on like sorting,
a stack: push, traversing,
pop, peek, reverse, push,
Methods etc. pop, etc.

It has only In arrays,


one pointer- memory can
Pointers the top. This be allocated
269

Basis of
Comparison Stacks Array

pointer in compile-
indicates the time and is
address of also known
the topmost as static
element or memory
the last allocation.
inserted one
of the stack.
Complexity Analysis:
Operation Stack Operation Array

push O(1) Insert O(n)

pop O(1) Delete O(n)

peek O(1) Access O(1)

isEmpty O(1) Traversal O(n)


Operations on stacks:
Basic Operations
Stack operations may involve initializing the stack, using it and
then de-initializing it. Apart from these basic stuffs, a stack is
used for the following two primary operations −
push() − Pushing (storing) an element on the stack.
 pop() − Removing (accessing) an element from the stack.

When data is PUSHed onto stack.


270

To use a stack efficiently, we need to check the status of stack


as well. For the same purpose, the following functionality is
added to stacks −
 peek() − get the top data element of the stack, without
removing it.
 isFull() − check if stack is full.

 isEmpty() − check if stack is empty.

At all times, we maintain a pointer to the last PUSHed data on


the stack. As this pointer always represents the top of the stack,
hence named top. The top pointer provides top value of the
stack without actually removing it.
First we should learn about procedures to support stack
functions −
peek()
Algorithm of peek() function −
begin procedure peek
return stack[top]
end procedure
Implementation of peek() function in C programming language

Example
int peek() {
return stack[top];
}
isfull()
Algorithm of isfull() function −
begin procedure isfull

if top equals to MAXSIZE


271

return true
else
return false
endif

end procedure
Implementation of isfull() function in C programming language

Example
bool isfull() {
if(top == MAXSIZE)
return true;
else
return false;
}
isempty()
Algorithm of isempty() function −
begin procedure isempty

if top less than 1


return true
else
return false
endif

end procedure
Implementation of isempty() function in C programming
language is slightly different. We initialize top at -1, as the
index in array starts from 0. So we check if the top is below
zero or -1 to determine if the stack is empty. Here's the code −
272

Example
bool isempty() {
if(top == -1)
return true;
else
return false;
}

Push Operation
The process of putting a new data element onto stack is known
as a Push Operation. Push operation involves a series of steps

 Step 1 − Checks if the stack is full.
 Step 2 − If the stack is full, produces an error and exit.
 Step 3 − If the stack is not full, increments top to point
next empty space.
 Step 4 − Adds data element to the stack location, where
top is pointing.
 Step 5 − Returns success.

If the linked list is used to implement the stack, then in step 3,


we need to allocate space dynamically.
273

Algorithm for PUSH Operation


A simple algorithm for Push operation can be derived as
follows −
begin procedure push: stack, data

if stack is full
return null
endif

top ← top + 1
stack[top] ← data

end procedure
Implementation of this algorithm in C, is very easy. See the
following code −
Example
void push(int data) {
if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf("Could not insert data, Stack is full.\n");
}
}

Pop Operation
Accessing the content while removing it from the stack, is
known as a Pop Operation. In an array implementation of pop()
operation, the data element is not actually removed,
instead top is decremented to a lower position in the stack to
274

point to the next value. But in linked-list implementation, pop()


actually removes data element and deallocates memory space.
A Pop operation may involve the following steps −
 Step 1 − Checks if the stack is empty.
 Step 2 − If the stack is empty, produces an error and exit.
 Step 3 − If the stack is not empty, accesses the data
element at which top is pointing.
 Step 4 − Decreases the value of top by 1.
 Step 5 − Returns success.

Algorithm for Pop Operation


A simple algorithm for Pop operation can be derived as follows

begin procedure pop: stack

if stack is empty
return null
endif

data ← stack[top]
top ← top - 1
return data
275

end procedure
Implementation of this algorithm in C, is as follows −
Example
int pop(int data) {

if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Could not retrieve data, Stack is empty.\n");
}
}

Applications of stacks:

Following is the various Applications of Stack in Data


Structure:
o Evaluation of Arithmetic Expressions

o Backtracking

o Delimiter Checking

o Reverse a Data

o Processing Function Calls

1. Evaluation of Arithmetic Expressions


A stack is a very effective data structure for evaluating
arithmetic expressions in programming languages. An
arithmetic expression consists of operands and operators.
276

In addition to operands and operators, the arithmetic expression


may also include parenthesis like "left parenthesis" and "right
parenthesis".
Example: A + (B - C)
To evaluate the expressions, one needs to be aware of the
standard precedence rules for arithmetic expression. The
precedence rules for the five basic arithmetic operators are:

Operators Associativity Precedence

^ exponentiation Right to left Highest followed by


*Multiplication and
/division

*Multiplication, Left to right Highest followed by +


/division addition and -
subtraction

+ addition, - Left to right lowest


subtraction

Evaluation of Arithmetic Expression requires two steps:


o First, convert the given expression into special notation.

o Evaluate the expression in this new notation.

Notations for Arithmetic Expression


There are three notations to represent an arithmetic expression:
o Infix Notation
o Prefix Notation
277

o Postfix Notation
Infix Notation
The infix notation is a convenient way of writing an expression
in which each operator is placed between the operands. Infix
expressions can be parenthesized or unparenthesized
depending upon the problem requirement.
Example: A + B, (C - D) etc.
All these expressions are in infix notation because the operator
comes between the operands.
Prefix Notation
The prefix notation places the operator before the operands.
This notation was introduced by the Polish mathematician and
hence often referred to as polish notation.
Example: + A B, -CD etc.
All these expressions are in prefix notation because the operator
comes before the operands.
Postfix Notation
The postfix notation places the operator after the operands. This
notation is just the reverse of Polish notation and also known
as Reverse Polish notation.
Example: AB +, CD+, etc.
All these expressions are in postfix notation because the
operator comes after the operands.
278

Conversion of Arithmetic Expression into various


Notations:

Infix Notation Prefix Notation Postfix Notation

A*B *AB AB*

(A+B)/C /+ ABC AB+C/

(A*B) + (D-C) +*AB - DC AB*DC-+

Let's take the example of Converting an infix expression


into a postfix expression.
279

In the above example, the only change from the postfix


expression is that the operator is placed before the operands
rather than between the operands.
Evaluating Postfix expression:
Stack is the ideal data structure to evaluate the postfix
expression because the top element is always the most recent
operand. The next element on the Stack is the second most
recent operand to be operated on.
Before evaluating the postfix expression, the following
conditions must be checked. If any one of the conditions fails,
the postfix expression is invalid.
o When an operator encounters the scanning process, the
Stack must contain a pair of operands or intermediate
results previously calculated.
o When an expression has been completely evaluated, the
Stack must contain exactly one value.
Example:
Now let us consider the following infix expression 2 * (4+3) -
5.
Its equivalent postfix expression is 2 4 3 + * 5.
The following step illustrates how this postfix expression is
evaluated.
280

2. Backtracking
Backtracking is another application of Stack. It is a recursive
algorithm that is used for solving the optimization problem.
3. Delimiter Checking
The common application of Stack is delimiter checking, i.e.,
parsing that involves analyzing a source program syntactically.
It is also called parenthesis checking. When the compiler
translates a source program written in some programming
language such as C, C++ to a machine language, it parses the
program into multiple individual parts such as variable names,
keywords, etc. By scanning from left to right. The main
problem encountered while translating is the unmatched
delimiters. We make use of different types of delimiters include
the parenthesis checking (,), curly braces {,} and square
brackets [,], and common delimiters /* and */. Every opening
delimiter must match a closing delimiter, i.e., every opening
281

parenthesis should be followed by a matching closing


parenthesis. Also, the delimiter can be nested. The opening
delimiter that occurs later in the source program should be
closed before those occurring earlier.

Valid Delimiter Invalid Delimiter

While ( i > 0) While ( i >

/* Data Structure */ /* Data Structure

{ ( a + b) - c } { ( a + b) - c

To perform a delimiter checking, the compiler makes use of a


stack. When a compiler translates a source program, it reads the
characters one at a time, and if it finds an opening delimiter it
places it on a stack. When a closing delimiter is found, it pops
up the opening delimiter from the top of the Stack and matches
it with the closing delimiter.
On matching, the following cases may arise.
o If the delimiters are of the same type, then the match is
considered successful, and the process continues.
o If the delimiters are not of the same type, then the syntax
error is reported.
When the end of the program is reached, and the Stack is
empty, then the processing of the source program stops.
Example: To explain this concept, let's consider the following
expression.
[{a -b) * (c -d)}/f]
282

4. Reverse a Data:
To reverse a given set of data, we need to reorder the data so
that the first and last elements are exchanged, the second and
second last element are exchanged, and so on for all other
elements.
Example: Suppose we have a string Welcome, then on
reversing it would be Emoclew.
There are different reversing applications:
o Reversing a string
o Converting Decimal to Binary
283

Reverse a String
A Stack can be used to reverse the characters of a string. This
can be achieved by simply pushing one by one each character
onto the Stack, which later can be popped from the Stack one
by one. Because of the last in first out property of the Stack,
the first character of the Stack is on the bottom of the Stack and
the last character of the String is on the Top of the Stack and
after performing the pop operation in the Stack, the Stack
returns the String in Reverse order.

Converting Decimal to Binary:


Although decimal numbers are used in most business
applications, some scientific and technical applications require
numbers in either binary, octal, or hexadecimal. A stack can be
used to convert a number from decimal to
binary/octal/hexadecimal form. For converting any decimal
number to a binary number, we repeatedly divide the decimal
number by two and push the remainder of each division onto
the Stack until the number is reduced to 0. Then we pop the
whole Stack and the result obtained is the binary equivalent of
the given decimal number.
284

Example: Converting 14 number Decimal to Binary:

In the above example, on dividing 14 by 2, we get seven as a


quotient and one as the reminder, which is pushed on the Stack.
On again dividing seven by 2, we get three as quotient and 1 as
the reminder, which is again pushed onto the Stack. This
process continues until the given number is not reduced to 0.
When we pop off the Stack completely, we get the equivalent
binary number 1110.
5. Processing Function Calls:
Stack plays an important role in programs that call several
functions in succession. Suppose we have a program containing
three functions: A, B, and C. function A invokes function B,
which invokes the function C.
285

When we invoke function A, which contains a call to function


B, then its processing will not be completed until function B
has completed its execution and returned. Similarly for
function B and C. So we observe that function A will only be
completed after function B is completed and function B will
only be completed after function C is completed. Therefore,
function A is first to be started and last to be completed. To
conclude, the above function activity matches the last in first
out behavior and can easily be handled using Stack.
Consider addrA, addrB, addrC be the addresses of the
statements to which control is returned after completing the
function A, B, and C, respectively.

The above figure shows that return addresses appear in the


Stack in the reverse order in which the functions were called.
After each function is completed, the pop operation is
286

performed, and execution continues at the address removed


from the Stack. Thus the program that calls several functions in
succession can be handled optimally by the stack data structure.
Control returns to each function at a correct place, which is the
reverse order of the calling sequence.

Polish notation:

The name comes from the Polish mathematician/logician


Lukasiewicz, who introduced it. Three types:

 Infix form
 Prefix form
 Postfix form

Infix form
Is exactly the fully parenthesized notation we have just
introduced. Let me remind you once again the Recursive
definition

infix-expression := (infix-expression operand infix-


expression)
infix-expression := atom

Examples
287

(3 * 7)
((1 + 3) * 2)
((1 + 3) * ( 2 - 3))

Main Feature: the binary operator is between the two


operands.

Question: what if we do not put all the parentheses? Then


there are ambiguities on how to interpret an expression: is
1+2*3 the same as (1+2)*3 or the same as 1+(2*3)? The
precedence of operators solves this problem.

Prefix form
Main Feature: the operator preceeds the two operands.

Recursive definition of fully parenthesized version:

prefix-expression := (operand prefix-expression prefix-


expression)
prefix-expression := atom

Recursive definition of classic version, without parentheses


(we do not need them, because there is no longer any
ambiguity on how to match the operands to the operators):

prefix-expression := operand prefix-expression prefix-


expression
288

prefix-expression := atom

Examples

(* 3 7) or simply * 3 7
(* ( + 1 3) 2) or simply * + 1 3 2
( * ( + 1 3) ( - 2 3)) or simply * + 1 3 - 2 3

Postfix form
Main Feature: the operator is after the two
operands. Recursive definition

postfix-expression := (operand postfix-expression postfix-


expression)
postfix-expression := atom

Recursive definition of classic version, without parentheses


(we do not need them, because there is no longer any
ambiguity on how to match the operands to the operators):

postfix-expression := operand postfix-expression postfix-


expression
postfix-expression := atom
289

Examples

(3 7 *) or simply 3 7 *
((1 3 + ) 2 *) or simply 1 3 + 2 *
((1 3 +) ( 2 3 -) * ) or simply 1 3 + 2 3 - *

Recursion:

What is Recursion?

The process in which a function calls itself directly or


indirectly is called recursion and the corresponding function
is called a recursive function. Using a recursive algorithm,
certain problems can be solved quite easily. Examples of
such problems are Towers of Hanoi
(TOH), Inorder/Preorder/Postorder Tree Traversals, DFS of
Graph, etc. A recursive function solves a particular problem
by calling a copy of itself and solving smaller subproblems of
the original problems. Many more recursive calls can be
generated as and when required. It is essential to know that
we should provide a certain case in order to terminate this
recursion process. So we can say that every time the function
calls itself with a simpler version of the original problem.
Need of Recursion
Recursion is an amazing technique with the help of which we
can reduce the length of our code and make it easier to read
and write. It has certain advantages over the iteration
technique which will be discussed later. A task that can be
defined with its similar subtask, recursion is one of the best
solutions for it. For example; The Factorial of a number.
Properties of Recursion:
290

 Performing the same operations multiple times with


different inputs.
 In every step, we try smaller inputs to make the problem

smaller.
 Base condition is needed to stop the recursion otherwise

infinite loop will occur.


A Mathematical Interpretation
Let us consider a problem that a programmer has to
determine the sum of first n natural numbers, there are
several ways of doing that but the simplest approach is
simply to add the numbers starting from 1 to n. So the
function simply looks like this,
approach(1) – Simply adding one by one
f(n) = 1 + 2 + 3 +……..+ n
but there is another mathematical approach of representing
this,
approach(2) – Recursive adding
f(n) = 1 n=1
f(n) = n + f(n-1) n>1
There is a simple difference between the approach (1) and
approach(2) and that is in approach(2) the function “ f( ) ”
itself is being called inside the function, so this phenomenon
is named recursion, and the function containing recursion is
called recursive function, at the end, this is a great tool in the
hand of the programmers to code some problems in a lot
easier and efficient way.
How are recursive functions stored in memory?
Recursion uses more memory, because the recursive function
adds to the stack with each recursive call, and keeps the
values there until the call is finished. The recursive function
uses LIFO (LAST IN FIRST OUT) Structure just like the
stack data structure. https://www.geeksforgeeks.org/stack-
291

data-structure/

What is the base condition in recursion?


In the recursive program, the solution to the base case is
provided and the solution to the bigger problem is expressed
in terms of smaller problems.

int fact(int n)
{
if (n < = 1) // base case
return 1;
else
return n*fact(n-1);
}
In the above example, the base case for n < = 1 is defined and
the larger value of a number can be solved by converting to a
smaller one till the base case is reached.
How a particular problem is solved using recursion?
The idea is to represent a problem in terms of one or more
smaller problems, and add one or more base conditions that
stop the recursion. For example, we compute factorial n if we
know the factorial of (n-1). The base case for factorial would
be n = 0. We return 1 when n = 0.
Why Stack Overflow error occurs in recursion?
If the base case is not reached or not defined, then the stack
overflow problem may arise. Let us take an example to
understand this.
int fact(int n)
{
// wrong base case (it may cause
292

// stack overflow).
if (n == 100)
return 1;

else
return n*fact(n-1);
}
If fact(10) is called, it will call fact(9), fact(8), fact(7), and so
on but the number will never reach 100. So, the base case is
not reached. If the memory is exhausted by these functions
on the stack, it will cause a stack overflow error.
What is the difference between direct and indirect
recursion?
A function fun is called direct recursive if it calls the same
function fun. A function fun is called indirect recursive if it
calls another function say fun_new and fun_new calls fun
directly or indirectly. The difference between direct and
indirect recursion has been illustrated in Table 1.
// An example of direct recursion
void directRecFun()
{
// Some code....

directRecFun();

// Some code...
}

// An example of indirect recursion


void indirectRecFun1()
293

{
// Some code...

indirectRecFun2();

// Some code...
}
void indirectRecFun2()
{
// Some code...

indirectRecFun1();

// Some code...
}
What is the difference between tailed and non-tailed
recursion?
A recursive function is tail recursive when a recursive call is
the last thing executed by the function. Please refer tail
recursion article for details.
How memory is allocated to different function calls in
recursion?
When any function is called from main(), the memory is
allocated to it on the stack. A recursive function calls itself,
the memory for a called function is allocated on top of
memory allocated to the calling function and a different copy
of local variables is created for each function call. When the
base case is reached, the function returns its value to the
function by whom it is called and memory is de-allocated and
the process continues.
Let us take the example of how recursion works by taking a
simple function.

294

// A C++ program to demonstrate working of

// recursion

#include <bits/stdc++.h>

using namespace std;

void printFun(int test)

if (test < 1)

return;

else {

cout << test << " ";

printFun(test - 1); // statement 2

cout << test << " ";

return;

}
295

// Driver Code

int main()

int test = 3;

printFun(test);

Output :
321123
When printFun(3) is called from main(), memory is
allocated to printFun(3) and a local variable test is
initialized to 3 and statement 1 to 4 are pushed on the stack
as shown in below diagram. It first prints ‘3’. In statement
2, printFun(2) is called and memory is allocated
to printFun(2) and a local variable test is initialized to 2 and
statement 1 to 4 are pushed into the stack.
Similarly, printFun(2) calls printFun(1) and printFun(1) c
alls printFun(0). printFun(0) goes to if statement and it
return to printFun(1). The remaining statements
of printFun(1) are executed and it returns
to printFun(2) and so on. In the output, values from 3 to 1
are printed and then 1 to 3 are printed. The memory stack has
been shown in below diagram.
296

Recursion VS Iteration
SR
No. Recursion Iteration

Terminates when the


Terminates when the base condition becomes
1) case becomes true. false.

2) Used with functions. Used with loops.

Every recursive call needs Every iteration does


extra space in the stack not require any extra
3) memory. space.

4) Smaller code size. Larger code size.


297

Now, let’s discuss a few practical problems which can be


solved by using recursion and understand its basic working.
For basic understanding please read the following articles.
Basic understanding of Recursion.
Problem 1: Write a program and recurrence relation to find
the Fibonacci series of n where n>2 .
Mathematical Equation:
n if n == 0, n == 1;
fib(n) = fib(n-1) + fib(n-2) otherwise;
Recurrence Relation:
T(n) = T(n-1) + T(n-2) + O(1)
Recursive program:
Input: n = 5
Output:
Fibonacci series of 5 numbers is : 0 1 1 2 3
Implementation:

// C++ code to implement Fibonacci series

#include <bits/stdc++.h>

using namespace std;

// Function for fibonacci

int fib(int n)
298

// Stop condition

if (n == 0)

return 0;

// Stop condition

if (n == 1 || n == 2)

return 1;

// Recursion function

else

return (fib(n - 1) + fib(n - 2));

// Driver Code

int main()
299

// Initialize variable n.

int n = 5;

cout<<"Fibonacci series of 5 numbers is: ";

// for loop to print the fibonacci series.

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

cout<<fib(i)<<" ";

return 0;

Output
Fibonacci series of 5 numbers is: 0 1 1 2 3
Time Complexity: O(2n)
Auxiliary Space: O(n)
Here is the recursive tree for input 5 which shows a clear
picture of how a big problem can be solved into smaller
ones.
300

fib(n) is a Fibonacci function. The time complexity of the


given program can depend on the function call.
fib(n) -> level CBT (UB) -> 2^n-1 nodes -> 2^n function call
-> 2^n*O(1) -> T(n) = O(2^n)
For Best Case.
T(n) = θ(2^n\2)
Working:

Problem 2: Write a program and recurrence relation to find


the Factorial of n where n>2 .
Mathematical Equation:
1 if n == 0 or n == 1;
f(n) = n*f(n-1) if n> 1;
Recurrence Relation:
T(n) = 1 for n = 0
T(n) = 1 + T(n-1) for n > 0
Recursive Program:
Input: n = 5
Output:
factorial of 5 is: 120
Implementation:
301

// C++ code to implement factorial

#include <bits/stdc++.h>

using namespace std;

// Factorial function

int f(int n)

// Stop condition

if (n == 0 || n == 1)

return 1;

// Recursive condition

else

return n * f(n - 1);

}
302

// Driver code

int main()

int n = 5;

cout<<"factorial of "<<n<<" is: "<<f(n);

return 0;

Output
factorial of 5 is: 120
Time complexity: O(2n).
Auxiliary Space: O(n)
Working:

Diagram of factorial Recursion function for user input 5.


303

What are the disadvantages of recursive programming


over iterative programming?
Note that both recursive and iterative programs have the
same problem-solving powers, i.e., every recursive program
can be written iteratively and vice versa is also true. The
recursive program has greater space requirements than the
iterative program as all functions will remain in the stack
until the base case is reached. It also has greater time
requirements because of function calls and returns overhead.
Moreover, due to the smaller length of code, the codes are
difficult to understand and hence extra care has to be
practiced while writing the code. The computer may run out
of memory if the recursive calls are not properly checked.
What are the advantages of recursive programming over
iterative programming?
Recursion provides a clean and simple way to write code.
Some problems are inherently recursive like tree
traversals, Tower of Hanoi, etc. For such problems, it is
preferred to write recursive code. We can write such codes
also iteratively with the help of a stack data structure. For
example refer Inorder Tree Traversal without
Recursion, Iterative Tower of Hanoi.
304

CHAPTER-6
QUEUES

Introduction of Queue:

What is Queue?
A queue is defined as a linear data structure that is open at
both ends and the operations are performed in First In First
Out (FIFO) order.
We define a queue to be a list in which all additions to the list
are made at one end, and all deletions from the list are made
at the other end. The element which is first pushed into the
order, the operation is first performed on that.

FIFO Principle of Queue:


 A Queue is like a line waiting to purchase tickets, where

the first person in line is the first person served. (i.e. First
come first serve).
 Position of the entry in a queue ready to be served, that is,

the first entry that will be removed from the queue, is


called the front of the queue(sometimes, head of the
queue), similarly, the position of the last entry in the
305

queue, that is, the one most recently added, is called


the rear (or the tail) of the queue. See the below figure.

FIFO property of queue

Characteristics of Queue:
 Queue can handle multiple data.

 We can access both ends.

 They are fast and flexible.

Queue Representation:

Like stacks, Queues can also be represented in an array: In this


representation, the Queue is implemented using the array.
Variables used in this case are
 Queue: the name of the array storing queue elements.
 Front: the index where the first element is stored in the
array representing the queue.
 Rear: the index where the last element is stored in an
array representing the queue.
306

Array and linked representation of queues:

Array Representation of Queue:

We can easily represent queue by using linear arrays. There


are two variables i.e. front and rear, that are implemented in
the case of every queue. Front and rear variables point to the
position from where insertions and deletions are performed in
a queue. Initially, the value of front and queue is -1 which
represents an empty queue. Array representation of a queue
containing 5 elements along with the respective values of
front and rear, is shown in the following figure.

The above figure shows the queue of characters forming the


English word "HELLO". Since, No deletion is performed in
the queue till now, therefore the value of front remains -1 .
However, the value of rear increases by one every time an
insertion is performed in the queue. After inserting an element
into the queue shown in the above figure, the queue will look
307

something like following. The value of rear will become 5


while the value of front remains same.

After deleting an element, the value of front will increase


from -1 to 0. however, the queue will look something like
following.
308

Algorithm to insert any element in a queue


Check if the queue is already full by comparing rear to max -
1. if so, then return an overflow error.
If the item is to be inserted as the first element in the list, in
that case set the value of front and rear to 0 and insert the
element at the rear end.
Otherwise keep increasing the value of rear and insert each
element one by one having rear as the index.
Algorithm
o Step 1: IF REAR = MAX - 1
Write OVERFLOW
Go to step
[END OF IF]
o Step 2: IF FRONT = -1 and REAR = -1
SET FRONT = REAR = 0
ELSE
SET REAR = REAR + 1
[END OF IF]
o Step 3: Set QUEUE[REAR] = NUM
o Step 4: EXIT
C Function
1. void insert (int queue[], int max, int front, int rear, int item)

2. {
3. if (rear + 1 == max)
4. {
5. printf("overflow");
6. }
309

7. else
8. {
9. if(front == -1 && rear == -1)
10. {
11. front = 0;
12. rear = 0;
13. }
14. else
15. {
16. rear = rear + 1;
17. }
18. queue[rear]=item;
19. }
20. }
Algorithm to delete an element from the queue
If, the value of front is -1 or value of front is greater than rear
, write an underflow message and exit.
Otherwise, keep increasing the value of front and return the
item stored at the front end of the queue at each time.
Algorithm
o Step 1: IF FRONT = -1 or FRONT > REAR
Write UNDERFLOW
ELSE
SET VAL = QUEUE[FRONT]
SET FRONT = FRONT + 1
[END OF IF]
o Step 2: EXIT
C Function
310

1. int delete (int queue[], int max, int front, int rear)
2. {
3. int y;
4. if (front == -1 || front > rear)
5.
6. {
7. printf("underflow");
8. }
9. else
10. {
11. y = queue[front];
12. if(front == rear)
13. {
14. front = rear = -1;
15. else
16. front = front + 1;
17.
18. }
19. return y;
20. }
21. }
Menu driven program to implement queue using array
1. #include<stdio.h>
2. #include<stdlib.h>
3. #define maxsize 5
4. void insert();
5. void delete();
6. void display();
7. int front = -1, rear = -1;
8. int queue[maxsize];
9. void main ()
10. {
311

11. int choice;


12. while(choice != 4)
13. {
14. printf("\n*************************Main Menu
*****************************\n");
15. printf("\n==============================
===================================\n");
16. printf("\n1.insert an element\n2.Delete an element\n
3.Display the queue\n4.Exit\n");
17. printf("\nEnter your choice ?");
18. scanf("%d",&choice);
19. switch(choice)
20. {
21. case 1:
22. insert();
23. break;
24. case 2:
25. delete();
26. break;
27. case 3:
28. display();
29. break;
30. case 4:
31. exit(0);
32. break;
33. default:
34. printf("\nEnter valid choice??\n");
35. }
36. }
37. }
38. void insert()
39. {
40. int item;
312

41. printf("\nEnter the element\n");


42. scanf("\n%d",&item);
43. if(rear == maxsize-1)
44. {
45. printf("\nOVERFLOW\n");
46. return;
47. }
48. if(front == -1 && rear == -1)
49. {
50. front = 0;
51. rear = 0;
52. }
53. else
54. {
55. rear = rear+1;
56. }
57. queue[rear] = item;
58. printf("\nValue inserted ");
59.
60. }
61. void delete()
62. {
63. int item;
64. if (front == -1 || front > rear)
65. {
66. printf("\nUNDERFLOW\n");
67. return;
68.
69. }
70. else
71. {
72. item = queue[front];
73. if(front == rear)
313

74. {
75. front = -1;
76. rear = -1 ;
77. }
78. else
79. {
80. front = front + 1;
81. }
82. printf("\nvalue deleted ");
83. }
84.
85.
86. }
87.
88. void display()
89. {
90. int i;
91. if(rear == -1)
92. {
93. printf("\nEmpty queue\n");
94. }
95. else
96. { printf("\nprinting values .....\n");
97. for(i=front;i<=rear;i++)
98. {
99. printf("\n%d\n",queue[i]);
100. }
101. }
102. }
Output:
*************Main Menu**************
314

============================================
==

1.insert an element
2.Delete an element
3.Display the queue
4.Exit

Enter your choice ?1

Enter the element


123

Value inserted

*************Main Menu**************

============================================
==

1.insert an element
2.Delete an element
3.Display the queue
4.Exit

Enter your choice ?1

Enter the element


90

Value inserted

*************Main Menu**************
315

===================================

1.insert an element
2.Delete an element
3.Display the queue
4.Exit

Enter your choice ?2

value deleted

*************Main Menu**************
============================================
==

1.insert an element
2.Delete an element
3.Display the queue
4.Exit

Enter your choice ?3

printing values .....

90

*************Main Menu**************

============================================
==

1.insert an element
316

2.Delete an element
3.Display the queue
4.Exit

Enter your choice ?4


Drawback of array implementation
Although, the technique of creating a queue is easy, but there
are some drawbacks of using this technique to implement a
queue.
o Memory wastage : The space of the array, which is used
to store queue elements, can never be reused to store the
elements of that queue because the elements can only be
inserted at front end and the value of front might be so
high so that, all the space before that, can never be filled.

The above figure shows how the memory space is wasted in


the array representation of queue. In the above figure, a queue
of size 10 having 3 elements, is shown. The value of the front
variable is 5, therefore, we can not reinsert the values in the
place of already deleted element before the position of front.
That much space of the array is wasted and can not be used in
the future (for this queue).
317

o Deciding the array size


On of the most common problem with array implementation
is the size of the array which requires to be declared in
advance. Due to the fact that, the queue can be extended at
runtime depending upon the problem, the extension in the
array size is a time taking process and almost impossible to be
performed at runtime since a lot of reallocations take place.
Due to this reason, we can declare the array large enough so
that we can store queue elements as enough as possible but
the main problem with this declaration is that, most of the
array slots (nearly half) can never be reused. It will again lead
to memory wastage.

Linked Representation of Queue:


Due to the drawbacks discussed in the previous section of this
tutorial, the array implementation can not be used for the large
scale applications where the queues are implemented. One of
the alternative of array implementation is linked list
implementation of queue.
The storage requirement of linked representation of a queue
with n elements is o(n) while the time requirement for
operations is o(1).
In a linked queue, each node of the queue consists of two parts
i.e. data part and the link part. Each element of the queue points
to its immediate next element in the memory.
In the linked queue, there are two pointers maintained in the
memory i.e. front pointer and rear pointer. The front pointer
318

contains the address of the starting element of the queue while


the rear pointer contains the address of the last element of the
queue.
Insertion and deletions are performed at rear and front end
respectively. If front and rear both are NULL, it indicates that
the queue is empty.
The linked representation of queue is shown in the following
figure.

Operation on Linked Queue


There are two basic operations which can be implemented on
the linked queues. The operations are Insertion and Deletion.
Insert operation
The insert operation append the queue by adding an element to
the end of the queue. The new element will be the last element
of the queue.
Firstly, allocate the memory for the new node ptr by using the
following statement.
1. Ptr = (struct node *) malloc (sizeof(struct node));
319

There can be the two scenario of inserting this new node ptr
into the linked queue.
In the first scenario, we insert element into an empty queue. In
this case, the condition front = NULL becomes true. Now, the
new element will be added as the only element of the queue and
the next pointer of front and rear pointer both, will point to
NULL.
1. ptr -> data = item;
2. if(front == NULL)
3. {
4. front = ptr;
5. rear = ptr;
6. front -> next = NULL;
7. rear -> next = NULL;
8. }
In the second case, the queue contains more than one element.
The condition front = NULL becomes false. In this scenario,
we need to update the end pointer rear so that the next pointer
of rear will point to the new node ptr. Since, this is a linked
queue, hence we also need to make the rear pointer point to the
newly added node ptr. We also need to make the next pointer
of rear point to NULL.
1. rear -> next = ptr;
2. rear = ptr;
3. rear->next = NULL;
In this way, the element is inserted into the queue. The
algorithm and the C implementation is given as follows.
320

Algorithm
o Step 1: Allocate the space for the new node PTR
o Step 2: SET PTR -> DATA = VAL
o Step 3: IF FRONT = NULL
SET FRONT = REAR = PTR
SET FRONT -> NEXT = REAR -> NEXT = NULL
ELSE
SET REAR -> NEXT = PTR
SET REAR = PTR
SET REAR -> NEXT = NULL
[END OF IF]
o Step 4: END
C Function
1. void insert(struct node *ptr, int item; )
2. {
3.
4.
5. ptr = (struct node *) malloc (sizeof(struct node));
6. if(ptr == NULL)
7. {
8. printf("\nOVERFLOW\n");
9. return;
10. }
11. else
12. {
13. ptr -> data = item;
14. if(front == NULL)
15. {
16. front = ptr;
17. rear = ptr;
18. front -> next = NULL;
321

19. rear -> next = NULL;


20. }
21. else
22. {
23. rear -> next = ptr;
24. rear = ptr;
25. rear->next = NULL;
26. }
27. }
28. }
Deletion
Deletion operation removes the element that is first inserted
among all the queue elements. Firstly, we need to check either
the list is empty or not. The condition front == NULL becomes
true if the list is empty, in this case , we simply write underflow
on the console and make exit.
Otherwise, we will delete the element that is pointed by the
pointer front. For this purpose, copy the node pointed by the
front pointer into the pointer ptr. Now, shift the front pointer,
point to its next node and free the node pointed by the node ptr.
This is done by using the following statements.
1. ptr = front;
2. front = front -> next;
3. free(ptr);
The algorithm and C function is given as follows.
Algorithm
o Step 1: IF FRONT = NULL
Write " Underflow "
322

Go to Step 5
[END OF IF]
o Step 2: SET PTR = FRONT
o Step 3: SET FRONT = FRONT -> NEXT
o Step 4: FREE PTR
o Step 5: END
C Function
1. void delete (struct node *ptr)
2. {
3. if(front == NULL)
4. {
5. printf("\nUNDERFLOW\n");
6. return;
7. }
8. else
9. {
10. ptr = front;
11. front = front -> next;
12. free(ptr);
13. }
14. }
Menu-Driven Program implementing all the operations on
Linked Queue
1. #include<stdio.h>
2. #include<stdlib.h>
3. struct node
4. {
5. int data;
6. struct node *next;
7. };
323

8. struct node *front;


9. struct node *rear;
10. void insert();
11. void delete();
12. void display();
13. void main ()
14. {
15. int choice;
16. while(choice != 4)
17. {
18. printf("\n*************************Main Menu
*****************************\n");
19. printf("\n==============================
===================================\n");
20. printf("\n1.insert an element\n2.Delete an element\n
3.Display the queue\n4.Exit\n");
21. printf("\nEnter your choice ?");
22. scanf("%d",& choice);
23. switch(choice)
24. {
25. case 1:
26. insert();
27. break;
28. case 2:
29. delete();
30. break;
31. case 3:
32. display();
33. break;
34. case 4:
35. exit(0);
36. break;
37. default:
324

38. printf("\nEnter valid choice??\n");


39. }
40. }
41. }
42. void insert()
43. {
44. struct node *ptr;
45. int item;
46.
47. ptr = (struct node *) malloc (sizeof(struct node));
48. if(ptr == NULL)
49. {
50. printf("\nOVERFLOW\n");
51. return;
52. }
53. else
54. {
55. printf("\nEnter value?\n");
56. scanf("%d",&item);
57. ptr -> data = item;
58. if(front == NULL)
59. {
60. front = ptr;
61. rear = ptr;
62. front -> next = NULL;
63. rear -> next = NULL;
64. }
65. else
66. {
67. rear -> next = ptr;
68. rear = ptr;
69. rear->next = NULL;
70. }
325

71. }
72. }
73. void delete ()
74. {
75. struct node *ptr;
76. if(front == NULL)
77. {
78. printf("\nUNDERFLOW\n");
79. return;
80. }
81. else
82. {
83. ptr = front;
84. front = front -> next;
85. free(ptr);
86. }
87. }
88. void display()
89. {
90. struct node *ptr;
91. ptr = front;
92. if(front == NULL)
93. {
94. printf("\nEmpty queue\n");
95. }
96. else
97. { printf("\nprinting values .....\n");
98. while(ptr != NULL)
99. {
100. printf("\n%d\n",ptr -> data);
101. ptr = ptr -> next;
102. }
103. }
326

104. }

Operations on queues:

Different operations in Queue Data Structure:


The various operations that are supported by a queue data
structure that helps the user to modify and manipulate the data
present in the queue are:
o Enqueue operation: The term "enqueue" refers to the act
of adding a new element to a queue. Where does a new
individual go and wait in a standard queue at a ticket
counter to join the queue? The individual walks to the
back of the room and takes a seat. A new element in a
queue is similarly added at the end of the queue.
o Dequeue operation: Dequeue is the process of deleting
an item from a queue. We must delete the queue member
that was put first since the queue follows the FIFO
principle. We'll delete the front element and make the
element behind it the new front element because the
element added initially will naturally be at the head of the
queue.
o Front Operation: This works similarly to the peek
operation in stacks in that it returns the value of the first
element without deleting it.
o isEmpty Operation: The isEmpty() function is used to
check if the Queue is empty or not.
o isFull Operation: The isFull() function is used to check
if the Queue is full or not.
327

After seeing all the different valid operations for the queue data
structure, let us write a code to implement all of these
operations as functions in the desired programming language.
Let us begin with writing a basic program in Java programming
language that will have the functions to simulate all the
operations discussed above on the queue data
C Code:
1. // Queue implementation in C
2. // Queue implementation in C
3.
4. #include <stdio.h>
5. #define SIZE_OF_QUEUE 7
6.
7. void enQueue(int);
8. void deQueue();
9. void display();
10.
11. int array_of_Queue[SIZE_OF_QUEUE], front_index = -
1, rear_index = -1;
12.
13. int main() {
14.
15. int data;
16. char ch;
17. /* Perform tree operations */
18. do
19. {
20. printf("\nSelect one of the operations::");
21. printf("\n1. To insert data in the Queue Data Stru
cture.");
22. printf("\n2. To display the data present in the Que
ue Data Structure.");
328

23. printf("\n3. To perform the deQueue operation on


the Queue Data Structure.\n");
24.
25. int choice;
26. scanf("%d",&choice);
27. switch (choice)
28. {
29. case 1 :
30. printf("\nEnter the value to be inserted\n");
31. scanf("%d",&data);
32. enQueue(data);
33. break;
34. case 2 :
35. printf("\nContents of the Queue are::\n");
36. display();
37. break;
38. case 3 :
39. printf("\nDequeue Done.\n");
40. deQueue();
41. break;
42. default :
43. printf("Wrong Entry\n");
44. break;
45. }
46.
47. printf("\nDo you want to continue (Type y or n)\n
");
48. scanf(" %c",&ch);
49. } while (ch == 'Y'|| ch == 'y');
50. return 0;
51. }
52.
53. void enQueue(int value) {
329

54. if (rear_index == SIZE_OF_QUEUE - 1)


55. printf("\nQueue is Full!!");
56. else {
57. if (front_index == -1)
58. front_index = 0;
59. rear_index++;
60. array_of_Queue[rear_index] = value;
61. printf("\nInserted -> %d", value);
62. }
63. }
64.
65. void deQueue() {
66. if (front_index == -1)
67. printf("\nQueue is Empty!!");
68. else {
69. printf("\nDeleted : %d", array_of_Queue[front_index])
;
70. front_index++;
71. if (front_index > rear_index)
72. front_index = rear_index = -1;
73. }
74. }
75.
76. // Function to print the queue
77. void display() {
78. if (rear_index == -1)
79. printf("\nQueue is Empty!!!");
80. else {
81. int i;
82. printf("\nQueue elements are:\n");
83. for (i = front_index; i <= rear_index; i++)
84. printf("%d ", array_of_Queue[i]);
85. }
330

86. printf("\n");
87. }
Output:
Select any one of the operations::
1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


23

Inserted -> 23
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


22

Inserted -> 22
Do you want to continue (Type y or n)
y

Select any one of the operations::


331

1. To insert data in the Queue Data Structure.


2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


87

Inserted -> 87
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


12

Inserted -> 12
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1
332

Integer value for the Queue


90

Inserted -> 90
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


78

Inserted -> 78
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


56

Inserted -> 56
333

Do you want to continue (Type y or n)


y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
1

Integer value for the Queue


45

Queue is Full!!
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
2

Contents of the Queue are::

Queue elements are:


23 22 87 12 90 78 56

Do you want to continue (Type y or n)


y

Select any one of the operations::


334

1. To insert data in the Queue Data Structure.


2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
3

Dequeue Done.

Deleted : 23
Do you want to continue (Type y or n)
y

Select any one of the operations::


1. To insert data in the Queue Data Structure.
2. To display the data present in the Queue Data Structure.
3. To perform the deQueue operation on the Queue Data
Structure.
2

Contents of the Queue are::

Queue elements are:


22 87 12 90 78 56

Do you want to continue (Type y or n)


n
So, in this article, we understood the Queue data structure and
various operations that we can do on the Queue data structure
to modify and manipulate the data present in the Queue. And
in the latter section of the article, we also have written code in
three different programming languages (C, C++, and Java)
where each operation of the Queue Data structure is
implemented as an individual function that is called whenever
335

we need to perform that particular operation on the data in the


Queue.

Basic Operations for Queue in Data Structure

Unlike arrays and linked lists, elements in the queue cannot be


operated from their respective locations. They can only be
operated at two data pointers, front and rear. Also, these
operations involve standard procedures like initializing or
defining data structure, utilizing it, and then wholly erasing it
from memory. Here, you must try to comprehend the
operations associated with queues:

 Enqueue() - Insertion of elements to the queue.


 Dequeue() - Removal of elements from the queue.
 Peek() - Acquires the data element available at the front
node of the queue without deleting it.
 isFull() - Validates if the queue is full.
 isNull() - Checks if the queue is empty.
When you define the queue data structure, it remains empty as
no element is inserted into it. So, both the front and rear
pointer should be set to -1 (Null memory space). This phase is
known as data structure declaration in the context of
programming.

First, understand the operations that allow the queue to


manipulate data elements in a hierarchy.
336

Enqueue() Operation

The following steps should be followed to insert (enqueue)


data element into a queue -

 Step 1: Check if the queue is full.


 Step 2: If the queue is full, Overflow error.
 Step 3: If the queue is not full, increment the rear pointer to
point to the next available empty space.
 Step 4: Add the data element to the queue location where
the rear is pointing.
 Step 5: Here, you have successfully added 7, 2, and -9.
337

Dequeue() Operation

Obtaining data from the queue comprises two subtasks: access


the data where the front is pointing and remove the data after
access. You should take the following steps to remove data
from the queue -

 Step 1: Check if the queue is empty.


 Step 2: If the queue is empty, Underflow error.
 Step 3: If the queue is not empty, access the data where the
front pointer is pointing.
 Step 4: Increment front pointer to point to the next available
data element.
 Step 5: Here, you have removed 7, 2, and -9 from the queue
data structure.
338

Now that you have dealt with the operations that allow
manipulation of data entities, you will encounter supportive
functions of the queues -

Peek() Operation

This function helps in extracting the data element where the


front is pointing without removing it from the queue. The
algorithm of Peek() function is as follows-

 Step 1: Check if the queue is empty.


 Step 2: If the queue is empty, return “Queue is Empty.”
 Step 3: If the queue is not empty, access the data where the
front pointer is pointing.
 Step 4: Return data.

isFull() Operation

This function checks if the rear pointer is reached at


MAXSIZE to determine that the queue is full. The following
steps are performed in the isFull() operation -

 Step 1: Check if rear == MAXSIZE - 1.


 Step 2: If they are equal, return “Queue is Full.”
 Step 3: If they are not equal, return “Queue is not Full.”

isNull() Operation

The algorithm of the isNull() operation is as follows -


339

 Step 1: Check if the rear and front are pointing to null


memory space, i.e., -1.
 Step 2: If they are pointing to -1, return “Queue is empty.”
 Step 3: If they are not equal, return “Queue is not empty.”
Deques:
The deque stands for Double Ended Queue. Deque is a linear
data structure where the insertion and deletion operations are
performed from both ends. We can say that deque is a
generalized version of the queue.
Though the insertion and deletion in a deque can be performed
on both ends, it does not follow the FIFO rule. The
representation of a deque is given as follows -

Types of deque
There are two types of deque -
o Input restricted queue
o Output restricted queue
Input restricted Queue
In input restricted queue, insertion operation can be performed
at only one end, while deletion can be performed from both
ends.
340

Output restricted Queue


In output restricted queue, deletion operation can be performed
at only one end, while insertion can be performed from both
ends.

Operations performed on deque


There are the following operations that can be applied on a
deque -
o Insertion at front
o Insertion at rear
o Deletion at front
o Deletion at rear
We can also perform peek operations in the deque along with
the operations listed above. Through peek operation, we can get
the deque's front and rear elements of the deque. So, in addition
341

to the above operations, following operations are also


supported in deque -
o Get the front item from the deque
o Get the rear item from the deque
o Check whether the deque is full or not
o Checks whether the deque is empty or not
Now, let's understand the operation performed on deque using
an example.
Insertion at the front end
In this operation, the element is inserted from the front end of
the queue. Before implementing the operation, we first have to
check whether the queue is full or not. If the queue is not full,
then the element can be inserted from the front end by using the
below conditions -
o If the queue is empty, both rear and front are initialized
with 0. Now, both will point to the first element.
o Otherwise, check the position of the front if the front is
less than 1 (front < 1), then reinitialize it by front = n - 1,
i.e., the last index of the array.
342

Insertion at the rear end


In this operation, the element is inserted from the rear end of
the queue. Before implementing the operation, we first have to
check again whether the queue is full or not. If the queue is not
full, then the element can be inserted from the rear end by using
the below conditions -
o If the queue is empty, both rear and front are initialized
with 0. Now, both will point to the first element.
o Otherwise, increment the rear by 1. If the rear is at last
index (or size - 1), then instead of increasing it by 1, we
have to make it equal to 0.
343

Deletion at the front end


In this operation, the element is deleted from the front end of
the queue. Before implementing the operation, we first have to
check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow
condition, and we cannot perform the deletion. If the queue is
not full, then the element can be inserted from the front end by
using the below conditions -
If the deque has only one element, set rear = -1 and front = -1.
Else if front is at end (that means front = size - 1), set front = 0.
Else increment the front by 1, (i.e., front = front + 1).
344

Deletion at the rear end


In this operation, the element is deleted from the rear end of the
queue. Before implementing the operation, we first have to
check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow
condition, and we cannot perform the deletion.
If the deque has only one element, set rear = -1 and front = -1.
If rear = 0 (rear is at front), then set rear = n - 1.
Else, decrement the rear by 1 (or, rear = rear -1).
345

Check empty
This operation is performed to check whether the deque is
empty or not. If front = -1, it means that the deque is empty.
Check full
This operation is performed to check whether the deque is full
or not. If front = rear + 1, or front = 0 and rear = n - 1 it means
that the deque is full.
The time complexity of all of the above operations of the deque
is O(1), i.e., constant.
Applications of deque
o Deque can be used as both stack and queue, as it supports
both operations.
o Deque can be used as a palindrome checker means that if
we read the string from both ends, the string would be the
same.
Implementation of deque
Now, let's see the implementation of deque in C programming
language.
1. #include <stdio.h>
2. #define size 5
3. int deque[size];
4. int f = -1, r = -1;
5. // insert_front function will insert the value from the front
6. void insert_front(int x)
7. {
8. if((f==0 && r==size-1) || (f==r+1))
9. {
346

10. printf("Overflow");
11. }
12. else if((f==-1) && (r==-1))
13. {
14. f=r=0;
15. deque[f]=x;
16. }
17. else if(f==0)
18. {
19. f=size-1;
20. deque[f]=x;
21. }
22. else
23. {
24. f=f-1;
25. deque[f]=x;
26. }
27. }
28.
29. // insert_rear function will insert the value from the rear

30. void insert_rear(int x)


31. {
32. if((f==0 && r==size-1) || (f==r+1))
33. {
34. printf("Overflow");
35. }
36. else if((f==-1) && (r==-1))
37. {
38. r=0;
39. deque[r]=x;
40. }
41. else if(r==size-1)
347

42. {
43. r=0;
44. deque[r]=x;
45. }
46. else
47. {
48. r++;
49. deque[r]=x;
50. }
51.
52. }
53.
54. // display function prints all the value of deque.
55. void display()
56. {
57. int i=f;
58. printf("\nElements in a deque are: ");
59.
60. while(i!=r)
61. {
62. printf("%d ",deque[i]);
63. i=(i+1)%size;
64. }
65. printf("%d",deque[r]);
66. }
67.
68. // getfront function retrieves the first value of the deque.

69. void getfront()


70. {
71. if((f==-1) && (r==-1))
72. {
73. printf("Deque is empty");
348

74. }
75. else
76. {
77. printf("\nThe value of the element at front is: %d", d
eque[f]);
78. }
79.
80. }
81.
82. // getrear function retrieves the last value of the deque.
83. void getrear()
84. {
85. if((f==-1) && (r==-1))
86. {
87. printf("Deque is empty");
88. }
89. else
90. {
91. printf("\nThe value of the element at rear is %d", de
que[r]);
92. }
93.
94. }
95.
96. // delete_front() function deletes the element from the fro
nt
97. void delete_front()
98. {
99. if((f==-1) && (r==-1))
100. {
101. printf("Deque is empty");
102. }
103. else if(f==r)
349

104. {
105. printf("\nThe deleted element is %d", deque[f]);
106. f=-1;
107. r=-1;
108.
109. }
110. else if(f==(size-1))
111. {
112. printf("\nThe deleted element is %d", deque[f]);
113. f=0;
114. }
115. else
116. {
117. printf("\nThe deleted element is %d", deque[f]);
118. f=f+1;
119. }
120. }
121.
122. // delete_rear() function deletes the element from the rear

123. void delete_rear()


124. {
125. if((f==-1) && (r==-1))
126. {
127. printf("Deque is empty");
128. }
129. else if(f==r)
130. {
131. printf("\nThe deleted element is %d", deque[r]);
132. f=-1;
133. r=-1;
134.
135. }
350

136. else if(r==0)


137. {
138. printf("\nThe deleted element is %d", deque[r]);
139. r=size-1;
140. }
141. else
142. {
143. printf("\nThe deleted element is %d", deque[r]);
144. r=r-1;
145. }
146. }
147.
148. int main()
149. {
150. insert_front(20);
151. insert_front(10);
152. insert_rear(30);
153. insert_rear(50);
154. insert_rear(80);
155. display(); // Calling the display function to retrieve th
e values of deque
156. getfront(); // Retrieve the value at front-end
157. getrear(); // Retrieve the value at rear-end
158. delete_front();
159. delete_rear();
160. display(); // calling display function to retrieve values
after deletion
161. return 0;
162. }
Output:
351

Priority Queues:

A priority queue is an abstract data type that behaves similarly


to the normal queue except that each element has some priority,
i.e., the element with the highest priority would come first in a
priority queue. The priority of the elements in a priority queue
will determine the order in which elements are removed from
the priority queue.
The priority queue supports only comparable elements, which
means that the elements are either arranged in an ascending or
descending order.
For example, suppose we have some values like 1, 3, 4, 8, 14,
22 inserted in a priority queue with an ordering imposed on the
values is from least to the greatest. Therefore, the 1 number
would be having the highest priority while 22 will be having
the lowest priority.
Characteristics of a Priority queue
A priority queue is an extension of a queue that contains the
following characteristics:
1
o Every element in a priority queue has some priority
associated with it.
352

o An element with the higher priority will be deleted before


the deletion of the lesser priority.
o If two elements in a priority queue have the same priority,
they will be arranged using the FIFO principle.
Let's understand the priority queue through an example.
We have a priority queue that contains the following values:
1, 3, 4, 8, 14, 22
All the values are arranged in ascending order. Now, we will
observe how the priority queue will look after performing the
following operations:
o poll(): This function will remove the highest priority
element from the priority queue. In the above priority
queue, the '1' element has the highest priority, so it will be
removed from the priority queue.
o add(2): This function will insert '2' element in a priority
queue. As 2 is the smallest element among all the numbers
so it will obtain the highest priority.
o poll(): It will remove '2' element from the priority queue
as it has the highest priority queue.
o add(5): It will insert 5 element after 4 as 5 is larger than 4
and lesser than 8, so it will obtain the third highest priority
in a priority queue.
Types of Priority Queue
There are two types of priority queue:
o Ascending order priority queue: In ascending order
priority queue, a lower priority number is given as a higher
priority in a priority. For example, we take the numbers
353

from 1 to 5 arranged in an ascending order like 1,2,3,4,5;


therefore, the smallest number, i.e., 1 is given as the
highest priority in a priority queue.

o Descending order priority queue: In descending order


priority queue, a higher priority number is given as a
higher priority in a priority. For example, we take the
numbers from 1 to 5 arranged in descending order like 5,
4, 3, 2, 1; therefore, the largest number, i.e., 5 is given as
the highest priority in a priority queue.
354

Representation of priority queue


Now, we will see how to represent the priority queue through a
one-way list.
We will create the priority queue by using the list given below
in which INFO list contains the data elements, PRN list
contains the priority numbers of each data element available in
the INFO list, and LINK basically contains the address of the
next node.

Let's create the priority queue step by step.


In the case of priority queue, lower priority number is
considered the higher priority, i.e., lower priority number =
higher priority.
Step 1: In the list, lower priority number is 1, whose data value
is 333, so it will be inserted in the list as shown in the below
diagram:
355

Step 2: After inserting 333, priority number 2 is having a


higher priority, and data values associated with this priority are
222 and 111. So, this data will be inserted based on the FIFO
principle; therefore 222 will be added first and then 111.
Step 3: After inserting the elements of priority 2, the next
higher priority number is 4 and data elements associated with 4
priority numbers are 444, 555, 777. In this case, elements
would be inserted based on the FIFO principle; therefore, 444
will be added first, then 555, and then 777.
Step 4: After inserting the elements of priority 4, the next
higher priority number is 5, and the value associated with
priority 5 is 666, so it will be inserted at the end of the queue.

Implementation of Priority Queue


The priority queue can be implemented in four ways that
include arrays, linked list, heap data structure and binary search
tree. The heap data structure is the most efficient way of
implementing the priority queue, so we will implement the
priority queue using a heap data structure in this topic. Now,
first we understand the reason why heap is the most efficient
way among all the other data structures.
Analysis of complexities using different implementations

Implementation add Remove peek


356

Linked list O(1) O(n) O(n)

Binary heap O(logn) O(logn) O(1)

Binary search tree O(logn) O(logn) O(1)

What is Heap?
A heap is a tree-based data structure that forms a complete
binary tree, and satisfies the heap property. If A is a parent node
of B, then A is ordered with respect to the node B for all nodes
A and B in a heap. It means that the value of the parent node
could be more than or equal to the value of the child node, or
the value of the parent node could be less than or equal to the
value of the child node. Therefore, we can say that there are
two types of heaps:
o Max heap: The max heap is a heap in which the value of
the parent node is greater than the value of the child nodes.
357

o Min heap: The min heap is a heap in which the value of


the parent node is less than the value of the child nodes.

Both the heaps are the binary heap, as each has exactly two
child nodes.
Priority Queue Operations
The common operations that we can perform on a priority
queue are insertion, deletion and peek. Let's see how we can
maintain the heap data structure.
o Inserting the element in a priority queue (max heap)
If we insert an element in a priority queue, it will move to the
empty slot by looking from top to bottom and left to right.
If the element is not in a correct place then it is compared with
the parent node; if it is found out of order, elements are
swapped. This process continues until the element is placed in
a correct position.
358

o Removing the minimum element from the priority


queue
As we know that in a max heap, the maximum element is the
root node. When we remove the root node, it creates an empty
slot. The last inserted element will be added in this empty slot.
Then, this element is compared with the child nodes, i.e., left-
359

child and right child, and swap with the smaller of the two. It
keeps moving down the tree until the heap property is restored.
Applications of Priority queue
The following are the applications of the priority queue:
o It is used in the Dijkstra's shortest path algorithm.
o It is used in prim's algorithm
o It is used in data compression techniques like Huffman
code.
o It is used in heap sort.
o It is also used in operating system like priority scheduling,
load balancing and interrupt handling.
Program to create the priority queue using the binary max
heap.

1. #include <stdio.h>
2. #include <stdio.h>
3. int heap[40];
4. int size=-1;
5.
6. // retrieving the parent node of the child node
7. int parent(int i)
8. {
9.
10. return (i - 1) / 2;
11. }
12.
13. // retrieving the left child of the parent node.
360

14. int left_child(int i)


15. {
16. return i+1;
17. }
18. // retrieving the right child of the parent
19. int right_child(int i)
20. {
21. return i+2;
22. }
23. // Returning the element having the highest priority
24. int get_Max()
25. {
26. return heap[0];
27. }
28. //Returning the element having the minimum priority
29. int get_Min()
30. {
31. return heap[size];
32. }
33. // function to move the node up the tree in order to restor
e the heap property.
34. void moveUp(int i)
35. {
36. while (i > 0)
37. {
38. // swapping parent node with a child node
39. if(heap[parent(i)] < heap[i]) {
40.
41. int temp;
42. temp=heap[parent(i)];
43. heap[parent(i)]=heap[i];
44. heap[i]=temp;
45.
361

46.
47. }
48. // updating the value of i to i/2
49. i=i/2;
50. }
51. }
52.
53. //function to move the node down the tree in order to rest
ore the heap property.
54. void moveDown(int k)
55. {
56. int index = k;
57.
58. // getting the location of the Left Child
59. int left = left_child(k);
60.
61. if (left <= size && heap[left] > heap[index]) {
62. index = left;
63. }
64.
65. // getting the location of the Right Child
66. int right = right_child(k);
67.
68. if (right <= size && heap[right] > heap[index]) {
69. index = right;
70. }
71.
72. // If k is not equal to index
73. if (k != index) {
74. int temp;
75. temp=heap[index];
76. heap[index]=heap[k];
77. heap[k]=temp;
362

78. moveDown(index);
79. }
80. }
81.
82. // Removing the element of maximum priority
83. void removeMax()
84. {
85. int r= heap[0];
86. heap[0]=heap[size];
87. size=size-1;
88. moveDown(0);
89. }
90. //inserting the element in a priority queue
91. void insert(int p)
92. {
93. size = size + 1;
94. heap[size] = p;
95.
96. // move Up to maintain heap property
97. moveUp(size);
98. }
99.
100. //Removing the element from the priority queue at a give
n index i.
101. void delete(int i)
102. {
103. heap[i] = heap[0] + 1;
104.
105. // move the node stored at ith location is shifted to the r
oot node
106. moveUp(i);
107.
108. // Removing the node having maximum priority
363

109. removeMax();
110. }
111. int main()
112. {
113. // Inserting the elements in a priority queue
114.
115. insert(20);
116. insert(19);
117. insert(21);
118. insert(18);
119. insert(12);
120. insert(17);
121. insert(15);
122. insert(16);
123. insert(14);
124. int i=0;
125.
126. printf("Elements in a priority queue are : ");
127. for(int i=0;i<=size;i++)
128. {
129. printf("%d ",heap[i]);
130. }
131. delete(2); // deleting the element whose index is 2.
132. printf("\nElements in a priority queue after deleting th
e element are : ");
133. for(int i=0;i<=size;i++)
134. {
135. printf("%d ",heap[i]);
136. }
137. int max=get_Max();
138. printf("\nThe element which is having the highest prio
rity is %d: ",max);
139.
364

140.
141. int min=get_Min();
142. printf("\nThe element which is having the minimum
priority is : %d",min);
143. return 0;
144. }
In the above program, we have created the following
functions:
o int parent(int i): This function returns the index of the
parent node of a child node, i.e., i.
o int left_child(int i): This function returns the index of the
left child of a given index, i.e., i.
o int right_child(int i): This function returns the index of
the right child of a given index, i.e., i.
o void moveUp(int i): This function will keep moving the
node up the tree until the heap property is restored.
o void moveDown(int i): This function will keep moving
the node down the tree until the heap property is restored.
o void removeMax(): This function removes the element
which is having the highest priority.
o void insert(int p): It inserts the element in a priority
queue which is passed as an argument in a function.
o void delete(int i): It deletes the element from a priority
queue at a given index.
o int get_Max(): It returns the element which is having the
highest priority, and we know that in max heap, the root
node contains the element which has the largest value, and
highest priority.
o int get_Min(): It returns the element which is having the
minimum priority, and we know that in max heap, the last
365

node contains the element which has the smallest value,


and lowest priority.
Output

Applications of queues:

Useful Applications of Queue


 When a resource is shared among multiple consumers.

Examples include CPU scheduling, Disk Scheduling.


 When data is transferred asynchronously (data not
necessarily received at the same rate as sent) between two
processes. Examples include IO Buffers, pipes, etc.
 Queues are widely used as waiting lists for a single shared

resource like printer, disk, CPU.


 Queues are used in asynchronous transfer of data (where

data is not being transferred at the same rate between two


processes) for eg. pipes, file IO, sockets.
 Queues are used as buffers in most of the applications like

MP3 media player, CD player, etc.


 Queue are used to maintain the play list in media players

in order to add and remove the songs from the play-list.


366

 Queues are used in operating systems for handling


interrupts.

Applications of Queue in Operating systems:


 Semaphores

 FCFS ( first come first serve) scheduling, example: FIFO

queue
 Spooling in printers

 Buffer for devices like keyboard

Applications of Queue in Networks:


 Queues in routers/ switches

 Mail Queues

 Variations: ( Deque, Priority Queue, Doubly Ended


Priority Queue )
Some other applications of Queue:
 Applied as waiting lists for a single shared resource like

CPU, Disk, and Printer.


 Applied as buffers on MP3 players and portable CD players.

 Applied on Operating system to handle the interruption.

 Applied to add song at the end or to play from the front.

 Applied on WhatsApp when we send messages to our

friends and they don’t have an internet connection then


these messages are queued on the server of WhatsApp.
367

SECTION-IV
CHAPTER-7
TREE
Introduction of Tree:
A tree is also one of the data structures that represent
hierarchical data. Suppose we want to show the employees and
their positions in the hierarchical form then it can be
represented as shown below:

The above tree shows the organization hierarchy of some


company. In the above structure, john is the CEO of the
company, and John has two direct reports named
as Steve and Rohan. Steve has three direct reports named Lee,
Bob, Ella where Steve is a manager. Bob has two direct reports
named Sal and Emma. Emma has two direct reports
368

named Tom and Raj. Tom has one direct report named Bill.
This particular logical structure is known as a Tree. Its structure
is similar to the real tree, so it is named a Tree. In this structure,
the root is at the top, and its branches are moving in a
downward direction. Therefore, we can say that the Tree data
structure is an efficient way of storing the data in a hierarchical
way.
Let's understand some key points of the Tree data
structure.
o A tree data structure is defined as a collection of objects
or entities known as nodes that are linked together to
represent or simulate hierarchy.
o A tree data structure is a non-linear data structure because
it does not store in a sequential manner. It is a hierarchical
structure as elements in a Tree are arranged in multiple
levels.
o In the Tree data structure, the topmost node is known as a
root node. Each node contains some data, and data can be
of any type. In the above tree structure, the node contains
the name of the employee, so the type of data would be a
string.
o Each node contains some data and the link or reference of
other nodes that can be called children.
369

Some basic terms used in Tree data structure.


Let's consider the tree structure, which is shown below:

In the above structure, each node is labeled with some number.


Each arrow shown in the above figure is known as
a link between the two nodes.
o Root: The root node is the topmost node in the tree
hierarchy. In other words, the root node is the one that
doesn't have any parent. In the above structure, node
numbered 1 is the root node of the tree. If a node is
directly linked to some other node, it would be called a
parent-child relationship.
o Child node: If the node is a descendant of any node, then
the node is known as a child node.
370

o Parent: If the node contains any sub-node, then that node


is said to be the parent of that sub-node.
o Sibling: The nodes that have the same parent are known
as siblings.
o Leaf Node:- The node of the tree, which doesn't have any
child node, is called a leaf node. A leaf node is the bottom-
most node of the tree. There can be any number of leaf
nodes present in a general tree. Leaf nodes can also be
called external nodes.
o Internal nodes: A node has atleast one child node known
as an internal
o Ancestor node:- An ancestor of a node is any predecessor
node on a path from the root to that node. The root node
doesn't have any ancestors. In the tree shown in the above
image, nodes 1, 2, and 5 are the ancestors of node 10.
o Descendant: The immediate successor of the given node
is known as a descendant of a node. In the above figure,
10 is the descendant of node 5.
Properties of Tree data structure
o Recursive data structure: The tree is also known as

a recursive data structure. A tree can be defined as


recursively because the distinguished node in a tree data
structure is known as a root node. The root node of the
tree contains a link to all the roots of its subtrees. The left
subtree is shown in the yellow color in the below figure,
and the right subtree is shown in the red color. The left
subtree can be further split into subtrees shown in three
different colors. Recursion means reducing something in
a self-similar manner. So, this recursive property of the
371

tree data structure is implemented in various applications.

o Number of edges: If there are n nodes, then there would


n-1 edges. Each arrow in the structure represents the link
or path. Each node, except the root node, will have atleast
one incoming link known as an edge. There would be one
link for the parent-child relationship.
o Depth of node x: The depth of node x can be defined as
the length of the path from the root to the node x. One edge
contributes one-unit length in the path. So, the depth of
node x can also be defined as the number of edges between
the root node and the node x. The root node has 0 depth.
o Height of node x: The height of node x can be defined as
the longest path from the node x to the leaf node.
Based on the properties of the Tree data structure, trees are
classified into various categories.
372

Implementation of Tree
The tree data structure can be created by creating the nodes
dynamically with the help of the pointers. The tree in the
memory can be represented as shown below:

The above figure shows the representation of the tree data


structure in the memory. In the above structure, the node
contains three fields. The second field stores the data; the first
field stores the address of the left child, and the third field stores
the address of the right child.
In programming, the structure of a node can be defined as:

1. struct node
2. {
3. int data;
4. struct node *left;
5. struct node *right;
6. }
The above structure can only be defined for the binary trees
because the binary tree can have utmost two children, and
373

generic trees can have more than two children. The structure of
the node for generic trees would be different as compared to the
binary tree.
Applications of trees
The following are the applications of trees:
o Storing naturally hierarchical data: Trees are used to
store the data in the hierarchical structure. For example,
the file system. The file system stored on the disc drive,
the file and folder are in the form of the naturally
hierarchical data and stored in the form of trees.
o Organize data: It is used to organize data for efficient
insertion, deletion and searching. For example, a binary
tree has a logN time for searching an element.
o Trie: It is a special kind of tree that is used to store the
dictionary. It is a fast and efficient way for dynamic spell
checking.
o Heap: It is also a tree data structure implemented using
arrays. It is used to implement priority queues.
o B-Tree and B+Tree: B-Tree and B+Tree are the tree data
structures used to implement indexing in databases.
o Routing table: The tree data structure is also used to store
the data in routing tables in the routers.
Types of Tree data structure
The following are the types of a tree data structure:
o General tree: The general tree is one of the types of tree
data structure. In the general tree, a node can have either
0 or maximum n number of nodes. There is no restriction
imposed on the degree of the node (the number of nodes
374

that a node can contain). The topmost node in a general


tree is known as a root node. The children of the parent
node are known as subtrees.

There can be n number of subtrees in a general tree. In the


general tree, the subtrees are unordered as the nodes in the
subtree cannot be ordered.
Every non-empty tree has a downward edge, and these
edges are connected to the nodes known as child nodes.
The root node is labeled with level 0. The nodes that have
the same parent are known as siblings.
o Binary tree
: Here, binary name itself suggests two numbers, i.e., 0
and 1. In a binary tree, each node in a tree can have utmost
two child nodes. Here, utmost means whether the node has
375

0 nodes, 1 node or 2 nodes.

o Binary Search tree


: Binary search tree is a non-linear data structure in which
one node is connected to n number of nodes. It is a node-
based data structure. A node can be represented in a binary
search tree with three fields, i.e., data part, left-child, and
right-child. A node can be connected to the utmost two
child nodes in a binary search tree, so the node contains
two pointers (left child and right child pointer).
Every node in the left subtree must contain a value less
than the value of the root node, and the value of each node
in the right subtree must be bigger than the value of the
root node.
A node can be created with the help of a user-defined data type
known as struct, as shown below:
376

1. struct node
2. {
3. int data;
4. struct node *left;
5. struct node *right;
6. }
The above is the node structure with three fields: data field, the
second field is the left pointer of the node type, and the third
field is the right pointer of the node type.

o AVL tree

It is one of the types of the binary tree, or we can say that it is


a variant of the binary search tree. AVL tree satisfies the
property of the binary tree as well as of the binary search tree.
It is a self-balancing binary search tree that was invented
by Adelson Velsky Lindas. Here, self-balancing means that
balancing the heights of left subtree and right subtree. This
balancing is measured in terms of the balancing factor.
We can consider a tree as an AVL tree if the tree obeys the
binary search tree as well as a balancing factor. The balancing
factor can be defined as the difference between the height of
the left subtree and the height of the right subtree. The
balancing factor's value must be either 0, -1, or 1; therefore,
377

each node in the AVL tree should have the value of the
balancing factor either as 0, -1, or 1.

o Red-Black Tree

The red-Black tree is the binary search tree. The prerequisite


of the Red-Black tree is that we should know about the binary
search tree. In a binary search tree, the value of the left-subtree
should be less than the value of that node, and the value of the
right-subtree should be greater than the value of that node. As
we know that the time complexity of binary search in the
average case is log2n, the best case is O(1), and the worst case
is O(n).
When any operation is performed on the tree, we want our tree
to be balanced so that all the operations like searching,
insertion, deletion, etc., take less time, and all these operations
will have the time complexity of log2n.
The red-black tree is a self-balancing binary search tree. AVL
tree is also a height balancing binary search tree then why do
we require a Red-Black tree. In the AVL tree, we do not know
how many rotations would be required to balance the tree, but
in the Red-black tree, a maximum of 2 rotations are required to
balance the tree. It contains one extra bit that represents either
the red or black color of a node to ensure the balancing of the
tree.
o Splay tree
The splay tree data structure is also binary search tree in which
recently accessed element is placed at the root position of tree
378

by performing some rotation operations. Here, splaying means


the recently accessed node. It is a self-balancing binary search
tree having no explicit balance condition like AVL tree.
It might be a possibility that height of the splay tree is not
balanced, i.e., height of both left and right subtrees may differ,
but the operations in splay tree takes order of logN time
where n is the number of nodes.
Splay tree is a balanced tree but it cannot be considered as a
height balanced tree because after each operation, rotation is
performed which leads to a balanced tree.
o Treap
Treap data structure came from the Tree and Heap data
structure. So, it comprises the properties of both Tree and Heap
data structures. In Binary search tree, each node on the left
subtree must be equal or less than the value of the root node
and each node on the right subtree must be equal or greater than
the value of the root node. In heap data structure, both right and
left subtrees contain larger keys than the root; therefore, we can
say that the root node contains the lowest value.
In treap data structure, each node has
both key and priority where key is derived from the Binary
search tree and priority is derived from the heap data structure.
The Treap data structure follows two properties which are
given below:
o Right child of a node>=current node and left child of a
node <=current node (binary tree)
o Children of any subtree must be greater than the node
(heap)
379

o B-tree

B-tree is a balanced m-way tree where m defines the order of


the tree. Till now, we read that the node contains only one key
but b-tree can have more than one key, and more than 2
children. It always maintains the sorted data. In binary tree, it
is possible that leaf nodes can be at different levels, but in b-
tree, all the leaf nodes must be at the same level.
If order is m then node has the following properties:
o Each node in a b-tree can have maximum m children
o For minimum children, a leaf node has 0 children, root
node has minimum 2 children and internal node has
minimum ceiling of m/2 children. For example, the value
of m is 5 which means that a node can have 5 children and
internal nodes can contain maximum 3 children.
o Each node has maximum (m-1) keys.
The root node must contain minimum 1 key and all other nodes
must contain atleast ceiling of m/2 minus 1 keys.

Representing Binary tree in memory:


Here we will see how to represent a binary tree in computers
memory. There are two different methods for representing.
These are using array and using linked list.
Suppose we have one tree like this −
380

The array representation stores the tree data by scanning


elements using level order fashion. So it stores nodes level by
level. If some element is missing, it left blank spaces for it. The
representation of the above tree is like below −
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
10 5 16 - 8 15 20 - - - - - - - 23
The index 1 is holding the root, it has two children 5 and 16,
they are placed at location 2 and 3. Some children are missing,
so their place is left as blank.
In this representation we can easily get the position of two
children of one node by using this formula −
child1=2∗parentchild1=2∗parent

child2=⟮2∗parent⟯+1child2=⟮2∗parent⟯+1
381

To get parent index from child we have to follow this formula



parent=[child2]parent=[child2]
This approach is good, and easily we can find the index of
parent and child, but it is not memory efficient. It will occupy
many spaces that has no use. This representation is good for
complete binary tree or full binary tree.
Another approach is by using linked lists. We create node for
each element. This will be look like below −

Traversing binary trees:


The tree can be defined as a non-linear data structure that stores
data in the form of nodes, and nodes are connected to each other
with the help of edges. Among all the nodes, there is one main
node called the root node, and all other nodes are the children
of these nodes.
In any data structure, traversal is an important operation. In the
traversal operation, we walk through the data structure visiting
382

each element of the data structure at least once. The traversal


operation plays a very important role while doing various other
operations on the data structure like some of the operations are
searching, in which we need to visit each element of the data
structure at least once so that we can compare each incoming
element from the data structure to the key that we want to find
in the data structure. So like any other data structure, the tree
data also needs to be traversed to access each element, also
known as a node of the tree data structure.
There are different ways of traversing a tree depending upon
the order in which the tree's nodes are visited and the types of
data structure used for traversing the tree. There are various
data structures involved in traversing a tree, as traversing a tree
involves iterating over all nodes in some manner.
As from a given node, there could be more than one way to
traverse or visit the next node of the tree, so it becomes
important to store one of the nodes traverses further and store
the rest of the nodes having a possible path for backtracking the
tree if needed. Backtracking is not a linear approach, so we
need different data structures for traversing through the whole
tree. The stack and queue are the major data structure that is
used for traversing a tree.
Traversal is a technique for visiting all of a tree's nodes and
printing their values. Traversing a tree involves iterating over
all nodes in some manner. We always start from the root (head)
node since all nodes are connected by edges (links). As the tree
is not a linear data structure, there can be more than one
possible next node from a given node, so some nodes must be
deferred, i.e., stored in some way for later visiting.
383

Types of Traversal of Binary Tree


There are three types of traversal of a binary tree.
1. Inorder tree traversal
2. Preorder tree traversal
3. Postorder tree traversal
Inorder Tree Traversal
The left subtree is visited first, followed by the root, and finally
the right subtree in this traversal strategy. Always keep in mind
that any node might be a subtree in and of itself. The output of
a binary tree traversal in order produces sorted key values in
ascending order.
C Code
Let's write a basic C program for Inorder traversal of the binary
search tree.
1. //C Program for Inorder traversal of the binary search tree
2.
3. #include<stdio.h>
4. #include<stdlib.h>
5.
6. struct node
7. {
8. int key;
9. struct node *left;
10. struct node *right;
11. };
12.
13. //return a new node with the given value
14. struct node *getNode(int val)
384

15. {
16. struct node *newNode;
17.
18. newNode = malloc(sizeof(struct node));
19.
20. newNode->key = val;
21. newNode->left = NULL;
22. newNode->right = NULL;
23.
24. return newNode;
25. }
26.
27. //inserts nodes in the binary search tree
28. struct node *insertNode(struct node *root, int val)
29. {
30. if(root == NULL)
31. return getNode(val);
32.
33. if(root->key < val)
34. root->right = insertNode(root->right,val);
35.
36. if(root->key > val)
37. root->left = insertNode(root->left,val);
38.
39. return root;
40. }
41.
42. //inorder traversal of the binary search tree
43. void inorder(struct node *root)
44. {
45. if(root == NULL)
46. return;
47.
385

48. //traverse the left subtree


49. inorder(root->left);
50.
51. //visit the root
52. printf("%d ",root->key);
53.
54. //traverse the right subtree
55. inorder(root->right);
56. }
57.
58. int main()
59. {
60. struct node *root = NULL;
61.
62.
63. int data;
64. char ch;
65. /* Do while loop to display various options to selec
t from to decide the input */
66. do
67. {
68. printf("\nSelect one of the operations::");
69. printf("\n1. To insert a new node in the Binary Tr
ee");
70. printf("\n2. To display the nodes of the Binary Tr
ee(via Inorder Traversal).\n");
71.
72. int choice;
73. scanf("%d",&choice);
74. switch (choice)
75. {
76. case 1 :
77. printf("\nEnter the value to be inserted\n");
386

78. scanf("%d",&data);
79. root = insertNode(root,data);
80. break;
81. case 2 :
82. printf("\nInorder Traversal of the Binary Tree::
\n");
83. inorder(root);
84. break;
85. default :
86. printf("Wrong Entry\n");
87. break;
88. }
89.
90. printf("\nDo you want to continue (Type y or n)\n
");
91. scanf(" %c",&ch);
92. } while (ch == 'Y'|| ch == 'y');
93.
94. return 0;
95. }
Output
The above C code hives the following output.
Select one of the operations::
1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree (via Inorder
Traversal).
1

Enter the value to be inserted


12
387

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Inorder
Traversal).
1

Enter the value to be inserted


98

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Inorder
Traversal).
1

Enter the value to be inserted


23

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Inorder
Traversal).
1
388

Enter the value to be inserted


78

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Inorder
Traversal).
1

Enter the value to be inserted


45

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Inorder
Traversal).
1

Enter the value to be inserted


87

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
389

2. To display the nodes of the Binary Tree(via Inorder


Traversal).
2

Inorder Traversal of the Binary Tree::


12 23 45 78 87 98
Do you want to continue (Type y or n)
n

Preorder Tree Traversal


In this traversal method, the root node is visited first, then the
left subtree, and finally the right subtree.
Code
Let's write a C code for the Preorder traversal of the binary
search tree.
1. /*
2. * Program: Preorder traversal of the binary search tree
3. * Language: C
4. */
5.
6. #include<stdio.h>
7. #include<stdlib.h>
8.
9. struct node
10. {
11. int key;
12. struct node *left;
13. struct node *right;
14. };
15.
390

16. //return a new node with the given value


17. struct node *getNode(int val)
18. {
19. struct node *newNode;
20.
21. newNode = malloc(sizeof(struct node));
22.
23. newNode->key = val;
24. newNode->left = NULL;
25. newNode->right = NULL;
26.
27. return newNode;
28. }
29.
30. //inserts nodes in the binary search tree
31. struct node *insertNode(struct node *root, int val)
32. {
33. if(root == NULL)
34. return getNode(val);
35.
36. if(root->key < val)
37. root->right = insertNode(root->right,val);
38.
39. if(root->key > val)
40. root->left = insertNode(root->left,val);
41.
42. return root;
43. }
44.
45. //preorder traversal of the binary search tree
46. void preorder(struct node *root)
47. {
48. if(root == NULL)
391

49. return;
50.
51. //visit the root
52. printf("%d ",root->key);
53.
54. //traverse the left subtree
55. preorder(root->left);
56.
57. //traverse the right subtree
58. preorder(root->right);
59. }
60.
61. int main()
62. {
63. struct node *root = NULL;
64.
65. int data;
66. char ch;
67. /* Do while loop to display various options to selec
t from to decide the input */
68. do
69. {
70. printf("\nSelect one of the operations::");
71. printf("\n1. To insert a new node in the Binary Tr
ee");
72. printf("\n2. To display the nodes of the Binary Tr
ee(via Preorder Traversal).\n");
73.
74. int choice;
75. scanf("%d",&choice);
76. switch (choice)
77. {
78. case 1 :
392

79. printf("\nEnter the value to be inserted\n");


80. scanf("%d",&data);
81. root = insertNode(root,data);
82. break;
83. case 2 :
84. printf("\nPreorder Traversal of the Binary Tree
::\n");
85. preorder(root);
86. break;
87. default :
88. printf("Wrong Entry\n");
89. break;
90. }
91.
92. printf("\nDo you want to continue (Type y or n)\n
");
93. scanf(" %c",&ch);
94. } while (ch == 'Y'|| ch == 'y');
95.
96. return 0;
97. }
Output:
Select one of the operations::
1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


45

Do you want to continue (Type y or n)


393

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


53

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


1

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


394

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


97

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


22

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
395

Preorder Traversal of the Binary Tree::


45 1 2 22 53 97
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


76

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


30

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
396

2. To display the nodes of the Binary Tree(via Preorder


Traversal).
1

Enter the value to be inserted


67

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
1

Enter the value to be inserted


4

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
2

Preorder Traversal of the Binary Tree::


45 1 2 22 4 30 53 97 76 67
Do you want to continue (Type y or n)
n
397

Postorder Tree Traversal


The root node is visited last in this traversal method, hence the
name. First, we traverse the left subtree, then the right subtree,
and finally the root node.
Code
Let's write a program for Postorder traversal of the binary
search tree.
1. /*
2. * Program: Postorder traversal of the binary search tree
3. * Language: C
4. */
5.
6. #include<stdio.h>
7. #include<stdlib.h>
8.
9. struct node
10. {
11. int key;
12. struct node *left;
13. struct node *right;
14. };
15.
16. //return a new node with the given value
17. struct node *getNode(int val)
18. {
19. struct node *newNode;
20.
21. newNode = malloc(sizeof(struct node));
22.
23. newNode->key = val;
24. newNode->left = NULL;
398

25. newNode->right = NULL;


26.
27. return newNode;
28. }
29. //inserts nodes in the binary search tree
30. struct node *insertNode(struct node *root, int val)
31. {
32. if(root == NULL)
33. return getNode(val);
34.
35. if(root->key < val)
36. root->right = insertNode(root->right,val);
37.
38. if(root->key > val)
39. root->left = insertNode(root->left,val);
40.
41. return root;
42. }
43.
44. //postorder traversal of the binary search tree
45. void postorder(struct node *root)
46. {
47. if(root == NULL)
48. return;
49.
50. //traverse the left subtree
51. postorder(root->left);
52.
53. //traverse the right subtree
54. postorder(root->right);
55.
56. //visit the root
57. printf("%d ",root->key);
399

58. }
59. int main()
60. {
61. struct node *root = NULL;
62.
63.
64. int data;
65. char ch;
66. /* Do while loop to display various options to selec
t from to decide the input */
67. do
68. {
69. printf("\nSelect one of the operations::");
70. printf("\n1. To insert a new node in the Binary Tr
ee");
71. printf("\n2. To display the nodes of the Binary Tr
ee(via Postorder Traversal).\n");
72.
73. int choice;
74. scanf("%d",&choice);
75. switch (choice)
76. {
77. case 1 :
78. printf("\nEnter the value to be inserted\n");
79. scanf("%d",&data);
80. root = insertNode(root,data);
81. break;
82. case 2 :
83. printf("\nPostorder Traversal of the Binary Tre
e::\n");
84. postorder(root);
85. break;
86. default :
400

87. printf("Wrong Entry\n");


88. break;
89. }
90.
91. printf("\nDo you want to continue (Type y or n)\n
");
92. scanf(" %c",&ch);
93. } while (ch == 'Y'|| ch == 'y');
94.
95. return 0;
96. }
Output:
Select one of the operations::
1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


12

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


31
401

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
24
Wrong Entry

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


24

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


402

88

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


67

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


56

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
403

Enter the value to be inserted


90

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


44

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


71

Do you want to continue (Type y or n)


y

Select one of the operations::


404

1. To insert a new node in the Binary Tree


2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


38

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


29

Do you want to continue (Type y or n)


y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Postorder
Traversal).
2

Postorder Traversal of the Binary Tree::


29 24 38 44 56 71 67 90 88 31 12
Do you want to continue (Type y or n)
n
405

We have seen the different C programs to implement inorder,


preorder, and postorder traversal of the nodes of the Binary
tree. Now let us write a code to perform all three types of
traversal in a single program.
Code
1. //Binary tree traversal:
2.
3. #include <stdio.h>
4. // #include <conio.h>
5. #include <malloc.h>
6. struct node
7. {
8. struct node *left;
9. int data;
10. struct node *right;
11. };
12.
13. void main()
14. {
15. void insert(struct node **,int);
16. void inorder(struct node *);
17. void postorder(struct node *);
18. void preorder(struct node *);
19.
20. struct node *ptr = NULL;
21. int no,i,num;
22.
23. // ptr = NULL;
24. // ptr->data=0;
25.
26. int data;
27. char ch;
406

28. /* Do while loop to display various options to selec


t from to decide the input */
29. do
30. {
31. printf("\nSelect one of the operations::");
32. printf("\n1. To insert a new node in the Binary Tr
ee");
33. printf("\n2. To display the nodes of the Binary Tr
ee(via Preorder Traversal).");
34. printf("\n3. To display the nodes of the Binary Tr
ee(via Inorder Traversal).");
35. printf("\n4. To display the nodes of the Binary Tr
ee(via Postorder Traversal).\n");
36.
37. int choice;
38. scanf("%d",&choice);
39. switch (choice)
40. {
41. case 1 :
42. printf("\nEnter the value to be inserted\n");
43. scanf("%d",&data);
44. insert(&ptr,data);
45. break;
46. case 2 :
47. printf("\nPreorder Traversal of the Binary Tree
::\n");
48. preorder(ptr);
49. break;
50. case 3 :
51. printf("\nInorder Traversal of the Binary Tree::
\n");
52. inorder(ptr);
53. break;
407

54. case 4 :
55. printf("\nPostorder Traversal of the Binary Tre
e::\n");
56. postorder(ptr);
57. break;
58. default :
59. printf("Wrong Entry\n");
60. break;
61. }
62.
63. printf("\nDo you want to continue (Type y or n)\n
");
64. scanf(" %c",&ch);
65. } while (ch == 'Y'|| ch == 'y');
66.
67.
68. // printf("\nProgram for Tree Traversal\n");
69. // printf("Enter the number of nodes to add to the tree.
<BR>\n");
70. // scanf("%d",&no);
71.
72. // for(i=0;i<no;i++)
73. // {
74. // printf("Enter the item\n");
75. // scanf("%d",&num);
76. // insert(&ptr,num);
77. // }
78.
79. // //getch();
80. // printf("\nINORDER TRAVERSAL\n");
81. // inorder(ptr);
82.
83. // printf("\nPREORDER TRAVERSAL\n");
408

84. // preorder(ptr);
85.
86. // printf("\nPOSTORDER TRAVERSAL\n");
87. // postorder(ptr);
88.
89. }
90.
91. void insert(struct node **p,int num)
92. {
93. if((*p)==NULL)
94. {
95. printf("Leaf node created.");
96. (*p)=malloc(sizeof(struct node));
97. (*p)->left = NULL;
98. (*p)->right = NULL;
99. (*p)->data = num;
100. return;
101. }
102. else
103. {
104. if(num==(*p)->data)
105. {
106. printf("\nREPEATED ENTRY ERROR VALUE
REJECTED\n");
107. return;
108. }
109. if(num<(*p)->data)
110. {
111. printf("\nDirected to left link.\n");
112. insert(&((*p)->left),num);
113. }
114. else
115. {
409

116. printf("Directed to right link.\n");


117. insert(&((*p)->right),num);
118. }
119. }
120. return;
121. }
122.
123. void inorder(struct node *p)
124. {
125. if(p!=NULL)
126. {
127. inorder(p->left);
128. printf("%d ",p->data);
129. inorder(p->right);
130. }
131. else
132. return;
133. }
134.
135. void preorder(struct node *p)
136. {
137. if(p!=NULL)
138. {
139. printf("%d ",p->data);
140. preorder(p->left);
141. preorder(p->right);
142. }
143. else
144. return;
145. }
146.
147. void postorder(struct node *p)
148. {
410

149. if(p!=NULL)
150. {
151. postorder(p->left);
152. postorder(p->right);
153. printf("%d ",p->data);
154. }
155. else
156. return;
157. }
Output:
Select one of the operations::
1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


2
Leaf node created.
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
411

4. To display the nodes of the Binary Tree(via Postorder


Traversal).
1

Enter the value to be inserted


5
Directed to right link.
Leaf node created.
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


7
Directed to right link.
Directed to right link.
Leaf node created.
Do you want to continue (Type y or n)
Y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
412

3. To display the nodes of the Binary Tree(via Inorder


Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


9
Directed to right link.
Directed to right link.
Directed to right link.
Leaf node created.
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


31
Directed to right link.
Directed to right link.
Directed to right link.
Directed to right link.
Leaf node created.
Do you want to continue (Type y or n)
413

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
1

Enter the value to be inserted


78
Directed to right link.
Directed to right link.
Directed to right link.
Directed to right link.
Directed to right link.
Leaf node created.
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
2
414

Preorder Traversal of the Binary Tree::


2 5 7 9 31 78
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
3

Inorder Traversal of the Binary Tree::


2 5 7 9 31 78
Do you want to continue (Type y or n)
y

Select one of the operations::


1. To insert a new node in the Binary Tree
2. To display the nodes of the Binary Tree(via Preorder
Traversal).
3. To display the nodes of the Binary Tree(via Inorder
Traversal).
4. To display the nodes of the Binary Tree(via Postorder
Traversal).
4

Postorder Traversal of the Binary Tree::


78 31 9 7 5 2
Do you want to continue (Type y or n)
415

Traversal algorithms using stacks:

Data structures of two types of Linear Data Structure and the


second is Non-linear Data Structure the main difference
between these Data structures is the way of transverse the
elements of these data structures. Linear Data Structure
Transverse all elements in a single run whereas Non-Linear
Data structure Transverse all elements in Hierarchy way.

In this article, we will implement different types of Depth First


Traversals in the Binary Tree of Non-Linear Data structure
using the stack data structure.
416

Input Binary Tree:

Depth First Traversals:


1. Inorder (Left, Root, Right) : 4 2 5 1 3
2. Preorder (Root, Left, Right) : 1 2 4 5 3
3. Postorder (Left, Right, Root) : 4 5 2 3 1

A. Inorder Traversal

1. Create an empty stack S.


2. Initialize the current node as root.
3. Push the current node to S and set current = current->left
until the current is NULL.
4. If the current is NULL and the stack is not empty then
 Pop the top item from the stack.

 Print the popped item, set current = popped_item->right

 Go to step 3.

5. If the current is NULL and the stack is empty then we are


done.
Below is the implementation of the above approach:
417

// Non-Recursive Java program for inorder traversal

import java.util.Stack;

// Class containing left and right child of

// current node and key value

class Node {

int data;

Node left, right;

public Node(int item)

data = item;

left = right = null;

}
418

// Class to print the inorder traversal

class BinaryTree {

Node root;

void inorder()

if (root == null)

return;

Stack<Node> s = new Stack<Node>();

Node curr = root;

// traverse the tree

while (curr != null || s.size() > 0) {

// Reach the left most

// Node of the current Node


419

while (curr != null) {

// place pointer to a tree node on

// the stack before traversing

// the node's left subtree

s.push(curr);

curr = curr.left;

// Current must be NULL at this point

curr = s.pop();

System.out.print(curr.data + " ");

// we have visited the node and its

// left subtree. Now, it's right

// subtree's turn
420

curr = curr.right;

public static void main(String args[])

// Creating a binary tree and

// entering the nodes

BinaryTree tree = new BinaryTree();

tree.root = new Node(1);

tree.root.left = new Node(2);

tree.root.right = new Node(3);

tree.root.left.left = new Node(4);

tree.root.left.right = new Node(5);

tree.inorder();
421

Output
42513
 TimeComplexity: O(n)
 SpaceComplexity: O(n)

B. Preorder Traversal

There are two approaches for Preorder Transversal of Binary


Tree using the Stack data structure:
Approach 1: This approach is totally the same which
discussed in the above Transversal.
1. Create an empty stack S.
2. Initialize the current node as root.
3. Push the current node to S and set current = current->left
print the peek element in the stack until the current is
NULL.
4. If current is NULL and stack is not empty then
a) Pop the top item from stack.
b) set current = popped_item->right.
c) Go to step 3.
5. If the current is NULL and the stack is empty then we are
done.
Below is the implementation of the above approach:

 Java
422

// Java Program for Pre order Traversal

import java.util.*;

import java.io.*;

class Node {

int data;

Node left, right;

public Node(int item)

data = item;

left = right = null;

// Class to print the preorder traversal

class BinaryTree {
423

Node root;

void preorder()

if (root == null)

return;

Stack<Node> s = new Stack<Node>();

Node curr = root;

// traverse the tree

while (curr != null || s.size() > 0) {

// Reach the left most

// Node of the curr Node

while (curr != null) {

// place pointer to a tree node on


424

// the stack before traversing

// the node's left subtree

s.push(curr);

// print the peak element

System.out.print(s.peek().data + " ");

curr = curr.left;

// Current must be NULL at this point

curr = s.pop();

// we have visited the node and its

// left subtree. Now, it's right

// subtree's turn

curr = curr.right;
425

public static void main(String args[])

// creating a binary tree and

// entering the nodes

BinaryTree tree = new BinaryTree();

tree.root = new Node(1);

tree.root.left = new Node(2);

tree.root.right = new Node(3);

tree.root.left.left = new Node(4);

tree.root.left.right = new Node(5);

tree.preorder();

}
426

Output
12453
 Time Complexity: O(n)
 Space Complexity: O(n)

Approach 2: In Preorder Transversal, First print the root


element first then the left subtree, and then the right subtree.
We know that Stack data structure follows LIFO(Last in First
Out), So we take the advantage of this feature of this stack we
first push the right part of the current tree and after the left part
of the current tree, and in every iteration, we pop the peak
element from stack and print and then again push right part of
the pop element and left part of the pop element till the size of
the stack is not equal to 1 because we have already printed the
first element.
Algorithm:
 Create an empty stack S.

 Print the root element.

 Push the right subtree to the stack.

 Push the left subtree to the stack.

 If the stack size is not 1 then

 Pop the top item from the stack.

 print the element

 Push right Subtree of pop element to the stack.

 Push left Subtree of pop element to the stack.

 If the Size of the stack is 1 return to the main method

Below is the implementation of the above approach:

 Java
427

// Non-Recursive Java Program for preorder traversal

import java.util.Stack;

// Class containing left and right child of

// current node and key value

class Node {

int data;

Node left, right;

public Node(int item)

data = item;

left = right = null;

// Class to print the preorder traversal


428

class BinaryTree {

Node root;

void preorder()

if (root == null) {

return;

Stack<Node> S = new Stack<>();

// Push root element in the stack

S.add(root);

// print the root element

System.out.print(root.data + " ");

// Push right subtree to the stack


429

if (root.right != null) {

S.add(root.right);

// Push left subtree to the stack

if (root.left != null) {

S.add(root.left);

// Iterate till Size of the Stack not equal to 1

while (S.size() != 1) {

// Peek element of the stack

Node temp = S.peek();

// pop the element from the stack

S.pop();

if (temp != null) {

// print the pop element


430

System.out.print(temp.data + " ");

// Push right subtree of the pop element

if (temp.right != null) {

S.add(temp.right);

// Push left subtree of the pop element

if (temp.left != null) {

S.add(temp.left);

public static void main(String args[])

{
431

// creating a binary tree and

// entering the nodes

BinaryTree tree = new BinaryTree();

tree.root = new Node(1);

tree.root.left = new Node(2);

tree.root.right = new Node(3);

tree.root.left.left = new Node(4);

tree.root.left.right = new Node(5);

tree.preorder();

Output
12453
 TimeComplexity: O(n)
 SpaceComplexity: O(n)

C. PostOrder Traversal

1. Create an empty stack


432

2. Do the following while the root is not NULL


 Push root’s right child and then the root to stack.

 Set root as root’s left child.

3. Pop an item from the stack and set it as root.


 If the popped item has a right child and the right child is at

top of the stack, then remove the right child from the stack,
push the root back, and set the root as root’s right child.
 Else print root’s data and set root as NULL.

4. Repeat steps 2 and 3 while the stack is not empty.


Below is the implementation of the above approach:

 Java

// Java program for iterative postorder

// traversal using stack

import java.util.ArrayList;

import java.util.Stack;

// A binary tree node

class Node {

int data;

Node left, right;


433

Node(int item)

data = item;

left = right;

class BinaryTree {

Node root;

// An iterative function to do postorder traversal

// of a given binary tree

void postOrder(Node node)

Stack<Node> S = new Stack<Node>();


434

// Check for empty tree

if (node == null)

return;

S.push(node);

Node prev = null;

while (!S.isEmpty()) {

Node current = S.peek();

// go down the tree in search of a leaf an if so

// process it and pop stack otherwise move down

if (prev == null || prev.left == current

|| prev.right == current) {

if (current.left != null)

S.push(current.left);

else if (current.right != null)


435

S.push(current.right);

else {

S.pop();

System.out.print(current.data + " ");

// go up the tree from left node, if the

// child is right push it onto stack

// otherwise process parent and pop

else if (current.left == prev) {

if (current.right != null)

S.push(current.right);

else {

S.pop();

System.out.print(current.data + " ");


436

// go up the tree from right node and after

// coming back from right node process parent

// and pop stack

else if (current.right == prev) {

S.pop();

System.out.print(current.data + " ");

prev = current;

// Driver program to test above functions


437

public static void main(String args[])

BinaryTree tree = new BinaryTree();

// Let us create trees shown in above diagram

tree.root = new Node(1);

tree.root.left = new Node(2);

tree.root.right = new Node(3);

tree.root.left.left = new Node(4);

tree.root.left.right = new Node(5);

tree.postOrder(tree.root);

Output
45231
 Time Complexity: O(n)
 Space Complexity: O(n)
438
439

CHAPTER-8

GRAPH

Introduction of Graph:

A Graph is a non-linear data structure consisting of vertices


and edges. The vertices are sometimes also referred to as nodes
and the edges are lines or arcs that connect any two nodes in
the graph. More formally a Graph is composed of a set of
vertices( V ) and a set of edges( E ). The graph is denoted
by G(E, V).
Components of a Graph
 Vertices: Vertices are the fundamental units of the graph.

Sometimes, vertices are also known as vertex or nodes.


Every node/vertex can be labeled or unlabelled.
 Edges: Edges are drawn or used to connect two nodes of the

graph. It can be ordered pair of nodes in a directed graph.


Edges can connect any two nodes in any possible way.
There are no rules. Sometimes, edges are also known as
arcs. Every edge can be labeled/unlabelled.
440

Types Of Graph
1. Null Graph
A graph is known as a null graph if there are no edges in the
graph.
2. Trivial Graph
Graph having only a single vertex, it is also the smallest graph
possible.

3. Undirected Graph
A graph in which edges do not have any direction. That is the
nodes are unordered pairs in the definition of every edge.
4. Directed Graph
A graph in which edge has direction. That is the nodes are
ordered pairs in the definition of every edge.
441

5. Connected Graph
The graph in which from one node we can visit any other node
in the graph is known as a connected graph.
6. Disconnected Graph
The graph in which at least one node is not reachable from a
node is known as a disconnected graph.
442

7. Regular Graph
The graph in which the degree of every vertex is equal to the
other vertices of the graph. Let the degree of each vertex
be K then the graph
8. Complete Graph
The graph in which from each node there is an edge to each
other node.
443

9. Cycle Graph
The graph in which the graph is a cycle in itself, the degree of
each vertex is 2.
10. Cyclic Graph
A graph containing at least one cycle is known as a Cyclic
graph.
444

11. Directed Acyclic Graph


A Directed Graph that does not contain any cycle.
12. Bipartite Graph
A graph in which vertex can be divided into two sets such that
vertex in each set does not contain any edge between them.
445

13. Weighted Graph


 A graph in which the edges are already specified with
suitable weight is known as a weighted graph.
 Weighted graphs can be further classified as directed
weighted graphs and undirected weighted graphs.

Tree v/s Graph


Trees are the restricted types of graphs, just with some more
rules. Every tree will always be a graph but not all graphs will
be trees. Linked List, Trees, and Heaps all are special cases of
graphs.
446

Representation of Graphs
There are two ways to store a graph:
 Adjacency Matrix
 Adjacency List

Adjacency Matrix
In this method, the graph is stored in the form of the 2D matrix
where rows and columns denote vertices. Each entry in the
matrix represents the weight of the edge between those
vertices.
447

Adjacency List
This graph is represented as a collection of linked lists. There
is an array of pointer which points to the edges connected to
that vertex.
448

Comparison between Adjacency Matrix and Adjacency


List
When the graph contains a large number of edges then it is
good to store it as a matrix because only some entries in the
matrix will be empty. An algorithm such
as Prim’s and Dijkstra adjacency matrix is used to have less
complexity.
Action Adjacency Matrix Adjacency List

Adding Edge O(1) O(1)

Removing and edge O(1) O(N)

Initializing O(N*N) O(N)

Basic Operations on Graphs

Below are the basic operations on the graph:


 Insertion of Nodes/Edges in the graph – Insert a node into
the graph.
 Deletion of Nodes/Edges in the graph – Delete a node from

the graph.
 Searching on Graphs – Search an entity in the graph.

 Traversal of Graphs – Traversing all the nodes in the graph.

Usage of graphs
 Maps can be represented using graphs and then can be used

by computers to provide various services like the shortest


path between two cities.
 When various tasks depend on each other then this situation

can be represented using a Directed Acyclic graph and we


449

can find the order in which tasks can be performed using


topological sort.
 State Transition Diagram represents what can be the legal

moves from current states. In-game of tic tac toe this can be
used.
Real-Life Applications of Graph

Graph theory terminology:

A graph is a diagram of points and lines connected to the points.


It has at least one line joining a set of two vertices with no
vertex connecting itself. The concept of graphs in graph theory
stands up on some basic terms such as point, line, vertex, edge,
degree of vertices, properties of graphs, etc. Here, in this
chapter, we will cover these fundamentals of graph theory.
450

Point
A point is a particular position in a one-dimensional, two-
dimensional, or three-dimensional space. For better
understanding, a point can be denoted by an alphabet. It can be
represented with a dot.
Example

Here, the dot is a point named ‘a’.


Line
A Line is a connection between two points. It can be
represented with a solid line.
Example

Here, ‘a’ and ‘b’ are the points. The link between these two
points is called a line.
Vertex
A vertex is a point where multiple lines meet. It is also called
a node. Similar to points, a vertex is also denoted by an
alphabet.
Example

Here, the vertex is named with an alphabet ‘a’.


451

Edge
An edge is the mathematical term for a line that connects two
vertices. Many edges can be formed from a single vertex.
Without a vertex, an edge cannot be formed. There must be a
starting vertex and an ending vertex for an edge.
Example

Here, ‘a’ and ‘b’ are the two vertices and the link between them
is called an edge.
Graph
A graph ‘G’ is defined as G = (V, E) Where V is a set of all
vertices and E is a set of all edges in the graph.
Example 1

In the above example, ab, ac, cd, and bd are the edges of the
graph. Similarly, a, b, c, and d are the vertices of the graph.
Example 2
452

In this graph, there are four vertices a, b, c, and d, and four


edges ab, ac, ad, and cd.
Loop
In a graph, if an edge is drawn from vertex to itself, it is called
a loop.
Example 1

In the above graph, V is a vertex for which it has an edge (V,


V) forming a loop.
Example 2

In this graph, there are two loops which are formed at vertex a,
and vertex b.
Degree of Vertex
It is the number of vertices adjacent to a vertex V.
453

Notation − deg(V).
In a simple graph with n number of vertices, the degree of any
vertices is −
deg(v) ≤ n – 1 ∀ v ∈ G
A vertex can form an edge with all other vertices except by
itself. So the degree of a vertex will be up to the number of
vertices in the graph minus 1. This 1 is for the self-vertex as
it cannot form a loop by itself. If there is a loop at any of the
vertices, then it is not a Simple Graph.
Degree of vertex can be considered under two cases of graphs

 Undirected Graph
 Directed Graph
Degree of Vertex in an Undirected Graph
An undirected graph has no directed edges. Consider the
following examples.
Example 1
Take a look at the following graph −
454

In the above Undirected Graph,


 deg(a) = 2, as there are 2 edges meeting at vertex ‘a’.
 deg(b) = 3, as there are 3 edges meeting at vertex ‘b’.
 deg(c) = 1, as there is 1 edge formed at vertex ‘c’
 So ‘c’ is a pendent vertex.
 deg(d) = 2, as there are 2 edges meeting at vertex ‘d’.
 deg(e) = 0, as there are 0 edges formed at vertex ‘e’.
 So ‘e’ is an isolated vertex.
Example 2
Take a look at the following graph −

In the above graph,


deg(a) = 2, deg(b) = 2, deg(c) = 2, deg(d) = 2, and deg(e) = 0.
455

The vertex ‘e’ is an isolated vertex. The graph does not have
any pendent vertex.
Degree of Vertex in a Directed Graph
In a directed graph, each vertex has an indegree and
an outdegree.
Indegree of a Graph
 Indegree of vertex V is the number of edges which are

coming into the vertex V.


 Notation − deg−(V).

Outdegree of a Graph
 Outdegree of vertex V is the number of edges which are

going out from the vertex V.


 Notation − deg+(V).

Consider the following examples.


Example 1
Take a look at the following directed graph. Vertex ‘a’ has two
edges, ‘ad’ and ‘ab’, which are going outwards. Hence its
outdegree is 2. Similarly, there is an edge ‘ga’, coming towards
vertex ‘a’. Hence the indegree of ‘a’ is 1.
456

The indegree and outdegree of other vertices are shown in the


following table −
Vertex Indegree Outdegree

a 1 2

b 2 0

c 2 1

d 1 1

e 1 1

f 1 1

g 0 2
457

Example 2
Take a look at the following directed graph. Vertex ‘a’ has an
edge ‘ae’ going outwards from vertex ‘a’. Hence its outdegree
is 1. Similarly, the graph has an edge ‘ba’ coming towards
vertex ‘a’. Hence the indegree of ‘a’ is 1.

The indegree and outdegree of other vertices are shown in the


following table −
Vertex Indegree Outdegree

a 1 1

b 0 2

c 2 0

d 1 1

e 1 1

Pendent Vertex
By using degree of a vertex, we have a two special types of
vertices. A vertex with degree one is called a pendent vertex.
458

Example

Here, in this example, vertex ‘a’ and vertex ‘b’ have a


connected edge ‘ab’. So with respect to the vertex ‘a’, there is
only one edge towards vertex ‘b’ and similarly with respect to
the vertex ‘b’, there is only one edge towards vertex ‘a’.
Finally, vertex ‘a’ and vertex ‘b’ has degree as one which are
also called as the pendent vertex.
Isolated Vertex
A vertex with degree zero is called an isolated vertex.
Example

Here, the vertex ‘a’ and vertex ‘b’ has a no connectivity


between each other and also to any other vertices. So the degree
of both the vertices ‘a’ and ‘b’ are zero. These are also called
as isolated vertices.
Adjacency
Here are the norms of adjacency −
 In a graph, two vertices are said to be adjacent, if there is
an edge between the two vertices. Here, the adjacency of
vertices is maintained by the single edge that is connecting
those two vertices.
 In a graph, two edges are said to be adjacent, if there is a
common vertex between the two edges. Here, the
adjacency of edges is maintained by the single vertex that
is connecting two edges.
459

Example 1

In the above graph −


 ‘a’ and ‘b’ are the adjacent vertices, as there is a common
edge ‘ab’ between them.
 ‘a’ and ‘d’ are the adjacent vertices, as there is a common
edge ‘ad’ between them.
 ab’ and ‘be’ are the adjacent edges, as there is a common
vertex ‘b’ between them.
 be’ and ‘de’ are the adjacent edges, as there is a common
vertex ‘e’ between them.
Example 2

In the above graph −


 ‘a’ and ‘d’ are the adjacent vertices, as there is a common
edge ‘ad’ between them.
 ‘c’ and ‘b’ are the adjacent vertices, as there is a common
edge ‘cb’ between them.
460

 ‘ad’ and ‘cd’ are the adjacent edges, as there is a common


vertex ‘d’ between them.
 ‘ac’ and ‘cd’ are the adjacent edges, as there is a common

vertex ‘c’ between them.


Parallel Edges
In a graph, if a pair of vertices is connected by more than one
edge, then those edges are called parallel edges.

In the above graph, ‘a’ and ‘b’ are the two vertices which are
connected by two edges ‘ab’ and ‘ab’ between them. So it is
called as a parallel edge.
Multi Graph
A graph having parallel edges is known as a Multigraph.
Example 1

In the above graph, there are five edges ‘ab’, ‘ac’, ‘cd’, ‘cd’,
and ‘bd’. Since ‘c’ and ‘d’ have two parallel edges between
them, it a Multigraph.
Example 2
461

In the above graph, the vertices ‘b’ and ‘c’ have two edges. The
vertices ‘e’ and ‘d’ also have two edges between them. Hence
it is a Multigraph.
Degree Sequence of a Graph
If the degrees of all vertices in a graph are arranged in
descending or ascending order, then the sequence obtained is
known as the degree sequence of the graph.
Example 1

Vertex A b c d e

Connecting to b,c a,d a,d c,b,e d

Degree 2 2 2 3 1

In the above graph, for the vertices {d, a, b, c, e}, the degree
sequence is {3, 2, 2, 2, 1}.
462

Example 2

Vertex A b c d e f

Connecting to b,e a,c b,d c,e a,d -

Degree 2 2 2 2 2 0

In the above graph, for the vertices {a, b, c, d, e, f}, the degree
sequence is {2, 2, 2, 2, 2, 0}.

Sequential and linked representation of graphs:

Graph representation
In this article, we will discuss the ways to represent the graph.
By Graph representation, we simply mean the technique to be
used to store some graph into the computer's memory.
463

A graph is a data structure that consist a sets of vertices (called


nodes) and edges. There are two ways to store Graphs into the
computer's memory:
o Sequential representation (or, Adjacency matrix
representation)
o Linked list representation (or, Adjacency list
representation)
In sequential representation, an adjacency matrix is used to
store the graph. Whereas in linked list representation, there is a
use of an adjacency list to store the graph.
In this tutorial, we will discuss each one of them in detail.
Now, let's start discussing the ways of representing a graph in
the data structure.
Sequential representation
In sequential representation, there is a use of an adjacency
matrix to represent the mapping between vertices and edges of
the graph. We can use an adjacency matrix to represent the
undirected graph, directed graph, weighted directed graph, and
weighted undirected graph.
If adj[i][j] = w, it means that there is an edge exists from vertex
i to vertex j with weight w.
An entry Aij in the adjacency matrix representation of an
undirected graph G will be 1 if an edge exists between V i and
Vj. If an Undirected Graph G consists of n vertices, then the
adjacency matrix for that graph is n x n, and the matrix A = [aij]
can be defined as -
464

aij = 1 {if there is a path exists from V i to Vj}


aij = 0 {Otherwise}
It means that, in an adjacency matrix, 0 represents that there is
no association exists between the nodes, whereas 1 represents
the existence of a path between two edges.
If there is no self-loop present in the graph, it means that the
diagonal entries of the adjacency matrix will be 0.
Now, let's see the adjacency matrix representation of an
undirected graph.

In the above figure, an image shows the mapping among the


vertices (A, B, C, D, E), and this mapping is represented by
using the adjacency matrix.
There exist different adjacency matrices for the directed and
undirected graph. In a directed graph, an entry Aij will be 1 only
when there is an edge directed from V i to Vj.
Adjacency matrix for a directed graph
In a directed graph, edges represent a specific path from one
vertex to another vertex. Suppose a path exists from vertex A
465

to another vertex B; it means that node A is the initial node,


while node B is the terminal node.
Consider the below-directed graph and try to construct the
adjacency matrix of it.

In the above graph, we can see there is no self-loop, so the


diagonal entries of the adjacent matrix are 0.
Adjacency matrix for a weighted directed graph
It is similar to an adjacency matrix representation of a directed
graph except that instead of using the '1' for the existence of a
path, here we have to use the weight associated with the edge.
The weights on the graph edges will be represented as the
entries of the adjacency matrix. We can understand it with the
help of an example. Consider the below graph and its adjacency
matrix representation. In the representation, we can see that the
weight associated with the edges is represented as the entries in
the adjacency matrix.
466

In the above image, we can see that the adjacency matrix


representation of the weighted directed graph is different from
other representations. It is because, in this representation, the
non-zero values are replaced by the actual weight assigned to
the edges.
Adjacency matrix is easier to implement and follow. An
adjacency matrix can be used when the graph is dense and a
number of edges are large.
Though, it is advantageous to use an adjacency matrix, but it
consumes more space. Even if the graph is sparse, the matrix
still consumes the same space.
Linked list representation
An adjacency list is used in the linked representation to store
the Graph in the computer's memory. It is efficient in terms of
storage as we only have to store the values for edges.
Let's see the adjacency list representation of an undirected
graph.
467

In the above figure, we can see that there is a linked list or


adjacency list for every node of the graph. From vertex A, there
are paths to vertex B and vertex D. These nodes are linked to
nodes A in the given adjacency list.
An adjacency list is maintained for each node present in the
graph, which stores the node value and a pointer to the next
adjacent node to the respective node. If all the adjacent nodes
are traversed, then store the NULL in the pointer field of the
last node of the list.
The sum of the lengths of adjacency lists is equal to twice the
number of edges present in an undirected graph.
Now, consider the directed graph, and let's see the adjacency
list representation of that graph.

For a directed graph, the sum of the lengths of adjacency lists


is equal to the number of edges present in the graph.
468

Now, consider the weighted directed graph, and let's see the
adjacency list representation of that graph.

In the case of a weighted directed graph, each node contains an


extra field that is called the weight of the node.
In an adjacency list, it is easy to add a vertex. Because of using
the linked list, it also saves space.
Implementation of adjacency matrix representation of
Graph
Now, let's see the implementation of adjacency matrix
representation of graph in C.
In this program, there is an adjacency matrix representation of
an undirected graph. It means that if there is an edge exists from
vertex A to vertex B, there will also an edge exists from vertex
B to vertex A.
Here, there are four vertices and five edges in the graph that are
non-directed.
1. /* Adjacency Matrix representation of an undirected graph in
C */
2.
3. #include <stdio.h>
4. #define V 4 /* number of vertices in the graph */
469

5.
6. /* function to initialize the matrix to zero */
7. void init(int arr[][V]) {
8. int i, j;
9. for (i = 0; i < V; i++)
10. for (j = 0; j < V; j++)
11. arr[i][j] = 0;
12. }
13.
14. /* function to add edges to the graph */
15. void insertEdge(int arr[][V], int i, int j) {
16. arr[i][j] = 1;
17. arr[j][i] = 1;
18. }
19.
20. /* function to print the matrix elements */
21. void printAdjMatrix(int arr[][V]) {
22. int i, j;
23. for (i = 0; i < V; i++) {
24. printf("%d: ", i);
25. for (j = 0; j < V; j++) {
26. printf("%d ", arr[i][j]);
27. }
28. printf("\n");
29. }
30. }
31.
32. int main() {
33. int adjMatrix[V][V];
34.
35. init(adjMatrix);
36. insertEdge(adjMatrix, 0, 1);
37. insertEdge(adjMatrix, 0, 2);
470

38. insertEdge(adjMatrix, 1, 2);


39. insertEdge(adjMatrix, 2, 0);
40. insertEdge(adjMatrix, 2, 3);
41.
42. printAdjMatrix(adjMatrix);
43.
44. return 0;
45. }
Output:
After the execution of the above code, the output will be -

Implementation of adjacency list representation of Graph


Now, let's see the implementation of adjacency list
representation of graph in C.
In this program, there is an adjacency list representation of an
undirected graph. It means that if there is an edge exists from
vertex A to vertex B, there will also an edge exists from vertex
B to vertex A.
1. /* Adjacency list representation of a graph in C */
2. #include <stdio.h>
3. #include <stdlib.h>
4.
5. /* structure to represent a node of adjacency list */
6. struct AdjNode {
7. int dest;
8. struct AdjNode* next;
9. };
471

10.
11. /* structure to represent an adjacency list */
12. struct AdjList {
13. struct AdjNode* head;
14. };
15.
16. /* structure to represent the graph */
17. struct Graph {
18. int V; /*number of vertices in the graph*/
19. struct AdjList* array;
20. };
21.
22.
23. struct AdjNode* newAdjNode(int dest)
24. {
25. struct AdjNode* newNode = (struct AdjNode*)mallo
c(sizeof(struct AdjNode));
26. newNode->dest = dest;
27. newNode->next = NULL;
28. return newNode;
29. }
30.
31. struct Graph* createGraph(int V)
32. {
33. struct Graph* graph = (struct Graph*)malloc(sizeof(s
truct Graph));
34. graph->V = V;
35. graph-
>array = (struct AdjList*)malloc(V * sizeof(struct AdjList));

36.
37. /* Initialize each adjacency list as empty by making he
ad as NULL */
472

38. int i;
39. for (i = 0; i < V; ++i)
40. graph->array[i].head = NULL;
41. return graph;
42. }
43.
44. /* function to add an edge to an undirected graph */
45. void addEdge(struct Graph* graph, int src, int dest)
46. {
47. /* Add an edge from src to dest. The node is added at t
he beginning */
48. struct AdjNode* check = NULL;
49. struct AdjNode* newNode = newAdjNode(dest);
50.
51. if (graph->array[src].head == NULL) {
52. newNode->next = graph->array[src].head;
53. graph->array[src].head = newNode;
54. }
55. else {
56.
57. check = graph->array[src].head;
58. while (check->next != NULL) {
59. check = check->next;
60. }
61. // graph->array[src].head = newNode;
62. check->next = newNode;
63. }
64.
65. /* Since graph is undirected, add an edge from dest to
src also */
66. newNode = newAdjNode(src);
67. if (graph->array[dest].head == NULL) {
68. newNode->next = graph->array[dest].head;
473

69. graph->array[dest].head = newNode;


70. }
71. else {
72. check = graph->array[dest].head;
73. while (check->next != NULL) {
74. check = check->next;
75. }
76. check->next = newNode;
77. }
78. }
79. /* function to print the adjacency list representation of gr
aph*/
80. void print(struct Graph* graph)
81. {
82. int v;
83. for (v = 0; v < graph->V; ++v) {
84. struct AdjNode* pCrawl = graph->array[v].head;
85. printf("\n The Adjacency list of vertex %d is: \n hea
d ", v);
86. while (pCrawl) {
87. printf("-> %d", pCrawl->dest);
88. pCrawl = pCrawl->next;
89. }
90. printf("\n");
91. }
92. }
93.
94. int main()
95. {
96.
97. int V = 4;
98. struct Graph* g = createGraph(V);
99. addEdge(g, 0, 1);
474

100. addEdge(g, 0, 3);


101. addEdge(g, 1, 2);
102. addEdge(g, 1, 3);
103. addEdge(g, 2, 4);
104. addEdge(g, 2, 3);
105. addEdge(g, 3, 4);
106. print(g);
107. return 0;
108. }
Output:
In the output, we will see the adjacency list representation of
all the vertices of the graph. After the execution of the above
code, the output will be -

You might also like