Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
77 views208 pages

Data Structures and Algorithms I Pune University

Data Structures and Algorithms I Pune University

Uploaded by

amadorrivas95
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)
77 views208 pages

Data Structures and Algorithms I Pune University

Data Structures and Algorithms I Pune University

Uploaded by

amadorrivas95
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/ 208

DATA STRUCTURES

AND
ALGORITHMS – I
[As per the New Syllabus CBCS Pattern 2020-21 of SP Pune University for
[S.Y.B.Sc. (Computer Science), Semester III]

Mrs. Vinaya Vishwas Keskar Mrs. Rupali Ajay Deshpande


M.Sc. (CS), M.Phil. (CS), Pursuing Ph.D. (CS) MCA
Head of Department of Computer Science, Assistant Professor,
ATSS College of Business Studies and Pratibha College of Commerce and
Computer Applications, Computer Studies,
Chinchwad, Pune. Chinchwad, Pune.

ISO 9001:2015 CERTIFIED


© Authors
No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any
means, electronic, mechanical, photocopying, recording and/or otherwise without the prior written permission of
the authors and the publisher.

FIRST EDITION: 2020

Published by : Mrs. Meena Pandey for Himalaya Publishing House Pvt. Ltd.,
“Ramdoot”, Dr. Bhalerao Marg, Girgaon, Mumbai - 400 004.
Phone: 022-23860170, 23863863; Fax: 022-23877178
E-mail: [email protected]; Website: www.himpub.com
Branch Offices :
New Delhi : “Pooja Apartments”, 4-B, Murari Lal Street, Ansari Road, Darya Ganj,
New Delhi - 110 002. Phone: 011-23270392, 23278631; Fax: 011-23256286
Nagpur : Kundanlal Chandak Industrial Estate, Ghat Road, Nagpur - 440 018.
Phone: 0712-2721215, 3296733; Telefax: 0712-2721216
Bengaluru : Plot No. 91-33, 2nd Main Road, Seshadripuram, Behind Nataraja Theatre,
Bengaluru - 560 020. Phone: 080-41138821; Mobile: 09379847017, 09379847005
Hyderabad : No. 3-4-184, Lingampally, Besides Raghavendra Swamy Matham, Kachiguda,
Hyderabad - 500 027. Phone: 040-27560041, 27550139
Chennai : New No. 48/2, Old No. 28/2, Ground Floor, Sarangapani Street, T. Nagar,
Chennai - 600 017. Mobile: 09380460419
Pune : “Laksha” Apartment, First Floor, No. 527, Mehunpura,
Shaniwarpeth (Near Prabhat Theatre), Pune - 411 030.
Phone: 020-24496323, 24496333; Mobile: 09370579333
Lucknow : House No. 731, Shekhupura Colony, Near B.D. Convent School, Aliganj,
Lucknow - 226 022. Phone: 0522-4012353; Mobile: 09307501549
Ahmedabad : 114, “SHAIL”, 1st Floor, Opp. Madhu Sudan House, C.G. Road, Navrang Pura,
Ahmedabad - 380 009. Phone: 079-26560126; Mobile: 09377088847
Ernakulam : 39/176 (New No. 60/251), 1st Floor, Karikkamuri Road, Ernakulam,
Kochi - 682 011. Phone: 0484-2378012, 2378016; Mobile: 09387122121
Cuttack : New LIC Colony, Behind Kamala Mandap, Badambadi,
Cuttack - 753 012, Odisha. Mobile: 09338746007
Kolkata : 108/4, Beliaghata Main Road, Near ID Hospital, Opp. SBI Bank,
Kolkata - 700 010. Phone: 033-32449649; Mobile: 07439040301
DTP by : Sudhakar Shetty (On behalf of HPH Pvt. Ltd.)
Printed at : Geetanjali Press Pvt. Ltd., Nagpur. (On behalf of HPH Pvt. Ltd.)
DEDICATION

I am extremely happy to come out with this book on ‘Data Structures’ for the students. The
primary goal for this book is to acquaint students with fundamental concepts of Data Structures.
First and foremost, I express my sincere thanks to my Parents who showed me the right path
in my career which enabled me to bring forth this book.
It is with due credit that I have to thank my husband Mr. Vishwas and daughters Aarohi,
Vaidehi and son Soham who supported and motivated me to bring out this book.
I cannot think that this book would have been possible without the inputs from my beloved
Principal Dr. Aruna Deoskar (ATSS-CBSCA) and Management members for giving me this
opportunity.
I am indebted to the Himalaya Publishing House Pvt. Ltd. and Mr. S.K. Srivastav and team
for the efforts they have put in making this book a reality and helping in the neat execution of the
text.
Mrs. Vinaya Vishwas Keskar

I am very happy to introduce this book on ‘Data Structures’ for the students. I would like to
thank the Almighty for empowering me with the knowledge and capability to write this book.
The book adheres to the revised syllabus of Data Structures of S.Y.B.Sc. (Computer
Science), Sem. III and has been intended to make students aware of the concepts in easy language,
using diagrams and illustrative examples.
I would like to dedicate this book to my Parents with love and gratitude. I am thankful to my
husband and my daughters for their seamless cooperation and support.
Finally, I take this opportunity to express my deep sense of gratitude towards Mr. S.K.
Srivastav and the entire team of Himalaya Publishing House Pvt. Ltd.
Prof. Rupali Deshpande
PREFACE

We take this opportunity to present this book on “Data Structures and Algorithms – I” to
the Second Year students of B.Sc. (Computer Science) Semester - III as per the new syllabus of
SPPU, June 2019-20.
The main intention of presenting this book to student community is to familiarize them with
the basic concepts of data structures. The book has five chapters which focus on fundamental
concepts of data structures, ADTs, sorting and searching techniques, linear and non-linear data
structures like stack, queues, etc. Each chapter includes explanatory diagrams, examples and
ample practicing questions.
We have tried to give our best inputs to this book. Any suggestions and comments are
always welcome towards the improvement of this book.
– Authors
SYLLABUS

Course Code: CS 231


Title: Data Structures and Algorithms – I

Unit Topic No. of


Lectures
1 Introduction to Data Structures and Algorithm Analysis 3
1.1 Introduction
1.1.1 Concept
1.1.2 Data Type, Data Object, ADT, Data Structure
1.1.3 Need of Data Structure
1.1.4 Types of Data Structure
1.2 Algorithm Analysis
1.2.1 Space Complexity, Time Complexity
1.2.2 Best, Worst, Average Case Analysis, Asymptotic Notation (Big O,
Omega Ÿ, Theta Ĭ)
2 Array as a Data Structure 8
ADT of Array, Operations
2.1 Searching Linear, Binary, Sentinel, Analysis and Comparison
2.2 Sorting Terminology – Internal, External, Stable, In-place Sorting
2.2.1 Comparison Based Sorting Lower Bound on Comparison-based Sorting,
Methods – Bubble Sort, Insertion Sort, Selection Sort, Quick Sort, Merge
Sort
2.2.2 Non-Comparison Based Sorting Radix Sort, Counting Sort
2.2.3 Comparison of Sorting Methods
3 Linked List 9
3.1 List as a Data Structure
3.2 Dynamic Implementation of Linked List
3.3 Types of Linked List – Singly, Doubly, Circular
3.4 Operations on Linked List – Create, Traverse, Insert, Delete, Search, Reverse
3.5 Applications of Linked List – Polynomial Manipulation, Memory Management
in OS
Generalized Linked List – Concept, Representation, Multiple-Variable Polynomial
Representation using Generalized List.
4 Stacks 8
4.1 Introduction
4.2 Representation – Static and Dynamic
4.3 Operations – init(), push(), pop(), isEmpty(), isFull(), peek()
4.4 Application – String Reversal, Function Call, Infix to Postfix, Infix to Prefix,
Postfix Evaluation, Backtracking – 4 Queens Problem
5 Queues 8
5.1 Introduction
5.2 Representation – Static and Dynamic
5.3 Operations – init(), enqueue(), dequeue(), isEmpty(), isFull(), peek()
5.4 Types of Queue
5.4.1 Simple Queue
5.4.2 Circular Queue
5.4.2 Priority Queue
5.4.3 Double Ended Queue
5.5 Applications – Job Scheduling with Priority
Total 48
CONTENTS

1. Introduction to Data Structures and Algorithm Analysis 1 – 14

2. Array as a Data Structure 15 – 51

3. Linked List 52 –120

4. Stacks 121 – 158

5. Queues 159 – 199


INTRODUCTION TO DATA
1 STRUCTURES AND
ALGORITHM ANALYSIS

Structure:
1.1 Introduction
1.1.1 Concept
1.1.2 Data Type, Data Object, ADT, Data Structure
1.1.3 Need of Data Structure
1.1.4 Types of Data Structure
1.2 Algorithm Analysis
1.2.1 Space Complexity, Time Complexity
1.2.2 Best, Worst, Average Case Analysis, Asymptotic Notation (Big O, Omega Ÿ, Theta Ĭ)

1.1 Introduction
1.1.1 Concept
Data is nothing but a collection of numbers, alphabets and symbols combined to represent
information.
Data Structures are 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. Examples of Data
Structures are arrays, Linked List, Stack, Queue, etc. In Computer Science data structures are
used in Operating System, Compiler Designing, Artificial intelligence, Graphics and many other
areas.
Data Structures are the main part of many computer science algorithms as they enable the
programmers to handle the data in an efficient way. They improve performance of a software or a
program by enabling the software to store and retrieve the user’s data efficiently.
Basic Terminology
Data structures form the main components of any program or software. A programmer has
to choose the appropriate data structure for his program. Let us see some basic terminology used
in relation to data structures.
Data: It is defined as an elementary value or the collection of values, for example,
employee’s name and his ID are the data about the employee.
Group Items: Data items which have sub data items are called Group item, for example,
name of an employee can have first name and the last name.
Record: It is the collection of various data items, for example, if we talk about the
employee’s entity, then name, address, department, date of joining and salary can be grouped
together to form the record for the employee.
2 Data Structures and Algorithms – I

File: A File is a collection of various records of one type of entity, for example, if there are
60 students in the class, then there will be 20 records in the related file where each record contains
the data about each student.
Attribute and Entity: An entity is something that represents the class of certain objects. It
may have various attributes, where each attribute represents the particular property of that entity.
Field: This is a single elementary unit of information that represents the entity’s attribute.
1.1.2 Data Type
A data type is a term which refers to the kinds of data that variables may “hold” in a
programming language. To represent data in computers, basic data types supported by most
programming languages are integer numbers (of varying sizes), Floating-point numbers (which
approximate real numbers), characters and Booleans.
Data types actually define the operations that can be performed on the data, the meaning of
the data, and what type of values can be stored. Data types are generally classified as follows:
Primitive Data Types
z These are the basic data types provided by a programming language as a basic building
block. Complicated, composite data types are constructed from primitive data types.
z These are also called as built-in types, for which many programming languages provide
built-in support.
For example,‘C’ language has primitive data types int, float, char, long and short and
double. In Java, primitive data types are byte, short, int, long, float, double, char and Boolean.
Composite Data Types
Composite data types are derived from more than one primitive type. A primitive type can
be derived to form a compound type which is generally a new data type, for example, array-of-
integer is a different type to integer.
z An array (also called vector, list, or sequence) stores a number of elements of same data
type and provides random access to individual elements. Arrays can be of fixed-length or
variable length.
z Record/tuples are among the simplest data structures. A record is a value that contains
other values, in fixed number and sequence and typically indexed by names. The
elements of records are usually called fields or members.
z Union. A union type may have a number of permitted primitive types stored in its
instances, e.g., “float or long integer”. Unlike a record, which may contain a float and an
integer; in union, there is only one type allowed at a time.
z In object oriented languages, an object contains various data fields, like a record, and one
or more subroutines for accessing or modifying them, called methods.
Data Object
A data object is a region of storage that contains a value or group of values. Each value can
be accessed using its identifier or a more complex expression that refers to the object. In addition,
each object has a unique data type. In object oriented languages, the individual class members are
also called objects.
Introduction to Data Structures and Algorithm Analysis 3

Definition: A data object represents a container for data values, a place where data values
may be stored and later retrieved.
Data objects can be of two types:
1. Programmer-defined: These are available at program execution. For example, variables,
constant, arrays, files etc.
2. System defined: These are not directly accessible to programmer. Examples are run time
storage like stacks, file buffers, free space lists.
Data values can be a single number or a pointer to other objects and characters.
Data object is usually represented as storage in computer memory and a data value is
represented by a pattern of bits. So we can represent the relation between data object and data
value as shown below.

Data Object – A location Data Value – Bit Data Object bound


in computer memory pattern to store to value 13
with name X number 13 in a program

Fig. 1.1: A Simple Data Object with Value 13

Abstract Data Types (ADT)


The abstract data type is special kind of data type, whose behaviour is defined by a set of
values and set of operations. The meaning of the keyword “Abstract” is that a user can use these
data types, he can perform different operations, but how these operations work is totally hidden
from the user. The ADT consists of primitive data types, but operation logic is hidden.
Some of the ADTs are Stack, Queue, List and following are some operations on ADTs:
z Stack í
1. isFull() - used to check whether stack is full.
2. isEmpty() - used to check whether stack is empty.
3. push(x) - used to push x into the stack
4. pop() - used to delete one element from top of the stack
5. peek() - used to get the top most element of the stack
6. size() - used to get number of elements present into the stack
z Queue í
1. isFull() - used to check whether queue is full.
2. isEmpty() - used to check whether queue is empty.
3. insert(x) - used to add x into the queue at the rear end.
4. delete() - used to delete one element from the front end of the queue.
5. size() - used to get number of elements present in the queue.
z List í
1. size() - used to get number of elements present into the list
2. insert(x) - used to insert one element into the list
4 Data Structures and Algorithms – I

3. remove(x) - used to remove given element from the list


4. get(i) - is used to get element at position i
5. replace(x, y) - used to replace x with y value.
1.1.3 Need of Data Structures
Over the period of time with the advancements in Computer Science, applications became
complex and amount of data increased day by day, certain problems arose:
1. Basic data types are not enough: Data stored in bits and bytes has to be accessed and
manipulated many times. Each programming language provides a set of built in data
types which allow data to be stored in a meaningful format and a set of operation to
manipulate this data.
2. As the computer industry grew, the requirement of users with respect to data processing
became complex and large. To cope up with these problems, advanced programming
languages were introduced with more advanced operating system.
3. To solve these data processing problems, the basic data types provided by programming
language are not enough. Thus, there is a need for better data structures. These data
structures may be a combination or collection of the basic data types with specific
properties and operations like:
z Processor Speed: As the data is growing day by day to the billions of files per entity,
processor may fail to deal with a large amount of data.
z Data Search: Consider and at a base of say 150 items in a store, and an application
needs to search for a particular item, it needs to traverse 150 items every time, which
may slow down the search process.
z Multiple Requests: If thousands of users are searching the data simultaneously on a
web server, then a large server can also fail during that process.
Here data structures play their role. 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.
Thus, a Computer Science program can be viewed as a function which can be applied to
certain input data in order to produce certain output. So, algorithm = Program + Data. Hence, we
must study the data structures which represent data. The success of a software project depends
upon the proper choice of algorithms and proper representation of the data. We need best methods
to describe and process the data. So, there is a need of data structure, which organizes the data
elements. Thus, the study of Data Structures is fundamental to computer Science.
Advantages of Data Structures
1. Efficiency: Efficiency of a program is highly dependent on the choice of data structures.
Suppose, we need to search for a particular record. If the data is organized in an array, we
have to search sequentially element by element. Hence, using array may not be very
efficient here. Better data structures which can make the search process efficient are
ordered array, binary search tree or hash tables.
2. 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 and can be used by different users.
Introduction to Data Structures and Algorithm Analysis 5

3. Abstraction: Data structure is specified by the ADT which provides a level of abstraction.
Good example of abstraction is a client program which accesses the data structure
through interface only, without getting into the implementation details.
1.1.4 Types of Data Structures
There are various types of data structures based on certain classifications, which are shown
in the diagram below. Two main categories are primitive and non-primitive data structures.
Let’s understand one by one in brief in this chapter, each of which will be studied in detail in
further chapters.
Primitive Data Structures
Primitive Data Structures are the basic data structures that directly operate upon the machine
instructions. These may be represented differently on different computers. Integers, Floating
point numbers, Character constants, String constants and Pointers come under this category.
Non-primitive Data Structures
These data structures are more complicated data structures and are derived from primitive
data structures. Here same or different data items are grouped with relationship between each data
item. Arrays, Lists and Files come under this category.

Data Structures

Primitive Non-Primitive
Data Structures Data Structures

Float Chat
Integer Pointers
Linear Non-linear

Static Dynamic Tree Graph

Array Linked List Stack Queue

Fig. 1.2

Linear Data Structures: Here 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.
6 Data Structures and Algorithms – I

Types of Linear Data Structures are given below:


1. 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:
marks[0],marks[1], marks[2], marks[3],......... marks[60].
2. Linked List: Linked list is a linear data structure 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.
3. 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 is same like a real-world stack, for example:
piles of plates or deck of cards etc.
4. Queue: Queue is a linear list in which elements can be added 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 open at both ends therefore it
follows First-In-First-Out (FIFO) methodology for storing the data items.
Non-Linear Data Structures: In this type, 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:
1. Trees: Trees have basic elements called nodes. Trees are multilevel data structures with a
hierarchical relationship among its nodes. The bottom most nodes in the hierarchy are
called leaf nodes 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 child except the leaf nodes whereas each node can
have at the most one parent except the root node. There are various categories of trees.
2. Graphs: Graphs can be defined as the pictorial representation of the set of elements
called as vertices which are 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 cannot have one.
Operations on Data Structures
Following operations can be performed on the data structures:
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.
For 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
Introduction to Data Structures and Algorithm Analysis 7

sum, then we will divide 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 this situation is called as underflow.
4. Searching: The process of finding the location of an element within the data structure is
called Searching.
5. Sorting: is a process of arranging the data structure in a specific order. There are various
algorithms that can be used to perform sorting like 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 are clubbed or joined to produce the third list, List C of size (m+n), then this
process is called merging.
1.2 Algorithm – Definition and Characteristics
Often computer science is viewed as a study of algorithms. This study covers four distinct
areas:
(i) Machines for executing algorithms — This area includes everything from the smallest
pocket calculator to the largest general-purpose digital computer.
(ii) Languages for describing algorithms — Languages can be closest to the physical
machine and at the other end are languages designed for sophisticated problem solving.
(iii) Foundations of algorithms — This includes checking whether a particular task is
accomplishable by a computing device; or what is the minimum number of operations
necessary for any algorithm which performs a certain function?
(iv) Analysis of algorithms — An algorithm’s behaviour pattern or performance profile is
measured in terms of the computing time and space that are consumed while the
algorithm is processing. Finding the worst and average time and how often they occur are
typical.
Definition: An algorithm is a finite set of instructions which, if followed, accomplish a
particular task.
Characteristics of an Algorithm
Not all procedures can be called an algorithm. An algorithm should have the following
characteristics:
1. Unambiguous/definiteness: An algorithm should be clear and unambiguous. Each of its
steps (or phases), and their inputs/outputs should be clear and must lead to only one
meaning.
2. Input: An algorithm should have 0 or more well-defined inputs which are externally
supplied.
3. Output: An algorithm should have 1 or more well-defined outputs, and should match the
desired output.
8 Data Structures and Algorithms – I

4. Finiteness: Algorithms must terminate after a finite number of steps.


5. Effectiveness/Feasibility: Every instruction must be sufficiently basic that it can in
principle be carried out by a person using only pencil and paper. It is not enough that each
operation be definite as in (1.), but it must also be feasible with the available resources.
6. Independent: An algorithm should have step-by-step directions, which should be
independent of any programming code.
An algorithm implemented ends with the transformation of data. That is why a computer is
often referred to as a data processing machine. Raw data is the input and algorithms are used to
transform it into refined data. So, instead of saying that computer science is the study of
algorithms, alternatively, we might say that computer science is the study of data. Following
factors need to be considered while studying data:
1. machines that hold data (8086, Pentium etc.).
2. languages for describing data manipulation (C, C++, Python, Java etc.).
3. foundations which describe what kinds of refined data can be produced from raw data
(Logic written in English like words).
4. structures for representing data (Bits, Bytes or words).
All programming languages share basic code constructs like loops (do, for, while), flow-
control (if-else), etc. These common constructs can be used to write an algorithm.
Let’s see how an algorithm is written for the adding two numbers.
(1) Algorithm-written in plain English:
1. START
2. declare three integers a, b & c
3. define values of a & b
4. add values of a & b
5. store output of step 4 to c
6. print c
7. STOP
(2) Algorithm written using pseudocode (using common programming constructs):
1. START ADD
2. get values of a & b
3. c ĸ a + b
4. display c
5. STOP
Usually the second method is used to describe an algorithm. It makes it easy for the analyst
to analyse the algorithm ignoring all unwanted definitions. He can observe what operations are
being used and how the process is flowing.
We design an algorithm to get a solution of a given problem. A problem can be solved more
than one way.
Introduction to Data Structures and Algorithm Analysis 9

Solution Solution
#1 #2

Problem

Solution Solution
#3 #4

Fig. 1.3

Hence, many solution algorithms can be derived for a given problem. The next step is to
analyse those proposed solution algorithms and implement the best suitable solution.
1.2.1 Space Complexity, Time Complexity
Algorithm Analysis
Efficiency of an algorithm can be analysed at two different stages, before implementation
and after implementation. They are the following:
1. Priori Analysis: Priori analysis consist of checking the algorithm before its
implementation. The efficiency of an algorithm is measured by assuming that factors like
processor speed, are constant and have no effect on the implementation. This is done
usually by the algorithm designer. It is in this method, that the Algorithm Complexity is
determined.
2. Posterior Analysis: Posterior analysis means checking the algorithm after its
implementation. In this, the algorithm is checked by implementing it in any programming
language and executing it. This gives actual analysis report about correctness, space
required, and time consumed etc.
Algorithm analysis deals with the execution or running time of various operations involved.
The running time of an operation can be defined as the number of computer instructions executed
per operation.
Algorithm Complexity
Suppose X is an algorithm and n is the size of input data, the time and space used by the
algorithm X are the two main factors, which decide the efficiency of X.
z Time Factor: Time is measured by counting the number of key operations such as
comparisons in the sorting algorithm.
z Space Factor: Space is measured by counting the maximum memory space required by
the algorithm.
10 Data Structures and Algorithms – I

The complexity of an algorithm f(n) gives the running time and/or the storage space
required by the algorithm in terms of n as the size of input data.
Space Complexity
One needs to find the amount of memory space required by the algorithm during its life
cycle. This represents Space complexity of an algorithm. The space required by an algorithm is
equal to the sum of the following two components –
z A fixed part: This is a space required to store certain data and variables, that are
independent of the size of the problem. For example, simple variables and constants used,
program size, etc.
z A variable part: This is a space required by a variable whose size depends on the size of
the problem. For example, dynamic memory allocation, recursion stack space, etc.
Let us denote Space complexity of any algorithm P by S(P). Then S(P) = C + SP(I), where
C is the fixed part and SP(I) is the variable part of the algorithm, which depends on instance
characteristic I. Let us have a simple example to explain the concept:
Algorithm: To find the sum of two numbers, say A and B
SUM(A, B)
1. START
2. C ĸ A + B + 10
3. Stop
There are three variables A, B, and C and one constant in the above algorithm. Thus, S(P) =
1 + 3. The space depends on data types of given variables and constant types and it will be
multiplied accordingly.
Time Complexity
While solving any problem using a program, there can be infinite number of solutions. For
example, consider two different algorithms to find square of a number as shown below. First
solution runs a loop for n times, starting with the number n and adding n to it, every time.

for i=1 to n
do n=n*n; return n*n;
return n;
Program 1 (Running a loop n times) Program 2 (Using * to find the square)

The second approach shown is definitely a better approach. Time complexity of an


algorithm signifies the total time required by the program to run till its completion. The time
complexity of algorithms is most commonly expressed using the big O notation. It’s an
asymptotic notation to represent the time complexity. We will study about it in detail in the next
topic.
How to Compute Time Complexity
z Time Complexity is most commonly estimated by counting the number of elementary
steps performed by any algorithm to finish execution.
Introduction to Data Structures and Algorithm Analysis 11

z For example, in the Program 1 given above, the loop will run n number of times, so the
time complexity will be n at least and as the value of n will increase, the time taken will
also increase.
z While for the second code, time complexity is constant, because it will never be
dependent on the value of n, it will always give the result in 1 step.
z Since the algorithm’s performance may vary with different types of input data, hence for
an algorithm we usually use the worst-case Time complexity of an algorithm because that
is the maximum time taken for any input size.
z Thus, Time complexity of an algorithm represents the amount of time required by the
algorithm to run to completion. Let us define time requirements as a numerical function
T(n), where T(n) can be measured as the number of steps, provided each step consumes
constant time.
z For example, addition of two n-bit integers takes n steps. As a result, the total
computational time is T(n) = c n, where c is the time taken for the addition of two bits.
Here, we observe that T(n) grows linearly as the input size increases.
1.2.2 Best, Worst, Average Case Analysis, Asymptotic Notation (Big O, Omega Ÿ, Theta Ĭ)
Asymptotic analysis of an algorithm refers to defining the mathematical bounding/framing
of its run-time performance. With the help of asymptotic analysis, one can very well conclude the
best case, average case, and worst-case scenario of an algorithm.
If there’s no input to the algorithm, it is concluded to work in a constant time. Hence
asymptotic analysis is input bound. Other than the “input” all other factors are considered
constant.
Asymptotic analysis is nothing but to compute the running time of any operation in
mathematical units of computation. For example, the running time of one operation is computed
as f(n) and may be for another operation it is computed as g(n2). This means the first operation’s
running time will increase linearly with the increase in n and the running time of the second
operation will increase exponentially as n increases. Similarly, the running time of both
operations will be nearly the same if n is significantly small.
Usually, the time required by an algorithm falls under three types –
z Best Case: Here we analyse the performance of an algorithm for the input, for which the
algorithm takes less time or space.
z Worst Case: Here we analyse the performance of an algorithm for the input, for which
the algorithm takes long time or space.
z Average Case: Here we analyse the performance of an algorithm for the input, for which
the algorithm takes time or space that lies between best and worst case.
Asymptotic notations are the mathematical notations used to describe the running time of an
algorithm when the input tends towards a particular value or a limiting value.
z For example: In bubble sort, when the input array is already sorted, the time taken by the
algorithm is linear, i.e., the best case.
z But, when the input array is in reversed, the algorithm takes the maximum time (quadratic)
to sort the elements this is the worst case.
z When the input array is neither sorted nor in reverse order, then it takes average time.
12 Data Structures and Algorithms – I

These durations are denoted using asymptotic notations. There are mainly three asymptotic
notations used to calculate the running time complexity of an algorithm: Theta notation, Omega
notation and Big-O notation.
z Big-O Notation (ȅ) – Big O notation specifically describes worst case scenario.
z Omega Notation (ȍ) – Omega(ȍ) notation specifically describes best case scenario.
z Theta Notation (ș) – This notation represents the average complexity of an algorithm.

Big-O Notation, ȅ
Big O notation specifically describes worst case scenario. It represents the upper bound
running time complexity of an algorithm. Below we give few examples to understand how we
represent the time and space complexity using Big O notation.

g(n)
f(n)
f(n)
g(n)

k k

Fig. 1.4: Big O Fig. 1.5: Omega Notation

For example, for a function f(n)


ȅ(f(n)) = {g(n): there exists c > 0 and n0 such that f(n) ” c. g(n) for all n > n0.}
These are few examples to understand how we represent the time and space complexity
using Big O notation.
O(1)
Big O notation O(1) represents the complexity of an algorithm that always execute in same
time or space regardless of the input data.
For example, the following step will always execute in same time (or space) regardless of
the size of input data.
Accessing array index(intnum=arr[5])

O(n)
Big O notation O(N) represents the complexity of an algorithm, whose performance will
grow linearly (in direct proportion) to the size of the input data.
For example, the execution time will depend on the size of array. When the size of the array
increases, the execution time will also increase in the same proportion (linearly).
Traversing an array
Introduction to Data Structures and Algorithm Analysis 13

O(n^2)
Big O notation O(n^2) represents the complexity of an algorithm, whose performance is
directly proportional to the square of the size of the input data.
O(n^2)example
Traversing a 2D array
Other examples: Bubble sort, insertion sort and selection sort algorithms
Similarly, there are other Big O notations such as: logarithmic growth O(logn), log-linear
growth O(nlogn), exponential growth O(2^n) and factorial growth O(n!).The performance of
algorithms denoted by these notations can be compared as:
O(1)< O(log n)< O (n)< O(n log n)< O(n^2)< O (n^3)< O(2^n)< O(n!)

Omega Notation, ȍ
Omega notation ȍ(n) particularly describes best case scenario. It represents the lower bound
running time complexity of an algorithm. So if we represent a complexity of an algorithm in
Omega notation, it means that the algorithm cannot be completed in less time than this, it
would at least take the time represented by Omega notation or it can take more (when not in best
case scenario). Refer to figure 5 shown above.
For example, for a function f(n)
ȍ(f(n)) • { g(n): there exists c > 0 and n0 such that g(n) ” c.f(n) for all n > n0}
Theta Notation, ș
Theta notation describes both upper bound and lower bound of an algorithm. Thus, it defines
exact asymptotic behaviour. In the real case scenario the algorithm not always run on best and
worst cases, the average running time lies between best and worst and can be represented by the
theta notation.
Complexity

Input data

Fig. 1.6: Theta Notation


14 Data Structures and Algorithms – I

The notation ș(n) is the formal way to express both the lower bound and the upper bound of
an algorithm’s running time. It is represented as follows:
ș(f(n)) = { g(n) if and only if g(n) = ȅ(f(n)) and g(n) = ȍ(f(n)) for all n > n0. }

QUESTIONS
Practice Questions (2 marks)
1. What is data structure?
2. How to calculate count of Best, Worst and Average case?
3. What are the different types of data structures?
4. Explain the following:
(a) Data object
(b) Primitive and non-primitive data structures
(c) Composite data types
5. What are Abstract Data types?
6. What is Big-O notation?
7. Explain different types of asymptotic notations in detail.
8. Explain different types of dynamic memory allocation functions.
9. How to measure performance of an algorithm?
10. What is Space and Time Complexity?
Practice Questions (4 marks)
1. Explain various types of asymptotic notations in detail.
2. What is an algorithm? Explain its characteristics.
3. Explain types of data structures.
ˆˆˆ
2 ARRAY AS A DATA STRUCTURE

Structure:
ADT of Array, Operations
2.1 Searching Linear, Binary, Sentinel, Analysis and Comparison
2.2 Sorting Terminology – Internal, External, Stable, In-place Sorting
2.2.1 Comparison Based Sorting Lower Bound on Comparison Based Sorting, Methods-
Bubble Sort, Insertion Sort, Selection Sort, Quick Sort, Merge Sort
2.2.2 Non Comparison Based Sorting Radix Sort, Counting Sort
2.2.3 Comparison of Sorting Method

ADT of Array, Operations


ADT indicates for Abstract Data Type.
The array is a basic abstract data type that holds an ordered collection of items accessible
by an integer index. These items can be anything from primitive types such as integers to more
complex types like instances of classes. Since it’s an ADT, it doesn’t specify an implementation,
but is almost always implemented by an array (data structure) or dynamic array. Some example in
C++ are:
int[] arrA = new int[1];
String[] arrB = new String[1];
Person[] arrC = new Person[3]; // where Person is treated as a defined
Array is a set of memory locations which can hold a fixed number of items of the same data
type. Most of the data structures make use of arrays to implement their algorithms. Important
terms to understand the concept of array are:
z Element í Each item stored in an array is called an element.
z Index í Each location of an element in an array has a numerical index, which is used to
identify the element.
Arrays can be declared in various ways in different languages. Let’s take C array declaration.
Following figure shows the elements and the index position for each element:
16 Data Structures and Algorithms – I

Fig. 2.1: Array Declaration and Representation

Following are the basic operations supported by an array:


1. Traversing 2. Insertion
3. Deletion 4. Search
5. Update
Here we give some C programs on array operations:
1. C Program: Traversing an Array
#include<stdio.h>
int main(){
int i=0;
int marks[5];//declaration of array
marks[0]=80;//initialization of array
marks[1]=60; marks[2]=70;
marks[3]=85; marks[4]=75;
//traversal of array
for(i=0;i<5;i++){
printf(“%d \n”,marks[i]);
}//end of for loop
return 0;
}

2. C Program: Insert an Element in an Array in a Given Position


#include <stdio.h>
void main()
{
int array[10];
int i, j, n, m, temp, key, pos;
printf(“Enter how many elements \n”);
scanf(“%d”, &n);
Array as a Data Structure 17

printf(“Enter the elements \n”);


for (i = 0; i< n; i++)
scanf(“%d”, &array[i]);
printf(“Input array elements are \n”);
for (i = 0; i< n; i++)
printf(“%d\n”, array[i]);
printf(“Enter the element to be inserted \n”); scanf(“%d”, &key);
for (i = 0; i< n; i++) {
if (key < array[i])
{
pos = i;
break;
}
if (key > array[n-1])
{
pos = n;
break;
}
}
if (pos != n)
{
m = n - pos + 1 ;
for (i = 0; i<= m; i++)
array[n - i + 2] = array[n - i + 1] ;
}
array[pos] = key;
printf(“Final list is \n”);
for (i = 0; i< n + 1; i++)
printf(“%d\n”, array[i]);
}

3. C Program: Delete an Element in an Array


#include <stdio.h>
int main()
{
int array[100], position, i, n;
printf(“Enter number of elements in array\n”);
scanf(“%d”, &n);
printf(“Enter %d elements\n”, n);
for (i = 0; i < n; i++)
scanf(“%d”, &array[i]);
printf(“Enter the location of the element to be deleted \n”);
scanf(“%d”, &position);
18 Data Structures and Algorithms – I

if (position >= n+1)


printf(“Deletion not possible.\n”);
else
{
for (i = position - 1; i < n - 1; i++)
array[i] = array[i+1];
printf(“Array after deletion:\n”);
for (i = 0;i < n - 1; i++)
printf(“%d\n”, array[i]);
}
return 0;
}

4. C Program: Search an Element in Array


#include <stdio.h>
int main()
{
int array[100], search, i, n;
printf(“Enter number of elements in array\n”);
scanf(“%d”, &n);
printf(“Enter %d integer(s)\n”, n);
for (i = 0; i < n; i++)
scanf(“%d”, &array[i]);
printf(“Enter a number to search\n”);
scanf(“%d”, &search);
for (i = 0; i < n; i++)
{
if (array[i] == search
{
printf(“%d is present at location%d.\n”, search,i+1);
break;
}
}
if (i == n)
printf(“%d is not found in the array.\n”, search);
return 0;
}
Arrays are categorized as one dimensional array and multidimensional array.
2.1 Searching
Searching is the process of finding some particular element in the list. If the element is
present in the list, then the search process is called successful and the process returns the location
of that element, otherwise the search is called unsuccessful.
Array as a Data Structure 19

There are two popular search methods that are widely used in order to search some item into
the list. However, choice of the algorithm depends upon the arrangement of the list.
z Linear Search
z Binary Search

Linear Search
Linear search is the simplest search algorithm and often called sequential search. In this type
of searching, we simply traverse the list completely and match each element of the list with the
item whose location is to be found. If the match found then location of the item is returned
otherwise the algorithm return NULL.
Linear search is generally used to search an unordered list where the items are not sorted.
The algorithm of linear search is given as follows.
Algorithm
Linear_search(a, n, val)
1: [initialize] set pos = -1
2: [initialize] set i = 1
3: repeat step 4 while i<=n
4: if a[i] = val
set pos = I
print pos, go to step 6
[end of if]
set i = i + 1
[end of loop]
5: if pos = -1
print “ value is not present in the array “
[end of if]
6: exit
Complexity of Algorithm
Time Comlexity: Best case: O(1), worst case: O(n), Worst Case: O(n)
Space Complexity: Worst case: O(1)
C Program: Linear Search
#include<stdio.h>
void main ()
{
int a[10] = {10, 23, 40, 1, 2, 0, 14, 13, 50, 9};

int item, i,flag;


printf(“\nEnter Item which is to be
searched\n”);
20 Data Structures and Algorithms – I

scanf(“%d”,&item);
for (i = 0; i< 10; i++)
{
if(a[i] == item)
{
flag = i+1;
break;
}
else
flag = 0;
}
if(flag != 0)
printf(“Item found at location %d\n”,flag);
else
printf(“\n Item not found\n”);
}

Output:
Enter Item which is to be searched
20
Item not found
Enter Item which is to be searched
23
Item found at location 2

Time Complexity of Linear Search


Best Case
z The element being searched may be found at the first position.
z In this case, the search terminates in success with just one comparison.
z Thus in best case, linear search algorithm takes O(1) operations.
Worst Case
z The element being searched may be present at the last position or not present in the array
at all.
z In the former case, the search terminates in success with n comparisons.
z In the latter case, the search terminates in failure with n comparisons.
z Thus, in worst case, linear search algorithm takes O(n) operations.
Thus, Time Complexity of Linear Search Algorithm is O(n),where n is the number of
elements in the linear array.
Array as a Data Structure 21

Binary Search
z Binary search technique works efficiently on the sorted lists. Hence, in order to search an
element into some list by using binary search technique, we must ensure that the list is
sorted.
z Binary search follows divide and conquer approach in which, the list is divided into two
halves and the item is compared with the middle element of the list.
z If the match is found then, the location of middle element is returned, otherwise we
search into either of the halves depending upon the result produced through the match.
Binary search algorithm is given below.
BINARY_SEARCH(A, lower_bound, upper_bound, VAL)
Step 1: [initialize] set beg = lower_bound
end = upper_bound, pos = - 1
Step 2: repeat steps 3 and 4 while beg <=end
Step 3: set mid = (beg + end)/2
Step 4: if a[mid] = val
set pos = mid
print pos
go to step 6
else if a[mid] >val
set end = mid - 1
else
set beg = mid + 1
[end of if]
[end of loop]
Step 5: if pos = -1
print “value is not present in the array”
[end of if]
Step 6: exit
Example of Binary Search Algorithm
Consider an example array arr = {1, 5, 7, 8, 13, 19, 20, 23, 29}. Find the location of the item
23 in the array.
In First Step:
BEG = 0
END = 8
MID = 4
a[mid] = a[4] = 13 < 23, therefore

In Second Step:
Beg = mid +1 = 5
End = 8
22 Data Structures and Algorithms – I

mid = 13/2 = 6
a[mid] = a[6] = 20 < 23, therefore;

In Third Step:
beg = mid + 1 = 7
End = 8
mid = 15/2 = 7
a[mid] = a[7]
a[7] = 23 = item;
Therefore, set location = mid;
The location of the item will be 7.
Following figure explains the binary search algorithm for this example.

Return location 7

Fig. 2.2
Array as a Data Structure 23

Binary Search Program in C Using Recursion


#include<stdio.h>
int b_search(int[], int, int, int);
void main ()
{
int arr[10] = {10, 19, 2, 63, 45, 51, 72, 91, 96, 100};
int item, location=-1;
printf(“Enter the item to search \n”);
scanf(“%d”,& item);
location = b_search (arr, 0, 9, item);
if(location != -1)
printf(“Item found at location %d”,
location);
else
printf(“Item not found”);
}
int b_search (int a[], int beg, int end, int item)
{
int mid;
if(end >= beg)
{
mid = (beg + end)/2;
if(a[mid] == item)
return mid+1;
else if(a[mid] < item)
return b_search(a,mid+1,end,item); else
return b_search (a,beg,mid-1,item);
}
return -1;
}

Output:
Enter the item which you want to search
19
Item found at location 2
Binary Search Algorithm searches an element by comparing it with the middle most element
of the array.
Then, following three cases are possible –
Case-1: If the element is found to be the middle most element, its index is returned.
Case-2: If the element is found to be greater than the middle most element, then its search is
further continued in the right sub array of the middle most element.
24 Data Structures and Algorithms – I

Case-3: If the element is found to be smaller than the middle most element, then the search is
further continued in the left sub array of the middle most element.
The search keeps on repeating on the sub arrays until the desired element is found or size of
the sub array reduces to zero.
Time Complexity Analysis of Binary Search –
Binary Search time complexity analysis is done below –
z In each iteration or in each recursive call, the search gets reduced to half of the array.
z So, for n elements in the array, there are log2n iterations or recursive calls.
Thus, we have – Time Complexity of Binary Search Algorithm is O(log2n). Here, n is the
number of elements in the sorted linear array. This time complexity of binary search remains
unchanged irrespective of the element position even if it is not present in the array.
Time Complexity: Worst case: O(log n), Best case: O(1), Average case: O(log n)
Worst case space complexity: O(1)
Sentinel Search
Sentinel Linear Search of Linear Search where the number of comparisons are reduced as
compared to a traditional linear search. When a linear search is performed on an array of size N
then in the worst case:
1. A total of N comparisons are made when the element to be searched is compared to all
the elements of the array.
2. and (N + 1) comparisons are made for the index of the element to be compared so that the
index is not out of bounds of the array.
The number of comparisons can be reduced in a Sentinel Linear Search. Here, the last
element of the array is replaced with the element to be searched and then the linear search is
carried out on the array without checking whether the current index is inside the index range of
the array, because the element to be searched will definitely be found inside the array even if it
was not present in the original array since the last element got replaced with it. So, the index to be
checked will never be out of bounds of the array. The number of comparisons in the worst case
here will be (N + 2).
Examples:
Input: arr[] = {10, 20, 180, 30, 60, 50, 110, 100, 70}, x = 180
Output: 180 is present at index 2
Input: arr[] = {10, 20, 180, 30, 60, 50, 110, 100, 70}, x = 90
Output: Not found
C Program: Sentinel Search
#include <iostream>
void sentinelSearch(int arr[], int n, int x) // Function to search x in the given array
{
int last = arr[n - 1]; // Last element of the array
arr[n - 1] = x; // Element to be searched is placed at the last index
int i = 0;
Array as a Data Structure 25

while (arr[i] != x)
i++;
arr[n - 1] = last; // Put the last element back
if ((i< n - 1) || (x == arr[n - 1]))
printf( “%d is present at index %d “,x,i);
else
printf(“Not found”);
}
//-----------------------------------------------------------------
int main()
{
int arr[] = {10, 20, 180, 30, 60, 50, 110, 100, 70};
int n = sizeof(arr) / sizeof(arr[0]);
int x = 30;
sentinelSearch(arr, n, x);
return 0;
}

Output:
30 is present at index 3.
Time Complexity Analysis of Linear Search
Best Case –
z The element being searched may be found at the first position.
z In this case, the search terminates in success with just one comparison.
z Thus in best case, linear search algorithm takes O(1) operations.

Worst Case –
The element being searched may be present at the last position or not present in the array at
all.
z In the former case, the search terminates in success with n comparisons.
z In the later case, the search terminates in failure with n comparisons.
z Thus, in worst case, linear search algorithm takes O(n) operations.
Thus, time Complexity of Linear Search Algorithm is O(n),where n is the number of
elements in the linear array.
Linear Search Efficiency
Linear Search is less efficient when compared with other algorithms like Binary Search &
Hash tables.
The other algorithms provide faster searching mechanism.
26 Data Structures and Algorithms – I

Time Complexity of Binary Search


z Certain complexities like O(1) and O(n) are simple to understand. O(1) means it requires
constant time to perform operations like to reach an element in constant time as in case of
dictionary . Complexity O(n) means, it depends on the value of n to perform operations
such as searching an element in an array of n elements.
z But for O(log n), it is not that simple. Let us discuss this with the help of Binary Search
Algorithm whose complexity is O(log n).
This is how binary search works:
z We search a sorted array by repeatedly dividing the search interval in half.
z Initially we start with an interval that covers the whole array.
z If the value of the search key is less than the item in the middle of the interval, narrow the
interval to the lower half. Otherwise, we narrow it to the upper half.
z We repeatedly check until the value is found or the interval is empty.
Example: Suppose we have sorted Array of 10 elements:
2, 5, 8, 10, 16, 20, 38, 56, 72, 91
Suppose that we want to search number 20.
Now to find 23, there will be many iterations with each having steps as given below:
z Iteration 1:

Array: 2, 5, 8, 10, 16, 20, 38, 56, 72, 91


z Select the middle element. (here 16)
z Since 20 is greater than 16, so we divide the array into two halves and consider the sub-
array after element 16.
z Now this subarray with the elements after 16 will be taken into next iteration.
z Iteration 2:
Array: 20, 38, 56, 72, 91
z Select the middle element. (now 56)
z Since 20 is smaller than 56, so we divide the array into two halves and consider the sub-
array before element 56.
z Now this subarray with the elements before 56 will be taken into next iteration.
z Iteration 3:
Array: 20, 38
z Select the middle element. (now 20)
z Since 20 is the middle element. So the iterations will now stop.
Calculating Time Complexity:
z The iterations in Binary Search terminate after k iterations. In the above example, it
terminates after 3 iterations, so here k = 3
z At each iteration, the array is divided by half. So let’s say the length of array at any
iteration is n.
z At Iteration 1, Length of array = n
Array as a Data Structure 27

z At Iteration 2, Length of array = n » 2


z At Iteration 3, Length of array = (n » 2) »2 = n » 22
z Therefore, after Iteration k, Length of array = n/2k
z Also, we know that after k divisions, the length of array becomes 1
z Therefore, Length of array = n/2k = 1
Ÿ n = 2k
z Applying log function on both sides:
Ÿ log2 (n) = log2 (2k)
Ÿ log2 (n) = k log2 (2)
z As (log a(a) = 1). Therefore,
Ÿ k = log2 (n)
Hence, the time complexity of Binary Search is log2(n)
Comparison between Linear Search and Binary Search
Following figures depict examples of linear and binary search to compare the two:

Fig. 2.3: Linear Search to find the Element “J” in a given Sorted List from A-X

Fig. 2.4: Binary Search to find the Element “J” in a given Sorted List from A-X

1. Linear search is iterative in nature and uses sequential approach. Whereas, binary search
implements divide and conquer approach.
2. The time complexity of linear search is O(N) ,while binary search has O(log2N).
3. The best case time in linear search is for the first element, i.e., O(1). As against, in binary
search, it is for the middle element, i.e., O(1).
4. In the linear search, worst case for searching an element is N number of comparisons. In
contrast, it is log2N number of comparisons for binary search.
28 Data Structures and Algorithms – I

5. Linear search can be implemented in an array as well as in linked list, whereas binary
search cannot be implemented directly on linked list.
6. As binary search requires the sorted array, it requires inserting a new element at proper
place to maintain a sorted list. Linear search does not require sorted elements, so elements
are easily inserted at the end of the list.
7. Linear search is easy to use, and there is no need for any ordered elements. Binary search
algorithm is bit tricky, and elements need to be necessarily arranged in order.
2.2 Sorting Terminology
A sorting algorithm puts elements of a list in a certain order. The most frequently used
orders are numerical order and lexicographical order. Efficient sorting is important for optimizing
the efficiency of other algorithms (such as search and merge algorithms) that require input data to
be in sorted lists. More formally, the output of any sorting algorithm must satisfy two conditions:
1. The output is in non-decreasing order (each element is no smaller than the previous
element).
2. The output is a permutation, which is nothing but retaining all of the original elements
after reordering of the input.
Sorting algorithms are classified on the basis of various characteristics like computational
complexity, memory usage, recursion, stability.
Internal Sort
In internal sorting, the sorting process takes place entirely within the main memory of a
computer. It is used when the data to be sorted is small enough to all be held in the main memory.
For sorting larger datasets, it may be necessary to hold only a chunk of data in memory at a
time, since main memory is insufficient to hold large datasets. The rest of the data is normally
held on some larger, but slower secondary storage device, like a hard-disk. Reading or writing of
data to and from this slower media can slow down the sorting process considerably.
External Sort
When data that we want to sort cannot be placed in-memory at a time, the sorting is called
external sorting. Data is stored on some external storage like hard-disk, CD. External Sorting is
used for huge or massive amount of data. MergeSort and its variations are generally used for
external sorting.
Stable Sorting
The algorithms that use stable sorting maintain the relative order of records with equal keys
(i.e., values). That is, a sorting algorithm is stable, if whenever there are two records R and S with
the same key and with R appearing before S in the original list, R will appear before S in the
sorted list.
In other words, a stable sort preserves the original order of input set, where the comparison
algorithm does not distinguish between two or more items. A stable sort ensures that the original
order of data having the same rank is preserved in the output.
Some sorting algorithms are stable by nature like Insertion Sort, Merge Sort and Bubble Sort
etc. Sorting algorithm like Quick Sort, Heap Sort etc. are not stable. Following figure illustrates
the stable and unstable sorting.
Array as a Data Structure 29

Fig. 2.5

In-Place Algorithm
A special feature of in-place algorithm is that it does not require an extra space because it
produces an output in the same memory area that contains the data by transforming the input ‘in-
place’. A small constant extra space used for variables is allowed.
1. An ‘in-place’ sorting algorithm directly modifies the list that is received as input instead
of creating a new list.
2. This sorting uses a small amount of extra space to manipulate the input data. In other
words, the output is placed in the correct position while the algorithm is still executing,
which means that the input will be overwritten by the desired output on run-time.
3. In-Place sorting algorithm updates input only through replacement or swapping of
elements.
4. An algorithm which is not in-place is sometimes called not-in-Place or out of Place.
5. An algorithm can only have a constant amount of extra space, counting everything
including function call and pointers. Usually, this space is O(log n).
2.2.1 Comparison Based Sorting
Any sorting problem can be represented as following:
Input: A sequence of n numbers <a1, a2, . . . , an>.
Output: A permutation (reordering) <s1,s2,…..sn> of the input sequence such that
s1<=s2…<= sn.
z A comparison based sorting algorithm uses comparison operators to find the order
between two numbers.
z Comparison sorts can be viewed abstractly in terms of decision trees. A decision tree is a
full binary tree that represents the comparisons between elements that are performed by a
particular sorting algorithm operating on an input of a given size.
z The execution of the sorting algorithm corresponds to tracing a path from the root of the
decision tree to a leaf.
z At each internal node, a comparison ai <= aj is made. The left subtree then dictates
subsequent comparisons for ai <= aj, and the right subtree dictates subsequent
comparisons for ai > aj. When it comes to a leaf, the algorithm has established the
ordering.
z Algorithms merge-sort, quicksort, and heap-sort take an input array and sort the elements
in ascending order in O(n log n) time.
30 Data Structures and Algorithms – I

Lower Bound on Comparison based Sorting


Consider sorting three numbers a1, a2, and a3. There are 3! = 6 possible combinations:
(a1, a2, a3), (a1, a3, a2),
(a2, a1, a3), (a2, a3, a1)
(a3, a1, a2), (a3, a2, a1)
The Comparison based algorithm defines a decision tree as:
Decision Tree: A decision tree is a full binary tree that shows the comparisons between
elements that are executed by an appropriate sorting algorithm operating on an input of a given
size.
Let n be the size of the array to be sorted. In a corresponding decision tree, number of leaves
will be n! (i.e., total number of comparisons). For above array of 3 numbers we have a decision
tree as follows:

Fig. 2.6: Decision Tree for Comparison Based Sorting

Consider the problem to sort n distinct numbers. Any sorting algorithm must be able to
distinguish between the n! permutations of these n numbers, since it must treat each permutation
differently in order to sort it.
The number of yes/no decisions (= the number of bits) necessary to distinguish between the
n! permutations is log2(n!); it is a lower bound for the complexity of any sorting algorithm. This
lower bound is known as the information theoretic lower bound.
We have,
n! >= (n/2)n/2
therefore,
log(n!) >= log((n/2)n/2)
= n/2. log(n/2)
€ Ÿ (n log (n))
Array as a Data Structure 31

A comparison based sorting algorithm derives exactly 1 bit of information from each
comparison (based on whether the two numbers compared are in order or not). Thus, it needs at
least log(n!) ¼ȍ(n log(n)) comparisons to distinguish between the n! possible permutations of n
distinct numbers.
This means that ȍ(n log(n)) is a lower bound for the time complexity of any sorting
algorithm that is based on comparisons.
Sorting Methods
z Bubble Sort
z Insertion Sort
z Merge Sort
z Quick Sort
z Selection Sort
Note:
1. Bubble sort, insertion sort, and selection sort are in-place sorting algorithms, because only
swapping of the element in the input array is required.
2. Bubble sort and insertion sort can be implemented as stable algorithms but selection sort
cannot be (without significant modifications).
3. Merge sort is a stable algorithm but not an in-place algorithm. It requires extra array
storage.
4. Quicksort is not stable but is an in-place algorithm.
Bubble Sort
Bubble sort also called as sinking sort, is a simple sorting algorithm where, each element of
the array is compared with its adjacent element. The algorithm sorts the list in passes. A list with
n elements requires n-1 passes for sorting.
Bubble sort is a comparison sort, the word ‘bubble’ indicates the way the smaller or larger
elements bubble up to the top of the list. If we have an array A of n elements whose elements are
to be sorted using Bubble sort. The algorithm processes in passes as follows.
z Pass 1
A[0] is compared with A[1], A[1] is compared with A[2], A[2] is compared with A[3]
and so on. At the end of pass 1, the largest element of the list is placed at the highest
index of the list.
z Pass 2
A[0] is compared with A[1], A[1] is compared with A[2] and so on. At the end of Pass 2
the second largest element of the list is placed at the second highest index of the list.
z Pass n-1
A[0] is compared with A[1], A[1] is compared with A[2] and so on. At the end of this
pass. The smallest element of the list is placed at the first index of the list.
Below given figure illustrates working of Bubble Sort:
32 Data Structures and Algorithms – I

Fig. 2.7

Bubble Sort: Algorithm


1: Repeat step 2 for i = 0 to n-1
2: For j = i + 1 to n - i
3: if a[j] > a[i]
swap a[j] and a[i]
4: exit
Bubble Sort: C Program
#include<stdio.h>
void main () {
int i, j, temp;
int a[10] = { 10, 9, 7, 101, 23, 44, 12, 78, 34, 23};
for(i = 0; i<10; i++)
Array as a Data Structure 33

{
for(j = i+1; j<10; j++)
{
if(a[j] > a[i])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
printf(“SortedList ...\n”);
for(i = 0; i<10; i++)
printf(“%d\n”,a[i]);
}

Output:
Sorted List . . .
7
9
10
12
23
34
34
44
78
101

Complexity Analysis of Bubble Sort


In bubble sort, n-1 comparisons are done in the 1st pass, n-2 in 2nd pass, n-3 in 3rd pass and
so n. The total number of comparisons thus will be,
(n-1) + (n-2) + (n-3) + ..... + 3 + 2 + 1
Sum = n(n-1)/2
i.e., O(n2)
Hence the time complexity of Bubble Sort is O(n2). Here are some facts related to bubble
sort:
z The main advantage of bubble sort is the simplicity of the algorithm.
z The space complexity for bubble sort is O(1), because only a single additional memory
space is required, i.e., for temp variable.
z Best case time complexity is O(n), it is when the list is already sorted.
34 Data Structures and Algorithms – I

z Worst and average case time complexity is O(n*n). Worst case occurs when array is
sorted in reverse order.
z Sorting In Place: Yes
z Stable: Yes
To summarize:
Scenario Complexity
Space O(1)
Worst case running time O(n2)
Average case running time O(n)
Best case running time O(n2)

z Because of its simplicity, bubble sort is mostly used to introduce the concept of a sorting
algorithm to the beginners.
z In computer graphics it is popular for its capability to detect a very small error (like swap
of just two elements) in almost-sorted arrays and fix it with just linear complexity (2n).
Like, it is used in a polygon filling algorithm, where polygon boundary lines are sorted by
their x coordinate at a specific scan line (a line parallel to x axis) and with incrementing y
their order changes (two elements are swapped) only at intersections of two lines.
z Although simplicity is its main feature, bubble sort is not a practical sorting algorithm.
This is because many other sorting algorithms have substantially better worst-case or
average complexity, often O(n log n). Even other Ɉ(n2) sorting algorithms, such as
insertion sort, generally run faster than bubble sort, and are no more complex.
Insertion Sort
Insertion sort is a simple sorting algorithm that builds the final sorted array (or list) one item
at a time. It is also much less efficient on large lists than more advanced algorithms such as
quicksort, heapsort, or merge sort. However, insertion sort provides several advantages:
1. Simple implementation technique.
2. It is efficient for smaller data sets, much like other quadratic sorting algorithms.
3. Also more efficient in practice than most other simple quadratic (i.e., O(n2)) algorithms
such as selection sort or bubble sort.
4. Adaptive, i.e., efficient for data sets that are already substantially sorted: the time
complexity is O(kn) when each element in the input is no more than k places away from
its sorted position.
5. Stable, i.e., does not change the relative order of elements with equal keys.
6. In-place, i.e., only requires a constant amount O(1) of additional memory space
7. Online, i.e., can sort a list as it receives it.
In the Bridge cards game where the players manually sort, a method similar to insertion sort
is used.
Array as a Data Structure 35

Technique used in Insertion Sort


z Consider an array X whose elements are to be sorted. Initially, X[0] is the only element
on the sorted set. In pass 1, X[2] is placed at its proper index in the array. Similarly, in
pass n-1, X[n-1] is placed at its proper index into the array.
z To insert an element X[k] to its proper index, we must compare it with all other elements
i.e., X[k-1], X[k-2], and so on until we find an element X[j] such that, X[j]<=X[k].
z All the elements from X[k-1] to X[j] need to be shifted and X[k] will be moved to X[j+1].

Algorithm: Insertion Sort


1: Repeat Steps 2 to 5 for k = 1 to n-1
2: temp = arr[k]
3: j=k-1
4: Repeat while temp <=arr[j]
arr[j + 1] = arr[j]
j=j-1
5: arr[j + 1] = temp
6: exit
Example: The following table shows the steps for sorting the sequence {3, 7, 4, 9, 5, 2, 6, 1}.
In each step, the key under consideration is underlined. The key that was moved (or left in place
because it was biggest yet considered) in the previous step is marked with an asterisk (*).
37495261
3* 7 4 9 5 2 6 1
3 7* 4 9 5 2 6 1
3 4* 7 9 5 2 6 1
3 4 7 9* 5 2 6 1
3 4 5* 7 9 2 6 1
2* 3 4 5 7 9 6 1
2 3 4 5 6* 7 9 1
1* 2 3 4 5 6 7 9
C Program: Insertion Sort
#include<stdio.h>
void main ()
{
int i,j, k,temp;
int a[10] = { 10, 9, 7, 101, 23, 44, 12, 78, 34, 23};
printf(“\nSorted elements...\n”);
for(k=1; k<10; k++)
{
temp = a[k];
j= k-1;
while(j>=0 && temp <= a[j])
{
36 Data Structures and Algorithms – I

a[j+1] = a[j];
j = j-1;
}
a[j+1] = temp;
}
for(i=0;i<10;i++)
printf(“\n%d\n”,a[i]);
}

Complexity Analysis of Insertion Sort


Insertion sort is an efficient sorting algorithm, as it does not run on pre-set conditions using
for loops, but instead it uses one while loop, which avoids extra steps once the array gets sorted.
Even though insertion sort is efficient, still, if we provide an already sorted array to the
insertion sort algorithm, it will still execute the outer for loop, thereby requiring n steps to sort an
already sorted array of n elements, which makes its best case time complexity a linear function of
n.
Worst Case Time Complexity [Big-O ]: O(n2)
Best Case Time Complexity [Big-omega]: O(n)
Average Time Complexity [Big-theta]: O(n2)
Space Complexity: O(1)
Merge Sort
Merge sort uses the rule of Divide and Conquer to sort a given set of numbers/elements,
recursively, hence consuming less time. We learned about Selection Sort and Insertion Sort, both
of which have a worst-case running time of O(n2). As the size of input grows, insertion and
selection sort can take a long time to run.
Merge sort, on the other hand, runs in O(n*log n) time in all the cases.
Before seeing the working of merge sort let us understand the rule of divide and conquer.
Divide and Conquer
If we can break a single big problem into smaller sub-problems, solve the smaller sub-
problems and combine their solutions to find the solution for the original big problem, it becomes
easier to solve the whole problem.
Consider an array A of n number of elements. The algorithm processes the elements in 3
steps.
1. If A Contains 0 or 1 elements then it is already sorted, otherwise, Divide A into two sub-
array of equal number of elements.
2. Conquer means sort the two sub-arrays recursively using the merge sort.
3. Combine the sub-arrays to form a single final sorted array maintaining the ordering of the
array.
The main idea behind merge sort is that, the short list takes less time to be sorted.
z Merge Sort is a Divide and Conquer algorithm.
z It divides input array in two halves, calls itself for the two halves and then merges the two
sorted halves.
Array as a Data Structure 37

z The merge() function is used for merging two halves. The merge(arr, l, m, r) is key
process that assumes that arr[l..m] and arr[m+1..r] are sorted and merges the two sorted
sub-arrays into one.
Algorithm: Merge Sort
MergeSort(arr[], l, r)
If r > l

1. Find the middle point to divide the array into two halves:
middle m = (l+r)/2
2. Call mergeSort for first half:
Call mergeSort(arr, l, m)
3. Call mergeSort for second half:
Call mergeSort(arr, m+1, r)
4. Merge the two halves sorted in step 2 and 3:
Call merge(arr, l, m, r)

The following diagram shows the complete merge sort process for an example array {38, 27,
43, 3, 9, 82, 10}. If we take a closer look at the diagram, we can see that the array is recursively
divided in two halves till the size becomes 1. Once the size becomes 1, the merge processes
comes into action and starts merging arrays back till the complete array is merged.

Fig. 2.8
38 Data Structures and Algorithms – I

C Program: Merge Sort


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

void merge(int arr[], int l, int m, int r)


{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

int left[n1], right[n2]; // temporary arrays left and right


//copy data to temp arrays left[] and right[]
for (i = 0; i< n1; i++)
left[i] = arr[l + i];
for (j = 0; j < n2; j++)
right[j] = arr[m + 1+ j];

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


I = 0;
j = 0;
k = l;
while (i< n1 && j < n2)
{
if (left [i] <= right[j])
{
arr[k] = left[i];
i++;
}
else
{
arr[k] = right[j];
j++;
}
k++;
}
// Copy the remaining elements of left[]
while (i< n1)
{
arr[k] = left[i];
i++;
k++;
}
Array as a Data Structure 39

//copy the remaining elements of right[]


while (j < n2)
{
arr[k] = right[j];
j++;
k++;
}
}
//----------------------------------------------------------------------------------------
// l is for left index and r is right index of the sub-array of arr to be sorted
void mergeSort(int arr[], int l, int r)
{
if (l < r)
{
int m = l+(r-l)/2;
mergeSort(arr, l, m);
mergeSort(arr, m+1, r);
merge(arr, l, m, r);
}
}
//-----------------------------------------------------------------------------------------
void display(int a[], int size)
{
int i;
for (i=0; i< size; i++)
printf(“%d”, a[i]);
printf(“\n”);
}
//-------------------------------------------------------------------------------------------
int main()
{
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr)/sizeof(arr[0]);
printf(“Given array is \n”);
display(arr, arr_size);
mergeSort(arr, 0, arr_size - 1);
printf(“\nSorted array is \n”);
display(arr, arr_size);
return 0;
}
40 Data Structures and Algorithms – I

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

Complexity Analysis of Merge Sort


z Merge Sort is quite fast, and has a time complexity of O(n*log n). It is also a stable sort,
which means the “equal” elements are ordered in the same order in the sorted list.
z When numbers are divided into half in every step, it can be represented using a
logarithmic function, which is log n and the number of steps can be represented by log n
+ 1(at most).
z To find out the middle of any subarray, we perform a single step operation, which takes
time O(1).To merge the subarrays, made by dividing the original array of n elements, a
running time of O(n) will be required.
z Hence the total time for mergeSort function will become n(log n + 1), which gives us a
time complexity of O(n*log n). Thus,
Worst Case Time Complexity [Big-O ]: O(n*log n)
Best Case Time Complexity [Big-omega]: O(n*log n)
Average Time Complexity [Big-theta]: O(n*log n)
Space Complexity: O(n)
z Time complexity of Merge Sort is O(n*Log n) in all the 3 cases (worst, average and best)
as merge sort always divides the array in two halves and takes linear time to merge two
halves.
z It requires equal amount of additional space as the unsorted array and hence is not at all
recommended for searching large unsorted arrays.
z It is the best Sorting technique used for sorting Linked Lists.

Quick Sort
Like Merge Sort, Quick Sort is a Divide and Conquer algorithm. It picks an element as pivot
element and partitions the given array around it. There are many different ways to select pivot
element.
1. Always first element is selected as pivot.
2. Always last element is selected as pivot.
3. Select a random element as pivot.
4. Select a median as pivot.
The key process in quickSort is function partition(). Target of partitions is, given an array
and an element x of array as pivot, put x at its correct position in sorted array and put all smaller
elements (smaller than x) before x, and put all greater elements (greater than x) after x. All this
should be done in linear time.
Array as a Data Structure 41

Pseudo Code for Recursive QuickSort function:


quickSort(arr[], low, high)
{
if (low < high)
{
/* pi is partitioning index, arr[pi] is now at right place */
pi = partition(arr, low, high);
quickSort(arr, low, pi - 1); // Before pi
quickSort(arr, pi + 1, high); // After pi
}
}

Partition Algorithm
We start from the leftmost element and keep track of index of smaller (or equal to) elements
as i. While traversing, if we find a smaller element, we swap current element with arr[i].
Otherwise we ignore current element.
Pseudo code for partition()
/* This function takes last element as pivot, places the pivot element at its correct position in
sorted array, and places all smaller (smaller than pivot) to left of pivot and all greater elements
to right of pivot */
partition (arr[], low, high)
{
// pivot (Element to be placed at right position)
pivot = arr[high];

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


for (j = low; j <= high- 1; j++)
{
// If current element is smaller than the pivot
if (arr[j] < pivot)
{
i++; // increment index of smaller element
swap arr[i] and arr[j]
}
}
swap arr[i + 1] and arr[high])
return (i + 1)
}

Illustration of function partition():


a[] = {10, 80, 30, 90, 40, 50, 70}
Indexes: 0 1 2 3 4 5 6
42 Data Structures and Algorithms – I

low = 0, high = 6, pivot = a[h] = 70


Initialize index of smaller element, i = -1

Traverse elements from j = low to high-1

j = 0: a[j] <= pivot, do i++ and swap(a[i], a[j])


i=0
a[] = {10, 80, 30, 90, 40, 50, 70} // No change as i and j are same

j = 1:arr[j] > pivot, do nothing


// No change in i and a[]
j = 2:a[j] <= pivot, do i++ and swap(a[i], a[j])
i=1
a[] = {10, 30, 80, 90, 40, 50, 70} // We swap 80 and 30

j = 3:a[j] > pivot, do nothing // No change in i and a[]

j = 4:a[j] <= pivot, do i++ and swap(a[i], a[j])


i=2
a[] = {10, 30, 40, 90, 80, 50, 70} // 80 and 40 Swapped

j = 5:a[j] <= pivot, do i++ and swap a[i] with a[j]


i=3
a[] = {10, 30, 40, 50, 80, 90, 70} // 90 and 50 Swapped

We come out of loop because j is now equal to high-1.


Finally we place pivot at correct position by swapping
a[i+1] and a[high] (or pivot)
a[] = {10, 30, 40, 50, 70, 90, 80} // 80 and 70 Swapped

Now 70 is at its correct place.


All elements smaller than 70 are before it and all elements greater than 70 are after it.

C Program: Quick Sort


#include <stdio.h>
int partition(int a[], int beg, int end);
void quickSort(int a[], int beg, int end);
void main()
{
int i;
int arr[10]={90,23,101,45,65,23,67,89,34,
23};
Array as a Data Structure 43

quickSort(arr, 0, 9);
printf(“\n The sorted array is: \n”);
for(i=0;i<10;i++)
printf(“ %d\t”, arr[i]);
}
int partition(int a[], int beg, int end)
{
int left, right, temp, loc, flag;
loc = left = beg;
right = end;
flag = 0;
while(flag!= 1)
{
while((a[loc] <= a[right]) && (loc!=right)) right--;
if(loc==right)
flag =1;
else if(a[loc]>a[right])
{
temp = a[loc];
a[loc] = a[right];
a[right] = temp;
loc = right;
}
if(flag!=1)
{
while((a[loc] >= a[left]) && (loc!=left))
left++;
if(loc==left)
flag =1;
else if(a[loc] <a[left])
{
temp = a[loc];
a[loc] = a[left];
a[left] = temp;
loc = left;
}
}
}
return loc;
}
void quickSort(int a[], int beg, int end)
{
44 Data Structures and Algorithms – I

int loc;
if(beg<end)
{
loc = partition(a, beg, end);
quickSort(a, beg, loc-1);
quickSort(a, loc+1, end);
} }

Selection Sort
In selection sort, the smallest value among the unsorted elements of the array is selected in
every pass and inserted to its appropriate position into the array. First, find the smallest element of
the array and place it on the first position, then, find the second smallest element of the array and
place it on the second position. The process continues until we get the sorted array.
The array with n elements is sorted by using n-1 pass of selection sort algorithm.
z In 1st pass, smallest element of the array is to be found along with its index pos. then,
swap A[0] and A[pos]. Now, A[0] is sorted, we now have n-1 elements which are to be
sorted.
z In 2nd pass, position pos of the smallest element present in the sub-array A[n-1] is found.
A[1] and A[pos] are then swapped. Thus A[0] and A[1] are sorted, we are now left with
n-2 unsorted elements.
z In n-1th pass, position pos of the smaller element between A[n-1] and A[n-2] is to be
found. Then A[pos] and A[n-1] are swapped.
Thus, by following the above explained process, the elements A[0], A[1], A[2],...., A[n-1]
are sorted.
Example: Consider the following array with 6 elements. Sort the elements of the array by
using selection sort.
A = {10, 2, 3, 90, 43, 56}
Pass Pos A[0] A[1] A[2] A[3] A[4] A[5]
1 1 2 10 3 90 43 56
2 2 2 3 10 90 43 56
3 2 2 3 10 90 43 56
4 4 2 3 10 43 90 56
5 5 2 3 10 43 56 90

Sorted A = {2, 3, 10, 43, 56, 90}


Algorithm
Selection sort(arr, n)
1: repeat steps 2 and 3 for k = 1 to n-1
2: call smallest(arr, k, n, pos)
3: swap a[k] with arr[pos]
[end of loop]
Array as a Data Structure 45

4: exit
Smallest (arr, k, n, pos)
1: [initialize] set small = arr[k]
2: [initialize] set pos = k
3: repeat for j = k+1 to n -1
if small >arr[j]
set small = arr[j]
set pos = j
[end of if]
[end of loop]
4: return pos
C Program: Selection Sort
#include<stdio.h>

int smallest(int[],int,int);
void main ()
{
int a[10] = {10, 9, 7, 101, 23, 44, 12, 78,
34, 23};
int i,j,k,pos,temp;
for(i=0;i<10;i++)
{
pos = smallest(a,10,i);
temp = a[i];
a[i]=a[pos];
a[pos] = temp;
}
printf(“\n Sorted elements...\n”);
for(i=0;i<10;i++)
{
printf(“%d\n”,a[i]);
}
}
int smallest(int a[], int n, int i)
{
int small,pos,j;
small = a[i];
pos = i;
for(j=i+1;j<10;j++)
{
if(a[j]<small)
46 Data Structures and Algorithms – I

{
small = a[j];
pos=j;
}
}
return pos;
}

Complexity Analysis of Selection Sort


Selection sort uses two nested for loops, one for loop is in the function selectionSort, and
inside the first loop we are making a call to another function Index of Minimum, which has the
second (inner) for loop. Hence for a given input size of n, following will be the time and space
complexity for selection sort algorithm:
Worst Case Time Complexity [Big-O ]: O(n2)
Best Case Time Complexity [Big-omega]: O(n2)
Average Time Complexity [Big-theta]: O(n2)
Space Complexity: O(1)
2.2.2 Non Comparison Based Sorting
Counting Sort
Counting sort is a sorting technique based on keys between a specific range. It works by
counting the number of objects having distinct key values (kind of hashing). Then some
arithmetic is performed to calculate the position of each object in the output sequence. Let us
understand it with the help of an example.
Consider the data in the range 0 to 9.
Input data: 1, 4, 1, 2, 7, 5, 2
1. Take a count array to store the count of each unique object.
Index: 0 1 2 3 4 5 6 7 8 9
Count: 0 2 2 0 1 1 0 1 0 0
2. Modify the count array such that each element at each index stores the sum of previous
counts.
Index: 0 1 2 3 4 5 6 7 8 9
Count: 0 2 4 4 5 6 6 7 7 7
The modified count array indicates the position of each object in the output sequence.
3. Output each object from the input sequence followed by decreasing its count by 1.
Sort the input data: 1, 4, 1, 2, 7, 5, 2. Position of 1 is 2. Put data 1 at index 2 in output.
Decrease count by 1 to place next data 1 at an index 1 smaller than this index.
C Program: Counting Sort
#include <stdio.h>
void counting_sort(int A[], int k, int n)
{
Array as a Data Structure 47

int i, j;
int B[15], C[100];
for (i = 0; i <= k; i++)
C[i] = 0;
for (j = 1; j <= n; j++)
C[A[j]] = C[A[j]] + 1;
for (i = 1; i <= k; i++)
C[i] = C[i] + C[i-1];
for (j = n; j >= 1; j--)
{
B[C[A[j]]] = A[j];
C[A[j]] = C[A[j]] - 1;
}
printf(“The Sorted array is:”);
for (i = 1; i <= n; i++)
printf(“%d”, B[i]);
}
/* End of counting_sort() */
//---------------------------------------------------------
int main()
{
int n, k = 0, A[15], i;
printf(“Enter the number of input:”);
scanf(“%d”, &n);
printf(“\nEnter the elements to be sorted :\n”);
for (i = 1; i <= n; i++)
{
scanf(“%d”, &A[i]);
if (A[i] > k) {
k = A[i];
}
}
counting_sort(A, k, n);
printf(“\n”);
return 0;
}

Radix Sort
We have seen that Counting sort is a linear time sorting algorithm that requires O(n+k) time
when elements are in range from 1 to k. But if the elements are in range from 1 to n2, counting
sort can’t be used because it will take O(n2) which is worse than comparison based sorting
algorithms. Such an array can be sorted in linear time by using radix sort. The idea behind radix
48 Data Structures and Algorithms – I

sort is to perform digit by digit sorting starting from least significant digit to most significant digit.
Radix sort uses counting sort as its subroutine to sort.
The Radix Sort Algorithm
1. For each digit i where i varies from least significant digit to the most significant digit,
(a) Sort input array using counting sort (or any stable sort) according to the ith digit.
Example:
Original, unsorted list:
170, 45, 75, 90, 802, 24, 2, 66
z Sorting by least significant digit (1s place) gives: [Note that we keep 802 before 2,
because 802 occurred before 2 in the original list, and similarly for pairs 170 & 90 and 45
& 75.]
z Sorting by next digit (10s place) gives: [Note that 802 again comes before 2 as 802 comes
before 2 in the previous list.]
802, 2, 24, 45, 66, 170, 75, 90
z Sorting by most significant digit (100s place) gives:
2, 24, 45, 66, 75, 90, 170, 802
C Program: Radix Sort
#include<stdio.h>

int get_Max(int arr[], int n) {


int mx = arr[0];
int i;
for (i = 1; i< n; i++)
if (arr[i] > mx)
mx = arr[i];
return mx;
}
//------------------------------------------------------
void countSort(int arr[], int n, int exp) {
int output[n]; // output array
int i, count[10] = { 0 };

// Store count of occurrences in count[]


for (i = 0; i< n; i++)
count[(arr[i] / exp) % 10]++;

for (i = 1; i< 10; i++)


count[i] += count[i - 1];

// Build the output array


for (i = n - 1; i>= 0; i--) {
Array as a Data Structure 49

output[count[(arr[i] / exp) % 10] - 1] = arr[i];


count[(arr[i] / exp) % 10]--;
}

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


arr[i] = output[i];
}
// ---------------------------------------------------
void radixsort(int arr[], int n) {
int m = get_Max(arr, n);

int exp;
for (exp = 1; m / exp > 0; exp *= 10)
countSort(arr, n, exp);
}

void print(int arr[], int n) {


int i;
for (i = 0; i< n; i++)
printf(“%d “, arr[i]);
}
//-------------------------------------------------------

int main() {
int arr[] = { 170, 45, 75, 90, 802, 24, 2, 66 };
int n = sizeof(arr) / sizeof(arr[0]);
radixsort(arr, n);
print(arr, n);
return 0;
}

Output:
2 24 45 66 75 90 170 802
Complexity of Radix Sort
Radix sort operates on n d-digit numbers, where each digit can be one of at most b different
values (since b is the base being used). For example, in base 10, a digit can be 0, 1, 2, 3, 4, 5, 6, 7,
8, \text{or } 90, 1, 2, 3, 4, 5, 6, 7, 8, or 9.
Radix sort uses counting sort on each digit. Each pass over n d-digit numbers will take
O(n+b) time, and there are d passes total. Therefore, the total running time of radix sort is
O(d(n+b)). When d is a constant and b isn’t much larger than n (in other words, b=O(n)), then
radix sort takes linear time.
50 Data Structures and Algorithms – I

2.2.3 Comparison of Sorting Method


Some sorting techniques are comparison-based sort, some are non-comparison-based sorting
technique.
Comparison Based Soring techniques are bubble sort, selection sort, insertion sort, Merge
sort, quicksort, heap sort etc. These techniques are considered as comparison-based sort because
in these techniques the values are compared, and placed into sorted position in different phases.
Here we will see time complexity of these techniques.
Analysis Type Bubble Sort Selection Sort Insertion Sort Merge Sort Quick Sort
2 2
Best Case O(n ) O(n ) O(n) O(log n) O(log n)
2 2
Average Case O(n ) O(n ) O(n2) O(log n) O(log n)
2 2
Worst Case O(n ) O(n ) O(n2) O(log n) O(n 2)

Some sorting algorithms are non-comparison based algorithm. Some of them are Radix sort,
Bucket sort, count sort. These are non-comparison based sort because here two elements are not
compared while sorting. Now we will see the difference between radix sort and counting sort
based on different type of analysis.
Analysis Type Radix Sort Counting Sort
(k is maximum digit) (k is size of count array)
Best Case O(nk) O(n + k)
Average Case O(nk) O(n + k)
Worst Case O(nk) O(n + k)

Sorting techniques can also be compared using some other parameters. Like based on
whether they in-place sorting algorithms or out-place sorting algorithms. Algorithms that do not
require any extra space is called in-place sorting algorithm. Quicksort, heapsort algorithms are in-
place. Whereas merge sort is out-place uses sorting mechanism.
QUESTIONS
Practice Questions (2 marks)
1. Compare the efficiency of Bubble Sort with Insertion Sort.
2. What is searching?
3. Compare linear and binary search.
4. Explain:
(a) Internal sorting
(b) Stable sorting
(c) In-Place algorithm
5. What is sorting? State the techniques of sorting.
Practice Questions (4 marks)
1. Explain Quick sort technique with an example.
2. What is efficiency of linear search method?
3. Explain selection sort technique with an example.
Array as a Data Structure 51

4. Explain Insertion sort technique with an example.


5. Explain Binary search method with an example.
6. Sort the following data by using quick sort technique:
48, 29, 8, 59, 72, 88, 34, 47
7. Sort the following data by using Insertion sort technique:
87, 45, 12, 90, 67, 54, 34, 23, 60
8. Sort the following data by using bubble sort technique:
56, 23, 98, 67, 3, 87, 45, 77, 99
9. Sort following data by using Merge sort technique:
12, 5, 122, 9, 7, 54, 4, 23, 88, 60
10. Sort the following data by selection sort technique:
56, 23, 2, 78, 122, 89, 43, 1
11. Sort the following data by using selection sort technique:
45, 85, 96, 78, 34, 12, 49, 38, 18
12. Sort following data by using Insertion sort technique:
12, 5, 122, 9, 7, 54, 4, 23, 88, 60.
13. What are non-comparison based sorting techniques? Explain any one such technique.
14. Explain counting sort algorithm.
15. Explain radix sort algorithm.
16. Compare complexities of sorting algorithms.

ˆˆˆ
3 LINKED LIST

Structure:
3.1 List as a Data Structure
3.2 Dynamic implementation of Linked List
3.3 Types of Linked List – Singly, Doubly, Circular
3.4 Operations on Linked List – create, traverse, insert, delete, search, and reverse
3.5 Applications of Linked List – polynomial manipulation, memory management in OS
Generalized Linked List – concept, representation, multiple-variable polynomial representation
using generalized list.

3.1 List as a Data Structure


The term list refers to a linear collection of data items. For example a simple list can be:
Red, green, blue, yellow, indigo.
We can add more items in this list at the beginning, at the end or insert at any other position.
One way to store such a list is using array, which is a static data structure, its size cannot be
increased when additional space is required.
z Linked List is a linear data structure and is defined as collection of objects called nodes
that are randomly stored in the memory.
z A node contains two fields, data stored at that particular address and the pointer which
contains the address of the next node in the memory.
z The last node of the list contains a special value called the null pointer, which is any
invalid address. It is denoted by NULL or X (cross sign) in the diagram of linked list.
Following figure shows the node structure:

Fig. 3.1: Node Structure


Linked list can be visualized as a chain of nodes, where every node points to the next node.

Fig. 3.2

The linked list also contains a list pointer variable – called HEAD or START, which
contains the address of the first node in the list. This address is required only to trace through the
list. A special case is the list which has no nodes, such a list is called as empty list or null list and
is denoted by the null pointer in the variable START.
Linked List 53

Comparison between Arrays and Linked List


Arrays are used to organize the group of elements that are to be stored individually in the
memory. However, there are several advantages and disadvantages of arrays which must be
known in order to decide the data structure which will be used throughout the program.
Array contains following limitations:
1. The size of array must be known in advance before using it in the program.
2. Increasing size of the array is a time consuming process. The size of the array can’t be
extended at run time.
3. The elements in the array must be contiguously stored in the memory. Therefore inserting
any element in the array requires shifting of all its predecessor elements.
Benefits of Linked List (Dynamic Storage) over Array (Sequential Storage)
1. Dynamic memory allocation: All the nodes of linked list are non-contiguously stored in
the memory and linked together with the help of pointers because of which the access
time is reduced in linked list.
2. Size is not a problem with since we do not need to define its size at the time of
declaration. List grows as per the program’s demand and limited to the available memory
space.
3. The main advantage of a linked list over an array is that the list elements can be easily
inserted or removed without reallocation or reorganization of the entire structure because
the data items need not be stored contiguously in memory or on disk, while restructuring
an array at run-time is a much more expensive operation. In linked lists elements can be
inserted at the beginning and end of the list.
Disadvantages of Linked Lists
1. Linked lists need more memory than arrays because of the storage used by their pointers.
2. Nodes in a linked list must be read in order from the beginning as linked lists are
inherently sequential accessed. That is, no element can be accessed randomly; it has to
access each node sequentially.
3. As the elements in the linked list are stored non-contiguously, the time required to access
individual elements increases considerably, especially with a CPU cache.
4. Reverse traversing in linked lists becomes difficult. Singly-linked lists are cumbersome to
navigate backward and while doubly linked lists are somewhat easier to read, memory is
consumed in allocating space for a back-pointer.
Applications of Linked List in Computer Science
We can find applications of linked lists in various areas in computer science, some of which
are:
1. Implementation of stacks and queues.
2. Implementation of graphs: Adjacency list representation of graphs where linked list is
used to store adjacent vertices.
3. Dynamic memory allocation uses linked list of free blocks.
4. To maintain directory of names.
5. Performing arithmetic operations on long integers.
54 Data Structures and Algorithms – I

6. Polynomial manipulation by storing constants in the nodes of linked list.


7. To represent sparse matrices.
Applications of Linked List in Real World
These are some real time applications where linked lists are used.
1. Image viewer – on the web pages, we find previous and next images. These are linked,
hence can be accessed by next and previous button.
2. Previous and next page in web browser – previous and next URL searched in web
browser by pressing back and next button as they are linked as linked list.
3. Music Player – songs in music player are linked to previous and next song. With this one
can play songs either from starting or ending of the list.
Basic Operations on Linked List
Following are the basic operations supported by a linked list.
z Insertion í Adds an element in the beginning/end/at specified location in the linked list.
z Deletion í Deletes an element at the beginning/end/at specified location in the linked list.
z Display í Displays the complete list from the first node to last node and display the data
part of each node.
z Search í Searches an element using the given key.

3.2 Dynamic Implementation of Linked List


Linked list is defined dynamically as collection of objects called nodes that are randomly
stored in the memory. A node contains two fields, i.e., data stored at that particular address and
the pointer which contains the address of the next node in the memory. The last node of the list
contains pointer to the null. The nodes in a linked list are linked using pointers as shown in the
below image. Dynamic representation makes use of dynamic memory allocation functions like
malloc() and calloc() in C language. We will use in further topics this representation in detail for
all operations on linked list.

Fig. 3.3

A node can be declared in C as follows:


struct Node {
int data;
struct Node* next;
};
Linked List 55

Let’s create a simple linked list consisting of three nodes:


#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
int main()
{
struct Node* head = NULL;
struct Node* second = NULL;
struct Node* third = NULL;
head = (struct Node*)malloc(sizeof(struct Node));
second = (struct Node*)malloc(sizeof(struct Node));
third = (struct Node*)malloc(sizeof(struct Node));
head ĺ data = 1;
head ĺ next = second;

second ĺ data = 2;
second ĺ next = third;
third ĺ data = 3;
third ĺ next = NULL;
return 0;}

3.3 Types of Linked List – Singly, Doubly, Circular


There are three different implementations of Linked List available, they are:
1. Singly Linked List – Item navigation is forward only.
2. Doubly Linked List – Items can be navigated forward and backward.
3. Circular Linked List – Last item contains link of the first element as next and the first
element has a link to the last element as previous.
Singly Linked List
Singly linked list (one way list) is defined as the collection of ordered set of elements. The
number of elements may vary according to need of the program. A node in the singly linked list
consist of two parts: data part and link part. Data part of the node stores actual information that is
to be represented by the node while the link part of the node stores the address of its immediate
successor.
Traversing is possible in singly linked list only in one direction. This is because each node
contains only next pointer, hence the list can’t be traversed in the reverse direction.
In the example shown in the figure below, the marks obtained by the student in three
subjects are stored in a linked list.
56 Data Structures and Algorithms – I

Fig. 3.4

As shown in the above figure, the arrow represents the links whereas the data part of every
node contains the marks obtained by the student in the different subjects. The last node in the list
is identified by the null pointer which is present in the address part of the last node.
Time Complexity
Average Worst
Access Search Insertion Deletion Access Search Insertion Deletion
ș(n) ș(n) ș(1) ș(1) O(n) O(n) O(n) O(1)

Space Complexity in worst case is O(n).


Doubly Linked list
In a singly linked list, traversing is possible only in one direction, because each node
contains address of the next node and it doesn’t have any record of its previous nodes. Hence it is
also called as forward direction linked list. If we want information of some previous node, we
have to traverse the list from the beginning of the list.
Doubly linked list overcomes this limitation of singly linked list. Also called as a two way
linked list, it is a linear collection of data elements called nodes in which a node contains a pointer
to the previous as well as the next node. Therefore, in a doubly linked list, a node consists of three
parts:
z An information field that contains data in the node.
z A next pointer field to point the node in the forward direction.
z A previous pointer field to point the node in the backward direction.
A sample node in a doubly linked list is shown in the following figure.

Fig. 3.5: Node of Doubly Linked List

A doubly linked list containing three nodes having numbers from 1 to 3 in their data part, is
shown in the following image.
Linked List 57

Fig. 3.6: Doubly Linked List

In C, structure of a node in doubly linked list can be given as:


struct node
{
int data;
struct node *prev, *next;
}

The prev part of the first node and the next part of the last node will always contain null
indicating end in each direction.
Memory Representation of a Doubly Linked List
Memory Representation of a doubly linked list is shown in the following image. Doubly
linked list consumes more space for every node and therefore, causes more expansive basic
operations such as insertion and deletion. But the elements of the list are easily manipulated, since
the list maintains pointers in both the directions (forward and backward).
In the following image, the first element of the list, i.e., 13 stored at address 1. The head
pointer points to the starting address 1. Since this is the first element being added to the list
therefore the prev of the list contains null. The next node of the list resides at address 4 therefore
the first node contains 4 in its next pointer. We can traverse the list in this way until we find any
node containing null or -1 in its next part.

Fig. 3.7: Memory Representation of Doubly Linked List


58 Data Structures and Algorithms – I

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 can have circular singly linked list as well as circular doubly linked list.
To display all elements in circular linked list, we traverse a circular singly linked list until
we reach the same node where we started. Thus the circular singly liked list has no beginning and
no ending. There is no null value present in the next part of any of the nodes. The following
image depicts a circular singly linked list.

Fig. 3.8: Circular Singly Linked List


Circular linked lists are mostly used in task maintenance in operating systems. There are
many examples where circular linked lists are being used in computer science including browser
surfing where a record of pages visited in the past by the user, is maintained in the form of
circular linked lists and can be accessed again on clicking the previous button.
Memory Representation of Circular Linked List
In the following image, memory representation of a circular linked list containing marks of a
student in 4 subjects. However, the image shows an instance of how the circular list is being
stored in the memory. The start or head of the list is pointing to the element with the index 1 and
containing marks 13 in the data part and 4 in the next part. Which means that it is linked with the
node that is being stored at 4th index of the list.
Also the last node of the list contains the address of the first node of the list.

Fig. 3.9: Memory Representation of Circular Linked List


Linked List 59

Circularly Doubly Linked List (CDLL)


z Circular doubly linked list is a more complexed type of data structure in which a node
contains pointers to its previous node as well as the next node.
z The previous pointer of the first node points to the last node.
z The next pointer of the last node points to the first node of the list.
z Circular doubly linked list doesn’t contain NULL in any of the node.
A circular doubly linked list is shown in the following figure.

Fig. 3.10: Circular Doubly Linked List

As a circular doubly linked list contains three parts in its structure, it needs more space per
node and more expensive basic operations. However, a circular doubly linked list provides easy
manipulation of the pointers and the searching becomes twice as efficient.
Memory Management of Circular Doubly Linked List
The figure below shows how the memory is allocated for a circular doubly linked list. The
variable head contains the address of the first element of the list, i.e., 1 hence the starting node of
the list contains data A is stored at address 1. Since, each node of the list is supposed to have three
parts therefore, the starting node of the list contains address of the last node, i.e., 8 and the next
node, i.e., 4. The last node of the list that is stored at address 8 and containing data as 6, contains
address of the first node of the list as shown in the image, i.e., 1. In circular doubly linked list, the
next part of the last node the address of the first node.

Fig. 3.11: Memory Representation of Circular Doubly Linked List


60 Data Structures and Algorithms – I

3.4 Operations on Linked List


We present here the operations (create, traverse, insert, delete, search and reverse) on all
three types of linked lists one after another.
(I) Operations on Singly Linked List
There are various operations which can be performed on singly linked list. A list of all such
operations is given below:
1. Node Creation
struct node
{
int data;
struct node *next;
};
struct node *head, *ptr;
ptr = (struct node *)malloc(sizeof(struct node *));

2. Insertion
The insertion into a singly linked list can be done at different positions. Based on the
position of the new node being inserted, the insertion is categorized into three categories. We can
add an element in the beginning/at the end/at specified location in the linked list.
Adding a new node in a linked list is a multi-step activity. Let us see this with diagrams here.
First, create a node using the same structure and find the location where it has to be inserted.
Suppose this is our original linked list with 3 nodes.

Fig. 3.12: Original List

(a) Inserting an element at the beginning:


To insert a new node with data ‘N’ in the beginning of the list following are the steps:
Step I: Create a space for new node, let pointer P point to this new node as:

Fig. 3.13: Create a New Empty Node Fig. 3.14: P ĺ DATA =‘N’, P ĺ Next=NULL

Put the value ‘N’ in the data field of new node as in Fig. 3.14.
Step II: Now add P in front of the list so that its Next field points to START and change the
address of START to P as follows:
Linked List 61

Fig. 3.15: P ĺ Next = START, START = P

(b) Inserting an element at the end:


Step I: Create a space for new node, let pointer P point to this new node as shown above in
Fig. 3.13.
Put the value ‘N’ in the data field of new node as in Fig. 3.14.
Step II: Traverse the list up to the last node using the for loop
for(temp = START ; temp ĺ Next != NULL ; temp=temp ĺ Next);
temp ĺ Next =P

Fig. 3.16

1. Inserting an element in between:


(a) Create a space for new node, let pointer P point to this new node as shown in the above
Figs. 3.13 and 3.14.
(b) Get the position POS where the new element is to be inserted, traverse the list till the
POS-1 with the for loop:
for ( i=1,temp= START; i<POS-1 && temp ĺ Next !=NULL; i++)
temp=temp ĺ Next
(c) Insert new element pointed to by P and change its Next field as:
P ĺ Next = temp ĺ Next
Temp ĺ Next = P

Fig. 3.17
62 Data Structures and Algorithms – I

Deletion í Deletes an element at the beginning/end/at specified location in the linked list.
Suppose we have a list of numbers as shown below. Let’s see each of the deletion types on
this list.
(a) Deleting an element at the beginning:
Set P = START

Fig. 3.18

(b) Deleting an element at the end:


To delete the last element in the list, we just set the next pointer of the second last node to
NULL and free last node.

Fig. 3.19: List after Deletion

(c) Deleting an element in between:


To delete the element in between, we traverse the list till the position of the element to be
deleted. Let the element to be deleted be pointed to by pointer P. Now we just change the Next
field of the node before P to the Next of P and free node P.
Linked List 63

Fig. 3.20

z Display: To display all the nodes in a sequential manner in the list, we need to traverse
the list from the first node and display the data part of each node.
z Search: Searches an element using the given key. Here, to search an element in the list,
we traverse the entire list and compare DATA of each node. If a match is found,
appropriate message can be displayed. If no match is found till last end, the element to be
searched does not exist.
z Reverse: To reverse the given linked list we will use three extra pointers that will be in
the process. The pointers used will be previous, after, current.
We initialize previous and after to NULL initially and current to head of the linked list.
After this, we will iterate until we reach the NULL of the initial (non-reversed linked list). And do
the following:
after ĺ current
next ĺ current
next ĺ previous
previous ĺ current
current ĺ after

Following C program is a menu driven program for singly linked list, which includes all
above mentioned operations on linked list. The code contains functions for each operation. The
readers are requested to note that the C code given below is printed in two columnar text display.
C Program: Singly Linked List
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
64 Data Structures and Algorithms – I

struct node
{
int data;
struct node *next;
};
struct node *head;
void insert_beg();
void insert_last ();
void insert_between();
void begin_delete();
void delete_last();
void delete_between();
void display_list();
void search();
void reverse_list();
void main ()
{
int choice = 0;
while(choice != 9)
{
printf(“\n------Singly Linked List--- \n”);
printf(“\n1.Insert element in beginning\n”
2. Insert at last\n
3. Insert in between the list \n
4. Delete element at beginning\n
5. Delete last element \n
6. Delete node after specified location\n
7. Search for an element\n
8. Display list\n
9. Exit\n”);
printf(“\nEnter your choice?\n”);
scanf(“\n%d”, &choice);
switch(choice)
{
case 1: insert_beg();
break;
case 2: insert_last();
break;
case 3: insert_between();
break;
case 4: delete_beg();
break;
Linked List 65

case 5: delete_last();
break;
case 6: delete_between();
break;
case 7: search();
break;
case 8: display_list();
break;
case 9: reverse_list();
break;
case 10: exit(0);
break;
default: printf(“Please enter valid choice..”); } } }
//----------------------------------------------
void insert_beg()
{
struct node *ptr;
int item;
ptr = (struct node *) malloc(sizeof(struct node *));
if(ptr == NULL)
printf(“\nOVERFLOW”);
else
{
printf(“\nEnter value\n”);
scanf(“%d”, &item);
ptr ĺ data = item;
ptr ĺ next = head;
head = ptr;
printf(“\nNode inserted”);
}
}
//--------------------------------------------------
void insert_last()
{
struct node *ptr,*temp;
int item;
ptr =(struct node*)malloc
(sizeof(struct node));
if(ptr == NULL)
printf(“\nOVERFLOW”);
else
{
66 Data Structures and Algorithms – I

printf(“\nEnter value?\n”);
scanf(“%d”, &item);
ptr ĺ data = item;
if(head == NULL)
{
ptr ĺ next = NULL;
head = ptr;
printf(“\nNode inserted”);
}
else
{
temp = head;
while (temp ĺ next ! = NULL)
{
temp = temp ĺ next;
}
temp ĺ next = ptr;
ptr ĺ next = NULL; printf(“\nNode inserted”);
}
}
}
//----------------------------------------------------
void insert_between()
{
int i,loc,item;
struct node *ptr, *temp;
ptr=(struct node *) malloc
(sizeof(struct node));
if(ptr == NULL)
printf(“\nOVERFLOW”);
else
{
printf(“\nEnter element value”);
scanf(“%d”, &item);
ptr ĺ data = item;
printf(“\nEnter the location after which you want to insert”);
scanf(“\n%d”,&loc);
temp=head;
for(i=0;i<loc;i++)
{
temp = temp ĺ next;
if(temp == NULL)
Linked List 67

{
printf(“\ncan’t insert\n”);
return;
}
}
ptr ĺ next = temp ->next;
temp ĺ next = ptr;
printf(“\nNode inserted”);
}
}
//------------------------------------------------
void delete_beg()
{
struct node *ptr;
if(head == NULL)
printf(“\nList is empty\n”);
else
{
ptr = head;
head = ptr ĺ next;
free(ptr);
printf(“\nNode deleted from the beginning.\n”); }
}
//-----------------------------------------------
void delete_last()
{
struct node *ptr,*ptr1;
if(head == NULL)
printf(“\nlist is empty”);
else if(head ĺ next == NULL)
{
head = NULL;
free(head);
printf(“\nOnly node of the list deleted..\n”);}
else
{
ptr = head;
while(ptr ĺ next != NULL)
{
ptr1 = ptr;
ptr = ptr ĺ next;
}
68 Data Structures and Algorithms – I

ptr1 ĺ next = NULL;


free(ptr);
printf(“\nDeleted Node from the last \n”); }
}
//----------------------------------------------------
void delete_between()
{
struct node *ptr,*ptr1;
int loc,i;
printf(“\n Enter the location of the node after which you want to perform deletion \n”);
scanf(“%d”,&loc);
ptr = head;
for(I = 0;i<loc;i++)
{
ptr1 = ptr;
ptr = ptr ĺ next;
if(ptr == NULL)
{
printf(“\nCan’t delete”);
return;
}
} // end of for loop
ptr1 ĺ next = ptr ĺ next;
free(ptr);
printf(“\nDeleted node %d “, loc+1);
}
//--------------------------------------------------
void search()
{
struct node *ptr;
int item, i=0,flag;
ptr = head;
if(ptr == NULL)
printf(“\nEmpty List\n”);
else
{
printf(“\nEnter item to search?\n”);
scanf(“%d”, &item);
while (ptr!=NULL)
{
if(ptr ĺ data == item)
{
Linked List 69

printf(“item found at location %d”, i+1); flag=0;


}
else{
flag=1;
}
i++;
ptr = ptr ĺ next;
} // end of while
if(flag==1)
{
printf(“Item not found\n”);
}
}
} //end of search function
//--------------------------------------------------void reverse_list()
{
struct node* current = head;
struct node *prev = NULL;
struct node*after = NULL;
while (current != NULL)
{
after = current ĺ next;
current ĺ next = prev;
prev = current;
current = after;
}
head =prev;
}
//---------------------------------------------------
void display_list()
{
struct node *ptr;
ptr = head;
if(ptr == NULL)
printf(“Nothing to print”);
else
{
printf(“\nprinting values . . . . .\n”);
while (ptr!=NULL)
{
printf(“\n%d”,ptr ĺ data);
ptr = ptr ĺ next;
70 Data Structures and Algorithms – I

}
}
}

Output: (Not all, but some options of run of the program are included here)

-------------Singly Linked List-------


1. Insert in beginning
2. Insert at last
3. Insert in between the list
4. Delete element at beginning
5. Delete last element
6. Delete node after specified location
7. Search for an element
8. Display list
9. Exit

Enter your choice?


1
Enter value
1
Node inserted
-------------Singly Linked List-------
1. Insert in beginning
2. Insert at last
3. Insert in between the list
4. Delete element at beginning
5. Delete last element
6. Delete node after specified location
7. Search for an element
8. Display list
9. Exit

Enter your choice?


2
Enter value?
2
Node inserted

(II) Operations on Doubly Linked List (DLL)


Creating a doubly linked list, inserting elements in DLL, deleting an element, searching an
element and displaying the list are the operations that can be performed on the doubly linked list.
Linked List 71

(a) Creating a Doubly Linked List


Let START/HEAD be a pointer that points to the start of DLL. Initially it will point to
NULL. Let P be the new node.

Fig. 3.21

The node is created in C using following code:


struct node
{
struct node *prev;
int data;
struct node *next;
};
struct node *start;
P = malloc(sizeof(struct node));
P ĺ data=‘X’;
P ĺ next=NULL;
P ĺ prev=NULL;
When the list is empty, we point START to P to create the list as:

Fig. 3.22

(b) Insertion in the Doubly Linked List


There are three situations while inserting a node in the DLL as follows:
(i) Insertion in DLL at the beginning:
The new node here is always added before the head of the given Linked List. And newly
added node becomes the new head of DLL. For example if the given Linked List is 10, 20, 30, 40
and we add an item 50 at the front, then the Linked List becomes 50, 10, 20, 30, 40. (See figure
below).
72 Data Structures and Algorithms – I

Consider that a new node is pointed to by new and first node of the list is pointed to by head.
Following instructions are needed to insert a node at the beginning:
z new ĺ next = head;
z new ĺ prev= NULL;
z head ĺ prev=new;
z new =head;

Fig. 3.23: Adding a Node at the Front of the Doubly Linked List

(ii) Insertion in DLL after a Given Node


Consider that a new node pointed to by new is to be inserted after the node pointed to by X
in a doubly linked list as shown in the figure below:
Following instructions are required to insert a new node new after a node X.
z new ĺ next = X ĺ next;
z X ĺ next = new;
z new ĺ prev =X;
z Y->prev = new;

Fig. 3.24: Insertion in DLL after a given Node

(iii) Insertion in DLL at the end:


If the list is not empty then we have to traverse the list till the last element and then new
node can be added at the end. In this case, if pointer temp points to the last node after traversing,
we set the next pointer of temp to new node P. This is shown as:
Linked List 73

If a new node is pointed to by new, we traverse the DLL till last and add a new node.
Following are the instructions needed to add a node at the end of DLL:
1. temp=head;
2. while(temp ĺ next != NULL)
temp = temp ĺ next;
3. temp ĺ next = new;
4. new ĺ next= NULL;

Fig. 3.25: Adding a Node at the end of Doubly Linked List

(c) Deletion of a Node in a Doubly Linked List


Consider the following doubly linked list:

Fig. 3.26: After deletion of Head Node

The deletion of a node in a doubly linked list can be divided into three main categories:
1. Deletion of head node.
We delete the node at first position pointed to by Head with following instructions:
1. If (P== head)
2. {
3. P ĺ next ĺ prev=NULL;
4. head=P ĺ next;
5. free(P);
6. return(head);
7. }
74 Data Structures and Algorithms – I

Fig. 3.27: After deletion of Head Node

2. Deletion of middle node


Suppose a node to be deleted is pointed to by P, X is the predecessor node and Y is the
successor node of P. We show this in the diagram below the links to be modified for this deletion.
These are the required instructions:
1. p ĺ prev ĺ next= P ĺ next;
2. p ĺ next ĺ prev = P ĺ prev;
3. free(P);

Node Pointed to by P is to be Delected

Links to be Modified for Deletion of P

Fig. 3.28

3. Deletion of Last Node:


We traverse the DLL from head to the last node and make the following modifications to
delete the last node.
1. P = head;
2. while(P ĺ next ! = NULL)
P = P ĺ next;
3. if P ĺ prev ! = NULL
P ĺ prev ĺ next = NULL;
free(P);
Linked List 75

(d) Searching in Doubly Linked List


We just need traverse the list in order to search for a specific element in the list. Perform
following operations in order to search a specific operation.
1. ptr = head
2. I = 0 (a local variable i is declared and assigned to 0.)
3. Traverse the list until the pointer ptr becomes null. Keep shifting pointer to its next and
incrementing it by1.
4. Compare each element of the list with the item which is to be searched.
5. If the item matched with any node value then the location of that value i will be returned
from the function else NULL is returned.
(e) Displaying Doubly Linked List (DLL)
To display the elements of doubly linked list we need to visit each node of the list at least
once. This can be done as:
temp = Head
if (temp == NULL), print “List is empty”
else
while(temp!=NULL)
{
print temp ĺ data
temp = temp ĺ next
}
C Program: Operations on Doubly Linked List
#include<stdio.h>
#include<stdlib.h>
void create_dll(void);
void display_dll(void);
struct node
{
int num;
struct node *next,*prev;
} *head ,*new,*temp,*ptr;
void main ()
{
int choice, item;
do
{
clrscr();

printf(“\n------Doubly Linked List-----\n”);


printf(“\n 1. Create Doubly Linked List\n”);
76 Data Structures and Algorithms – I

printf(“\n 2. Insertion in Doubly Linked List\n”);


printf(“\n 3. Deletion in Doubly Linked List\n”);
printf(“\n 4. Search in Doubly Linked List\n”);
printf(“\n 5.Display in Doubly Linked List\n”);
printf(“\n 6. Exit\n”);
printf(“Enter your choice\n”);
scanf(“%d”, &choice);
switch(choice)
{
case 1: create_dll();
break;
case 2: insert_dll();
break;
case 3: delete_dll();
break;
case 4: search_dll();
break;
case 5: display_dll();
break;
case 6: exit(1);
break;
default: printf(“Incorrect option selected\n”);
break;
}
}while(choice!=3);
}
//----------------------------------------------------
void create_dll(void)
{
//create a new node
new = malloc(sizeof(struct node));
printf(“enter data in the new node\n”);
scanf(“%d”, &num);
new ĺ next = NULL;
new ĺ prev = NULL;
if (head = NULL) // check if list is empty
head = new;
else
{
temp = head;
while(temp!=NULL)
temp = temp ĺ next;
Linked List 77

temp ĺ next = new;


}
}
//----------------------------------------------------
insert_dll()
{
int opt;
do
{
clrscr();
printf(“\nEnter the item to insert?\n”);
scanf(“%d”, &item);

printf(“\n------Insertion Menu in DLL-----\n”);


printf(“\n 1. Insert at begining\n”);
printf(“\n 2. Insert at middle n”);
printf(“\n 3. Insert at end \n”);
printf(“\n 4. Insert at end \n”);
printf(“Enter your choice\n”);
scanf(“%d”, &opt);
switch(opt)
{
case 1: insert_beg();
break;
case 2: insert_middle();
break;
case 3: insert_end();
break;
default: printf(“Incorrect option selected\n”);
break;
}
}while(choice!=4);
}
//------------------------------------------
insert_beg()
{
new ĺ data = item;
new ĺ next = head;
new ĺ prev = NULL;
head ĺ prev = new;
new = head;
}
78 Data Structures and Algorithms – I

//-----------------------------------------------------
insert_middle()
{
int loc;
printf(“\nEnter the location to insert \n”);
scanf(“%d”,&loc);
temp=head;
for(i=0;i<loc;i++)
{
temp = temp->next;
if(temp == NULL)
{
printf(“\ncan’t insert\n”);
return;
}
}
ptr ĺ data = item;
ptr ĺ next = temp ĺ next;
ptr ĺ prev = temp;
temp ĺ next = ptr;
temp ĺ next ĺ prev = ptr;
printf(“Node Inserted\n”);
}
}
//----------------------------------------------------
insert_end()
{
ptr ĺ data=item;
temp = head;
while(temp ĺ next!=NULL)
{
temp = temp ĺ next;
}
temp ĺ next = ptr;
ptr ĺ prev=temp;
ptr ĺ next = NULL;
}
printf(“\nNode Inserted\n”);
}
}
}
//--------------------------------------------------------
Linked List 79

