Module 1
Module 1
Data Structure can be defined as a group of data elements that provides an efficient way of storing and
organising data in the computer so that it can be used efficiently.
A data structure is a way of organising, managing, and storing data in a computer so that it can be accessed
and modified efficiently. An array is a collection of memory elements in which data is stored sequentially,
i.e., one after another. In other words, an array stores the elements in a continuous manner. This
organisation of data is done with the help of an array of data structures. There are also other ways to
organise the data in memory. Examples: Arrays, Linked List, Stack, Queue, etc.
• Processor Speed: To handle the large amount of data high-speed processor is required.
• Data Search: If our application contains 100 data, one of the users of our application needs to find a
particular data. At that time, we have to traverse the entire 100 data, in each and every file, so the result
will slow down.
• Multiple Requests: As thousands of users can search data simultaneously on a web server, even the fast
server fails while searching the data.
• Efficiency: Efficiency of a program depends upon the choice of data structures. For example: suppose, we
have some data and we need to perform the search for a particular record. In that case, if we organize our
data in an array, we will have to search sequentially element by element. There are better data structures
which can make the search process efficient like ordered array, binary search tree or hash tables.
• Reusability: Data structures are reusable, i.e. once we have implemented a particular data structure, we
can use it at any other place. Implementation of data structures can be compiled into libraries which can be
used by different clients.
• Abstraction: Data structure is specified by the ADT which provides a level of abstraction. The client
program uses the data structure through interface only, without getting into the implementation details.
• Time Complexity − Running time or the execution time of operations of data structure must be as
small as possible.
• Space Complexity − Memory usage of a data structure operation should be as little as possible.
Basic Terminology
• Data − Data are values or set of values.
• Data Item − Data item refers to single unit of values.
• Group Items − Data items that are divided into sub items are called as Group Items.
• Elementary Items − Data items that cannot be divided are called as Elementary Items.
• Attribute and Entity − An entity is that which contains certain attributes or properties, which may be
assigned values.
Examples:
Integer (int)
Float (float)
Character (char)
Boolean (bool)
Characteristics:
Simple and atomic (cannot be broken further).
Fixed size in memory.
Directly supported by the programming language.
Example (Python):
a = 10 # integer
b = 3.14 # float
c = 'A' # character
flag = True # Boolean
Linear Data Structures: A data structure is called linear if all of its elements are arranged in the linear order.
In linear data structures, the elements are stored in non-hierarchical way where each element has the
successors and predecessors except the first and last element.
Types of Linear Data Structures are :-
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 like char, int, float or double. The array can be one dimensional, two dimensional or
multidimensional. Example:- age[0], age[1], age[2], age[3],......... age[98], age[99].
Linked List:- Linked list is a linear data structure which is used to maintain a list in the memory. It can be
seen as the collection of nodes stored at non-contiguous memory locations. Each node of the list contains a
pointer to its adjacent node.
Stack:- Stack is a linear list in which insertion and deletions are allowed only at one end, called top.
Queue:- Queue is a linear list in which elements can be inserted only at one end called rear and deleted
only at the other end called front.
This data structure does not form a sequence i.e. each item or element is connected with two or more
other items in a non-linear arrangement. The data elements are not arranged in sequential structure.
➢ Trees:- Trees are multilevel data structures with a hierarchical relationship among its elements known as
nodes. The bottommost nodes in the herierchy are called leaf node while the topmost node is called root
node. Each node contains pointers to point adjacent nodes.
➢ Graphs:- Graphs can be defined as the pictorial representation of the set of elements connected by the
links known as edges.
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.
2) Insertion: Insertion can be defined as the process of adding the elements to the data structure at any
location.
3) Deletion: The process of removing an element from the data structure is called Deletion.
4) Searching: The process of finding the location of an element within the data structure is called Searching.
There are two algorithms to perform searching, Linear Search and Binary Search.
5) Sorting: The process of arranging the data structure in a specific order is known as Sorting. There are
many algorithms that can be used to perform sorting, for example, insertion sort, selection sort, bubble
sort, etc.
6) Merging: When two lists List A and List B of size M and N respectively, of similar type of elements, joined
to produce the third list, List C of size (M+N), then this process is called merging.
Algorithm
An Algorithm is a finite sequence of instructions, each of which has a clear meaning and can be
performed with a finite amount of effort in a finite length of time. No matter what the input values
may be, an algorithm terminates after executing a finite number of instructions. In addition every
algorithm must satisfy the following criteria:
Input: there are zero or more quantities, which are externally supplied;
Output: at least one quantity is produced
Definiteness: each instruction must be clear and unambiguous;
Finiteness: if we trace out the instructions of an algorithm, then for all
cases the algorithm will terminate after a finite number of steps;
Effectiveness: 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, but it must also be feasible.
In formal computer science, one distinguishes between an algorithm, and a program. A program does not
necessarily satisfy the fourth condition. One important example of such a program for a computer is its
operating system, which never terminates (except for system crashes) but continues in a wait loop until
more jobs areentered.
We represent algorithm using a pseudo language that is a combination of the constructs of a
programming language together with informal English statements.
For example, we write an algorithm to find the maximum from a set of $n$ positive numbers. We assume
that the numbers are stored in an array, X.
ANALYSIS OF ALGORITHMS
The efficiency of an algorithm can be decided by measuring the performance of an algorithm. We call this
as the performance analysis of the algorithm. The execution of an algorithm needs various computing
resources to perform a task. The performance of an algorithm can be measured by computing two factors
as follows:
1. Amount of time required by an algorithm to execute (time complexity).
2. Amount of storage required by an algorithm (space complexity).
Analysis of algorithms may be done on the basis of following parameters:
1. Measuring space and time complexities.
2. Measuring the input size.
3. Measuring the running time.
4. Computing best case, worst case and average case efficiencies of the algorithm so developed.
5. Computing the order of growth of algorithms.
The algorithm that needs less space and less time is more efficient than the other ones. The complexity tree
is shown in figure 1.2.
Performance Analysis
The performance of a program is the amount of computer memory and time needed to run a program. We
use two approaches to determine the performance of a program. One is analytical, and the other
experimental. In performance analysis we use analytical methods, while in performance measurement we
conduct experiments.
Time Complexity:
The time needed by an algorithm expressed as a function of the size of a problem is called the time
complexity of the algorithm. The time complexity of a program is the amount of computer time it needs to
run to completion. The limiting behavior of the complexity as size increases is called the asymptotic time
complexity. It is the asymptotic complexity of an algorithm, which ultimately determines the size of
problems that can be solved by the algorithm.
The Running time of a program
When solving a problem, we are faced with a choice among algorithms. The basis for this can be any one of
the following:
i. We would like an algorithm that is easy to understand code and debug.
ii. We would like an algorithm that makes efficient use of the computer’s resources,
especially, one that runs as fast as possible
Measuring the running time of a program
The running time of a program depends on factors such as:
1. The input to the program.
2. The quality of code generated by the compiler used to create the object program.
3. The nature and speed of the instructions on the machine used to execute the program,
4. The time complexity of the algorithm underlying the program.
Space Complexity:
The space complexity of a program is the amount of memory it needs to run to completion. The space need
by a program has the following components:
Instruction space: Instruction space is the space needed to store the compiled version of the program
instructions.
Data space: Data space is the space needed to store all constant and variable values. Data space has two
components:
Space needed by constants and simple variables in program.
Space needed by dynamically allocated objects such as arrays and class instances.
Environment stack space: The environment stack is used to save information needed to resume execution of
partially completed functions.
Instruction Space: The amount of instructions space that is needed depends on factors such as:
The compiler used to complete the program into machine code.
The compiler options in effect at the time of compilation
The target computer.
The space requirement s(p) of any algorithm p may therefore be written as,
S(P) = c+ Sp(Instance characteristics)
Where ‘c’ is a constant.
Example 2:
Algorithm sum(a,n)
{
s=0.0;
for I=1 to n
do s= s+a[I];
return s;
}
The problem instances for this algorithm are characterized by n,the number of elements to be summed.
The space needed d by ‘n’ is one word, since it is of type integer.
The space needed by ‘a’a is the space needed by variables of type array of floating point numbers.
This is atleast ‘n’ words, since ‘a’ must be large enough to hold the ‘n’ elements to be summed.
So,we obtain Ssum(n)>=(n+s) [ n for a[],one each for n,I a&s]
Complexity of Algorithms
The complexity of an algorithm M is the function f(n) which gives the running time and/or storage space
requirement of the algorithm in terms of the size ‘n’ of the input data. Mostly, the storage space required by
an algorithm is simply a multiple of the data size ‘n’. Complexity shall refer to the running time of the
algorithm.
The function f(n), gives the running time of an algorithm, depends not only on the size ‘n’ of the input data
but also on the particular data. The complexity function f(n) for certain cases are:
1. Best Case : The minimum possible value of f(n) is called the best case.
2. Average Case : The expected value of f(n).
3. Worst Case : The maximum value of f(n) for any key possible input
Asymptotic Notations:
The following notations are commonly use notations in performance
analysis and used to characterize the complexity of an algorithm:
1. Big–OH (O)
2. Big–OMEGA (Ω),
3. Big–THETA (Θ) and
4. Little–OH (o)
Big–OH O (Upper Bound)
f(n) = O(g(n)), (pronounced order of or big oh), says that the growth rate of f(n) is
less than or equal (<) that of g(n).
Big–OMEGA Ω (Lower Bound)
f(n) = Ω (g(n)) (pronounced omega), says that the growth rate of f(n) is greater
than or equal to (>) that of g(n).
Creating a Array
Array in Python can be created by importing array module. array(data_type, value_list) is used to create an
array with data type and value list specified in its arguments.
# Python program to demonstrate
# Creation of Array
# importing "array" for array creations
import array as arr
# creating an array with integer type
a = [Link]('i', [1, 2, 3])
# printing original array
print ("The new created array is : ", end =" ")
for i in range (0, 3):
print (a[i], end =" ")
print()
# creating an array with float type
b = [Link]('d', [2.5, 3.2, 3.3])
# printing original array
print ("The new created array is : ", end =" ")
for i in range (0, 3):
print (b[i], end =" ")
Output :
The new created array is : 1 2 3
The new created array is : 2.5 3.2 3.3
Some of the data types are mentioned below which will help in creating an array of different data types.
Adding Elements to a Array
Elements can be added to the Array by using built-in insert() function. Insert is used to insert one or more
data elements into an array. Based on the requirement, a new element can be added at the beginning, end, or
any given index of array. append() is also used to add the value mentioned in its arguments at the end of the
array.
Stack
A stack is an Abstract Data Type (ADT), commonly used in most programming languages. It
is named stack as it behaves like a real-world stack, for example – a deck of cards or a pile of
plates, etc.
A real-world stack allows operations at one end only. For example, we can place or remove a
card or plate from the top of the stack only. Likewise, Stack ADT allows all data operations
at one end only. At any given time, we can only access the top element of a stack.
This feature makes it LIFO data structure. LIFO stands for Last-in-first-out. Here, the
element that is placed (inserted or added) last, is accessed first. In stack terminology,
the insertion operation is called PUSH operation and the removal operation is called POP operation.
Stack Representation
The following diagram depicts a stack and its operations –
A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack
can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are going
to implement stack using arrays, which makes it a fixed size stack implementation-
Static implementation.
Basic Operations
Stack operations may involve initializing the stack, using it and then de-initializing it. Apart
from these basic stuffs, a stack is used for the following two primary operations −
push() − Pushing (storing) an element on the stack.
pop() − Removing (accessing) an element from the stack.
To use a stack efficiently, we need to check the status of stack as well. For the same purpose,
the following functionality is added to stacks −
peek() − get the top data element of the stack, without removing it.
isFull() − check if stack is full.
isEmpty() − check if stack is empty.
Push Operation
The process of putting a new data element onto stack is known as a
Push Operation. Push
operation involves a series of steps −
Step 1 − Checks if the stack is full.
Step 2 − If the stack is full, produces an error and exit.
Step 3 − If the stack is not full, increments top to point next empty
space.
Step 4 − Adds data element to the stack location, where top is
pointing.
Step 5 − Returns success.
If the linked list is used to implement the stack, then in step 3, we need to allocate space
dynamically.
Algorithm for PUSH Operation
A simple algorithm for Push operation can be derived as follows −
def push(stack, data, max_size):
if len(stack) == max_size:
return None
[Link](data)
Example
stack = []
max_size = 5
Pop Operation
Accessing the content while removing it from the stack, is known as a Pop Operation. In an
array implementation of pop() operation, the data element is not actually removed, instead
top is decremented to a lower position in the stack to point to the next value. But in linkedlist
implementation, pop() actually removes data element and deallocates memory space.
A Pop operation may involve the following steps −
Step 1 − Checks if the stack is empty.
Step 2 − If the stack is empty, produces an error and exit.
Step 3 − If the stack is not empty, accesses the data element at which top is pointing.
Step 4 − Decreases the value of top by 1.
Step 5 − Returns success.
Algorithm for Pop Operation
A simple algorithm for Pop operation can be derived as follows –
def pop(stack):
# check if stack is empty
if len(stack) == 0:
return None
def isEmpty():
return len(stack) == 0
def isFull():
return len(stack) == MAXSIZE
def push():
if isFull():
print("Stack is full!")
else:
data = input("Enter element to push: ")
[Link](data)
print("Pushed:", data)
def pop():
if isEmpty():
print("Stack is empty!")
else:
data = [Link]()
print("Popped:", data)
def peek():
if isEmpty():
print("Stack is empty!")
else:
print("Top element:", stack[-1])
def display():
if isEmpty():
print("Stack is empty!")
else:
print("Stack elements:", stack)
if choice == '1':
push()
elif choice == '2':
pop()
elif choice == '3':
peek()
elif choice == '4':
display()
elif choice == '5':
print("Exiting program.")
break
else:
print("Invalid choice! Try again.")
Queue
Queue is a linear structure which follows a particular order in which the operations are
performed. The order is First In First Out (FIFO). A good example of queue is any queue of
consumers for a resource where the consumer that came first is served first.
The difference between stacks and queues is in removing. In a stack we remove the item the
most recently added; in a queue, we remove the item the least recently added.
Operations on Queue:
Mainly the following basic operations are performed on queue:
Enqueue: Adds an item to the queue. If the queue is full, then it is said to be an Overflow
condition.
Dequeue: Removes 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.
Queue Representation
As in stacks, a queue can also be implemented using Arrays, Linked-lists, Pointers and
Structures. For the sake of simplicity, we shall implement queues using one-dimensional
array.
Basic Operations
Queue operations may involve initializing or defining the queue, utilizing it, and then
completely erasing it from the memory. Here we shall try to understand the basic operations
associated with queues −
enqueue() − add (store) an item to the queue.
dequeue() − remove (access) an item from the queue.
Few more functions are required to make the above-mentioned queue operation efficient.
These are −
peek() − Gets the element at the front of the queue without removing it.
isfull() − Checks if the queue is full.
isempty() − Checks if the queue is empty.
In queue, we always dequeue (or access) data, pointed by front pointer and while enqueing
(or storing) data in the queue we take help of rear pointer.
Let's first learn about supportive functions of a queue −
peek()
This function helps to see the data at the front of the queue. The algorithm of peek() function
is as follows –
Algorithm
def peek(queue, front):
# return the front element of the queue
return queue[front]
Example
queue = [10, 20, 30]
front = 0
print(peek(queue, front))
isfull()
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 –
Algorithm
def isFull(rear, MAXSIZE):
if rear == MAXSIZE:
return True
else:
return False
Example
def isFull(rear, MAXSIZE):
return rear == MAXSIZE – 1
isempty()
Algorithm of isempty() function −
Algorithm
def isEmpty(front, rear, MIN):
if front < MIN or front > rear:
return True
else:
return False
If the value of front is less than MIN or 0, it tells that the queue is not yet initialized, hence
empty.
Example:
def isEmpty(front, rear):
return front < 0 or front > rear
Enqueue Operation
Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks.
The following steps should be taken to enqueue (insert) data into a queue −
Step 1 − Check if the queue is full.
Step 2 − If the queue is full, produce overflow error and exit.
Step 3 − If the queue is not full, increment rear pointer to point the next empty space.
Step 4 − Add data element to the queue location, where the rear is pointing.
Step 5 − return success.
Sometimes, we also check to see if a queue is initialized or not, to handle any unforeseen
situations.
Dequeue Operation
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 −
Step 1 − Check if the queue is empty.
Step 2 − If the queue is empty, produce underflow error and exit.
Step 3 − If the queue is not empty, access the data where front is pointing.
Step 4 − Increment front pointer to point to the next available data element.
Step 5 − Return success.
Queue Code
MAXSIZE = 5
queue = [None] * MAXSIZE
front = -1
rear = -1
def isEmpty():
return front < 0 or front > rear
def isFull():
return rear == MAXSIZE - 1
def enqueue():
global rear, front
if isFull():
print("Queue is full!")
else:
data = input("Enter element to enqueue: ")
if front == -1:
front = 0
rear += 1
queue[rear] = data
print("Enqueued:", data)
def dequeue():
global front, rear
if isEmpty():
print("Queue is empty!")
else:
data = queue[front]
front += 1
print("Dequeued:", data)
def peek():
if isEmpty():
print("Queue is empty!")
else:
print("Front element:", queue[front])
def display():
if isEmpty():
print("Queue is empty!")
else:
print("Queue elements:", queue[front:rear + 1])
if choice == '1':
enqueue()
elif choice == '2':
dequeue()
elif choice == '3':
peek()
elif choice == '4':
display()
elif choice == '5':
print("Exiting program.")
break
else:
print("Invalid choice! Try again.")
Sparse Matrices
A sparse matrix is a matrix in which most of the elements are zero.
Example:
007
000
Array Representation
In this representation, we consider only non-zero values along with their row and column index values. In this
representation, the 0th row stores the total number of rows, total number of columns and the total number of
non-zero values in the sparse matrix. For example, consider a matrix of size 5 X 6 containing 6 number of non-
zero values. This matrix can be represented as shown in the image.
function evaluate_postfix(expr):
stack = empty stack
return [Link]()
Example: Evaluate 2 3 4 * + 5 -
push 2
push 3
push 4
*: pop 4,3 => 3*4=12, push 12
+: pop 12,2 => 2+12=14, push 14
push 5
-: pop 5,14 => 14-5=9 → result = 9
Prefix evaluation (algorithm)
1. Read tokens from right → left.
2. If token is operand, push it on the stack.
3. If token is operator:
o Pop required operands (for binary: pop op1 then op2).
o Compute operator op1 op2.
o Push the result back.
4. At the end, the stack contains a single result.
function evaluate_prefix(expr):
stack = empty stack
if token is operand:
[Link](token)
else: # operator
a = [Link]()
b = [Link]()
result = apply_operator(token, a, b)
[Link](result)
return [Link]()
Example: Evaluate + 9 * 2 3
Scan right → left
• push 3
• push 2
• * : pop 2,3 → 2*3 = 6 → push 6
• push 9
• + : pop 9,6 → 9+6 = 15 → push 15
Final result = 15
Linked List
A linked list is a non sequential collection of data items. For every data item in the linked list, there is an
associated pointer that gives the memory location of the next data item in the linked list.
The data items in the linked list are not in a consecutive memory locations. But they may be anywhere in
memory. However, accessing of these items is easier as each data item contained within itself the address of
the next data item.