Unit 2 Notes
Unit 2 Notes
• Abstract Data type (ADT) is a type (or class) for objects whose behavior is
defined by a set of values and a set of operations.
• The definition of ADT only mentions what operations are to be performed but
not how these operations will be implemented.
List ADT -Array Abstract Data Type
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.
SYNTAX
Array_name.insert(1, 4)
Array_name.append(4)
Accessing elements from the Array
Array_name.remove()
Array_name.pop()
Slicing of a Array
print (Array_name.index(value))
In order to update an element in the array we simply reassign a new value to the
desired index we want to update.
Syntax:
Example Program:
List ADT
A linked list in programming terms is an Abstract Data Type that acts as a linear collection of
data elements organized as a collection of nodes that contains information about what that
node contains and then a link to another node.
The benefit of this over a regular array or list is that elements can be easily inserted and
removed without the need of changing the index of all other items and the memory used to
store the linked list.
This data structure can be useful when:
• You want to insert items easily in between other items
• The size of the total collection is unknown
• You don’t need random access when searching for item
• There is no concern about memory usage for storing the data
Key methods commonly used in list ADT:
• insert(): Add an item to the linked list at the head of the list
• find(): Find an item within the linked list
• remove(): Remove a given item with a given value
• is_empty(): Returns whether the linked list is empty or not
• get_count(): Returns the number of items in the linked list
INSERT()
*Create a new node at the Head of the Linked List
*This has a time complexity of O(1) as we are simply changing the current head of
the Linked List and no indices have to change.
FIND()
*Search for item in Linked List with data = val
*Time complexity is O(n) as in worst case scenario have to iterate over whole Linked
List
* The item we can then return that certain Node, or in the case that that value does not
exist then we can return None to avoid any errors being thrown.
REMOVE()
*Remove Node with value equal to item
*Time complexity is O(n) as in the worst case we have to iterate over the whole
linked list.
GET_COUNT
*Return the length of the Linked List
*Time complexity O(1) as only returning a single value
IS_EMPTY
*Returns whether the Linked List is empty or not
*Time complexity O(1) as only returns True or False
Program:
class Node:
def __init__(self,val):
self.val=val
self.next=None
class LinkedList():
def __init__(self,head=None):
self.head=head
self.count=0
def insert(self,data):
new_node=Node(data)
mew_node.next=self.head
self.head=new_node
self.count+=1
def find(self,val):
item=self.head
while item!=None:
if item.val==val:
return item
else:
item=item.next
return None
def remove(self,item):
current=self.head.next
previous=self.head
while current is not None:
if current.val==item:
break
previous=previous.next
current=current.next
if previous is None:
self.head=current.next
self.count-=1
def get_count(self):
return self.count
def is_empty(self):
return self.head==None
def display(self):
temp=self.head
while temp:
print9temp.val,"-->",end="")
temp=temp.next
print()
obj=LinkedList()
obj.insert(10)
obj.insert(20)
obj.insert(30)
obj.insert(40)
obj.insert(50)
obj.display()
print(obj.find(30))
print(obj.remove(40))
obj.display()
print(obj.get_count())
obj.is_empty()
Output:
The singly linked list is a linear data structure in which each element of the list
contains a pointer which points to the next element in the list.
Each element in the singly linked list is called a node.
Each node has two components: data and a pointer next which points to the next node
in the list.
The first node of the list is called as head, and the last node of the list is called a tail.
The last node of the list contains a pointer to the null.
Each node in the list can be accessed linearly by traversing through the list from head
to tail.
Example:
Insertion
Inserting at the Beginning
Step 1:
Adding node at the front of the existing node.
nb=Node(data)
nb=self.head
self.head=nb
Output:
Time Complexity: O(1), We have a pointer to the head and we can directly attach a node
and change the pointer. So the Time complexity of inserting a node at the head position is
O(1) as it does a constant amount of work.
np=Node(data)
temp=self.head
for i in range(pos-1):
temp=temp.next
np.data=data
np.next=temp.next
temp.next=np
Output
temp=self.head
self.head=self.head.next
temp.next=None
Output
temp=self.head.next
prev=self.head
while temp.next is not None:
temp=temp.next
prev=prev.next
prev.next=None
Output:
temp=self.head.next
prev=self.head
for i in range(1,pos-1):
temp=temp.next
prev=prev.next
prev.next=temp.next
Output:
Program:
class Node:
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
defInsert_Beg(self,data):
new = Node(data)
new.next = self.head
self.head = new
defInsert_Mid(self,pos,data):
new = Node(data)
temp = self.head
for i in range(pos-1):
temp = temp.next
new.next = temp.next
temp.next = new
defInsert_End(self,data):
temp = self.head
while temp.next:
temp = temp.next
temp.next = Node(data)
defDelete_Beg(self):
temp = self.head
self.head = self.head.next
temp.next = None
defDelete_Mid(self,pos):
temp = self.head.next
prev = self.head
for i in range(1,pos-1):
temp = temp.next
prev = prev.next
prev.next = temp.next
defDelete_End(self):
temp = self.head.next
prev = self.head
temp = temp.next
prev = prev.next
prev.next = None
def Display(self):
if self.head is None:
else:
temp = self.head
while temp:
print(temp.data,"-->",end = "")
temp = temp.next
print()
obj = SinglyLinkedList()
n1 = Node(8)
obj.head = n1
n2 = Node(3)
n1.next = n2
n3 = Node(7)
n2.next = n3
n4 = Node(2)
n3.next = n4
n5 = Node(9)
n4.next = n5
obj.Display()
obj.Insert_Beg(20)
obj.Display()
obj.Insert_Mid(3,30)
obj.Display()
obj.Insert_End(40)
obj.Display()
obj.Delete_Beg()
obj.Display()
obj.Delete_Mid(2)
obj.Display()
obj.Delete_End()
obj.Display()
DISPLAY
Insertion
Insertion at Beginning
Insertion at End
Insertion at Middle
Deletion
Deletion at Beginning
Deletion at End
Deletion at Middle
class Node:
self.data = data
self.next = None
class CircularlyLinkedList:
def __init__(self):
self.head = None
self.tail = None
self.nodes = 0
def Display(self):
temp = self.head
for i in range(self.nodes):
temp = temp.next
print()
defInsert_Beg(self,data):
self.nodes += 1
new = Node(data)
temp = self.head
new.next = temp
self.head = new
self.tail.next = new
defInsert_Mid(self,pos,data):
self.nodes += 1
new = Node(data)
temp = self.head
for i in range(pos-1):
cur = temp
temp = temp.next
cur.next = new
new.next = temp
defInsert_End(self, data):
self.nodes += 1
new = Node(data)
if self.head is None:
self.head = new
self.tail = new
else:
temp = self.head
for i in range(self.nodes-2):
temp = temp.next
temp.next = new
self.tail = new
new.next = self.head
defDelete_Beg(self):
self.nodes -= 1
temp = self.head
self.head = self.head.next
temp.next = None
self.tail.next = self.head
defDelete_Mid(self,pos):
self.nodes -= 1
temp = self.head.next
prev = self.head
for i in range(1,pos-1):
temp = temp.next
prev = prev.next
prev.next = temp.next
defDelete_End(self):
self.nodes -= 1
temp = self.head.next
prev = self.head
for i in range(self.nodes-1):
temp = temp.next
prev = prev.next
prev.next = temp.next
temp.next = None
self.tail = prev
obj = CircularlyLinkedList()
n1 = Node(8)
obj.head = n1
n2 = Node(3)
n1.next = n2
n3 = Node(7)
n2.next = n3
n4 = Node(2)
n3.next = n4
n5 = Node(9)
n4.next = n5
obj.tail = n5
n5.next = n1
obj.nodes = 5
obj.Display()
obj.Insert_Beg(20)
obj.Display()
obj.Insert_Mid(3,30)
obj.Display()
obj.Insert_End(40)
obj.Display()
obj.Delete_Beg()
obj.Display()
obj.Delete_Mid(2)
obj.Display()
obj.Delete_End()
obj.Display()
print("Circular Linked List Deletion at End")
Output:
A Doubly Linked List (DLL) contains an extra pointer, typically called the previous pointer,
together with the next pointer and data which are there in the singly linked list.
Link − Each link of a linked list can store a data called an element.
Next − Each link of a linked list contains a link to the next link called Next.
Prev − Each link of a linked list contains a link to the previous link called Prev.
LinkedList − A Linked List contains the connection link to the first link called First
and to the last link called Last.
Creation
class Node:
self.data = data
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
Display
def Display(self):
temp = self.head
while temp:
temp = temp.next
print("<-->")
Insertion
Insertion at Beginning
defInsert_Beg(self,data):
new = Node(data)
new.next = self.head
self.head.prev = new
new.prev = None
self.head = new
Insertion at End
defInsert_End(self,data):
new = Node(data)
if self.head is None:
new.prev = None
self.head = new
else:
temp = self.head
while temp.next:
temp = temp.next
temp.next = new
new.prev = temp
self.tail = new
Insertion at Middle
defInsert_Mid(self,pos,data):
new = Node(data)
temp = self.head
for i in range(pos-1):
temp = temp.next
new.next = temp.next
temp.next = new
new.prev = temp
new.next.prev = new
Deletion
Deletion at Beginning
defDelete_Beg(self):
temp = self.head.next
self.head.next = None
self.head = temp
temp.prev = None
Deletion at End
defDelete_End(self):
temp = self.head.next
cur = self.head
while temp.next:
temp = temp.next
cur = cur.next
temp.prev = None
cur.next = None
Deletion at Middle
defDelete_Mid(self,pos):
temp = self.head.next
cur = self.head
for i in range(1,pos):
temp = temp.next
cur = cur.next
cur.next = temp.next
temp.next.prev = cur
temp.prev = None
temp.next = None
Example Program:
class Node:
self.data = data
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
defInsert_Beg(self,data):
new = Node(data)
new.next = self.head
self.head.prev = new
new.prev = None
self.head = new
defInsert_Mid(self,pos,data):
new = Node(data)
temp = self.head
for i in range(pos-1):
temp = temp.next
new.next = temp.next
temp.next = new
new.prev = temp
new.next.prev = new
defInsert_End(self,data):
new = Node(data)
if self.head is None:
new.prev = None
self.head = new
else:
temp = self.head
while temp.next:
temp = temp.next
temp.next = new
new.prev = temp
self.tail = new
defDelete_Beg(self):
temp = self.head.next
self.head.next = None
self.head = temp
temp.prev = None
defDelete_Mid(self,pos):
temp = self.head.next
cur = self.head
for i in range(1,pos):
temp = temp.next
cur = cur.next
cur.next = temp.next
temp.next.prev = cur
temp.prev = None
temp.next = None
defDelete_End(self):
temp = self.head.next
cur = self.head
while temp.next:
temp = temp.next
cur = cur.next
temp.prev = None
cur.next = None
def Display(self):
temp = self.head
while temp:
temp = temp.next
print("<-->")
obj = DoublyLinkedList()
n1 = Node(8)
obj.head = n1
n1.prev = None
n2 = Node(3)
n1.next = n2
n2.prev = n1
n3 = Node(7)
n2.next = n3
n3.prev = n2
n4 = Node(2)
n3.next = n4
n4.prev = n3
n5 = Node(9)
n4.next = n5
n5.prev = n4
obj.tail = n5
n5.next = None
obj.Display()
obj.Insert_Beg(20)
obj.Display()
obj.Insert_Mid(3,30)
obj.Display()
obj.Display()
obj.Delete_Beg()
obj.Display()
obj.Delete_Mid(2)
obj.Display()
obj.Delete_End()
obj.Display()
Output:
STACK ADT
o It is called as stack because it behaves like a real-world stack, piles of books, etc.
o A Stack is an abstract data type with a pre-defined capacity, which means that it can
store the elements of a limited size.
o It is a data structure that follows some order to insert and delete the elements, and that
order can be LIFO or FILO.
Working of Stack
Stack works on the LIFO pattern. As we can observe in the below figure there are five
memory blocks in the stack; therefore, the size of the stack is 5.
Suppose we want to store the elements in a stack and let's assume that stack is empty.
We have taken the stack of size 5 as shown below in which we are pushing the
elements one by one until the stack becomes full.
o push(): When we insert an element in a stack then the operation is known as a push.
If the stack is full then the overflow condition occurs.
o pop(): When we delete an element from the stack, the operation is known as a pop. If
the stack is empty means that no element exists in the stack, this state is known as an
underflow state.
o isEmpty(): It determines whether the stack is empty or not.
o isFull(): It determines whether the stack is full or not.'
o peek(): It returns the element at the given position.
o count(): It returns the total number of elements available in a stack.
o change(): It changes the element at the given position.
o display(): It prints all the elements available in the stack.
PUSH operation
POP operation
o Before deleting the element from the stack, we check whether the stack is empty.
o If we try to delete the element from the empty stack, then the underflow condition
occurs.
o If the stack is not empty, we first access the element which is pointed by the top
o Once the pop operation is performed, the top is decremented by 1, i.e., top=top-1.
APPLICATIONS OF STACK
• Matching tags in HTML and XML
• Stack is used for evaluating expression with operands and operations.
• Undo function in any text editor.
• Infix to Postfix conversion.
• Stacks are used for backtracking and parenthesis matching.
• Stacks are used for conversion of one arithmetic notation to another arithmetic
notation.
• Stacks are useful for function calls, storing the activation records and deleting them
after returning from the function. It is very useful in processing the function calls.
• Stacks help in reversing any set of data or strings.
ALGORITHM:
■ STEP 1: Start the program
■ STEP 3:defining a class variable that is calling items and it is assigned to an empty
list
■ STEP 4:create a push method push(self.item).here, item is an argument
■ STEP 6:Create a pop() method and get_stack() method, this will returns the items list
Program:
OUTPUT:
For example: (a + b) * (c + d)
The above expression contains three operators. The operands for the first plus operator are a
and b, whereas the operands for the second plus operator are c and d. Evaluating the result of
these operations requires applying some set of rules. At last, after using the addition operator
on (a + b) and (c + d), the multiplication operation will be performed to look towards the final
answer.
Syntax:
Postfix Notation
The expression in which the operator is written after the operands is known as postfix
expression or reverse polish notation.
For example, the postfix notation of infix expression (a + b) can be written as ab+.
Postfix expression is an arithmetic expression in which operators are applied from left to
right. There is no need for parentheses while working with postfix notation, unlike infix
expressions. Moreover, no operator precedence rules or associativity rules are needed,
meaning that coders do not need to memorize a special set of rules to help them determine the
order in which operations will be performed.
Here, we will start scanning the expression from the left side. As the multiplication operator
is the first operator appearing while scanning expression from left to right, the expression will
be
Again, we scan the expression from left to right, and the next operator encountered will be
plus. As a result, the final output of the expression will be
Therefore, the final algorithmic steps for the postfix expression are:
Using the stack data structure is the best method for converting an infix expression to a
postfix expression. It holds operators until both operands have been processed, and it reverses
the order of operators in the postfix expression to match the order of operation.
Here is an algorithm that returns the string in postfix order, left to right. For each token, there
are four cases:
Example
Let the expression to be evaluated be m*n+(p-q)+r
1 M m
2 * Push * m
3 N * mn
4 + Push + mn*
5 ( Push +( mn*
6 P +( mn*p
8 Q +(- mn*pq
10 + Push + mn*pq-+
11 R + mn*pq-+r
Program:
Operators = set(['+', '-', '*', '/', '(', ')', '^']) # collection of Operators
Priority = {'+':1, '-':1, '*':2, '/':2, '^':3} # dictionary having priorities of Operators
def infixToPostfix(expression):
output = ''
output+= character
stack.append('(')
elif character==')':
output+=stack.pop()
stack.pop()
else:
output+=stack.pop()
stack.append(character)
while stack:
output+=stack.pop()
return output
QUEUE
1. A queue can be defined as an ordered list which enables insert operations to be performed
at one end called REAR and delete operations to be performed at another end
called FRONT.
2. Queue is referred to be as First In First Out list.
3. For example, people waiting in line for a rail ticket form a queue.
Working of Queue
Enqueue Operation
Applications of Queue
Due to the fact that queue performs actions on first in first out basis which is quite fair for the
ordering of actions. There are various applications of queues discussed as below.
1. Queues are widely used as waiting lists for a single shared resource like printer, disk,
CPU.
2. Queues are used in asynchronous transfer of data (where data is not being transferred
at the same rate between two processes) for eg. pipes, file IO, sockets.
3. Queues are used as buffers in most of the applications like MP3 media player, CD
player, etc.
4. Queue is used to maintain the play list in media players in order to add and remove
the songs from the play-list.
5. Queues are used in operating systems for handling interrupts.
Complexity
Data Time Complexity Space
Structur Compleity
e
Queue θ(n) θ(n) θ(1) θ(1) O(n) O(n) O(1) O(1) O(n)
REPRESENTATION OF DEQUE
TYPES OF DEQUE
There are two types of deque :-
o Input restricted queue
o Output restricted queue
In input restricted queue, insertion operation can be performed at only one end, while deletion
can be performed from both ends.
o Insertion at front
o Insertion at rear
o Deletion at front
o Deletion at rear
In this operation, the element is inserted from the front end of the queue. Before
implementing the operation, we first have to check whether the queue is full or not. If the
queue is not full, then the element can be inserted from the front end by using the below
conditions -
o If the queue is empty, both rear and front are initialized with 0. Now, both will point
to the first element.
o Otherwise, check the position of the front if the front is less than 1 (front < 1), then
reinitialize it by front = n - 1, i.e., the last index of the array.
In this operation, the element is inserted from the rear end of the queue. Before implementing
the operation, we first have to check again whether the queue is full or not. If the queue is not
full, then the element can be inserted from the rear end by using the below conditions -
o If the queue is empty, both rear and front are initialized with 0. Now, both will point
to the first element.
o Otherwise, increment the rear by 1. If the rear is at last index (or size - 1), then instead
of increasing it by 1, we have to make it equal to 0.
In this operation, the element is deleted from the front end of the queue. Before implementing
the operation, we first have to check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the
deletion. If the queue is not full, then the element can be inserted from the front end by using
the below conditions -
If the deque has only one element, set rear = -1 and front = -1.
Else if front is at end (that means front = size - 1), set front = 0.
In this operation, the element is deleted from the rear end of the queue. Before implementing
the operation, we first have to check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the
deletion.
If the deque has only one element, set rear = -1 and front = -1.
Check empty
This operation is performed to check whether the deque is empty or not. If front = -1, it
means that the deque is empty.
Check full
This operation is performed to check whether the deque is full or not. If front = rear + 1, or
front = 0 and rear = n - 1 it means that the deque is full.
The time complexity of all of the above operations of the deque is O(1), i.e., constant.
Example Program:
import collections
my_deque = collections.deque([10, 20, 30, 40, 50])
my_deque.append(60)
print( "The deque after appending at right: " )
print( my_deque )
my_deque.appendleft(70)
print( "The deque after appending at left: " )
print( my_deque )
my_deque.pop()
print( "The deque after removing from right: " )
print( my_deque )
my_deque.popleft()
print("The deque after removing from left: " )
print( my_deque )
Output:
Applications of deque
o Deque can be used as both stack and queue, as it supports both operations.
o Deque can be used as a palindrome checker means that if we read the string from both
ends, the string would be the same.