delete_dll()
{
int opt;
do
{
clrscr();
printf(“\nEnter the item to insert?\n”);
scanf(“%d”, &item);
printf(“\n------Deletion Menu in DLL-----\n”);
printf(“\n 1. Delete first node\n”);
printf(“\n 2. Delete middle node \n”);
printf(“\n 3. Delete last node \n”);
printf(“\n 4. Exit \n”);
printf(“Enter your choice\n”);
scanf(“%d”, &opt);
switch(opt)
{
case 1: delete_first();
break;
case 2: delete_middle();
break;
case 3: delete_last();
break;
default: printf(“Incorrect option selected\n”);
break;
}
}while(choice!=4);
}
//---------------------------------------------------
delete_first()
{
if(head->next == NULL)
{
head = NULL;
free(head);
printf(“\nNode Deleted\n”);
}
else
{
ptr = head;
head = head ĺ next;
head ĺ prev = NULL;
80 Data Structures and Algorithms – I

free(ptr);
printf(“\nNode Deleted\n”);
}
}
}
//------------------------------------------------------------
delete_middle()
{
int val;
printf(“Enter the value to be deleted”);
scanf(“%d”,&val);
temp = head;
while(temp ĺ data != val)
temp = temp ĺ next
if(temp ĺ next == NULL)
{
printf(“\nCan’t delete\n”);
}
else if(temp ĺ next ĺ next == NULL)
{
temp ĺ next = NULL;
printf(“\nNode Deleted\n”);
}
else
{
ptr = temp ĺ next;
temp ĺ next = ptr ĺ next;
ptr ĺ next ĺ prev = temp;
free(ptr);
printf(“\nNode Deleted\n”);
}
}
//---------------------------------------------------------------
delete_last()
{
if(head ĺ next == NULL)
{
head = NULL;
free(head);
printf(“\nNode Deleted\n”);
}
else
Linked List 81

{
ptr = head;
while(ptr ĺ next !=NULL)
ptr = ptr ĺ next;
if ptr ĺ prev != NULL
ptr ĺ prev ĺ next = NULL;
free(ptr);
printf(“\nNode Deleted\n”);
}
}
}//------------------------------------------------------search_dll()
{
int item,i=0,flag;
ptr = head;
if(ptr == NULL)
printf(“\n List is empty\n”);
else
{
printf(“\nEnter item to search?\n”);
scanf(“%d”, &item);
while (ptr!=NULL)
{
if(ptr ĺ data == item)
{
printf(“\nitem found at location %d “,i+1); flag=0;
break;
}
else
{
flag=1;
}
i++;
ptr = ptr ĺ next;
}
if(flag==1)
{
printf(“\nItem not found\n”);
}
}
}
//------------------------------------------------------void display_dll(void)
{
82 Data Structures and Algorithms – I

if (head= NULL) // check if list is empty


printf(“ DLL is empty, no items to display”);
else
{
temp = head;
printf(“Elements in DLL are ….\n”);
while(temp! = NULL)
{
printf(“%d\t”, temp ĺ num);
temp = temp ĺ next;
}
}
}

Applications of Doubly Linked List


Doubly linked lists are used in the applications where traversal in either direction is required.
Some examples are:
z Doubly linked lists are used for dynamic memory management in operating system. In
many operating systems, the thread scheduler (program that chooses what processes need
to run at which times) maintains a doubly-linked list of all the processes running at any
time.
z To implement double ended queue.
z To represent a deck of cards in a game.
z The browser cache which allows you to hit the BACK-FORWARD pages.
z Applications that have a Most Recently Used list (a linked list of file names)
z Undo-Redo functionality

(III) Operations on Circular Singly Linked List:


(A) Creating a Circular Singly Linked List
There are two possible situations in which a node can be inserted in circular singly linked
list at beginning. Either the node will be inserted in an empty list or the node is to be inserted in
an already filled list.
Firstly, allocate the memory space for the new node by using the malloc method of C
language.
struct node *new = (struct node *)malloc(sizeof(struct node));
In the first situation, the condition head == NULL will be true. The only node of the list
which is just inserted into the list will point to itself only. We also need to make the head pointer
point to this node. This will be done by using the following statements.

if(head == NULL)
{
head = new;
new ĺ next = head;
Linked List 83

}
In the second situation, the condition head == NULL will become false which means that
the list contains at least one node. In this case, we need to traverse the list in order to reach the last
node of the list. This will be done by using the following statement.
temp = head;
while (temp ĺ next != head)
temp = temp ĺ next;
At the end of the loop, the pointer temp would point to the last node of the list. Since, in a
circular singly linked list, the last node of the list contains a pointer to the first node of the list. we
need to make the next pointer of the last node point to the head node of the list and the new node
which is being inserted into the list will be the new head node of the list therefore the next pointer
of temp will point to the new node ptr. We illustrate by creating a circular linked list with
elements 11, 22, 33.

Fig. 3.29

(b) Insertion into Circular Singly Linked List at a Given Position


Suppose we want to insert a new node in the circular linked list at 3 position i.e., just after
2nd position. The list initially contains 3 nodes. We will follow below steps to insert node at 2nd
position in the list.
1. Create a new node new and assign some data to its data field.
new =(struct node *)malloc(sizeof(struct node));
new ĺ num= data;
84 Data Structures and Algorithms – I

2. Traverse to N-1 position in the list, in our case since we want to insert node at 3rd
position therefore we would traverse to 3-1 = 2nd position in the list. Say current pointer
points to N-1th node.
current = head;
for(i=2; I ĺ position-1; i++)
current = current ĺ next;
3. Make the following link changes:
new ĺ next = current ĺ next.
4. Connect the next pointer field of current node with the newly created node which means
now next pointer field of current node will point to new and you are done.
new ĺ next = current ĺ next;
current ĺ next = new;
Following figure depicts insertion in CLL at given position:

Fig. 3.30: Insertion of a Node at given position in Circular Linked List

(c) Deletion of a Node in Circular Linked List


Given a circular linked list, deletion of a node from this list can be done at:
1. First position.
2. Last Position.
3. At any given position
Deleting first node from Singly Circular Linked List
Following steps can be followed to delete first node in a circular linked list:
1. Take two pointers current and previous and traverse the list.
2. Point current to the first node and move previous until it reaches the last node.
3. Once the pointer previous reaches the last node, do the following:
z previous ĺ next = current ĺ next
z head = previous ĺ next;
Linked List 85

Fig. 3.31: Remove First Node

Deleting the last Node of the Circular Linked List


Following steps can be followed to delete first node in a circular linked list:
1. Take two pointers current and previous and traverse the list.
2. Move both pointers such that next of previous is always pointing to current. Keep moving
the pointers current and previous until current reaches the last node and previous is at
second last node.
3. Once, the pointer current reaches the last node, make the following changes:
z previous ĺ next = current ĺ next
z head = previous ĺ next;

Fig. 3.32: After Removing Last Node

Deleting Node at given position in the Circular Linked List


1. First, find the length of the list. That is, the number of nodes in the list.
2. Take two pointers previous and current to traverse the list, such that previous is one
position behind the current node.
3. Take a variable count initialized to 0 to keep track of the number of nodes traversed.
4. Traverse the list until the given index is reached.
5. Once the given index is reached, make the following changes:
previous ĺ next = current ĺ next.
86 Data Structures and Algorithms – I

Fig. 3.33: After Deletion of Node at Index 2

(d) Searching a node in CLL


Following steps can be used to search an element in the CLL.
1. Declare a temp pointer, and initialize it to head of the list.
2. Iterate the loop until temp reaches start address (last node in the list, as it is in a circular
fashion), check for the n element, whether present or not.
3. If it is present, raise a flag, increment count and break the loop.
4. At the last, as the last node is not visited yet check for the n element, if present do step 3.
(e) Display (Traversing) in Circular Singly Linked List
Traversing in circular singly linked list can be done through a loop. Initialize the temporary
pointer variable temp to head pointer and run the while loop until the next pointer of temp
becomes head.
(f) Reversing a Circular Linked List
This is same as reversing a singly linked list. Only here we have to make one more
adjustment by linking the last node of the reversed list to the first node. The function for reversing
is also included in the program below. The following C program implements all above operations
on circular linked list.
C Program: Operations in Circular Singly Linked List
#include <stdio.h>
#include <stdlib.h>

struct node
{
int data;
struct node *link;
};
Linked List 87

struct node*head=NULL,*first,*second, *third;


void create();
void ins_at_beg();
void ins_at_pos();
void del_at_beg();
void del_at_pos();
void traverse();
void search();
void reverse_CLL(struct node *p);

void main()
{
int ch;

printf(“\n 1. Creation \n 2. Insertion at beginning \n 3. Insertion at remaining”);


printf(“\n4. Deletion at beginning \n 5. Deletion at remaining \n 6. traverse”);
printf(“\n7.Search\n 8.Exit\n”);
while (1)
{
printf(“\n Enter your choice:”);
scanf(“%d”, &ch);
switch(ch)
{
case 1:
create();
break;
case 2:
ins_at_beg();
break;
case 3:
ins_at_pos();
break;
case 4:
del_at_beg();
break;
case 5:
del_at_pos();
break;
case 6:
traverse();
break;
case 7:
88 Data Structures and Algorithms – I

search();
break;
case 10:
reverse_CLL(head);
break;
default:
exit(0);
}
}
}
//------------------------------------------------------
void create()
{
int c;
first = (struct node*)malloc
(sizeof(struct node));
printf(“\n Enter the data:”);
scanf(“%d”, &first ĺ data);
first ĺ link = first;
head = first;
printf(“\n If you wish to continue press 1 otherwise 0:”);
scanf(“%d”, &c);
while (c != 0)
{
second = (struct node*)malloc(sizeof(struct node));
printf(“\n Enter the data:”);
scanf(“%d”, &second ĺ data);
first ĺ link = second;
second ĺ link = head;
first = second;
printf(“\n If you wish to continue press 1 otherwise 0:”);
scanf(“%d”, &c);
}
}
//------------------------------------------------------
void ins_at_beg()
{
first = head;
second = (structnode*)malloc(sizeof(struct node));
printf(“\n Enter the data:”);
scanf(“%d”, &second ĺ data);
while (first ĺ link != head)
Linked List 89

{
first = first ĺ link;
}
first ĺ link = second;
second ĺ link = head;
head = second;
}
//-----------------------------------------------------
void ins_at_pos()
{
struct node *ptr;
int c = 1, pos, count = 1;
second = (struct node*)malloc(sizeof(struct node));
if (head == NULL)
{
printf(“cannot enter an element at this place”);
}
printf(“\n Enter the data:”);
scanf(“%d”, &second ĺ data);
printf(“\n Enter the position to be inserted:”);
scanf(“%d”, &pos);
first = head;
ptr = head;
while (ptr ĺ link != head)
{
count++;
ptr = ptr ĺ link;
}
count++;
if (pos ĺ count)
{
printf(“OUT OF BOUND”);
return;
}
while (c <pos)
{
third = first;
first = first->link;
c++;
}
second ĺ link = first;
third ĺ link = second;
90 Data Structures and Algorithms – I

}
//-----------------------------------------------------
void del_at_beg()
{
if (head == NULL)
printf(“\n List is empty”);
else
{
first = head;
second = head;
while (first ĺ link != head)
{
first = first ĺ link;
}
head = second ĺ link;
first ĺ link = head;
free(second);
}
}
//-----------------------------------------------------
void del_at_pos()
{
if (head == NULL)
printf(“\n List is empty”);
else
{
int c = 1, pos;
printf(“\n Enter the position to be deleted:”);
scanf(“%d”, &pos);
first = head;
while (c <pos)
{
second = first;
first = first ĺ link;
c++;
}
second ĺ link = first ĺ link;
free(first);
}
}
//----------------------------------------------------
void traverse()
Linked List 91

{
if (head == NULL)
printf(“\n List is empty”);
else
{
first = head;
while (first ĺ link != head)
{
printf(“%d ĺ ”, first->data);
first = first->link;
}
printf(“%d”, first ĺ data);
}
}
//---------------------------------------------------
void search()
{
int search_val, count = 0, flag = 0;
printf(“\n enter the element to search\n”);
scanf(“%d”, &search_val);
if (head == NULL)
printf(“\nList is empty nothing to search”);
else
{
first = head;
while (first ĺ link != head)
{
if (first ĺ data == search_val)
{
printf(“\n the element is found at %d”, count);
flag = 1;
break;
}
count++;
first = first ĺ link;
}
if (first ĺ data == search_val)
{
printf(“element found at postion %d”, count);
}
if (flag == 0)
{
92 Data Structures and Algorithms – I

printf(“\nelement not found”);


}
}
}
//-----------------------------------------------------
void reverse_CLL(struct node *p)
{
int i = 0;
if (head == NULL)
{
printf(“empty linked list”);
}
else
{
if (p ĺ link != head)
{
i = p ĺ data;
reverse_CLL(p ĺ link);
printf(“ %d”, i);
}
if (p ĺ link == head)
{
printf(“ %d”, p ĺ data);
}
}
}

Output:
1. Creation
2. Insertion at beginning
3. Insertion at remaining
4. Deletion at beginning
5. Deletion at remaining
6. Traverse
7. Search
10. Exit
Enter your choice: 6
List is empty
Enter your choice: 5
List is empty
Enter your choice: 9
empty list
Linked List 93

Enter your choice: 7


enter the element to search
12
List is empty nothing to search
Enter your choice: 1
Enter the data: 10
If you wish to continue press 1 otherwise 0:0
Enter your choice: 3
Enter the data: 20
Enter the position to be inserted: 5
OUT OF BOUND
Enter your choice: 2
Enter the data: 12
Enter your choice: 6
12 ĺ 10
Enter your choice: 3
Enter the data: 13
Enter the position to be inserted: 3
Enter your choice: 3
Enter the data: 14
Enter the position to be inserted: 4
Enter your choice: 6
12 ĺ 10 ĺ 13 ĺ 14
Enter your choice: 3
Enter the data: 24
Enter the position to be inserted: 4
Enter your choice: 6
12 ĺ 10 ĺ 13 ĺ 24 ĺ 14
Enter your choice: 3
Enter the data: 10
Enter the position to be inserted: 100
OUT OF BOUND
Enter your choice: 4
Enter your choice: 6
10 ĺ 13 ĺ 24 ĺ 14
Enter your choice: 5
Enter the position to be deleted: 4
Enter your choice: 6
10 ĺ 13 ĺ 24
Enter your choice: 5
94 Data Structures and Algorithms – I

Enter the position to be deleted: 2


Enter your choice: 6
10 ĺ 24
Enter your choice: 7
enter the element to search
26
element found at postion 1
element not found
Enter your choice: 7
enter the element to search
27
element not found
Enter your choice: 8

(IV) Operations on Circularly Doubly Linked List(CDLL):


1. Create CDLL
Following is representation of a Circular doubly linked list node in C/C++:
// Structure of the node
struct node
{
int data;
struct node *next; // Pointer to next node
struct node *prev; // Pointer to previous node
};

Creating a circular doubly linked list is actually inserting in circular doubly linked list at
beginning. There are two scenarios of inserting a node in circular doubly linked list at beginning.
Either the list is empty or it contains more than one element in the list.
Let us understand the insertion in CDLL in various scenarios:
2. Insertion in an Empty List
Consider an empty List (start = NULL): A node (Say N) is inserted with data = 5, so
previous pointer of N points to N and next pointer of N also points to N. But now start pointer
points to the first node the list.

Fig. 3.34
Linked List 95

(A) Insertion at the Beginning of the List:


To insert a node at the beginning of the list, create a node (say new) with data = 5, new next
pointer points to first node of the list, new previous pointer points to last node the list, last node’s
next pointer points to this node new, first node’s previous pointer also points this new node and at
last don’t forget to shift ‘head’ pointer to this new node.

Fig. 3.35: Inserting a Node at the Beginning in CDLL

Following instructions enables the insertion of new node new at beginning.


1. new ĺ data = value; // Inserting the data
2. new ĺ next = head; // setting up previous and next of new node
new ĺ prev = last;
3. last ĺ next = head ĺ prev = new; // Update next and previous pointers of start and last.
4. head = new;// Update head pointer.
3. Inserting a Node in a CDLL at the end
A node (say new) is inserted with data = 7, so previous pointer of new points to last node,
next pointer of new points to first node and last node’s next pointer points to this new node and
first node’s previous pointer points to this new node.

Fig. 3.36
96 Data Structures and Algorithms – I

Following link changes are needed for inserting in CDLL:


Here the node to be inserted is pointed to by new.
1. last = head ĺ prev;
2. new ĺ data = value;
3. new ĺ next = head;
4. head ĺ prev = new;
5. new ĺ prev = last;
6. last ĺ next = new;
3. Insertion in between the Nodes of the List:
To insert a node in between the list, two data values are required one is the position after
which new node will be inserted and another is the data of the new node.

Fig. 3.37: Insertion in Between the Nodes of the CDLL


Following instructions will insert a node new with data value1 new after a node with data
value2.
1. temp = head;
2. while(temp ĺ data != value2)
temp = temp ĺ next;
3. ptr=temp ĺ next;
4. temp ĺ next = new_node; // insert new node between temp and ptr.
5. new ĺ prev = temp;
6. new ĺ next = ptr;
7. ptr ĺ prev = new;
5. Searching in CDLL
Searching in a doubly circular linked list, the task is to find the position of an element in the
list.
1. Declare a temp pointer, and initialize it to head of the list.
temp = head;
2. Iterate the loop until temp reaches head address (last node in the list, as it is in a circular
fashion), check for the element, whether present or not.
3. If it is present, raise a flag, increment count and break the loop.
Linked List 97

4. At the last, as the last node is not visited yet check for the n element if present do step 3.
while(temp ĺ next != start)
{
count++; // Increment count for location
if(temp ĺ data == search) // If it is found raise the flag and break the loop
{
flag = 1;
count--;
break;
}
temp = temp ĺ next; // Increment temp pointer
}
6. Displaying Data in CDLL
Displaying the data in Circular Doubly Linked List is same as in Circular Singly Linked List.
C Program: Operations on Circularly Doubly Linked List (CDLL)
#include<stdio.h>
#include<stdlib.h>
struct node
{
struct node *prev;
struct node *next;
int data;
};
struct node *head;
void insert_at_beginning();
void insert_at_last();
void insert_after();
void delete_at_beginning();
void delete_at_last();
void display();
void search();
void main ()
{
int choice =0;
while(choice != 9)
{
printf(“\n1. Insert in Beginning\n 2. Insert at last 3. Insert after a node\n3. Delete from
Beginning\n 4. Delete from last\n 5. Search\n 6. Show\n7.Exitt\n”);
printf(“\nEnter your choice?\n”);
scanf(“\n%d”, &choice);
98 Data Structures and Algorithms – I

switch(choice)
{
case 1:
insert_at_beginning();
break;
case 2:
insert_at_last();
break;
case 3:
delete_at_beginning();
break;
case 4:
delete_at_last();
break;
case 5:
search();
break;
case 6:
display();
break;
case 7:
exit(0);
break;
default:
printf(“Please enter valid choice..”);
}
}
}
//----------------------------------------------------------
void insert_at_beginning()
{
struct node *ptr,*temp;
int item;
ptr = (struct node *)malloc(sizeof(struct node));
if(ptr == NULL)
{
printf(“\nOVERFLOW”);
}
else
{
printf(“\nEnter Item value”);
scanf(“%d”, &item);
Linked List 99

ptr ĺ data = item;


if(head == NULL)
{
head = ptr;
ptr ĺ next = head;
ptr ĺ prev = head;
}
else
{
temp = head;
while(temp ĺ next != head)
{
temp = temp ĺ next;
}
temp ĺ next = ptr;
ptr ĺ prev = temp;
head ĺ prev = ptr;
ptr ĺ next = head;
head = ptr;
}
printf(“\nNode inserted\n”);
}
}
//----------------------------------------------------------
void insert_at_last()
{
struct node *ptr,*temp;
int item;
ptr = (struct node *) malloc(sizeof(struct node));
if(ptr == NULL)
{
printf(“\nOVERFLOW”);
}
else
{
printf(“\nEnter value”);
scanf(“%d”, &item);
ptr ĺ data = item;
if(head == NULL)
{
head = ptr;
ptr ĺ next = head;
100 Data Structures and Algorithms – I

ptr ĺ prev = head;


}
else
{
temp = head;
while(temp ĺ next !=head)
{
temp = temp ĺ next;
}
temp ĺ next = ptr;
ptr ĺ prev=temp;
head ĺ prev = ptr;
ptr ĺ next = head;
}
}
printf(“\n node inserted\n”);
}
//---------------------------------------------------------
void insert_after()
{
struct node *new,*temp,*ptr;
int vaue1,value2;
printf(“Enter value of the node after which to insert\n”);
scanf(“%d”, &value2);

printf(“Enter value of the new node to insert\n);


scanf(“%d”, &value1);
new ĺ data = value1; // Inserting the data
// Find node having value2 and next node of it
temp = head;
while (temp ĺ data != value2)
temp = temp ĺ next;
ptr = temp ĺ next;

// insert new between temp and ptr.


temp ĺ next = new;
new ĺ prev = temp;
new ĺ next = ptr;
ptr ĺ prev = new;
}
//---------------------------------------------------------
void delete_at_beginning()
Linked List 101

{
struct node *temp;
if(head == NULL)
{
printf(“\n UNDERFLOW”);
}
else if(head ĺ next == head)
{
head = NULL;
free(head);
printf(“\n node deleted\n”);
}
else
{
temp = head;
while(temp ĺ next != head)
{
temp = temp ĺ next;
}
temp ĺ next = head ĺ next;
head ĺ next ĺ prev = temp;
free(head);
head = temp ĺ next;
}
}
//-----------------------------------------------------------
void delete_at_last()
{
struct node *ptr;
if(head == NULL)
{
printf(“\n UNDERFLOW”);
}
else if(head ĺ next == head)
{
head = NULL;
free(head);
printf(“\nnode deleted\n”);
}
else
{
ptr = head;
102 Data Structures and Algorithms – I

if(ptr ĺ next != head)


{
ptr = ptr ĺ next;
}
ptr ĺ prev ĺ next = head;
head ĺ prev = ptr ĺ prev;
free(ptr);
printf(“\n Node deleted\n”);
}
}
//---------------------------------------------------------
void display()
{
struct node *ptr;
ptr = head;
if(head == NULL)
{
printf(“\n List is empty”);
}
else
{
printf(“\n printing values ... \n”);
while(ptr ĺ next != head)
{
printf(“%d\n”, ptr ĺ data);
ptr = ptr ĺ next;
}
printf(“%d\n”, ptr ĺ data);
}
}
//----------------------------------------------------------
void search()
{
struct node *ptr;
int item, I = 0,flag = 1;
ptr = head;
if(ptr == NULL)
printf(“\nEmpty List\n”);
else
{
printf(“\nEnter item to search?\n”);
Linked List 103

scanf(“%d”, &item);
if(head ĺ data == item)
{
printf(“item found at location %d”, i+1);
flag = 0;
}
else
{
while (ptr ĺ next != head)
{
if(ptr ĺ data == item)
{
printf(“Item found at location %d “, i+1);
flag = 0;
break;
}
else
{
flag = 1;
}
i++;
ptr = ptr ĺ next;
}
}
if(flag != 0)
{
printf(“Item not found\n”);
}
}
}

Output:
1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit
104 Data Structures and Algorithms – I

Enter your choice?


1

Enter Item value123

Node inserted

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


2

Enter value234

node inserted

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


1

Enter Item value90

Node inserted

1. Insert in Beginning
2. Insert at last
3. Insert after a node
Linked List 105

4. Delete from Beginning


5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


2

Enter value80

node inserted

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


4

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


5

node deleted

1. Insert in Beginning
2. Insert at last
106 Data Structures and Algorithms – I

3. Insert after a node


4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


7

printing values ...


123

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


6

Enter item which you want to search?


123
item found at location 1

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


3
Enter value of the node after which to “insert\n”);
2
Linked List 107

Enter value of the new node to insert


4

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit

Enter your choice?


7

printing values ...


1234

1. Insert in Beginning
2. Insert at last
3. Insert after a node
4. Delete from Beginning
5. Delete from last
6. Search
7. Show
8. Exit
Enter your choice?
8
3.5 Applications of Linked List
There are various applications of linked list in computer science like:
1. Implementation of stacks and queues.
2. Implementation of graphs: Adjacency list representation of graphs which use linked list to
store adjacent vertices.
3. Dynamic memory allocation: We use linked list of free blocks.
4. Maintaining directory of names.
5. Performing arithmetic operations on long integers
6. Manipulation of polynomials by storing constants in the node of linked list.
7. Representation of sparse matrices
We see here in brief two of them:
108 Data Structures and Algorithms – I

1. Polynomial Manipulation
Given two polynomial numbers represented by a linked list. We write a function that adds
these lists, means we add the coefficients who have same variable powers.
Example:
Input:
1st number = 5x^2 + 4x^1 + 2x^0
2nd number = 5x^1 + 5x^0
Output:
5x^2 + 9x^1 + 7x^0
Input:
1st number = 5x^3 + 4x^2 + 2x^0
2nd number = 5x^1 + 5x^0
Output:
5x^3 + 4x^2 + 5x^1 + 7x^0

Addition of two polynomials


Let us see addition of two polynomials using linked list. Following figure shows the linked
list representation of polynomials.

Fig. 3.38

Following is the C program for addition of two polynomials:


#include<stdio.h>
struct Node
{
int coeff;
int pow;
struct Node *next;
};
// -----------------------------------------------------
void create_node(int x, int y, struct Node **temp)
Linked List 109

{
struct Node *r, *z;
z = *temp;
if(z == NULL)
{
r =(struct Node*)malloc(sizeof(struct Node));
r ĺ coeff = x;
r ĺ pow = y;
*temp = r;
r ĺ next = (struct Node*)malloc(sizeof(struct Node));
r = r ĺ next;
r ĺ next = NULL;
}
else
{
r ĺ coeff = x;
r ĺ pow = y;
r ĺ next = (struct Node*)malloc(sizeof(struct Node));
r = r ĺ next;
r ĺ next = NULL;
}
}
// ------------------------------------------------
void polyadd(struct Node *poly1, struct Node *poly2, struct Node *poly)
{
while(poly1 ĺ next && poly2 ĺ next)
{
// If power of 1st polynomial is greater than 2nd, then store 1st as it is and move its pointer
if(poly1ĺ pow ĺ poly2 ĺ pow)
{
poly ĺ pow = poly1 ĺ pow;
poly ĺ coeff = poly1 ĺ coeff;
poly1 = poly1 ĺ next;
}
// If power of 2nd polynomial is greater than 1st, then store 2nd as it is and move its pointer
else if(poly1 ĺ pow < poly2 ĺ pow)
{
poly ĺ pow = poly2 ĺ pow;
poly ĺ coeff = poly2 ĺ coeff;
poly2 = poly2 ĺ next;
}
// If power of both polynomial numbers is same then add their coefficients
110 Data Structures and Algorithms – I

else
{
poly ĺ pow = poly1 ĺ pow;
poly ĺ coeff = poly1 ĺ coeff+poly2 ĺ coeff;
poly1 = poly1 ĺ next;
poly2 = poly2 ĺ next;
}
// Dynamically create new node
poly ĺ next = (struct Node *)malloc(sizeof(struct Node));
poly = poly ĺ next;
poly ĺ next = NULL;
}
while(poly1 ĺ next || poly2 ĺ next)
{
if(poly1 ĺ next)
{
poly ĺ pow = poly1 ĺ pow;
poly ĺ coeff = poly1 ĺ coeff;
poly1 = poly1 ĺ next;
}
if(poly2 ĺ next)
{
poly ĺ pow = poly2 ĺ pow;
poly ĺ coeff = poly2 ĺ coeff;
poly2 = poly2 ĺ next;
}
poly ĺ next = (struct Node *)malloc(sizeof(struct Node));
poly = poly ĺ next;
poly ĺ next = NULL;
}
}
// Display Linked list
void show(struct Node *node)
{
while(node ĺ next != NULL)
{
printf(“%dx^%d”, node ĺ coeff, node ĺ pow);
node = node ĺ next;
if(node ĺ next != NULL)
printf(“ + ”);
Linked List 111

}
}
//----------------------------------------------------------
int main()
{
struct Node *poly1 = NULL, *poly2 = NULL, *poly = NULL;
// Create first list of 5x^2 + 4x^1 + 2x^0
create_node(5,2,&poly1);
create_node(4,1,&poly1);
create_node(2,0,&poly1);
// Create second list of 5x^1 + 5x^0
create_node(5,1,&poly2);
create_node(5,0,&poly2);
printf(“1st Number:”);
show(poly1);
printf(“\n2nd Number:”);
show(poly2);
poly = (struct Node *)malloc(sizeof(struct Node));
// Function add two polynomial numbers
Poly add(poly1, poly2, poly);
// Display resultant List
printf(“\nAdded polynomial:”);
show(poly);
return 0;
}

Output:
1st Number: 5x^2 + 4x^1 + 2x^0
2nd Number: 5x^1 + 5x^0
Added polynomial: 5x^2 + 9x^1 + 7x^0
Multiplication of two polynomials using Linked list
Given two polynomials in the form of linked list. The task is to find the multiplication of
both polynomials.
Examples:
Input: Poly1: 3x^2 + 5x^1 + 6, Poly2: 6x^1 + 8
Output: 18x^3 + 54x^2 + 76x^1 + 48
On multiplying each element of 1st polynomial with elements of 2nd polynomial, we get
18x^3 + 24x^2 + 30x^2 + 40x^1 + 36x^1 + 48
On adding values with same power of x, we get
18x^3 + 54x^2 + 76x^1 + 48
112 Data Structures and Algorithms – I

Input: Poly1: 3x^3 + 6x^1 + 9, Poly2: 9x^3 + 8x^2 + 7x^1 + 2


Output: 27x^6 + 24x^5 + 75x^4 + 135x^3 + 114x^2 + 75x^1 + 18

Following figures depicts the linked list representation for multiplication of two polynomials:

Fig. 3.39

We use the following steps to multiply two polynomials:


1. Multiply the 2nd polynomial with each term of 1st polynomial.
2. Store the multiplied value in a new linked list.
3. Add the coefficients of elements having the same power in resultant polynomial.
Here is the C program implementation for addition of two polynomials:

#include <stdio.h>
struct Node {
int coeff, power;
Node* next;
};
// Function add a new node at the end of list
Node* add node(Node* start, int coeff, int power)
{
// Create a new node
Node* newnode = new Node;
newnode ĺ coeff = coeff;
newnode ĺ power = power;
newnode ĺ next = NULL;
// If linked list is empty
Linked List 113

if (start == NULL)
return new node;
// If linked list has nodes
Node* ptr = start;
while (ptr ĺ next != NULL)
ptr = ptr ĺ next;
ptr ĺ next = newnode;
return start;
}
//----------------------------------------------------------------------------------------------------------
void printList(struct Node* ptr)
{
while (ptr ĺ next != NULL) {
cout<<ptr ĺ coeff<< “x^” <<ptr ĺ power << “ +”;
ptr = ptr ĺ next;
}
cout<<ptr ĺ coeff<< “\n”;
}
// Function to add coefficients of two elements having same power
void remove Duplicates(Node* start)
{
Node *ptr1, *ptr2, *dup;
ptr1 = start;
/* Pick elements one by one */
while (ptr1 != NULL && ptr1 ĺ next != NULL) {
ptr2 = ptr1;
// Compare the picked element
// with rest of the elements
while (ptr2 ĺ next != NULL) {
// If power of two elements are same
if (ptr1 ĺ power == ptr2 ĺ next ĺ power) {
// Add their coefficients and put it in 1st element
ptr1 ĺ coeff = ptr1 ĺ coeff + ptr2 ĺ next ĺ coeff;
dup = ptr2 ĺ next;
ptr2 ĺ next = ptr2 ĺ next ĺ next;
// remove the 2nd element
delete (dup);
}
else
ptr2 = ptr2 ĺ next;
}
ptr1 = ptr1 ĺ next;
114 Data Structures and Algorithms – I

}
}
// Function two Multiply two polynomial Numbers
Node* multiply(Node* poly1, Node* poly2,
Node* poly3)
{
// Create two pointer and store the address of 1st and 2nd polynomials
Node *ptr1, *ptr2;
ptr1 = poly1;
ptr2 = poly2;
while (ptr1 != NULL) {
while (ptr2 != NULL) {
int coeff, power;
// Multiply the coefficient of both polynomials and store it in coeff
coeff = ptr1 ĺ coeff * ptr2 ĺ coeff;
// Add the power of both polynomials and store it in power
power = ptr1 ĺ power + ptr2 ĺ power;
//Invoke add node function to create a new node by passing three parameters
poly3 = add node(poly3, coeff, power);
// move the pointer of 2nd polynomial two get its next term
ptr2 = ptr2 ĺ next;
}
// Move the 2nd pointer to the starting point of 2nd polynomial
ptr2 = poly2;
// move the pointer of 1st polynomial
ptr1 = ptr1 ĺ next;
}
// this function will be invoked to add the coefficient of the elements having same power
from the resultant linked list
removeDuplicates(poly3);
return poly3;
}
//-------------------------------------------------------------------------------------------------------
int main()
{
Node *poly1 = NULL, *poly2 = NULL, *poly3 = NULL;
// Creation of 1st Polynomial: 3x^2 + 5x^1 + 6
poly1 = addnode(poly1, 3, 2);
poly1 = addnode(poly1, 5, 1);
poly1 = addnode(poly1, 6, 0);
// Creation of 2nd polynomial: 6x^1 + 8
poly2 = addnode(poly2, 6, 1);
Linked List 115

poly2 = addnode(poly2, 8, 0);


cout<< “1st Polynomial:- “;
printList(poly1);
cout<< “2nd Polynomial:- “;
printList(poly2);
poly3 = multiply(poly1, poly2, poly3);
cout<< “Resultant Polynomial:- “;
printList(poly3);
return 0;
}

Output:
1st Polynomial: 3x^2 + 5x^1 + 6
2nd Polynomial: 6x^1 + 8
Resultant Polynomial: 18x^3 + 54x^2 + 76x^1 + 48
2. Memory Management in OS
The memory is one of the important resources of computer system and plays an important
role in achieving good system performance. A processor cannot achieve its required performance
if it cannot access data at sufficient speed.
The memory manager tries to use the available memory in the most efficient way. It keeps
track of which parts of memory are in use and which parts are free, it allocates memory to
processes when they need it and deallocates it when they are finished.
Operating system keeps track of memory by maintaining a linked list of allocated memory
segments, and free memory segments, where a segment is either a process or a hole between the
two processes.
When processes and holes are kept on a list that is sorted by address, several algorithms can
be used to allocate memory for a newly created process or an existing process being swapped in
from the disk. We assume here that memory manages knows how much memory to allocate.
There are different types of algorithms to perform this.
Following are the different types of algorithms:
First fit:
In first fit algorithm, the memory manager scans along the list of segments until it finds a
hole that is big enough. The hole is broken into two parts, that is, for process and for unused
memory except in likely case of an exact fit. This is a fast algorithm because it searches as little as
possible.
Next fit:
The next fit algorithm works similar to first fit algorithm, except that it keeps track of where
it is whenever it finds a suitable hole
116 Data Structures and Algorithms – I

Best fit:
This algorithm searches the whole list and takes the smallest hole that is adequate. Rather
than breaking up a big hole that might be needed later, this algorithm tries to find a hole that is
close to the actual size that needed.
Worst fit:
This algorithm always takes the largest available hole, so that the hole broken off will be big
enough to be useful.
Quick fit:
This algorithm maintains separate lists for some of the more common sizes requested.
For example, it might have a table with n entries, in which the first entry is a pointer to the
head of a list of 5-KB holes, the second entry is a pointer to a list of 7-KB holes, the third entry a
pointer to 13-KB holes, and so on. Holes of say, 21 KB, could either be put on the 20-KB list or
on a special list of odd-sized holes. With quick fit, finding a hole of the required size is extremely
fast, but it has the same disadvantage as all schemes that sort by hole size, that, when a process
terminates or is swapped out, finding its neighbours to see if a merge is possible is expensive. If
merging is not done, memory will quickly fragment into a large number of small holes into which
no processes fit.
Generalized Linked List
Concept
A Generalized Linked List L, is defined as a finite sequence of n>=0 elements, l1, l2, l3,
l4, …, ln, such that li are either atom or the list of atoms. Elements of the linear list are atomic in
nature.
Thus L = (l1, l2, l3, l4, …, ln), where n is total number of nodes in the list and li are either
atoms or lists.
This is a special type of list, which contains not only individual data items but also pointers
to other list.
Examples:
z A = (): empty or NULL list
z P = (a,b,c): a list of three elements, P(1) = a , P(2) = b, P(3) =c
z In a generalized list L, each element li is either atom or a list itself.
z C = (p,(q,r),t): a list of three elements where,
ˆ first element ‘p’ is atom.
ˆ Second element (q, r) is a list.
ˆ Third element ‘t’ is atom.

Representation
To represent a list of items following are certain assumptions about the node structure:
z Flag = 1 implies that down pointer exists
z Flag = 0 implies that next pointer exists
z Data means the item
Linked List 117

z Down pointer is the address of node which is down of the current node
z Next pointer is the address of node which is attached as the next node

Fig. 3.40

Use of Generalized Linked List


Although the efficiency of polynomial operations using linked list is good but still, the
disadvantage is that the linked list is unable to use multiple variable polynomial equation
efficiently. Generalized linked lists help us to represent multi-variable polynomial along with the
list of elements.
Typical ‘C’ Structure of Generalized Linked List
typedef struct node {
char c; //Data
int flag; //Flag
struct node *next, *down; //Next & Down pointer
}GLL;

Fig. 3.41

When first field is 0, it indicates that the second field is variable. If first field is 1 means the
second field is a down pointer, means some list is starting.
Multiple-Variable Polynomial Representation Using Generalized Linked List
The typical node structure will be:

Fig. 3.42
118 Data Structures and Algorithms – I

Here
z Flag = 0 means variable is present
z Flag = 1 means down pointer is present
z Flag = 2 means coefficient and exponent is present
Example:
9x5 + 7xy4 + 10xz

Fig. 3.43

In the above example the head node is of variable x. The temp node shows the first field as 2
means coefficient and exponent are present.

Fig. 3.44

Since temp node is attached to head node and head node is having variable x, temp node
having coefficient = 9 and exponent = 5. The above two nodes can be read as 9x5.

Fig. 3.45
Linked List 119

Similarly, in the above figure, the node temp1 can be read as x4.
z The flag field is 1 means down pointer is there
z temp2 = y
z temp3 = coefficient = 7
z exponent = 1
z flag = 2 means the node contains coefficient and exponent values.
z temp2 is attached to temp3 this means 7y1 and temp2 is also attached to temp1 means
z temp1 x temp2
z x4 x 7y1 = 7x4y1 value is represented by above figure.

QUESTIONS
Practice Questions (2 marks)
1. List the advantages of Dynamic Memory Allocation.
2. What are applications of linked lists in computer science?
3. What are applications of linked lists in real world?
4. What are types of linked lists?
5. Give the representation of generalized linked list for the following (1 mark each).
z B= (p, (q, r), s)
z A= (1, 2, (3, (4, 5)), 6)
z G= (a, (b, c, d ),e,f)

Practice Questions (4 marks)


1. Write a function to remove given node of singly linked list and add it at the beginning.
2. What are the drawbacks of sequential storage?
3. Write a function to display odd positions nodes from singly linked list.
4. What is singly circular linked list? Explain its node structure.
5. What is doubly circular linked list? Explain its node structure.
6. Write a function to sort a given singly linked lists.
7. Write a function to reverse a given singly linked lists.
8. Write a function to create and display circular singly linked list.
9. Write a function to find the average of elements nodes in a singly linked list.
E.g., (Value of 1st node + value of 2nd node+……)/ number of nodes.
10. Write a function for concatenation of two singly linked list.
11. Write a C program to accept the details of student (roll nos name, per) and display it.
(Use Dynamic memory allocation).
12. Write a function to display doubly linked list in reverse order.
13. Write a function to remove given node from singly linked list and add it at the given position in
singly linked list.
14. Write a function to remove first node from singly linked list and display remaining list.
15. Write a function to remove last node from doubly linked list.
16. Write a function to add node at the beginning of circular singly linked list.
120 Data Structures and Algorithms – I

17. How are polynomials represented using linked lists? Give a C function for addition of two
polynomials.
18. How are polynomials represented using linked lists? Give a C function for multiplication of two
polynomials.
19. Explain following applications of linked list:
(a) Polynomial manipulation (b) Memory management in OS
20. What is a generalized linked list? What are its advantages?

ˆˆˆ
4 STACKS

Structure:
4.1 Introduction
4.2 Representation – Static and Dynamic
4.3 Operations – init(), push(), pop(), isEmpty(), isFull(), peek()
4.4 Application – String reversal, Function Call, Infix to postfix, Infix to prefix, Postfix Evaluation,
Backtracking – 4 Queens Problem

4.1 Introduction
Stack
z Stack is a linear data structure which performs the operations in a specific order. The
order may be LIFO (Last-In-First-Out) or FILO (First-In-Last-Out).
z In case of Last-In-First-Out (LIFO) lists, the element which is inserted first in the stack,
will be deleted last from the stack.
z Stack can be also called as an ordered list in which, insertion and deletion can be
performed only at one end that is called top.
z Stack is a recursive data structure having pointer to its top element.
There are many real-life examples of a stack. Consider an example of plates stacked over
one another in the 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 bottom most position remains in the stack for the longest
period of time. So, it can be simply seen to follow LIFO (Last-In-First-Out)/FILO (First-In-Last-
Out) order.

Fig. 4.1: Stack


122 Data Structures and Algorithms – I

4.2 Representation – Static and Dynamic


There are two ways to represent (implement) a stack:
z Static Implementation using an array.
z Dynamic Implementation using a linked list.

Static Implementation of Stack


In static implementation, the stack is formed by using the array. All the operations regarding
the stack are performed using arrays. Let’s see how each operation can be implemented on the
stack using array data structure.
There can be three possible cases while performing operations on stack:
Case 1: Stack is empty
The stack is called empty if it doesn’t contain any element inside it. At this stage, the
value of variable top is -1.
Case 2: Stack is not empty
Value of top will get increased by 1 every time when we add any element to the stack. In
the following stack, after adding first element, top = 2.
Case 3: Deletion of an element
Value of top will get decreased by 1 whenever an element is deleted from the stack.
In the stack in Scenario 3, after deleting 10 from the stack, top = 1.
Time Complexities of Operations on Stack
We do not run any loop in any of the following operations – push(), pop(), isEmpty() and
peek() and hence all take O(1) time.
Following figure explains the PUSH and POP Operations:

Fig. 4.2: PUSH and POP Operations


Stacks 123

The stack grows and shrinks like this:

Scenario 1 Scenario 2 Scenario 3

Fig. 4.3

4.3 Operations – init(), push(), pop(), isEmpty(), isFull(), peek()


These are the operations that can be performed on stack:
1. init(): Used to initialize a new empty stack.
2. push(): Add a new item to the stack.
3. pop(): Remove and return an item from the stack. The item that is returned is always the
last one that was added.
4. is_empty(): Check whether the stack is empty. isEmpty() conventionally returns a
Boolean value: True if size is 0, else False.
5. isFull() í check if stack is full.
6. peek() í gets the top data element of the stack, without removing it.
Push Operation
When an element is added onto the top of the stack it is referred to as push operation. Push
operation involves following two steps.
1. Increment the variable top so that it can now refer to the next memory location.
2. Add element at the position of incremented top. This is referred to as adding new element
at the top of the stack.
Stack is overflown when we try to insert an element into a completely filled stack therefore,
our main function must always avoid stack overflow condition.
Algorithm:
begin
if top = n then stack full
top = top + 1
stack (top) : = item;
end
124 Data Structures and Algorithms – I

Time Complexity: O(1)


Pop Operation
Deletion of an element from the top of the stack is called pop operation. The top most
element of the stack is stored in another variable and then the top is decremented by 1. The
operation returns the deleted value that was stored in another variable as the result. The underflow
condition occurs when we try to delete an element from an already empty stack.
Algorithm:
begin
if top = 0 then stack empty;
item := stack(top);
top = top - 1;
end;

Time Complexity: O(1)


Peek Operation
Visiting each element is called as peek operation, it involves returning the element which is
present at the top of the stack without deleting it. Underflow condition can occur if we try to
return the top element in an already empty stack.
Algorithm: peek (stack, top)
if top = -1 then stack empty
item = stack[top]
return item
Time Complexity: O(n)
Here we have the simple implementation of above 6 operations in C language:
init(int max)
{
int stack[max];
top = -1
}
//----------------------------------------------------------------------------------------------------------
int peek()
{
if ( isEmpty() == True )
{
print( “Stack is empty!” )
return -1
}
else
return stack[top]
}
Stacks 125

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

void push(int data) {


if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf(“Could not insert data, Stack is full.\n”);
}
}
//-----------------------------------------------------------------------------------------------------------
int pop(int data) {
if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf(“Could not retrieve data, Stack is empty.\n”);
}
}

C Program: Static Implementation of Stack


#include <stdio.h>
#DEFINE MAXSIZE 100
int stack[MAXSIZE],i,j,ch=0,n,top;
void init_stack();
void push();
void pop();
void display();
void main ()
126 Data Structures and Algorithms – I

{
printf(“How many elements in the stack?\n”);
scanf(“%d”, &n);
printf(“-----STACK -----\n”);
while(ch != 4)
{
printf(“\n1.Push\n 2.Pop\n 3.Display\n 4.Exit”);
printf(“\n Enter your choice \n”);
scanf(“%d”, &choice);
switch(ch)
{
case 1:
{
push();
break;
}
case 2:
{
pop();
break;
}
case 3:
{
display ();
break;
}
case 4:
{
printf(“Exiting....”);
break;
}
default:
printf(“Please Enter valid choice”);
}
}
}
//----------------------------------------------------
void init_stack()
{
top = -1;
}
//---------------------------------------------------
Stacks 127

intisempty()
{
if(top == -1)
return 1;
else
return 0;
}
//-----------------------------------------------------
intisfull() {
if(top == MAXSIZE)
return 1;
else
return 0;
}
//----------------------------------------------------
void push ()
{
int ele;
if (isfull())
printf(“\n Stack Overflow”);
else
{
printf(“Enter the element to push”);
scanf(“%d”,&ele);
top = top +1;
stack[top] = ele;
}
}
//-------------------------------------------------void pop ()
{
if(isempty())
printf(“Stack Underflow”);
else
{
top = top -1;
printf(“Element removed…\n”);
}
//-------------------------------------------------void display()
{
if(isempty())
printf(“Stack is empty”);
else
128 Data Structures and Algorithms – I

{
printf(“Stack elements are…..\n”)
for (i=top;i>=0;i--)
printf(“%d\t”, stack[i]);
}

Output
----------STACK-------
1. Push
2. Pop
3. Display
4. Exit
Enter your choice
1
Enter element to push
10
1. Push
2. Pop
3. Display
4. Exit
Enter your choice
1
Enter element to push
20
1. Push
2. Pop
3. Display
4. Exit
Enter your choice
1
30
1. Push
2. Pop
3. Display
4. Exit
Enter your choice
3
Stack elements are…….
102030
1. Push
2. Pop
3. Display
Stacks 129

4. Exit
Enter your choice
2
Element removed
1. Push
2. Pop
3. Display
4. Exit
Enter your choice
3
Stack elements are…….
1020

Dynamic Implementation of Stack


In static implementation, the stack can be implemented using sequential representation with
array. Instead, we can also use linked list to implement stack. Linked list allocates the memory
dynamically. But the time complexity in both the scenario is same for all the operations, i.e., push,
pop and peek.
With stack as linked list, the stack can grow to any size, and hence is called as dynamic
implementation of stack. When an element is popped, the memory can be freed.
The top most node in the stack always contains null in its address field. Let’s discuss the
way in which, each operation is performed in linked list implementation of stack.
Push
Adding a node to the stack is referred to as push operation. Pushing an element to a stack in
linked list implementation is different from that of an array implementation. Push operation
involves the following steps.
1. Create a node first and allocate memory to it. The node structure for this is:
struct node
{
int data;
struct node *next;
}*top, *new;

Here top is a pointer that points to top of the stack.


Let’s understand with the following sequence of stack operations:
push(5), push(6), push(7), pop(), pop().
Step I: If the list is empty then the item is to be pushed as the start node of the list, so
top = NULL
130 Data Structures and Algorithms – I

Step II: push(5)

Step III: push(20)


Now, since top is not NULL, the new node now is connected to top as:

Step IV: push(7)

The new element is thus pushed at the top.


Pop
Deleting a node from the top of stack is referred to as pop operation. Pop operation involves
the following steps:
1. Check for the underflow condition: The underflow condition occurs when we try to
pop from an already empty stack. The stack will be empty if the head pointer of the list
points to null.
2. Adjust the head pointer accordingly: In stack, the elements are popped only from one
end, therefore, the value stored in the head pointer must be deleted and the node must be
freed. The next node of the head node now becomes the head node.
Stacks 131

Step V: pop()

Step VI: pop()


After adjusting pointers as in step V, we get:

Display
Displaying all the nodes of a stack needs traversing all the nodes of the linked list organized
in the form of stack. For this purpose, we need to follow the following steps.
1. Copy the head pointer into a temporary pointer.
2. Move the temporary pointer through all the nodes of the list and print the value field
attached to every node.
C Program: Dynamic Implementation of Stack Operations using Linked List:
#include <stdio.h>
#include <stdlib.h>
void push();
void pop();
void display();
struct node
{
int num;
struct node *next;
} *top,*new;
132 Data Structures and Algorithms – I

void main ()
{
int choice=0;
top = NULL;
printf(“\nStack operations using linked list\n”);while(choice != 4)
{
printf(“Chose one from the below options\n”);
printf(“\n1.Push\n2.Pop\n3.Display\n4.Exit”);
printf(“\n Enter your choice \n”);
scanf(“%d”, &choice);
switch(choice)
{
case 1:
push();
break;
case 2:
pop();
break;
case 3:
display();
break;
default:
printf(“Please Enter valid choice”);
} // end of switch
} // end of while loop
} // end of main
//---------------------------------------------------
void push ()
{
int item;
new=(struct node *)malloc(sizeof(struct node));
printf(“Enter the item to push\n”);
scanf(“%d”, &item);
new ĺ num=item;
if(top == NULL)
top=new;
else
{
new ĺ next = top;
top= new;
}
printf(“Item pushed”);
Stacks 133

}
//------------------------------------------------
void pop()
{
int item;
struct node *temp;
if (top == NULL)
printf(“Underflow”);
else
{
item = top ĺ num;
temp = top;
top= top ĺ next;
free(temp);
printf(“Item popped”);
}
}
void display()
{
int i;
struct node *temp;
temp=head;
if(top== NULL)
printf(“Stack is empty\n”);
else
{
printf(“Printing Stack elements \n”);
while(temp!=NULL)
{
printf(“%d\n”, temp->num);
temp= temp ĺ next;
}
}
}

4.4 Applications of Stack


This is the list of some common applications of Stack.
1. Expression Evaluation
Prefix, postfix and infix expressions evaluation makes use of stack.
2. Expression Conversion
There are three ways an expression can be represented – prefix, postfix and infix notation.
Stack is used to convert one form of expression to another.
134 Data Structures and Algorithms – I

3. Syntax Parsing
Compilers also use stack for parsing the syntax of expressions, program blocks etc. before
translating into machine level code.
4. Backtracking
Backtracking is one of the algorithm designing techniques. In backtracking, we follow
some execution path, if that path is not found efficient at a particular stage, one can come
back to the previous state and go into some other paths.
To get back from current state, we need to store the previous state. Stack is used for this.
Some examples of backtracking are finding the solution for Knight Tour problem or N-
Queen Problem etc.
Another example is finding a path for solving maze problem, we choose a path and after
following it, if we realize that it is wrong, need to go back to the beginning of the path to
start with new path. This can be done with the help of stack.
5. String Reversal
Stack is used to reverse a string. We push the characters of string one by one into stack
and then pop character from stack.
6. Memory Management
Any modern computer environment uses a stack as the primary memory management
model for a running program. Whether it’s native code (x86, Sun, VAX) or JVM, a stack
is at the centre of the run-time environment for Java, C++, Ada, FORTRAN, etc.
7. Function Call, Recursion
Stack is used to keep information about the active functions or subroutines.
When we call a function from one other function, that function call statement may not be
the first statement.
After executing the called function, the control of execution should resume back to the
instruction after the function call statement, where it was left. This is done by storing the
address of the program counter onto the stack. After completion of the called function
execution, it pops out the address from stack and assign it into the program counter to
resume the task again.
8. Checking correctness of nested parenthesis.
9. Browsers: Forward and backward feature in web browsers
10. Editors: Redo-undo features at many places like editors, Photoshop.
11. Tree Traversals
Here we discuss the following applications of stack in detail:
1. String reversal
2. Function Call
3. Infix to postfix
4. Infix to prefix
5. Postfix Evaluation
6. Backtracking – 4 Queens problem
Stacks 135

String Reversal
The logic used to reverse a string is as follows:
1. Read a string.
2. Push all characters until NULL is not found – Characters will be stored in stack variable.
3. Pop all characters until NULL is not found – As we know stack is a LIFO technique, so
last character will be pushed first and finally we will get reversed string in a variable in
which we store inputted string.
C Program to Reverse a String using STACK
#include <stdio.h>
#include <string.h>
#define MAX 100
int top=-1;
int item;
charstack[MAX];
voidpush(char item);
char pop(void);
intisEmpty(void);
intisFull(void);
intmain()
{
charstr[MAX];
inti;
printf(“Input a string:”);
gets(str);
for(i=0;i<strlen(str);i++)
push(str[i]);
for(i=0;i<strlen(str);i++)
str[i]=pop();
printf(“Reversed String is: %s\n”,str);
return 0;
}
//-------------------------------------------
Voidpush (char item)
{
if(isFull())
{
printf(“\nStack is FULL !!!\n”);
return;
}
top=top+1;
stack[top]=item;
136 Data Structures and Algorithms – I

}
charpop()
{
if(isEmpty())
{
printf(“\nStack is EMPTY!!!\n”);
return 0;
}
item = stack[top];
top=top-1;
return item;
}
//----------------------------------
int isEmpty()
{
if(top==-1)
return 1;
else
return 0;
}
//---------------------------------
int isFull()
{
if(top==MAX-1)
return 1;
else
return 0;
}

Output:
Input a string: Hello World!
Reversed String is: !dlroW olleH

2. Function Call
In computer science, a stack data structure is used to store information about the active
subroutines(functions) of a computer program. This kind of stack is also known as a call stack, an
execution stack, program stack, control stack, run-time stack, or machine stack, and is often
shortened to just “the stack”.
A call stack is used mainly to keep track of the point to which each active subroutine
(function) should return control when it finishes executing. Thus, the stack stores the return
addresses. When a function is called, the location (address) of the instruction at which the calling
routine can later resume needs to be saved somewhere.
Stacks 137

A function frequently needs memory space for storing the values of local variables, and do
not retain their values after it returns. This space is allocated by simply moving the top of the
stack. Each separate activation of a function gets its own separate space in the stack for local
variables.
An active function is one that has been called, and is in execution process, after which
control should be returned back to the point of call. Such function calls can be nested to any level
(recursive functions), hence the stack structure is needed.
For example, if a function Draw_Square() calls a function Draw_Line() from four different
places, Draw_Line() must know where to return when its execution gets completed. For this, the
address following the instruction that jumps to Draw_Line(), the return address, is pushed onto
the top of the call stack with each call.
Recursion is a process where a function calls itself. Each recursive call in such function
creates a new copy of that function in the memory. When this call returns its data, the copy is
removed from the memory. The compiler uses a stack to store the values of the variables in each
call. So, recursion has to maintain the stack and track the values of the variables defined in the
stack.
Let us understand the application of stack for handling function calls with the example of
recursive calls. Consider the following recursive definition of the factorial() function.
int factorial(int n)
{
if n=0
return(1);
else
return( n* factorial(n-1));
}
This is the most common way to define factorial function. But for very large values of n, we
get a StackOverflowError. This is because recursion is implemented as growing and shrinking a
stack in memory.
For example, consider the following simulation of the computation of factorial(3).

Fig. 4.4
138 Data Structures and Algorithms – I

That is, to compute factorial(3), we need to compute factorial(2) and then multiply the result
by 3. Since we have very specific instructions to perform after computing factorial(2) (i.e.,
multiplying by 3).
These instructions are to be saved so we can come back to them after computing factorial(2).
The local variables are stored on a stack and then the function factorial(2) is computed. We repeat
this process until the values of factorial(2) is computed.
Here is one more example to explain the memory allocation of the recursive functions.
int print (int n)
{
if(n == 0)
return 0; // terminating condition
else
{
printf(“%d”,n);
return print(n-1); // recursive call
}
}
Let us examine this recursive function for n = 4. First, all the stacks are maintained which
prints the corresponding value of n until n becomes 0, once the termination condition is reached,
the stacks get destroyed one by one by returning 0 to its calling stack. Following image stack
trace for these recursive functions.

Fig. 4.5: Stack Tracing for Recursive Function Call


Stacks 139

3. Infix to Postfix
An arithmetic expression consists of operators and operands. An arithmetic expression can
be written in three different but equivalent notations, i.e., without changing its meaning or output.
These notations are í
z Infix Notation
z Prefix (Polish) Notation
z Postfix (Reverse-Polish) Notation
Above notations are named based on how operators are placed in the expression.
Infix Notation
In infix notation, operators are placed between operands, as in a - b + c. In Mathematics, we
use infix notation, but this notation is not efficient with computing devices. This is because, an
algorithm to process infix notation could be difficult and costly in terms of time and space
consumption.
Suppose we want to divide a number a by difference of b and c, the expression will be a/b-c.
According to the precedence rules of the operators, division will be performed first, which would
create a wrong result. Hence to remove the ambiguity of operators we use parenthesis and the
correct expression will be a/(b-c). This problem of ambiguity is eliminated in the prefix and
postfix notations.
Prefix Notation
In prefix notation, operator is prefixed to operands, i.e., operator is written ahead of
operands. For example, +ab. This is equivalent to its infix notation a + b. Prefix notation is also
known as Polish Notation.
Postfix Notation
This notation style is known as Reversed Polish Notation. The name is given postfix, as the
operator is postfixed to the operands, i.e., the operator is written after the operands. For example,
ab+. This is equivalent to its infix notation a + b.
The following table briefly shows the example expressions in all three notations:

Sr. No. Infix Notation Prefix Notation Postfix Notation


1. a+b +ab ab+
2. (a + b) c +abc ab+c
3. a (b + c) a+bc abc+
4. a/b+c/d +/ab/cd ab/cd/+
5. (a + b) (c + d) +ab+cd ab+cd+
6. ((a + b) c) – d - +abcd ab+c d-

Since infix notation is not very efficient way to design an algorithm or program to parse
infix notations. So, infix notations are first converted into either postfix or prefix notations and
then computed. To parse any arithmetic expression, we need to take care of operator precedence
and associativity also.
140 Data Structures and Algorithms – I

Precedence
When an operand is in between two different operators, which operator will take the operand
first, is decided by the precedence of an operator over others. For example –
a + b * c ĺ a + (b * c)
As multiplication operation has precedence over addition, b * c will be evaluated first. A
table of operator precedence is given in the table below.
Associativity
When more than one operator with the same precedence appear in an expression,
associativity rule of operators needs to be applied. For example, in expression a + b í c, both +
and – have the same precedence. Whether a+b will be evaluated first or b-c should be evaluated
first, is determined by associativity of operators + and í. Here, both + and í are left associative,
so the expression will be evaluated as (a + b) í c.
Precedence and associativity determine the order of evaluation of an expression. Following
is an operator precedence and associativity table (highest to lowest) –
Sr. No. Operator Precedence Associativity
1. Exponentiation ^ Highest Right Associative
2. Multiplication ( ) & Division ( / ) Second Highest Left Associative
3. Addition ( + ) & Subtraction ( í ) Lowest Left Associative

The order of expression evaluation, can be altered by using parenthesis. For example í
In a + b*c, the expression part b*c will be evaluated first, with multiplication as precedence
over addition. We here use parenthesis for a + b to be evaluated first, like (a + b)*c.
Significance of Postfix and Prefix Notations:
z One of the applications of Stack is to convert arithmetic expressions in high-level
programming languages into machine readable form. An infix expression is difficult for
the machine to know and keep track of precedence of operators, whereas a postfix
expression itself determines the precedence of operators as the position of operators in a
postfix expression depends upon its precedence. Therefore, for the machine it is easier to
carry out a postfix expression than an infix expression.
z Computer system can only understand and work on a binary language, it assumes that an
arithmetic operation can take place in two operands only, e.g., a+b, c*d, d/a etc. But an
arithmetic expression may consist of more than one operator and two operands e.g.,
(a+b)*c(d/(j+d)).
z These complex arithmetic operations can be converted into polish notation using stacks
which then can be executed in two operands and an operator form. These expressions thus
become parenthesis free.
Following data structures are required to convert an infix expression to postfix:
1. A string that contains infix expression.
2. A stack which will store the operators.
3. A string that will store resultant postfix expression.
Algorithm to convert the infix to postfix expression.
Stacks 141

1. Push “(“onto Stack, and add”)” to the end of infix string.


2. Read a character from the infix string from left to right and repeat Step 3 to 6 for each
character of infix string until the Stack is empty.
3. If it is an operand, add it to postfix string.
4. If it is a left parenthesis, push it onto Stack.
5. If an operator is encountered, then:
(i) Repeatedly pop from Stack and add to postfix string each operator (on the top of Stack)
which has the same precedence as or higher precedence than operator.
(ii) Add operator to Stack.
6. If a right parenthesis is encountered, then:
(i) Repeatedly pop from Stack and add to postfix string each operator (on the top of Stack)
until a left parenthesis is encountered.
(ii) Remove the left Parenthesis.
Following example illustrates above algorithm: here infix expression is A+ (B*C-
(D/E^F)*G)*H, where ^ is an exponential operator.
Infix to Postfix Conversion using Stack

Converted Postfix Expression is ABC*DEF^/G*-H*+


142 Data Structures and Algorithms – I

C Program: Conversion of Infix to Postfix Expression


#include<stdio.h>
#include<stdlib.h>
#include<ctype.h> /* for isdigit(char ) */
#include<string.h>
#define SIZE 100
char stack[SIZE];
int top = -1;
void push(char item)
{
if(top >= SIZE-1)
printf(“\nStack Overflow.”);
else
{
top = top+1;
stack[top] = item;
}
}
char pop()
{
char item;
if(top <0)
{
printf(“stack under flow”);
getchar();
exit(1);
}
else
{
item = stack[top];
top = top-1;
return(item);
}
}
//-------------------------------------------------------------------------------------
int check_operator(char opr)
{
if(opr == ‘^’ || opr == ‘*’ || opr == ‘/’ || opr == ‘+’ || opr ==‘-’)
return 1;
else
return 0;
}
Stacks 143

//-------------------------------------------------------------------------------------
int check_precedence(char opr)
{
if(opr == ‘^’)
return(3);
elseif(opr == ‘*’ || opr == ‘/’)
return(2);
elseif(opr == ‘+’ || opr == ‘-’)
return(1);
else
return(0);
}
//-----------------------------------------------------------------------------------------
void infix_postfix(char infix_str[], char postfix_str[])
{
int i, j;
char ch;
char x;
push(‘(‘); /* push ‘(‘ onto stack */
strcat(infix_str,”)”); /* add ‘)’ to infix expression */
i=0;
j=0;
item=infix_str[i];
while(ch != ‘\0’) /* to read each character till end of infix expression */
{
if(ch == ‘(‘)
push(ch);
elseif( isdigit(ch) || isalpha(ch))
{
postfix_str[j] = ch;
j++;
}
elseif(check_operator(ch) == 1)
{
x=pop();
while(is_operator(x) == 1 &&check_precedence(x)>= check_precedence(ch))
{
// pop all higher precedence operator and add them to postfix expression
postfix_str[j] = x;
j++;
x = pop();
}
144 Data Structures and Algorithms – I

push(x);
push(ch);
}
elseif(ch == ‘)’) // check if current symbol is ‘)’
{
x = pop(); // pop until ‘(‘ is encountered
while(x != ‘(‘)
{
postfix_str[j] = x;
j++;
x = pop();
}
}
else
{
printf(“\nInvalid infix Expression.\n”);
getchar();
exit(1);
}
i++;
ch = infix_str[i]; /* go to next symbol of infix expression */
} // end of while
if(top>0)
{
printf(“\nInvalid infix Expression.\n”);
getchar();
exit(1);
}
postfix_str[j] = ‘\0’;
}
//-----------------------------------------------------------------------------------------------------
void main()
{
char infix[SIZE], postfix[SIZE];
printf(“\nEnter Infix expression:”);
gets(infix);

infix_postfix(infix,postfix);
printf(“Postfix Expression:”);
puts(postfix);
getch();
}
Stacks 145

Output:
Enter Infix expression: a+(b*c-(d/e^f)*g)*h
Postfix Expression:abc*def^/g*-h*+
Enter Infix expression: (3^2*5)/(3*2-3)+5
Postfix Expression: 32^5*32*3-/5+

4. Infix to Prefix
For writing arithmetic expressions, we use infix notations. Computers do not understand this
format because they need to keep in mind rules of operator precedence and also brackets. Hence
computers find Prefix and Postfix expressions easier to understand and evaluate.
Following data structures are required to convert an infix expression to postfix:
1. A string that contains parenthesized infix expression.
2. A stack which will store the operators.
3. A string that will store resultant prefix expression.
Examples of prefix expression:
Infix: a * b + c/d, Prefix: + * a b/cd
Infix: (a - b/c) * (a/k-l), prefix: *-a/bc-/akl
Algorithm to convert Infix to Prefix
1. Push “)” onto stack, and add “(“ to end of the A.
2. Read each character of infix string from right to left and repeat step 3 to 6 for each
element of infix string until the stack is empty.
3. If an operand is encountered add it to prefix string.
4. If a right parenthesis is encountered push it onto stack.
5. If the character is operator then:
(a) Repeatedly pop from stack and add to prefix string, each operator (on the top of stack)
which has same or higher precedence than the operator.
(b) Push operator to stack.
6. If left parenthesis is encountered then
(a) Repeatedly pop from the stack and add to prefix string (each operator on top of stack =
until a left parenthesis is encountered.)
(b) Remove the left parenthesis.
7. Exit
C Program: Infix to Prefix
#include<stdio.h>
#include<string.h>
#include <ctype.h>
#define MAX 50
char stack[MAX];
int top=-1;
push(char ch)
146 Data Structures and Algorithms – I

{
stack[++top]=ch;
}
//------------------------------------------------------------------------------------------------------------
char pop()
{
return(stack[top--]);
}
//------------------------------------------------------------------------------------------------------------
int chk_precedence(char opr)
{
switch(opr)
{
case ‘#’: return 0;
case ‘)’: return 1;
case ‘+’
case ‘-’:
return 2;
case ‘*’:
case ‘/’: return 3;
}
}
//----------------------------------------------------------------------------------------------------------
main()
{
char infix[50],prefix[50],ch,x;
int i=0,k=0;
printf(“\n\nEnter the Infix Expression”);
scanf(“%s”, infix);
push(‘#’);
strrev(infix);
while( (ch=infix[i++]) != ‘\0’)
{
if( ch == ‘)’)
push(ch);
else if(isalnum(ch))
prefix[k++]=ch;
else if( ch == ‘(‘)
{
while( stack[top] != ‘)’)
prefix[k++]=pop();
x=pop();
Stacks 147

}
else
{
while( chk_precedence(stack[top]) >= chk_precedence (ch) )
perfix[k++]=pop();
push(ch);
}
}
while( stack[top] != ‘#’) /* Pop element from stack till it becomes empty
prefix[k++]=pop();
prefix[k]=‘\0’; // Make prefix a valid string by adding sentinel
strrev(prefix);
strrev(infix);
printf(“\n\n Infix String %s \nPrefix String: %s\n”, infix, prefix);
}

Output:
Enter the infix expression
((a+b) * (c-d))
Infix String: ((a+b) * (c-d))
Prefix String: *+ab-cd
We use the following method to convert an infix to prefix expression:
z Reverse the infix expression, i.e., a+b*c will become c*b+a. Note while reversing each
‘(‘ will become ‘)’ and each ‘)’ becomes ‘(‘.
z Obtain the postfix expression of the modified infix expression, i.e., cb*a+.
z Reverse the postfix expression. Hence in our example prefix is +a*bc.

5. Postfix Evaluation
We use infix expression in Mathematics and evaluate its value. Let’s see now how to
evaluate expression postfix and prefix notation.
Rules used while evaluating a Postfix Expression:
1. While reading the expression from left to right, push the element in the stack if it is an
operand.
2. Pop two operands from the stack, if the element is an operator and then evaluate it.
3. Push back the result of the evaluation. Repeat it till the end of the expression.
Algorithm
1. Add ) to postfix expression.
2. Read postfix expression Left to Right until ‘)’ encountered
3. If operand is encountered, push it onto Stack
4. If operator is encountered, Pop two elements
(i) A ĺ Top element
148 Data Structures and Algorithms – I

(ii) B ĺ Next to Top element


(iii) Evaluate B operator A
push B operator A onto Stack
5. result = pop
6. End
Let’s see an example to better understand the evaluation of postfix expression with
algorithm:
Consider Postfix Expression: 5, 8, 16, /, 2, 2, *, -, +
1. Add ‘)’ to the given postfix expression as: 5, 8, 16, /, 2, 2, *, -, +, ). Create an empty stack.
2. Scan each symbol in the expression from left to right until ‘)’ is encountered.
3. Following table shows step 3 to step 6 in the above algorithm and the diagram below also
shows the stack contents in each of the 10 steps.
Step No. Symbol Operation Performed Stack Contents
Scanned
1. 5 Push 5 on Stack [5]
2. 8 Push 8 on Stack [5,8]
3. 16 Push 16 on Stack [5,8,16]
4. / Pop two elements 8 and 16 and evaluate 16/8 =2, push 2 on stack [5,2]
5. 2 Push 2 on stack [5,2,2]
6. 2 Push 2 on stack [5,2,2,2]
7. * Pop two elements 2 and 2 and evaluate 2*2 =4, push 4 on stack [5,2,4]
8. - Pop two elements 4 and 2 and evaluate 4-2 =2, push 2 on stack [5,2]
9. + Pop two elements 5 and 2 and evaluate 5+2 =4, push 7 on stack [7]
10. ) Result is the top of stack i.e., 7 . Stop ----

Fig. 4.6: Stack Contents in Each Step


Stacks 149

C Program: Evaluation of Postfix Expression


#include <stdio.h>
#define MAX 25
int stack[MAX];
int top = -1;
void push(int item)
{
if (top >= MAX - 1)
{
printf(“stack over flow”);
return;
}
else {
top = top + 1;
stack[top] = item;
}
}
//-------------------------------------------------------------------
int pop()
{
int item;
if (top < 0) {
printf(“stack under flow”);
}
else {
item = stack[top];
top = top - 1;
return item;
}
}
//--------------------------------------------------------------------
void evaluate(char postfix[])
{
int i;
char ch;
int result;
int operand1,operand2;
for (i = 0; postfix[i] != ‘)’; i++)
{
ch = postfix[i];
if (isdigit(ch))
{
150 Data Structures and Algorithms – I

push(ch - ‘0’);
}
else if (ch == ‘+’ || ch == ‘-’ || ch == ‘*’ || ch == ‘/’)
{
operand1 = pop();
operand2 = pop();
switch (ch)
{
case ‘+’:
result = operand1 + operand2;
break;
case ‘-’:
result = operand2 - operand1;
break;
case ‘*’:
result = operand1 * operand2;
break;
case ‘/’:
result = operand2/operand1;
break;
}
push(result);
}
}
printf(“ \n Result of expression evaluation: %d \n”, pop());
}
//--------------------------------------------------------------------------------------------------
int main()
{
int i;
char postfix[MAX];
printf(“ \nEnter postfix expression, press right parenthesis ‘)’ for end expression: “);
for (i = 0; i<= MAX - 1; i++)
{
scanf(“%c”, &postfix[i]);
if (postfix[i] == ‘)’)
break;
}
evaluate(postfix);
return 0;
}
Stacks 151

Output:
Enter postfix expression, press right parenthesis ‘)’ for end expression: 248+-8+)
Result of expression evaluation: -2
Enter postfix expression, press right parenthesis ‘)’ for end expression: 255+86-/)
Result of expression evaluation: 2
Evaluation of Prefix Expression
Evaluation of prefix expression can be done by reversing the given expression to obtain
postfix expression and then evaluating it using the algorithm for postfix evaluation. So we have to
include string reversing function in the above program.
A prefix string is converted to equivalent postfix string, for example:
Prefix Expression: -/*2*5+3652
After reversing Postfix equivalent: 2563+5*2*/-
6. Backtracking – 4 Queens problem
Here we give an interesting application of stacks: implementing backtracking to solve the N-
Queens problem.
We will start with a description of a problem which involves queens from a chess game, and
a chess board.
The N-Queens problem
z Some of you may have seen this problem before. The goal is to place all the queens on
the board so that none of the queens are attacking each other.
z Two queens are not allowed in the same row... or in the same column... or along the same
diagonal.
z The number of queens and the size of the board can vary. We give below a program to
solve N-queens problem for varying number of queens. The number of queens must
always equal the size of the chess board. For example, if you have six queens, then the
board will be a six by six chess board.
z Our program tries to find a way to place N queens on an N x N chess board.
z Let us understand N-Queens problem stepwise with N = 4.
1. A stack is used a stack to keep track where each queen is placed. (Fig. 4.7)

Fig. 4.7 Fig. 4.8


152 Data Structures and Algorithms – I

2. Each time the program decides to place a queen on the board, the position of the new
queen is pushed onto the stack.
For example, when we place the first queen in the first column of the first row, we record
this placement by pushing a record onto the stack. This record contains both the row and
column number of the newly placed queen (Fig. 4.8).
3. We need to keep track of one more variable: an integer which tells us how many rows
currently have a queen placed (Fig. 4.9).

Fig. 4.9 Fig. 4.10

4. Whenever we try to place a new queen in the next queen in the next row, we start by row,
we start by placing the queen in the first column. When we successfully place a queen in
one row, we move to the next row. We always start by trying to place the queen in the
first column of the new row (Fig. 4.10).
5. If there is a conflict with another queen, then we shift the new queen to the next column
(Fig. 4.11).

Fig. 4.11 Fig. 4.12


Stacks 153

6. Sometimes another conflict will occur, and the newly-placed queen must continue
shifting rightward. When there are no conflicts, we stop and add one to the value of filled
(Fig. 4.12).
7. Let’s look at the third row. The first position we try has a conflict. (Fig. 4.13)

Fig. 4.13 Fig. 4.14

8. As shown in Fig. 4.13, there is a conflict with the placement of the new queen, so we
move her rightward to the second column (Fig. 4.14).
9. Again a conflict arises, so we move rightward to the third column (Fig. 4.15).

Fig. 4.15 Fig. 4.16

10. Again another conflict arises, so we move to the fourth column. The key idea is that each
time we try a particular location for the new queen, we should check whether the new
location causes conflicts with our previous queens. If so, then we move the new queen to
the next possible location (Fig.4.16).
154 Data Structures and Algorithms – I

11. But as we see in Fig. 4.16 there’s again a conflict and there’s nowhere else to go.
Sometimes we run out of possible locations for the new queens. This is where
backtracking comes into play.
12. So now, we backtrack with following steps:
倯 pop the stack,
倯 reduce filled by 1
倯 and continue working on the previous row.
Now, at the previous row, we continue shifting the queen rightward.
This is shown in (Fig. 4.17).

Fig. 4.17 Fig. 4.18

13. Now we continue working on row 2, shifting the queen to the right. (Fig. 4.18). Notice
that we continue the previous row from the spot where we left off. The queen shifts from
column 3 to column 4. We don’t return her back to column 1. It is the use of the stack that
lets us easily continue where we left off. The stack contains the position of the previous
queen, so we can just move the queen rightward one more position.
14. This position has no conflicts, so no conflicts, so we can increase filled by 1, and move to
row 3 (Fig. 4.19).
Stacks 155

Fig. 4.19 Fig. 4.20

15. At the new row, we again start at the first column. So the general rules are: When the
algorithm moves forward, it always starts with the first column (Fig. 4.20). But when the
algorithm backtracks, it continues wherever it left off.
Here we give Pseudocode for N-Queens problem:
1. Initialize a stack to keep track of our decisions.
2. Place the first queen, push its position onto the stack and set filled to 0.
3. Repeat the following steps:
(a) If there are no conflicts with the queens then
 Increment filled by 1.
 If filled is equal to N, then the algorithm is done.
 Otherwise, move to the next row and place a queen in the first column.
(b) Else if there is a conflict and there is room to shift the current queen rightward then
 Move the current queen rightward and adjust the record on top of the stack to indicate
the new position.
(c) Else if there is a conflict and there is no room to shift the current queen rightward then
 Backtrack! Keep popping the stack, and reducing filled by 1, until you reach a row
where the queen can be shifted rightward.
 Shift this queen right.
But there is one potential pitfall here!
The potential pitfall: The stack may become empty during this popping. What does this
indicate?
Answer: It means that we backtracked right back to the beginning, and ran out of possible
places to place the first queen. In that case, the problem has no solution.
C Program: N-Queens Problem
#define N 4
#include <stdio.h>
void print_Solution(int board[N][N]) /* A function to print solution */
156 Data Structures and Algorithms – I

{
int i,j;
for (i = 0; i< N; i++)
{
for (j = 0; j < N; j++)
p rintf(“ %d”, board[i][j]);
printf(“\n”);
}
}
//-----------------------------------------------------------------------------------------------------------
/* A function place()checks if a queen can be placed on board[row][col]. It is called when
“col” queens are already placed in columns from 0 to col -1. So check only left side for
attacking queens */
int place(int board[N][N], int row, int col)
{
int i, j;

for (i = 0; i< col; i++) /* Check this row on left side */


if (board[row][i])
return 0;
for (i = row, j = col; i>= 0 && j >= 0; i--, j--)/* Check upper diagonal on left side */
if (board[i][j])
return 0;
/* Check lower diagonal on left side */
for (i = row, j = col; j >= 0 &&i< N; i++, j--)
if (board[i][j])
return 0;
return 1;
}
//---------------------------------------------------------------------------------------------------------
/*solve_NQ is a recursive function to solve N Queen problem */
int solve_NQ(int board[N][N], int col)
{
/* If all queens are placed then return true */
int i;
if (col >= N)
return 1;
// Consider this column and try placing this queen in all rows one by one
for (i = 0; i< N; i++)
{
if (place(board, i, col)) /* Check if the queen can be placed on board[i][col] */
Stacks 157

{
board[i][col] = 1; /* Place this queen in board[i][col] */
if (solve_NQ(board, col + 1)) /* call recursively to place remaining queens */
return 1;
/*If placing queen in board[i][col] doesn’t lead to a solution, then backtrack i.e., remove
queen from board[i][col] */
board[i][col] = 0; // BACKTRACK
}
}
/* If the queen cannot be placed in any row in this column col then return false */
return 0;
}
//----------------------------------------------------------------------------------------------------------
/* FunctionN_Queens() returns false if queens cannot be placed, otherwise, returns true and
prints placement of queens in the form of 1s. Note: There may be more than one solutions, this
function prints one of the feasible solutions.*/
int N_Queens()
{
int board[N][N] = { { 0, 0, 0, 0 },
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0} };
if (solve_NQ(board, 0) == 0) {
printf(“Solution does not exist”);
return 0;
}
print_Solution(board);
return 1;
}
// -----------------------------------------------------------------------------------------------------------
int main()
{
N_Queens();
return 0;
}
158 Data Structures and Algorithms – I

Output:
The 1 values indicate placements of queens
0010
1000
0001
0100

QUESTIONS
Practice Questions (2 marks)
1. What are the applications of stack?
2. State the difference between stack and linked list.
3. Explain the basic operations performed on stack.
4. Define the following operations in stack:
(a) Push()
(b) pop()
(c) peek()
(d) isempty()
(e) isfull()
5. Write the postfix form of the following expressions:
(a) A ** B ** C
(b) -A + B - C + D
(c) A ** -B + C
(d) (A + B) * D + E/(F + A * D) + C
6. What is backtracking in N-queens problem?
Practice Questions (4 marks)
1. Write a function to reverse a string using stack.
2. Write a function to check whether a given string is palindrome or not (use stack).
3. Write a ‘C’ program for dynamic implementation of stack.
4. Write a function to reverse a string using stack.
5. Write an algorithm to evaluate postfix expression.
6. Write an algorithm for evaluation of prefix expression.
7. Write a function to check whether given expression is parenthesis or not.
8. Write an algorithm to convert given infix expression to postfix expression.
9. Evaluate the following Postfix expression:
4, 5, 4, 2, ^, +, *, 2, 2, ^, 9, 3, 1, *, -
10. Explain 4-queens problem in detail. Also give an algorithm for it.
11. Discuss 4-queens problem. Give a C function to solve N-queens problem.

ˆˆˆ
5 QUEUES

Structures
5.1 Introduction
5.2 Representation – Static and Dynamic
5.3 Operations – init(), enqueue(), dequeue(), isEmpty(), isFull(), peek()
5.4 Types of Queue
5.4.1 Simple Queue
5.4.2 Circular Queue
5.4.2 Priority Queue
5.4.3 Double Ended Queue
5.5 Applications – Job Scheduling with priority

5.1 Introduction
Queue
A Queue is a linear data structure which performs operations in a particular order. The order
is called as First-In-First-Out (FIFO). Common example of a queue is any queue of consumers for
a resource where the consumer that comes first is served first. People waiting in line for a rail
ticket or bus-stops is another example in day to day life.
The difference between stacks and queues is in deleting an element. In a stack we delete or
remove the item the most recently added; in a queue, we remove the item the least recently added.
1. A queue is defined as an ordered list which enables insertion one end called REAR and
deletion at another end called FRONT.
2. Queue is therefore called as First In-First Out list.

Fig. 5.1: Simple Queue


160 Data Structures and Algorithms – I

Applications of Queue
A queue performs operations on first in first out basis. This feature of queue makes it
suitable for the ordering of actions in various applications. Below are some applications of queues.
1. Queues are mostly used as waiting lists for a single shared resource like printer, disk,
CPU.
2. In asynchronous transfer of data, where data is not being transferred at the same rate
between two processes, queues are used. For example pipes, file IO, sockets.
3. Queues are used for buffering in most of the applications like MP3 media player, CD
player, etc.
4. The play lists in media players make use of queues to add and remove the songs.
5. Queues are used in operating systems for handling interrupts.
5.2 Representation of Queue
Queue can be implemented in two ways – Static and Dynamic
1. Static Representation of Queue Using Array
Static implementation or array representation of queue requires three entities–
z An array to hold queue elements.
z A variable to hold the index of the front element.
z A variable to hold the index of the rear element.
A queue is easily represented using linear array. Two variables, front and rear, are
implemented in every queue. These two 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. Insertion and deletion operations in queue are called enqueue and dequeue
respectively. A queue represented using array with 5 elements along with the respective values of
front and rear, is shown in the following figure.

Fig. 5.2: Queue

The above figure shows the queue of characters forming the English word “ABCDE”. With
the first element added in the queue, value of front is set to 0 now. The value of rear has become 4
with the insertions performed in the queue. After inserting an element ‘N’ into the queue, the
queue will look as shown in the figure below. The value of rear will become 5 while the value of
front remains same.
Queues 161

Fig. 5.3: Queue after Inserting an Element


After deleting an element, the value of front will increase from 0 to 1and the queue will look
something like following.

Fig. 5.4: Queue after Deleting an Element

Steps to Insert an Element in a Queue


z Check if the queue is already full by comparing rear to max -1. if so, then return an
overflow error.
z 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.
z Otherwise keep increasing the value of rear and insert each element one by one having
rear as the index.
Algorithm: Enqueue
Here num is the element to insert and max is the maximum size of queue.
1. if rear = max -1
print overflow
go to step 4.
2. if front = -1 and rear = -1
front = rear = 0
else
rear = rear + 1
3. queue[rear] = num
4. exit
162 Data Structures and Algorithms – I

Steps to Delete an Element from the Queue


z If, the value of front is -1 or value of front is greater than rear, print an underflow
message and exit.
z Otherwise, increment the value of front and return the item stored at the front end of the
queue at each time.
Algorithm: Dequeue
1. if front = -1 or front > rear
print underflow
else
val = queue[front]
front = front + 1
2. exit
C Program: Static Implementation of Queue Using Array
#include<stdio.h>
#include<stdlib.h>
#define MAX 10
void insert();
void delete();
void display();
int front = -1, rear = -1;
int queue[MAX];
void main ()
{
int choice;
printf(“-----QUEUE--------\n”);
while(choice != 4)
{
printf(“\n1.Insert\n2.Delete\n
3. Display\n4.Exit\n”);
printf(“\nSelect the operation”);
scanf(“%d”, &choice);
switch(choice)
{
case 1:
insert();
break;
case 2:
delete();
break;
case 3:
display();
break;
case 4:
Queues 163

exit(0);
break;
default:
printf(“\nEnter valid choice\n”);
}
}
}
//-----------------------------------------------------
void insert()
{
int num;
printf(“\nEnter the element\n”);
scanf(“\n%d”, &num);
if(rear == MAX-1)
{
printf(“\nQueue OVERFLOW\n”);
return;
}
if(front == -1 && rear == -1)
{
front = 0;
rear = 0;
}
else
{
rear = rear+1;
}
queue[rear] = num;
printf(“\nElement added in queue”);
}
//--------------------------------------------------------
void delete()
{
int item;
if (front == -1 || front > rear)
{
printf(“\nQueue UNDERFLOW\n”);
return;
}
else
{
item = queue[front];
if(front == rear)
{
front = -1;
rear = -1 ;
164 Data Structures and Algorithms – I

}
else
{
front = front +1;
}
printf(“\nElement deleted from queue”);
}
}
//------------------------------------------------------
void display()
{
int i;
if(rear == -1)
printf(“Queue is empty\n”);
else
{
printf(“Queue contains.....\n”);
for(i=front;i<=rear;i++)
{
printf(“\n%d\n”,queue[i]);
}
}
}

Output:
-----QUEUE--------
1. Insert
2. Delete
3. Display
4. Exit

Select the operation 1

Enter the element


123

Element added in queue

1. Insert an element
2. Delete an element
3. Display the queue
4. Exit

Select the operation 1


Queues 165

Enter the element


90

Element added in queue

1. Insert an element
2. Delete an element
3. Display the queue
4. Exit

Select the option 2

Element deleted from queue

1. Insert an element
2. Delete an element
3. Display the queue
4. Exit

Select the option 3

Queue contains.....

90

1. Insert an element
2. Delete an element
3. Display the queue
4. Exit

Select the option 4

Advantages of Static Implementation


1. Easy to implement.
Drawback of Static Implementation
If the queue has a large number of enqueue and dequeue operations, at some point we may
not be able to insert elements in the queue even if the queue is empty (this problem is avoided by
using circular queue). Some more drawbacks of using static implementation technique to
implement a queue are:
(a) 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.
166 Data Structures and Algorithms – I

Fig. 5.5: Limitation of Array Representation of Queue

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 rear
variable is 5, therefore, we cannot reinsert the values in the place of already deleted element
before the position of front. That much space of the array is wasted and cannot be used in the
future (for this queue).
(b) Deciding the Array Size
Deciding the size of the array is a problem with static implementation. In this
implementation, array size is to be declared in advance. The size of the queue can be extended at
runtime if required. When we use array for implementing queue, extending array size is a time
consuming process and almost impossible to be performed at runtime since a lot of reallocations
take place.
If, to avoid this, we declare the array large enough so that we can store queue elements as
enough as possible, but most of the array slots (nearly half) can never be reused. This will again
lead to memory wastage.
2. Dynamic Implementation of Queue (Using Linked List)
Because of the drawbacks discussed above, the array implementation cannot be used for the
large-scale applications where the queues are implemented. Another alternative for implementing
queues is using linked list. We will call such queue a linked 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,
1. Each node of the queue has two parts, data part and the next part. Each element of the
queue points to its immediate next element in the memory.
2. Two pointers are maintained in the memory, i.e., front pointer and rear pointer. The front
pointer contains the address of the starting element of the queue while the rear pointer
contains the address of the last element of the queue.
3. Insertion is performed at rear end and deletion is performed at front end. If both front and
rear pointers are NULL, it indicates that the queue is empty.
The linked representation of queue is shown in the following figure:

Fig. 5.6: Linked Queeue


Queues 167

Operation on Linked Queue


There are two basic operations implemented on the linked queues. The operations are:
1. Insert an element
2. Delete an element
Insert Operation
The insert operation appends the queue by adding an element to the end of the queue. The
new element will be the last element of the queue. The structure for queue using linked list will be:
struct node
{
struct node *next;
int data;
}*front,*rear;
Initialize the queue by setting front= rear=NULL and allocate the memory for the new
node new by using the following statement.
new= (struct node *) malloc(sizeof(struct node));
There are two possible situations while inserting an element in the linked queue.
z Queue is empty where the condition front = NULL becomes true. The new element will
be the only element of the queue and the next pointer of front and rear pointers both, will
point to NULL. This is shown in the code below.
new ĺ data = item;
if(front == NULL)
{
front = new;
rear = new;
front ĺ next = NULL;
rear ĺ next = NULL;
}
z The queue already contains more than one element, where the condition front = NULL
becomes false. Here we set next pointer of rear to the new node new. Since, this is a
linked queue, we also need to make the rear pointer point to the newly added node new
and the next pointer of rear point to NULL.
rear ĺ next = new;
rear = new;
rear ĺ next = NULL;
In this way, the element is inserted into the queue. The algorithm is given as follows.
Algorithm
1. Allocate the space for the new node new
2. ptr ĺ data = val
3. if front = null
front = rear = new
168 Data Structures and Algorithms – I

front ĺ next = rear -> next = null


else
rear ĺ next = ptr
rear = ptr
rear ĺ next = null
4. end
Deletion
While deleting, the element that is first added in the queue is deleted first. If the condition
front == NULL becomes true, then 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 to by the pointer front. For this purpose,
copy the node pointed by the front pointer into the pointer temp. Now, shift the front pointer,
point to its next node and free the node pointed by the node temp. This is done by using the
following statements.
temp = front;
front = front ĺ next;
free(temp);
The algorithm is given as follows.
Algorithm
1. if front = null
print “underflow”
go to step 5
2. temp = front
3. front = front ĺ next
4. free (temp)
5. end
C Program: Dynamic Implementation of Linked Queue
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
};
struct node *front;
struct node *rear;
void enqueue();
void dequeue();
void display();
void main ()
{
Queues 169

int choice;
printf(“-----Dynamic implementation of queue-----\n”);
while(choice != 4)
{
printf(“\n1. Insert \n 2. Delete \n
3. Display \n 4. Exit\n”);
printf(“\nEnter your choice ?”);
scanf(“%d”, & choice);
switch (choice)
{
case 1:
enqueue ();
break;
case 2:
dequeue ();
break;
case 3:
display();
break;
case 4:
exit(0);
break;
default:
printf(“\nEnter valid choice\n”);
}
}
}
//---------------------------------------------------
void enqueue ()
{
struct node *ptr;
int num;
ptr=(struct node *) malloc(sizeof(struct node)); if(ptr == NULL)
{
printf(“\nOVERFLOW\n”);
return;
}
else
{
printf(“\nEnter element to add\n”);
scanf(“%d”, &num);
ptr ĺ data = num;
if(front == NULL)
{
front = ptr;
rear = ptr;
170 Data Structures and Algorithms – I

front ĺ next = NULL;


rear ĺ next = NULL;
}
else
{
rear ĺ next = ptr;
rear = ptr;
rear ĺ next = NULL;
}
}
}
//---------------------------------------------------
void dequeue ()
{
struct node *ptr;
if(front == NULL)
{

printf(“\nUNDERFLOW\n”);
return;
}
else
{
ptr = front;
front = front ĺ next;
free(ptr);
}
}
//------------------------------------------------------
void display()
{
struct node *ptr;
ptr = front;
if(front == NULL)
printf(“\nEmpty queue\n”);
else
{
printf(“\nQueue contains .....\n”);
while(ptr != NULL)
{
printf(“\n%d\n”,ptr ĺ data);
ptr = ptr ĺ next;
}
}
}
Queues 171

5.3 Operations
Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks. The basic operations performed on queue
are:
1. init() – initializes a queue
2. enqueue() í add (store) an item to the queue. If the queue is full, then it is said to be an
Overflow condition.
The following steps should be taken to enqueue (insert) data into a queue í
1. Check if the queue is full.
2. If the queue is full, print overflow error and exit.
3. If the queue is not full, increment rear pointer to point the next empty space.
4. Add new element to the queue location, pointed to by rear.
5. Return success.
Implementation of enqueue() in C Language í
int enqueue(int data)
{
if(isfull())
return 0;
rear = rear +1;
queue[rear] = data;
return 1;
}

3. dequeue() í remove (access) an item from the queue. The items are popped in the same
order in which they are pushed. If the queue is empty, then it is said to be an Underflow
condition.
Accessing data from the queue is a process of two tasks í access the data where front is
pointing and remove the data after access. The following steps are taken to perform dequeue
operation í
1. Check if the queue is empty.
2. If the queue is empty, produce underflow error and exit.
3. If the queue is not empty, access the data where front is pointing.
4. Increment front pointer to point to the next available data element.
5. Return success.
Implementation of dequeue() in C language í
int dequeue() {
if(isempty())
return 0;

int data = queue[front];


front = front +1;
172 Data Structures and Algorithms – I

return data;
}

4. peek() í Gets the element at the front of the queue without removing it.
This function helps to see the data at the front of the queue. The algorithm of peek()
function is as follows –
Implementation of peek() function in C language í
int peek() {
return queue[front];
}
5. isfull() í Check if the queue is full.
As we are using single dimension array to implement queue, we just check for the rear
pointer to reach at MAXSIZE to determine that the queue is full. In case we maintain the queue in
a circular linked-list, the algorithm will differ. Algorithm of isfull() function –
Implementation of isfull() function in C Language í
intisfull()
{
if(rear == MAXSIZE - 1)
return 1;
else
return 0;
}

6. isempty() í Checks if the queue is empty.


If the value of front is less than MIN or 0, it tells that the queue is not yet initialized, hence
empty.
Here’s the C programming code í
intisempty()
{
if(front < 0 || front > rear)
return 1;
else
return 0;
}

5.4 Types of Queue


There are following types of queues:
1. Simple Queue
2. Circular Queue
3. Priority Queue
4. Double Ended Queue
Queues 173

Let’s understand them one-by-one with an illustration.


5.4.1 Simple Queue
Please refer to section 5.2 for simple queue operations. We have explained simple queue, its
operations and C program including enqueue and dequeue operations in the section static and
dynamic representation of queues.
5.4.2 Circular Queue
Consider the simple queue shown in the following figure.

Fig. 5.7: Queue

The Queue shown in above figure is completely filled and no element can be inserted any
more due to the condition rear == max - 1 becomes true.
However, if we delete 2 elements at the front end of the queue, we still cannot insert any
element since the condition rear = max -1 still holds. This is the main problem with the linear
queue, although we have space available in the array, we cannot insert new elements in the queue.
This leads to memory wastage and the problem should be overcome.

Fig. 5.8: Queue after Deletion of First 2 Elements


One of the solutions to this problem is circular queue. In the circular queue, the first index
comes right after the last index. You can think of a circular queue as shown in the following
figure.

Fig. 5.9
174 Data Structures and Algorithms – I

Circular queue will be full when front = -1 and rear = max-1. Implementation of circular
queue is similar to that of a linear queue except the logic for insertion and deletion is different
from that of a linear queue.
Working of Circular Queue
Circular queue works by the process of circular increment i.e., when we try to increment any
variable and we reach the end of the queue, we start from the beginning of the queue by modulo
division with the queue size.
i.e.,
if REAR + 1 == 5 (overflow!), REAR = (REAR + 1) % 5 = 0 (start of queue)
Queue operations work as follows:
z Two pointers, front and rear are used to keep track of the first and last elements in the
queue.
z Initialize the queue, set the value of front and rear to -1.
z On enqueuing an element, we circularly increase the value of index and place the new
element in the position pointed to by rear.
z On dequeuing an element, we return the value pointed to by front and circularly increase
the front index.
z Before enqueuing, we check if the queue is already full.
z Before dequeuing, we check if the queue is already empty.
z When enqueuing the first element, we set the value of front to 0.
z When dequeuing the last element, we reset the values of front and rear to -1.
However, the check for full queue has a new additional case:
Case 1: FRONT = 0 && REAR == SIZE - 1
Case 2: FRONT = REAR + 1
Case 2 happens when REAR starts from 0 due to circular increment and when its value is
just 1 less than FRONT, the queue is full.
Following figure explains the working of circular Queue:
Queues 175

Fig. 5.10: Working of Circular Queue

Time Complexity for each of the operations, enqueue(),dequeue(),Front, Rear is O(1).


Insertion in Circular Queue
There are three scenarios while inserting an element in a queue:
1. If (rear + 1) %max = front, the queue is full. This is overflow condition and therefore,
insertion cannot be performed in the queue.
2. If rear! = max - 1, then rear will be incremented to the mod(max) and the new value will
be inserted at the rear end of the queue.
3. If front! = 0 and rear = max - 1, then it means that queue is not full therefore, set the value
of rear to 0 and insert the new element there.
176 Data Structures and Algorithms – I

Algorithm to insert an element in circular queue


1. if (rear+1)%max = front
write “overflow”
goto step 4
2. if front = -1 and rear = -1
front = rear = 0
else if rear = max - 1 and front ! = 0
rear = 0
else
rear = (rear + 1) % max
3. queue[rear] = val
4. exit
To delete an element from the circular queue, we must check for the three following conditions.
1. If front = -1, then there are no elements in the queue and this is called underflow
condition.
2. If there is only one element in the queue, the condition rear = front holds and therefore,
both are set to -1 and the queue is deleted completely.
3. If front = max -1 then, the value is deleted from the front end the value of front is set to 0.
Otherwise, the value of front is incremented by 1 and then delete the element at the front
end.
Algorithm to delete an element from circular queue
1. if front = -1
write “underflow”
goto step 4
2. set val = queue[front]
3. if front = rear
front = rear = -1
else
if front = max -1
front = 0
else
front = front + 1
4. exit
C Program: Static Implementation of Circular Queue
#include<stdio.h>
#include<stdlib.h>
#define MAX 5
void insert();
void delete();
void display();
Queues 177

int front = -1, rear = -1;


int queue[MAX];
void main ()
{
int choice;
printf(“---Circular Queue----\n”);
while(choice != 4)
{
printf(“\n1.Insert\n
2. Delete \n
3. Display \n 4. Exit\n”);
printf(“\nEnter your choice ?”);
scanf(“%d”, &choice);
switch(choice)
{
case 1:
insert();
break;
case 2:
delete();
break;
case 3:
display();
break;
case 4:
exit(0);
break;
default:
printf(“\nEnter valid choice\n”);
}
}
}
//---------------------------------------------------
void insert()
{
int item;
printf(“\nEnter the element\n”);
scanf(“%d”, &item);
if((rear+1)%MAX == front)
{
printf(“\nOVERFLOW”);
return;
178 Data Structures and Algorithms – I

}
else if(front == -1 && rear == -1)
{
front = 0;
rear = 0;
}
else if(rear == MAX -1 && front != 0)
rear = 0;
else
rear = (rear+1)%MAX;
queue[rear] = item;
printf(“\nValue inserted”);
}
//---------------------------------------------------
void delete()
{
int item;
if(front == -1 & rear == -1)
{
printf(“\nUNDERFLOW\n”);
return;
}
else if(front == rear)
{
front = -1;
rear = -1;
}
else if(front == max size -1)
{
front = 0;
}
else
front = front + 1;
printf(“\nValue deleted”);
}
//----------------------------------------------------
void display()
{
int i;
if(front == -1)
printf(“\nCircular Queue is Empty!!!\n”);
else
Queues 179

{
i = front;
printf(“\nCircular Queue Elements are : \n”);
if(front <= rear)
{
while (i <= rear)
printf(“%d %d %d\n”, queue[i++], front, rear);
}
else
{
while (i <= MAX - 1)
printf(“%d %d %d\n”, queue[i++], front, rear); i = 0;
while (i <= rear)
printf(“%d %d %d\n”, queue[i++], front, rear);
}
}
}

Output:
---Circular Queue----
1. Insert
2. Delete
3. Display
4. Exit
Enter your choice? 1
Enter the element
1
Value inserted
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 1
Enter the element
2
Value inserted
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 1
Enter the element
180 Data Structures and Algorithms – I

3
Value inserted
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 3
Circular Queue Elements are:
1
2
3
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 2
Value deleted
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 1
Enter the element
4
Value inserted
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 3
Circular Queue Elements are:
2
3
4
1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice? 1
Enter the element
1
OVERFLOW
Queues 181

1. Insert an element
2. Delete an element
3. Display the queue
4. Exit
Enter your choice?
4

Dynamic Implementation of Circular Queue


Another method of circular queue implementation is discussed, using Circular Singly
Linked List. The node structure will be same as for singly linked list.
Following diagram explains a sequence of enqueue and dequeue operations using circular
singly linked list.

Fig. 5.11

C Program: Dynamically Implementation of a Circular Queue


#include<stdio.h>
#include<stdlib.h>
struct cqueue{
int data;

struct queue *next;


}*front,*rear;
int count=0;
front=rear=NULL;
void enqueue(int num)
{
cqueue *new;
new=(structcqueue*)malloc(sizeof(struct cqueue));
new->data=num;
new->next=NULL;
if(count==0)
182 Data Structures and Algorithms – I

front=new;
else
rear->next =new;
rear=new;
rear->next =front;
count++;
}
//----------------------------------------------------
int dequeue(void)
{
int n;
cqueue *temp;
if(count==0)
return (-1);
count--;
if(front==rear)
{
n=front->data;
free(front);
front=NULL;
rear=NULL;
}else
{
temp= front;
n = temp->data;
front = front ->next;
rear ->next = front;
free ( temp );
}
return n;
}
//-------------------------------------------------
void display(void)
{
cqueue *temp;
int i;
if(count==0)
pf(“Empty”);
else
{
temp=front;
for(i=0;i<count; i++)
Queues 183

{
printf(“%d “,temp->data);
temp=temp->next;
}
}
printf(“\n”);
}
//---------------------------------------------
int find_size(void)
{
return count;
}
//----------------------------------------------
int main()
{
int n, ch=10;
while(ch!=0)
{
printf(“\n Select the option\n”);
printf (“1. Insert\n”);
printf (“2. Delete\n”);
printf (“3. SizeOfQueue\n”);
printf (“4. Display\n”);
printf (“0. EXIT\n”);
scanf(“%d”,&ch);
switch(ch)
{
case 1:
{
printf(“Enter element to insert \n”);
scanf(“%d”, &n);
enqueue (n);
break;
}
case 2:
{
n=dequeue();
if(n==-1)
printf(“Queue is empty\n”);
else
printf(“Number deleted from queue is%d\n”, n);
break;
184 Data Structures and Algorithms – I

}
case 3:
{
n=find_size();
printf(“Size of queue is %d\n”, n);
break;
}
case 4:
{
printf(“Queue is ….”);
display();
}
case 0:
break;
default:
printf(“Wrong Choice\n”);
break;
}
}
}

Output:
Select the option
1. Insert
2. Delete
3. Size of Queue
4. Display
0. EXIT
1
Enter the element to insert in queue
10
Select the option
1. Insert
2. Delete
3. Size of Queue
4. Display
0. EXIT
1
20
Select the option
1. Insert
2. Delete
Queues 185

3. Size of Queue
4. Display
0. EXIT
3
Size of queue is 2
Select the option
1. Insert
2. Delete
3. Size of Queue
4. Display
0. EXIT
4
Queue is …10 20
Select the option
1. Insert
2. Delete
3. Size of Queue
4. Display
0. EXIT
2
Number deleted from queue is 10
Select the option
1. Insert
2. Delete
3. Size of Queue
4. Display
0. EXIT
4
Queue is …20

5.4.3 Priority Queue


z A priority queue is a special type of queue in which each element is associated with a
priority and is served according to its priority. If elements with the same priority occur,
they are served according to their order in the queue.
z Generally, the value of the element itself is considered for assigning the priority.
z For example: The element with the highest value is considered as the highest priority
element. We can also consider the element with the lowest value as the highest priority
element. In other cases, we can set priorities according to our needs.
186 Data Structures and Algorithms – I

Fig. 5.12

Difference between Queue and Priority Queue


In a queue, the first-in-first-out rule is implemented whereas, in a priority queue, the
values are deleted on the basis of priority. The element with the highest priority is deleted first.
Thus priority queue is an extension of queue with following properties.
1. Every item has a priority associated with it.
2. An element with high priority is dequeued before an element with low priority.
3. If two elements have the same priority, they are served according to their order in the
queue.
A typical priority queue supports following operations:
1. insert(item, priority): Inserts an item with given priority.
2. getHighestPriority(): Returns the highest priority item.
3. deleteHighestPriority(): Removes the highest priority item.
Implementation of priority queue
Using Array: A simple implementation is to use array of following structure.
struct item {
int item;
int priority;
}

z insert() operation can be implemented by adding an item at end of array in O(1) time.
z getHighestPriority() operation can be implemented by linearly searching the highest
priority item in array. This operation takes O(n) time.
z deleteHighestPriority() operation can be implemented by first linearly searching an
item, then removing the item by moving all subsequent items one position back.
Queues 187

1. Using Linked List: The time complexity of all operations with linked list remains same
as array. The advantage with linked list is deleteHighestPriority() can be more efficient as
we don’t have to move items.
2. Using Heaps: Heap is generally preferred for priority queue implementation because
heaps provide better performance compared arrays or linked list. In a Binary Heap,
getHighestPriority() can be implemented in O(1) time, insert() can be implemented in
O(Log n) time and deleteHighestPriority() can also be implemented in O(Log n) time.
Applications of Priority Queue:
Priority Queue is used in all queue applications where priority is involved like:
z In Operating Systems: CPU Scheduling, load balancing on server and interrupt handling.
z Graph algorithms like Dijkstra’s shortest path algorithm, Prim’s Minimum Spanning Tree
etc.
z Artificial Intelligence: A* Search Algorithm : The A* search algorithm finds the shortest
path between two vertices of a weighted graph, trying out the most promising routes first.
The priority queue (also known as the fringe) is used to keep track of unexplored routes,
the one for which a lower bound on the total path length is smallest is given highest
priority.
z Heap Sort: Heap sort is typically implemented using Heap which is an implementation of
Priority Queue.
z Used for data compression in Huffman code.

C Program: Implementation of Priority Queue Using Array


#include<stdio.h>
#include<stdlib.h>
#define MAX 5
void insert(int);
void delete(int);
voidcreate();
void check(int);
voiddisplay();
intpri_que[MAX];
int front, rear;
voidmain()
{
int n,ch;
printf(“\n1. Insert “);
printf(“\n2. Delete “);

printf(“\n3. Display”);
printf(“\n4. Exit”);
create();
while(1)
{
188 Data Structures and Algorithms – I

printf(“\nEnter your choice:”);


scanf(“%d”,&ch);
switch(ch)
{
case1:
printf(“\nEnter value to be inserted:”);
scanf(“%d”, &n);
insert(n);
break;
case2:
printf(“\nEnter value to delete:”);
scanf(“%d”, &n);
delete(n);
break;
case3:
display();
break;
case4:
exit(0);
default:
printf(“\nChoice is incorrect, Enter a correct choice”);
}
}
}
//-------------------------------------------------
voidcreate()
{
front = rear =-1;
}
//------------------------------------------------
voidinsert(int data)
{
if(rear >= MAX -1)
{
printf(“\nQueue overflow no more elements can be inserted”);
return;
}
if((front ==-1)&&(rear ==-1))
{
front++;
rear++;
pri_que[rear]= data;
Queues 189

return;
}
else
check(data);
rear++;
}
//-----------------------------------------------------
voidcheck(int data)
{
inti,j;
for(i=0;i<= rear;i++)
{
if(data >=pri_que[i])
{
for(j = rear +1; j >i; j--)
{
pri_que[j]=pri_que[j -1];
}
pri_que[i]= data;
return;
}
}
pri_que[i]= data;
}
//-------------------------------------------------
voiddelete(int data)
{
inti;
if((front==-1)&&(rear==-1))
{
printf(“\nQueue is empty no elements to delete”);
return;
}
for(i=0;i<= rear; i++)
{
if(data ==pri_que[i])
{
for(;i< rear; i++)
{
pri_que[i]=pri_que[i+1];
}
pri_que[i]=-99;
190 Data Structures and Algorithms – I

rear--;
if(rear ==-1)
front =-1;
return;
}
}
printf(“\n%d not found in queue to delete”, data);
}
//-------------------------------------------------
voiddisplay()
{
if((front ==-1)&&(rear ==-1))
{
printf(“\nQueue is empty”);
return;
}
for(; front <= rear; front++)
printf(“%d”, pri_que[front]);
front =0;
}

Output:
1. Insert an element into queue
2. Delete an element from queue
3. Display queue elements
4. Exit
Enter your choice: 1
Enter value to be inserted: 10
Enter your choice: 1
Enter value to be inserted: 14
Enter your choice: 1
Enter value to be inserted: 43
Enter your choice: 3
43 14 10
Enter your choice: 5
Choice is incorrect, Enter a correct choice
Enter your choice: 1
Enter value to be inserted: 5
Enter your choice: 3
43 14 10 5
Enter your choice: 1
Enter value to be inserted: 50
Queues 191

Enter your choice: 3


50 43 14 10 5
Enter your choice: 4
Press any key to continue . . .

5.4.4 Double Ended Queue


Double ended queue is a more generalized form of queue data structure which allows
insertion and removal of elements from both the ends, i.e., front and rear. That means, we can
insert at both front and rear positions and can delete from both front and rear positions.

Fig. 5.13: Double Ended Queue

Double Ended Queue can be represented in two ways:


1. Input Restricted Double Ended Queue
2. Output Restricted Double Ended Queue
Input Restricted Double Ended Queue
In input restricted double-ended queue, the insertion operation is performed at only one end
and deletion operation is performed at both the ends.

Fig. 5.14: Input Restricted Double Ended Queue

Output Restricted Double Ended Queue


In output restricted double ended queue, the deletion operation is performed at only one end
and insertion operation is performed at both the ends.
192 Data Structures and Algorithms – I

Fig. 5.15: Output Restricted Double Ended Queue

Here is a C program which illustrates a code for input-restricted as well output-restricted


double ended Queue.
C Program: Implementation of Double Ended Queue (Deque)
#include<stdio.h>
#include<conio.h>
#define SIZE 10

void enQueue(int);
int deQueueFront();
int deQueueRear();
void enQueueRear(int);
void enQueueFront(int);
void display();
int queue[SIZE];
int rear = 0, front = 0;

int main()
{
char ch;
int choice, opt, value;
printf(“\n***Double Ended queue ***\n”);
do
{
printf(“\n1. Input-restricted deque \n”);
printf(“2. Output-restricted deque \n”);
printf(“\nSelect the option\n”);
scanf(“%d”, &opt);
switch(opt)
{
case 1:
printf(“\nSelect the Operation\n”);
printf(“1. Insert\n2. Delete from Rear\n 3. Delete from Front\n 4. Display”);
do
Queues 193

{
printf(“\nEnter your choice for the operation in deque:”);
scanf(“%d”, &choice);
switch(choice)
{
case 1: enQueueRear(value);
display();
break;
case 2:
value = deQueueRear();
printf(“\nThe value deleted is %d”, value);
display();
break;
case 3:
value=deQueueFront();
printf(“\n The value deleted is %d”, value);
display();
break;
case 4:
display();
break;
default:
printf(“Wrong choice”);
}
printf(“\nDo you want to perform another operation (Y/N):”);
ch=getch();
}while(ch== ‘y’||ch==‘Y’);
getch();
break;
case 2:
printf(“\n---- Select the Operation ----\n”);
printf(“1. Insert at Rear\n 2. Insert at Front \n3. Delete\n 4. Display”);
do
{
printf(“\nEnter your choice for the operation:”);

scanf(“%d”,&choice2);
switch(choice2)
{
case 1: enQueueRear(value);
display();
break;
194 Data Structures and Algorithms – I

case 2: enQueueFront(value);
display();
break;
case 3: value = deQueueFront();
printf(“\nThe value deleted is %d”, value);
display();
break;
case 4: display();
break;
default:printf(“Wrong choice”);
}
printf(“\nDo you want to perform another operation (Y/N):”);
ch=getch();
} while(ch==‘y’||ch==‘Y’);
getch();
break;
}
printf(“\nDo you want to continue(y/n):”);
ch=getch();
}while(ch==‘y’||ch==‘Y’);
}
//------------------------------------------------------
void enQueueRear(int value)
{
char ch;
if(front == SIZE/2)
{
printf(“\nQueue is full\n”);
return;
}
do
{
printf(“\nEnter the value to be inserted:”);
scanf(“%d”, &value);
queue[front] = value;
front++;
printf(“Do you want to continue insertion Y/N”);
ch=getch();
}while(ch==‘y’);
}
//------------------------------------------------------
void enQueueFront(int value)
Queues 195

{
char ch;
if(front==SIZE/2)
{
printf(“\nQueue is full \n”);
return;
}
do
{
printf(“\nEnter the value to be inserted:”);
scanf(“%d”, &value);
rear--;
queue[rear] = value;
printf(“Do you want to continue insertion Y/N”);
ch = getch();
}
while(ch == ‘y’);
}
//------------------------------------------------------
int deQueueRear()
{
int x;
if(front == rear)
{
printf(“\nQueue is Empty!!! \n”);
return 0;
}
front--;
x = queue[front+1];
return x;
}
//---------------------------------------------------
int deQueueFront()
{
int x;
if(front == rear)
{
printf(“\nQueue is Empty\n”);
return 0;
}
rear++;
x = queue[rear-1];
196 Data Structures and Algorithms – I

return x;
}
//--------------------------------------------------------
void display()
{
int i;
if(front == rear)
printf(“\nQueue is Empty\n”);
else{
printf(“\nThe Queue elements are:”);
for(i=rear; i< front; i++)
printf(“%d\t”,66 queue[i]);
}
}

Output
*** Double Ended Queue ***
1. Input-restricted deque
2. output-restricted deque
Select the option 1
Select the Operation
1. Insert
2. Delete from Rear
3. Delete from Front
4. Display
Enter your choice for the operation in c deque: 1
Enter the value to be inserted: 10
Do you want to continue insertion Y/N
Enter the value to be inserted: 20
Do you want to continue insertion Y/N
Enter the value to be inserted: 30
Do you want to continue insertion Y/N
The Queue elements are: 10 20 30
5.5 Applications – Job Scheduling with Priority
Job scheduling is an essential part of a multiprogramming operating systems. Such operating
systems allow more than one process (job) to be loaded into the memory at a time and the loaded
processes share the CPU using time multiplexing.
The job scheduling is done by a software called process manager that handles the removal of
the running process from the CPU and the selection of another process on the basis of a particular
strategy.
Queues 197

A process can be in one of the two states:


1. Running
When a new process is created, it enters into the system as in the running state.
2. Not Running
Processes that are not running are kept in queue, waiting for their turn to execute. Each entry
in the queue is a pointer to a particular process. Queue is implemented by using linked list. A
program called dispatcher works as follows. When a process is interrupted, it is transferred in the
waiting queue. If the process has completed or aborted, the process is discarded. In either case,
the dispatcher then selects another process from the queue to execute.
Process Scheduling Queues
The Operating System maintains the following important process scheduling queues í
z Job queue: This queue keeps all the processes in the system.
z Ready queue: This queue keeps a set of all processes residing in main memory, ready
and waiting to execute. A new process is always put in this queue.
z Device queues: The processes which are blocked due to unavailability of an I/O device
constitute this queue.
Schedulers are special systems software which handle process scheduling in various ways.
Their main task is to select the jobs to be submitted into the system and to decide which process
to run. Schedulers are of three types í
z Long-Term Scheduler
z Short-Term Scheduler
z Medium-Term Scheduler

Long Term Scheduler


It is also called a job scheduler. A long-term scheduler determines which programs are
admitted to the system for processing. It selects processes from the queue and loads them into
memory for execution. Process loads into the memory for CPU scheduling.
The primary objective of the job scheduler is to provide a balanced mix of jobs, such as I/O
bound and processor bound. It also controls the degree of multiprogramming. If the degree of
multiprogramming is stable, then the average rate of process creation must be equal to the average
departure rate of processes leaving the system.
Short-term Scheduler
Also known as dispatchers, short term schedulers make the decision of which process to
execute next. Short-term schedulers are faster than long-term schedulers.
Medium Term Scheduler
Medium-term scheduling is a part of swapping. It removes the processes from the memory.
It reduces the degree of multiprogramming. The medium-term scheduler is in-charge of handling
the swapped out-processes.
198 Data Structures and Algorithms – I

Fig. 5.16

The operating system (OS) uses different policies to manage each queue (FIFO, Round
Robin, Priority, etc.). The OS scheduler determines how to move processes between the ready and
run queues which can only have one entry per processor core on the system; in the above diagram,
it has been merged with the CPU.
A Process scheduler schedules different processes to be assigned to the CPU based on
particular scheduling algorithms. There are six popular process scheduling algorithms used:
z First-Come, First-Served (FCFS) Scheduling
z Shortest-Job-Next (SJN) Scheduling
z Priority Scheduling
z Shortest Remaining Time
z Round Robin (RR) Scheduling
z Multiple-Level Queues Scheduling
These algorithms are either non-pre-emptive or pre-emptive. Non-pre-emptive algorithms
are designed so that once a process enters the running state, it cannot be pre-empted until it
completes its allotted time. Pre-emptive scheduling is based on priority where a scheduler may
pre-empt a low priority running process anytime when a high priority process enters into a ready
state.
Let us see in detail priority based scheduling.
Priority Based Scheduling
z Priority scheduling is a non-pre-emptive algorithm and one of the most common
scheduling algorithms in batch systems.
z Each process is assigned a priority. Process with highest priority is to be executed first
and so on.
z Processes with same priority are executed on first come first served basis.
z Priority can be decided based on memory requirements, time requirements or any other
resource requirement.
Consider an example that illustrates this algorithm.
Given: Table of processes, and their Arrival time, Execution time, and Priority.
Here we are considering 1 is the lowest priority.
Queues 199

Process Arrival Execution Priority Service


Time Time Time
P0 0 5 1 0
P1 1 3 2 11
P2 2 8 1 14
P3 3 6 3 5

Waiting time of each process is as follows –


Process Waiting Time
P0 0-0=0
P1 11 - 1 = 10
P2 14 - 2 = 12
P3 5-3=2

Average Wait Time: (0 + 10 + 12 + 2)/4 = 24 / 4 = 6


QUESTIONS
Practice Questions (2 marks)
1. What are the applications of queue?
2. What is Double Ended Queue?
3. What are the operations that can be performed on queue?
4. What is priority queue?
5. Differentiate between a queue and a priority queue.
6. What are the applications of priority queue?
7. Explain the following operations in circular queue: enQueue, deQueue
Practice Questions (4 marks)
1. Write a C function which compares the contents of two queues and display message accordingly.
2. What is circular queue? Explain it with an example.
3. What is circular queue? How is it different from static queue?
4. Write a note on input-restricted and output restricted double ended queue.
5. Write a C function to insert and delete an element in circular queue.
6. Write a C function to implement input restricted double ended queue.
7. Write a C function to implement output restricted double ended queue.
8. Write a C function to implement priority queue.
9. Explain application of queues – job scheduling with priority.

ˆˆˆ
ISBN: 978-93-5433-116-9

ISBN: 978-93-5433-116-9 ESM 0090

You might also like