Data Structures Using Python
Data Structures Using Python
SYLLUBUS
UNIT – II Linked List ADT - Singly Linked Lists - Doubly Linked Lists - Circular
Linked Lists – Stacks ADT - Queues implementation – Dequeues (Concept).
UNIT - III Searching: Linear Search - Binary search. Sorting: Merge Sort - Bubble
Sort - Selection Sort - Insertion Sort – Quick Sort (Concepts and
Implementation).
UNIT - IV Trees: Binary Tree - Binary search trees: find, insert, and delete -
Height-balanced binary search trees - Tree Traversal Algorithms (Inorder,
Preorder, Postorder Traversal)
UNIT -V Graphs: Data Structures for Graphs - Graph Traversals: Breadth First
Search, Depth First Search - Directed Acyclic Graphs - Shortest Paths
Object-oriented programming
• Some of the most widely used object-oriented programming languages are Java, C++, and
Ruby.
• These objects contain data, which we also refer to as attributes or properties, and methods.
• In object-oriented programming data structures or objects are defined, each with its own
properties or attributes
• Software is designed by using objects that interact with one another. This offers various
benefits, like:
• being faster and easier to execute;
• providing a clear structure for a program;
• making code easier to modify, debug and maintain; and
• Making it easier to reuse code.
• Better able to create GUI (graphical user interface) applications.
• Programmers are able to produce faster, more accurate and better Applications.
• Class
• Objects
• Inheritance
• Encapsulation
• Polymorphism
• Data Abstraction
.
Class :
A class is a collection of objects. A class contains the blueprints or the
prototype from which the objects are being created. It is a logical entity that
contains some attributes and methods.
# Python3 program to
# demonstrate defining
# a class
class Student:
pass
In the above example, we have created a class named Student using the class
keyword.
Object:
An object (instance) is an instantiation of a class. When class is defined, then only
the description for the object is defined. Therefore, no memory or storage is
allocated.
• The entire OOPS methodology has been derived from a single root concept
called ‘Object’.
• An object is anything that really exists in the world and can be distinguished
from others.
• Ex: Table, Cat, dog and Person
• Every object has some behaviour.
• Behaviour of an object is represented by attributes and actions.
Syntax:
objectname= classname()
Example:
std = Student()
➢ Accessing data members from Student class
print(“value of a=“,std.a)
The self :
1. Class methods must have an extra first parameter in the method definition.
We do not give a value for this parameter when we call the method, Python
provides it
2. If we have a method that takes no arguments, then we still have to have one
argument.
Class classname(object):
def __init__(self):
def method1():
def method2():
• Constructor: The constructor must have a special name __init__() and a special
The task of the constructor is to initialize the data members of the class when
object is created.
Syntax:
self.a=parameter1
class Student:
self.sname = name
self.sage = age
Output:
student Age: 25
To summarize
A constructor is called if you
create an object.
In the constructor you can set
variables and
call methods.
Types of Variables:
The variables which are written inside a class are 3 types:
2.Instance variables.
3.Local variables.
Types of methods:
• The purpose of method is to process the variables provided in class or in
method.
1. Instance Methods
2. Class Methods
3. Static Methods
1.Instance Method:
• Instance methods are methods which act upon the instance variables.
instancename.method().
• While calling the instance methods we need not pass any value to self variable.
Example:
class sample:
#Calling constructor
def __init__(self,a,b):
print(“Hello”)
self.a=a
self.b=b
#Instance Method
def display(self):
print(self.a,self.b)
S1=sample(10,20)
S1.display()
Output:
10 20
2.Class Method:
➢ Using @classmethod decorator we can create class method. The class
method and instance method both are same except some modifications i.e
➢ To make a instance method as class method by add @classmethod
decorator before the method definition, and add cls as the first parameter
to that method means ‘self’ parameter is replaced by ‘cls’ parameter.
Output:
Student age= 20
3.Static Method:
➢ Using @staticmethod decorator we can create static method.
➢ A class method takes cls as the first parameter while a static method needs
no specific parameters.
➢ Syntax:
# static method
@staticmethod
def display( ):
statement-1
statement-2
Example program using static method:
class Student:
@staticmethod
def age():
sage=20
print("Student age=",sage)
f=Student()
f.age() # call by using object
Student.age() # call by using Class_name
.
Output:
Student age= 20
Student age= 20
Inheritance :
Inheritance is the process of deriving a new class from an existing class.
➢ The existing class is called the parent class, or superclass, or base class
➢ The new class is called the child class or subclass, or derived class.
➢ The child class inherits properties from the parent class(i.e the child class
inherits the methods and data from the parent class) .
Fig: Inheritance
Types of inheritances
There are five types of inheritances:
➢ Multiple Inheritance
➢ Multi-Level Inheritance
➢ Hierarchical Inheritance
➢ Hybrid Inheritance
*Single inheritance : one child class derived properties from one parent
class
Syntax:
class parent_class_name:
------------------
---------------------
------------------
---------------------
Note: child class object is enough for accessing both child class properties and
parent class properties.
Example program :
Output:
Hi
Hello
Ramu
Example program :
class A:
def display1(self):
print("Hi")
class C:
def display2(self):
print("Hello")
class B(A,C):
def display3(self):
print("ramu")
s=B()
s.display1()
s.display2()
s.display3()
Output:
Hi
Hello
ramu
*Multilevel Inheritance:
one child class derives from a class which is already derived from another class
class Family:
def show_family(self):
print("This is our family")
Output:
Hi
ramu
This is our family
*Hierarchical Inheritance:
Two or more child classes derives from one parent class
class B(A):
def display2(self):
print("Hello")
class C(A):
def display3(self):
print("Hello")
s=B()
s1=C()
s.display1()
s1.display1()
OUTPUT:
Hi
Hi
*Hybrid Inheritance:
It is the combination of all the inheritances
Example:
class Class1: # parent class
def m(self):
print("In Class1")
class Class2(Class1):
def m(self):
print("In Class2")
class Class3(Class1):
def m(self):
print("In Class3")
obj = Class4()
obj.m()
Output:
In Class2
ENCAPSULATION:
• Encapsulation is the packing of data and functions that work on that data within
a single object.
• We can hide the internal state of the object from the outside. This is known
as information hiding.
• A class is an example of encapsulation.
EXAMPLE:
class A():
def __init__(self):
self._a=10
self.__b=20
self.c=30
def display(self):
print(self._a)
print(self.__b)
print(self.c)
class B(A):
def __init__(self):
super().__init__()
def display(self):
print(self._a)
print(self.c)
class c():
def __init__(self):
self.d=40
def display(self):
print(self.d)
a1=A()
a1.display()
b1=B()
b1.display()
c1=c()
c1.display()
OUTPUT:
10
20
30
10
30
40
Polymorphism:
The word polymorphism means having many forms. In programming,
polymorphism means the same function name (but different
signatures) being used for different types.
Example:
class sample:
def add(self,a,b,c=0,d=0):
print(a+b+c+d)
s1=sample()
s1.add(1,2,3,4)
s1.add(3,4)
s1.add(1,2,3)
Output:
10
7
6
Abstraction:
Abstraction is used to hide the internal functionality of the function from the users.
An abstraction is used to hide the irrevalent data/class in order to reduce the
complexity.
Abstraction classes in python:
In python,abstraction can be achieved by using abstraction classes and interfaces.
Example:
from abc import abstractmethod,ABC
class polygon(ABC):
@abstractmethod
def sides(self):
pass
class triangle(polygon):
def sides(self):
self.s=3
def display(self):
print(self.s)
t1=triangle()
t1.sides()
t1.display()
Output:
3
UNIT – II
Linked Lists:
Linked lists are one of the most commonly used data structures in any programming
language. Linked Lists, on the other hand, are different. Linked lists, do not store data at
contiguous memory locations. For each item in the memory location, linked list stores value
of the item and the reference or pointer to the next item. One pair of the linked list item and
the reference to next item constitutes a node.
The program creates a linked list using data items input from the user and displays it.
Solution:
1. Create a class Node with instance variables data and next.
2. Create a class Linked List with instance variables head and last_node.
3. The variable head points to the first element in the linked list while last_node points to the
last.
4. Define methods append and display inside the class Linked List to append data and display
the linked listrespectively.
5. Create an instance of Linked List, append data to it and display the list.
30
Program:
class Node:
def init (self, data):
self.data= data
self.next=None
class LinkedList:
def init (self):
self.head=None
self.last_node=None
def append(self,data):
ifself.last_nodeisNone:
self.head= Node(data)
self.last_node=self.head
else:
self.last_node.next= Node(data)
self.last_node=self.last_node.next
def display(self):
current =self.head
while current isnot None:
print(current.data, end =' ')
current = current.next
a_llist = LinkedList()
n =int(input('How many elements would you like to add? '))
for i in range(n):
data =int(input('Enter data item: '))
a_llist.append(data)
print('The linked list: ', end ='')
a_llist.display()
Program Explanation
1. An instance of Linked List is created.
2. The user is asked for the number of elements they would like to add. This is stored in n.
3. Using a loop, data from the user is appended to the linked list n times.
4. The linked list is displayed.
31
Output:
How many elements would you like to add? 5
Enter data item: 4
Enter data item: 4
Enter data item: 6
Enter data item: 8
Enter data item: 9
The linked list: 4 4 6 8 9
A doubly linked list is a linked data structure that consists of a set of sequentially linked
records callednodes.
Each node contains three fields: two link fields (references to the previous and to the next
node in thesequence of nodes) and one data field.
1. Traversal can be done on either side means both in forward as well as backward.
2. Deletion Operation is more efficient if the pointer to delete node is given.
So, a typical node in the doubly linked list consists of three fields:
The above picture represents a doubly linked list in which each node has two pointers to
point to previous and next node respectively. Here, node 1 represents the head of the list.
The previous pointer of the head node will always point to NULL. Next pointer of node one
will point to node 2. Node 5 represents the tail of the list whose previous pointer will point
to node 4, and the next will point to NULL.
32
ALGORITHM:
1. Define a Node class which represents a node in the list. It will have three properties: data,
previouswhich will point to the previous node and next which will point to the next node.
2. Define another class for creating a doubly linked list, and it has two nodes: head and tail.
Initially,head and tail will point to null.
• It first checks whether the head is null, then it will insert the node as the head.
• Both head and tail will point to a newly added node.
• Head's previous pointer will point to null and tail's next pointer will point to null.
• If the head is not null, the new node will be inserted at the end of the list such
that new node'sprevious pointer will point to tail.
• The new node will become the new tail. Tail's next pointer will point to null.
• a.display() will show all the nodes present in the list.
• Define a new node 'current' that will point to the head.
• Print current.data till current points to null.
• Current will point to the next node in the list in each iteration.
PROGRAM:
1. #Represent a node of doubly linked list
2. class Node:
3. def __init (self,data):
4. self.data = data;
5. self.previous = None;
6. self.next = None;7.
8. class DoublyLinkedList:
9. #Represent the head and tail of the doubly linked list
10. def __init (self):
11. self.head = None;
12. self.tail = None;13.
14. #addNode() will add a node to the list
15. def addNode(self, data):
16. #Create a new node
17. newNode = Node(data);18.
19. #If list is empty
20. if(self.head == None):
21. #Both head and tail will point to newNode
22. self.head = self.tail = newNode;
23. #head's previous will point to None
24. self.head.previous = None;
33
25. #tail's next will point to None, as it is the last node of the list
26. self.tail.next = None;
27. else:
28. #newNode will be added after tail such that tail's next will point to newNode
29. self.tail.next = newNode;
30. #newNode's previous will point to tail
31. newNode.previous = self.tail;
32. #newNode will become new tail
33. self.tail = newNode;
34. #As it is last node, tail's next will point to None
35. self.tail.next = None;36.
37. #display() will print out the nodes of the list
38. def display(self):
39. #Node current will point to head
40. current = self.head;
41. if(self.head == None):
42. print("List is empty");
43. return;
44. print("Nodes of doubly linked list: ");
45. while(current != None):
46. #Prints each node by incrementing pointer.
47. print(current.data),;
48. current = current.next;49.
50. dList = DoublyLinkedList();
51. #Add nodes to the list
52. dList.addNode(1);
53. dList.addNode(2);
54. dList.addNode(3);
55. dList.addNode(4);
56. dList.addNode(5);57.
58. #Displays the nodes present in the list
59. dList.display();
Output:
The circular linked list is a kind of linked list. First thing first, the node is an element of the
list, and it has two parts that are, data and next. Data represents the data stored in the node,
and next is the pointer that will point to the next node. Head will point to the first element of
34
the list, and tail will point to the last element in the list. In the simple linked list, all the nodes
will point to their next element and tail will point to null.
The circular linked list is the collection of nodes in which tail node also point back to head
node. The diagram shown below depicts a circular linked list. Node A represents head and
node D represents tail. So, in this list, A is pointing to B, B is pointing to C and C is pointing
to D but what makes it circular is that node D is pointing back to node A.
ALGORITHM:
1. Define a Node class which represents a node in the list. It has two properties data and
next whichwill point to the next node.
2. Define another class for creating the circular linked list, and it has two nodes: head and tail.
It hastwo methods: add() and display() .
3. add() will add the node to the list:
• It first checks whether the head is null, then it will insert the node as the head.
• Both head and tail will point to the newly added node.
• If the head is not null, the new node will be the new tail, and the new tail will point to
the head as itis a circular linked list.
• a.display() will show all the nodes present in the list.
• Define a new node 'current' that will point to the head.
• Print current.data till current will points to head
• Current will point to the next node in the list in each iteration.
PROGRAM:
1. #Represents the node of list.
2. class Node:
3. def __init (self,data):
4. self.data = data;
5. self.next = None;
6. class CreateList:
7. #Declaring head and tail pointer as null.
8. def __init (self):
9. self.head = Node(None);
35
10. self.tail = Node(None);
11. self.head.next = self.tail;
12. self.tail.next = self.head;14.
13. #This function will add the new node at the end of the list.
14. def add(self,data):
15. newNode = Node(data);
16. #Checks if the list is empty.
17. if self.head.data is None:
18. #If list is empty, both head and tail would point to new node.
19. self.head = newNode;
20. self.tail = newNode;
21. newNode.next = self.head;
22. else:
23. #tail will point to new node.
24. self.tail.next = newNode;
25. #New node will become new tail.
26. self.tail = newNode;
27. #Since, it is circular linked list tail will point to head.
28. self.tail.next = self.head;31.
29. #Displays all the nodes in the list
30. def display(self):
31. current = self.head;
32. if self.head is None:
33. print("List is empty");
34. return;
35. else:
36. print("Nodes of the circular linked list: ");
37. #Prints each node by incrementing pointer.
38. print(current.data),
39. while(current.next != self.head):
40. current = current.next;
41. print(current.data)
Output:
Stacks:
Stack works on the principle of “Last-in, first-out”. Also, the inbuilt functions in Python make
the code short and simple. To add an item to the top of the list, i.e., to push an item, we use
append() function and to pop out an element we use pop() function.
Output:
['Amar', 'Akbar', 'Anthony', 'Ram', 'Iqbal']
Iqbal
['Amar', 'Akbar', 'Anthony', 'Ram']
Ram
['Amar', 'Akbar', 'Anthony']
37
Stack Using Linked List
A stack using a linked list is just a simple linked list with just restrictions that any element
will be added andremoved using push and pop respectively. In addition to that, we also
keep top pointer to represent the top of the stack. This is described in the picture given
below.
Stack Operations:
1. push() : Insert the element into linked list nothing but which is the top node of Stack.
2. pop() : Return top element from the Stack and move the top pointer to the second node of
linked listor Stack.
3. peek(): Return the top element.
4. display(): Print all element of Stack.
IS_EMPTY(S)
if S.top == null
return TRUE
return FALSE
A stack will be empty if the linked list won’t have any node i.e., when the top pointer of the
linked list willbe null. So, let’s start by making a function to check whether a stack is empty
or not.
Now, to push any node to the stack (S) - PUSH(S, n), we will first check if the stack is empty
or not. If thestack is empty, we will make the new node head of the linked list and also point
the top pointer to it.
38
PUSH(S, n)
if IS_EMPTY(S) //stack is empty
S.head = n //new node is the head of the linked listS.top = n //new node is the also the top
If the stack is not empty, we will add the new node at the last of the stack. For that, we will
point next ofthe top to the new node - (S.top.next = n) and the make the new node top of the
stack - (S.top = n).
PUSH(S, n)
if IS_EMPTY(S) //stack is empty
...
else
S.top.next = nS.top = n
PUSH(S, n)
if IS_EMPTY(S) //stack is empty
S.head = n //new node is the head of the linked listS.top = n
//new node is the also the top
else
S.top.next = n
S.top = n
39
Similarly, to remove a node (pop), we will first check if the stack is empty or not as we did
in theimplementation with array.
POP(S)
if IS_EMPTY(S)
Error “Stack Underflow”
In the case when the stack is not empty, we will first store the value in top node in a
temporary variablebecause we need to return it after deleting the node.
POP(S)
if IS_EMPTY(S)
...
else
x = S.top.data
Now if the stack has only one node (top and head are same), we will just make both top and
head null.
POP(S)
if IS_EMPTY(S)
...
else
...
if S.top == S.head //only one nodeS.top = NULL
S.head = NULL
If the stack has more than one node, we will move to the node previous to the top node
and make the next ofpoint it to null and also point the top to it.
POP(S)
...
...
if S.top == S.head //only one node
...
40
else
tmp = S.head
while tmp.next != S.top //iterating to the node previous to toptmp = tmp.next
tmp.next = NULL //making the next of the node nullS.top = tmp //changing the top pointer
We first iterated to the node previous to the top node and then we marked its next to null -
tmp.next =NULL. After this, we pointed the top pointer to it - S.top = tmp.
At last, we will return the data stored in the temporary variable - return x.
POP(S)
if IS_EMPTY(S)
Error "Stack Underflow"else
x = S.top.data
if S.top == S.head //only one nodeS.top
= NULL
S.head = NULLelse
tmp = S.head
while tmp.next != S.top //iterating to the node previous to toptmp =
tmp.next
tmp.next = NULL //making the next of the node nullS.top = tmp
//changing the top pointer
return x
Program
class Node():
def __init__(self, data):
self.data = data self.next = None
class Stack():
def __init__(self):
self.head = Noneself.top = None
def traversal(s):
temp = s.head #temporary pointer to point to head
a = ''
while temp != None: #iterating over stacka = a+str(temp.data)+'\t'
temp = temp.next
print(a)
def is_empty(s):
if s.top == None:
return True return False
def push(s, n):
if is_empty(s): #emptys.head = n
s.top = nelse:
s.top.next = ns.top = n
def pop(s):
41
if is_empty(s):
print("Stack Underflow")return -1000
else:
x = s.top.data
if s.top == s.head: # only one nodes.top = None
s.head = Noneelse:
temp = s.head
while temp.next != s.top: #iterating to the last elementtemp = temp.next
temp.next = Nonedel s.top
s.top = temp
return x
if __name__ == '__main__':
s = Stack()
a = Node(10)b = Node(20)c = Node(30)
pop(s) push(s, a)
push(s, b)
push(s, c)
traversal(s)
pop(s)
traversal(s)
Applications of Stack
There are many applications of a stack. Some of them are:
Queue
Similar to stacks, a queue is also an Abstract Data Type or ADT. A queue follows FIFO (First-
in, First out) policy. It is equivalent to the queues in our general life. For example, a new
person enters a queue at the last and the person who is at the front (who must have entered
the queue at first) will be served first.
42
Similar to a queue of day to day life, in Computer Science also, a new element enters a queue
at the last (tailof the queue) and removal of an element occurs from the front (head of the
queue).
Similar to the stack, we will implement the queue using a linked list as well as with an array.
But let’s firstdiscuss the operations which are done on a queue.
Enqueue → Enqueue is an operation which adds an element to the queue. As stated earlier,
any new itementers at the tail of the queue, so Enqueue adds an item to the tail of a queue.
Dequeue → It is similar to the pop operation of stack i.e., it returns and deletes the front
element from thequeue.
isEmpty → It is used to check whether the queue has any element or not.
isFull → It is used to check whether the queue is full or not.
Front → It is similar to the top operation of a stack i.e., it returns the front element of the
queue (but don’tdelete it).
Before moving forward to code up these operations, let’s discuss the applications of a queue.
43
Applications of Queue
• Queue is used to implement many algorithms like Breadth First Search (BFS), etc.
• It can be also used by an operating system when it has to schedule jobs with equal priority
• Customers calling a call center are kept in queues when they wait for someone to pick up
the callsQueue Using an Array
We will maintain two pointers - tail and head to represent a queue. head will always point
to the oldestelement which was added and tail will point where the new element is going to
be added.
To insert any element, we add that element at tail and increase the tail by one to point to
the next element ofthe array.
Suppose tail is at the last element of the queue and there are empty blocks before head as
shown in thepicture given below.
In this case, our tail will point to the first element of the array and will follow a circular
order.
44
Initially, the queue will be empty i.e., both head and tail will point to the same location i.e., at
index 1. We can easily check if a queue is empty or not by checking if head and tail are
pointing to the same location or not at any time.
IS_EMPTY(Q)
If Q.tail == Q.headreturn
True
return False
Similarly, we will say that if the head of a queue is 1 more than the tail, the queue is full.
IS_FULL(Q)
if Q.head = Q.tail+1
return True
Return False
Now, we have to deal with the enqueue and the dequeue operations.
To enqueue any item to the queue, we will first check if the queue is full or not i.e.,
Enqueue(Q, x)
if isFull(Q)
Error “Queue Overflow”
else…
45
If the queue is not full, we will add the element to the tail i.e, Q[Q.tail] = x.
While adding the element, it might be possible that we have added the element at the last
of the array and inthis case, the tail will go to the first element of the array.
Enqueue(Q, x)
if isFull(Q)
Error “Queue Overflow”
else
Q[Q.tail] = x
if Q.tail == Q.size
Q.tail = 1
else
Q.tail = Q.tail+1
46
To dequeue, we will first check if the queue is empty or not. If the queue is empty, then we
will throw anerror.
Dequeue(Q, x) if isEmpty(Q)
Error “Queue Underflow”else
…
To dequeue, we will first store the item which we are going to delete from the queue in a
variable becausewe will be returning it at last.
Dequeue(Q, x) if isEmpty(Q)
Error “Queue Underflow”else
x = Q[Q.head]
…
Now, we just have to increase the head pointer by 1. And in the case when the head is at
the last element ofthe array, it will go 1.
Dequeue(Q, x) if
isEmpty(Q)
Error “Queue Underflow”else
x = Q[Q.head]
if Q.head == Q.sizeQ.head = 1
else
Q.head = Q.head+1return
x
Program
class Queue:
def __init__(self, size):self.head = 1
self.tail = 1
self.Q = [0]*(size)self.size = size
def is_empty(self):
if self.tail == self.head:
return True
47
return False
def is_full(self):
if self.head == self.tail+1:
return True
return False
def enqueue(self, x):
if self.is_full():
print("Queue Overflow")
else:
self.Q[self.tail] = x
if self.tail == self.size:
self.tail = 1
else:
self.tail = self.tail+1
def dequeue(self):
if self.is_empty():
print("Underflow")
else:
x = self.Q[self.head]
if self.head == self.size:
self.head = 1
else:
self.head = self.head+1
return x
def display(self):
i = self.head
while(i < self.tail):
print(self.Q[i])
if(i == self.size):
i=0
i = i+1
if __name__ == '__main__':
q = Queue(10)
q.enqueue(10)
q.enqueue(20)
q.enqueue(30)
q.enqueue(40)
q.enqueue(50)
q.display()
print("")
q.dequeue()
q.dequeue()
48
q.display()
print("")
q.enqueue(60)
q.enqueue(70)
q.display()
As we know that a linked list is a dynamic data structure and we can change the size of it
whenever it is needed. So, we are not going to consider that there is a maximum size of the
queue and thus the queue willnever overflow. However, one can set a maximum size to
restrict the linked list from growing more than that size.
As told earlier, we are going to maintain a head and a tail pointer to the queue. In the case
of an emptyqueue, head will point to NULL.
49
IS_EMPTY(Q)
if Q.head == null
return True
return False
We will point the head pointer to the first element of the linked list and the tail pointer to
the last element ofit as shown in the picture given below.
The enqueue operation simply adds a new element to the last of a linked list.
However, if the queue is empty, we will simply make the new node head and tail of the
queue.
50
ENQUEUE(Q, n) if
IS_EMPTY(Q)
Q.head = nQ.tail =
n
else
Q.tail.next = nQ.tail = n
To dequeue, we need to remove the head of the linked list. To do so, we will first store its data
in a variablebecause we will return it at last and then point head to its next element.
x = Q.head.data Q.head = Q.head.nextreturn x
We will execute the above codes when the queue is not empty. If it is, we will throw the "Queue
Underflow" error.
DEQUEUE(Q, n) if
IS_EMPTY(Q)
Error "Queue Underflow"else
x = Q.head.data Q.head =
Q.head.next
return x
Program
class Node():
def __init__(self, data):
self.data = data
self.next = None
class Queue():
def __init__(self):
self.head = None
self.tail = None
def traversal(q):
temp = q.head #temporary pointer to point to head
a = ''
while temp != None:
#iterating over queuea = a+str(temp.data)+'\t'
temp = temp.next
print(a)
def is_empty(q):
if q.head == None:
return True
51
return False
def enqueue(q, n):
if is_empty(q):
#emptyq.head = n
q.tail = n
else:
q.tail.next = n
q.tail = n
def dequeue(q):
if is_empty(q):
print("Queue Underflow")
return -1000
else:
x = q.head.data
temp = q.head
q.head = q.head.next
del temp
return x
if __name__ == '__main__':
q = Queue()
a = Node(10)
b = Node(20)
c = Node(30)
dequeue(q)
enqueue(q, a)
enqueue(q, b)
enqueue(q, c)
traversal(q)
dequeue(q)
traversal(q)
Priority Queues
A queue has FIFO (first-in-first-out) ordering where items are taken out or accessed on a
first-come-first-served basis. Examples of queues include a queue at a movie ticket stand, as
shown in the illustration above. But what is a priority queue?
A priority queue is an abstract data structure (a data structure defined by its behaviour) that is
like a normal queue but where each item has a special “key” to quantify its “priority”. For
example, if the movie cinema decides to serve loyal customers first, it will order them by their
loyalty (points or number of tickets purchased). In such a case, the queue for tickets will no longer
be first-come-first-served, but most-loyal- first-served. The customers will be the “items” of this
priority queue while the “priority” or “key” will be their loyalty.
52
A priority queue can be of two types:
1. Max Priority Queue: Which arranges the data as per descending order of their priority.
2. Min Priority Queue: Which arranges the data as per ascending order of their priority.
53
# add the new node self.queue.append(node)
else:
# traverse the queue to find the right place for new node
for x in range(0, self.size()):
# if the priority of new node is greater
if node.priority >= self.queue[x].priority:
# if we have traversed the complete
queueif x == (self.size()-1):
# add new node at the end
self.queue.insert(x+1, node)
else:
continue
else:
self.queue.insert(x, node)
return True
def delete(self):
# remove the first node from the queue
return self.queue.pop(0)
def show(self):
for x in self.queue:
print str(x.info)+" - "+str(x.priority)
def size(self):
return len(self.queue)
pQueue = PriorityQueue()
node1 = Node("C", 3)
node2 = Node("B", 2)
node3 = Node("A", 1)
node4 = Node("Z", 26)
node5 = Node("Y", 25)
node6 = Node("L", 12)
pQueue.insert(node1)
pQueue.insert(node2)
pQueue.insert(node3)
pQueue.insert(node4)
pQueue.insert(node5)
pQueue.insert(node6)
pQueue.show()
print(" -------------- ")
pQueue.delete()
pQueue.show()
54
Cyber security
UNIT-3
Unit includes:
• Linear search
• Binary search
• Merge sort
• Bubble sort
• Selection sort
• Insertion sort
• Quick sort
Linear search:
Linear search is a method of finding elements within a list. It is also called a sequential
search. It is the simplest searching algorithm because it searches the desired element in a
sequential manner.
It compares each and every element with the value that we are searching for. If both are
matched, the element is found, and the algorithm returns the key's index position.
Let's understand the following steps to find the element key = 7 in the given list.
Step - 1: Start the search from the first element and Check key = 7 with each element
of list x.
LinearSearch(list, key)
for each item in the list
if item == value
return its index position
return -1
EXAMPLE:
OUTPUT:
“Enter numbers,separated by whitespace: 10 5 67 56 78 45
“Enter searching element: 67
“Element Found”
Binary search:
A binary search is an algorithm to find a particular element in the list. Suppose we have
a list of thousand elements, and we need to get an index position of a particular
element. We can find the element's index position very fast using the binary search
algorithm.
The elements in the list must be sorted to apply the binary search algorithm. If
elements are not sorted then sort them first.
In the binary search algorithm, we can find the element position using the following
methods.
o Recursive Method
o Iterative Method
The divide and conquer approach technique is followed by the recursive method. In
this method, a function is called itself again and again until it found an element in the
list.
Binary search is more effective than the linear search because we don't need to search
each list index. The list must be sorted to achieve the binary search algorithm.
So, we are setting two pointers in our list. One pointer is used to denote the smaller
value called low and the second pointer is used to denote the highest value
called high.
1. mid = (low+high)/2
2. Here, the low is 0 and the high is 7.
3. mid = (0+7)/2
4. mid = 3 (Integer)
Now, we will compare the searched element to the mid index value. In this case, 32 is
not equal to 45. So we need to do further comparison to find the element.
If the number we are searching equal to the mid. Then return mid otherwise move to
the further comparison.
The number to be search is greater than the middle number, we compare the n with
the middle element of the elements on the right side of mid and set low to
low = mid + 1.
Otherwise, compare the n with the middle element of the elements on the left side
of mid and set high to high = mid - 1.
Output:
Enter Numbers,separated by whitespace: 5 15 20 25 30 35
Enter Searching Element:22
Element not found.
Merge Sort:
Merge sort algorithm works on the concept of divide and conquer. It divides the given
list in the two halves, calls itself for the two halves and then merges the two sorted
halves. We define the merge() function used to merging two halves.
The sub lists are divided again and again into halves until we get the only one element
each. Then we combine the pair of one element lists into two element lists, sorting
them in the process. The sorted two element pairs is merged into the four element
lists, and so on until we get the sorted list.
Example:
Output:
Enter Numbers,separated by whitespace: 35 67 32 13 78
Before sorting: 35 67 32 13 78
After sorting: 13 32 35 67 78
Quick Sort:
Quicksort is the widely used sorting algorithm that makes n log n comparisons in
average case for sorting an array of n elements. It is a faster and highly efficient sorting
algorithm. This algorithm follows the divide and conquer approach.
Divide and conquer is a technique of breaking down the algorithms into subproblems,
then solving the subproblems, and combining the results back together to solve the
original problem.
Quicksort picks an element as pivot, and then it partitions the given array around the
picked pivot element. In quick sort, a large array is divided into two arrays in which
one holds values that are smaller than the specified value (Pivot), and another array
holds the values that are greater than the pivot.
After that, left and right sub-arrays are also partitioned using the same approach. It
will continue until the single element remains in the sub-array.
Choosing the pivot:
Picking a good pivot is necessary for the fast implementation of quicksort. However, it
is typical to determine a good pivot. Some of the ways of choosing a pivot are as
follows -
o Pivot can be random, i.e. select the random pivot from the given array.
o Pivot can either be the rightmost element of the leftmost element of the given array.
o Select median as the pivot element.
Example:
Out put:
Enter Numbers separated by whitespace: 45 56 3 51 14 44
Unsorted Array
[45,56,3,51,14,44]
Sorted Array in Ascending Order: [3,14,44,45,51,56]
Bubble sort:
Bubble sort is a sorting algorithm that compares two adjacent elements and swaps them
until they are in the intended order. Just like the movement of air bubbles in the water
that rise up to the surface, each element of the array move to the end in each iteration.
Therefore, it is called a bubble sort.
Bubble short is majorly used where :
o complexity does not matter
o simple and shortcode is preferred.
Example:
Output:
Selection Sort:
In this algorithm, we select the smallest element from an unsorted array in each pass and swap
with the beginning of the unsorted array. This process will continue until all the elements are
placed at right place. It is simple and an in-place comparison sorting algorithm.
The following are the steps to explain the working of the Selection sort in Python.
length = len(array) → 6
Step - 2: First, we set the first element as minimum element.
Step - 3: Now compare the minimum with the second element. If the second element is
smaller than the first, we assign it as a minimum.
Again we compare the second element to the third and if the third element is smaller than
second, assign it as minimum. This process goes on until we find the last element.
Step - 4: After each iteration, minimum element is swapped in front of the unsorted array.
Step - 5: The second to third steps are repeated until we get the sorted array.
Example:
Output:
Insertion sort:
Insertion sort is a simple sorting algorithm that builds the final sorted array (or list) one item
at a time by comparisons .
To sort the array using insertion sort below is the algorithm of insertion sort.
Example:
UNIT-4
Trees:
The tree is another data structure that allows the data to reside in a
hierarchical position. Each tree has a node, and each node has a leaf also called
a child.
• Root → The topmost node of the hierarchy is called the root of the tree.
• Child → Nodes next in the hierarchy are the children of the previous node.
• Parent → The node just previous to the current node is the parent of the
current node.
• Siblings → Nodes with the same parent are called siblings.
. • Internal Nodes → Nodes with at least one child are internal nodes.
• External Nodes/Leaves → Nodes which don't have any child are called leaves
of a tree.
• Edge → The link between two nodes is called an edge.
• Level → The root of a tree is at level 0 and the nodes whose parent is root are
at level 1 and so on.
→ Node Degree: It is the maximum number of children a node has.
o Tree Degree → Tree degree is the maximum of the node degrees. So, the
tree degree in the abovepicture is 3.
Height → The height of a node is the number of nodes (excluding the node) on
the longest pathfrom the node to a leaf
The full binary tree is also known as a strict binary tree. The tree can only
be considered as the full binary tree if each node must contain either 0
or 2 children. The full binary tree can also be defined as the tree in which
each node must contain 2 children except the leaf nodes.
o The number of leaf nodes is equal to the number of internal nodes plus 1. In
the above example, the number of internal nodes is 5; therefore, the number of
leaf nodes is equal to 6.
o The maximum number of nodes is the same as the number of nodes in the
binary tree, i.e., 2h+1 -1.
o The minimum number of nodes in the full binary tree is 2*h-1.
o The minimum height of the full binary tree is log2(n+1) - 1.
o The maximum height of the full binary tree can be computed as:
n= 2*h - 1
n+1 = 2*h
h = n+1/2
The complete binary tree is a tree in which all the nodes are completely filled except
the last level. In the last level, all the nodes must be as left as possible. In a complete
binary tree, the nodes should be added from the left.
The above tree is a complete binary tree because all the nodes are completely filled,
and all the nodes in the last level are added at the left first.
A tree is a perfect binary tree if all the internal nodes have 2 children, and all the leaf
nodes are at the same level.
All the nodes except one node has one and only one child.
AVL Tree can be defined as height balanced binary search tree in which each node is
associated with a balance factor which is calculated by subtracting the height of its
right sub-tree from that of its left sub-tree.
If balance factor of any node is 0, it means that the left sub-tree and right sub-tree
contain equal height.
If balance factor of any node is -1, it means that the left sub-tree is one level lower
than the right sub-tree.
An AVL tree is given in the following figure. We can see that, balance factor associated
with each node is in between -1 and +1. therefore, it is an example of AVL tree.
RR Rotation:
LL Rotation
LR Rotation
State Action
RL Rotation
State Action
Preorder:
Postorder traversal:
This technique follows the 'left-right root' policy.
Inorder traversal
This technique follows the 'left root right' policy.
o Searching an element in the Binary search tree is easy as we always have a hint
that which subtree has the desired element.
o As compared to array and linked lists, insertion and deletion operations are
faster in BST.
GRAPHS
Graphs-Introduction:
Components of a Graph:
Edges: Edges are drawn or used to connect two nodes of the graph. It can
be ordered pair of nodes in a directed graph. Edges can connect any two
nodes in any possible way. There are no rules. Sometimes, edges are also
known as arcs. Every edge can be labeled/unlabelled.
In the figure below, we have a simple graph where there are five nodes in total
and seven edges.
The nodes in any graph can be referred to as entities and the edges that connect
different nodes define the relationships between these entities. In the above
graph we have a set of nodes {V} = {A, B, C, D,E} and a set of edges, {E} = {A-B, A-
D, B-C, B-D, C-D, D-E} respectively.
Path - A sequence of alternating nodes and edges such that each of the
successive nodes areconnected by the edge.
Cycle - A path where the starting and the ending node is the same.
Bridge - An edge whose removal will simply make the graph disconnected.
Degree - The degree in a graph is the number of edges that are incident on a
particular node.
Neighbour - We say vertex "A" and "B" are neighbours if there exists an edge
between them.
Self-loop - An edge is called a self-loop if its two endpoints coincide with each
other.
1 . Null Graphs :
2 . Undirected Graphs :
4. Cyclic Graphs :
A graph that contains at least one node that traverses back to itself is known as a
cyclic graph. In simple words, a graph should have at least one cycle formation for
it to be called a cyclic graph.
A graph where there's no way we can start from one node and can traverse back
to the same one, or simply doesn't have a single cycle is known as an acyclic
graph.
6 . Weighted Graphs :
When the edge in a graph has some weight associated with it, we call that graph
as a weighted graph. The weight is generally a number that could mean anything,
totally dependent on the relationship between the nodes of that graph.
An unweighted graph does not have a value associated with every edge.
8 . Connected Graphs :
A graph where we have a path between every two nodes of the graph is known as
a connected graph. A path here means that we are able to traverse from a node
"A" to say any node "B". In simple terms, we can say that if we start from one
node of the graph we will always be able to traverse to all the other nodes of the
graph from that node, hence the connectivity.
10 . Complete Graphs :
A simple graph with n vertices is called a complete graph if the degree of each
vertex is n-1, that is, one vertex is attached with n-1 edges or the rest of the
vertices in the graph. A complete graph is also called Full Graph.
A graph is said to be a multigraph if there exist two or more than two edges
between any pair of nodes in the graph.
Any graph which contains some parallel edges but doesn’t contain any self-loop is
called a multigraph.
This algorithm selects a single node (initial or source point) in a graph and then
visits all the nodes adjacent to the selected node. Once the algorithm visits and
marks the starting node, then it moves towards the nearest unvisited nodes and
analyses them.
Once visited, all nodes are marked. These iterations continue until all the nodes of
the graph have been successfully visited and marked. The full form of BFS is the
Breadth-first search.
Steps in Breadth First Search:
1. Start by putting any one of the graph’s vertices at the back of a queue.
2. Take the front item of the queue and add it to the visited list.
3. Create a list of that vertex’s adjacent nodes. Add the ones which aren’t in the
visited list to the back of the queue.
8. Remaining 0 adjacent and unvisited nodes are visited, marked, and inserted
into the queue.
Example:
Step 3: Remove node 0 from the front of queue and visit the unvisited neighbours
and push them into queue.
Remove node 0 from the front of queue and visited the unvisited neighbours and push into queue.
Step 4: Remove node 1 from the front of queue and visit the unvisited neighbours
and push them into queue.
Remove node 1 from the front of queue and visited the unvisited neighbours and push
Step 5: Remove node 2 from the front of queue and visit the unvisited neighbours
and push them into queue.
Remove node 2 from the front of queue and visit the unvisited neighbours and push them into queue.
Step 6: Remove node 3 from the front of queue and visit the unvisited
neighbours and push them into queue.
As we can see that every neighbours of node 3 is visited, so move to the next
node that are in the front of the queue.
Remove node 3 from the front of queue and visit the unvisited neighbours and push them into queue.
Steps 7: Remove node 4 from the front of queue and and visit the unvisited
neighbours and push them into queue.
As we can see that every neighbours of node 4 are visited, so move to the next
node that is in the front of the queue.
Remove node 4 from the front of queue and and visit the unvisited neighbours and push them into
queue.
class Graph:
def __init__(self,gdict=None):
if gdict is None:
gdict={}
self.gdict=gdict
def addEdge(self,vertex,edge):
self.gdict[vertex].append(edge)
def bfs(self,vertex):
visited=[vertex]
queue=[vertex]
while queue:
devertex=queue.pop(0)
print(devertex)
visited.append(adjacentvertex)
queue.append(adjacentvertex)
customdict={"a":["b","c"],
"b":["a","d","g"],
"c":["a","d","e"],
"d":["b","c","f"],
"e":["c","f"],
"f":["d","e","g"],
"g":["b","f"]}
graph=Graph(customdict)
graph.addEdge("a","f")
graph.bfs("a")
OUTPUT:
Breadth First Search
e
Depth First Search (DFS) :
2. Take the top item of the stack and add it to the visited list.
3. Create a list of that vertex’s adjacent nodes. Add the ones which aren’t in the
visited
Example of DFS :
We have started from vertex 0. The algorithm begins by putting it in the visited
list and simultaneously putting all its adjacent vertices in the data structure called
stack.
Step 2)
You will visit the element, which is at the top of the stack, for example, 1 and go
to its adjacent nodes. It is because 0 has already been visited. Therefore, we visit
vertex 2.
Step 3)
Vertex 2 has an unvisited nearby vertex in 4. Therefore, we add that in the stack
and visit it.
Step 4)
Finally, we will visit the last vertex 3, it doesn't have any unvisited adjoining
nodes. We have completed the traversal of the graph using DFS algorithm.
CODE:
class Graph:
def __init__(self,gdict=None):
if gdict is None:
gdict={}
self.gdict=gdict
def addEdge(self,vertex,edge):
self.gdict[vertex].append(edge)
def dfs(self,vertex):
visited=[vertex]
stack=[vertex]
while stack:
popvertex=stack.pop()
print(popvertex)
stack.append(adjacentvertex)
customdict={"a":["b","c"],
"b":["a","d","g"],
"c":["a","d","e"],
"d":["b","c","f"],
"e":["c","f"],
"f":["d","e","g"],
"g":["b","f"]}
graph=Graph(customdict)
graph.addEdge("a","f")
print(graph.gdict)
graph.dfs("a")
OUTPUT:
{'a': ['b', 'c', 'f'], 'b': ['a', 'd', 'g'], 'c': ['a', 'd', 'e'], 'd': ['b', 'c', 'f'], 'e': ['c', 'f'], 'f': ['d',
'e', 'g'], 'g': ['b', 'f']}
e
d
Example:
Input :
0 - - - > 1 - - - -> 4
| / \ ^
| / \ |
| / \ |
| / \ |
| / \ |
| / \ |
v v v |
2 - - - - - - - - -> 3
Topological Sorting :
class Graph:
def __init__(self,vertices):
def addEdge(self,u,v):
self.graph[u].append(v)
def topologicalSortUtil(self,v,visited,stack):
visited[v] = True
for i in self.graph[v]:
if visited[i] == False:
self.topologicalSortUtil(i,visited,stack)
stack.insert(0,v)
# topologicalSortUtil()
def topologicalSort(self):
visited = [False]*self.V
stack =[]
for i in range(self.V):
if visited[i] == False:
self.topologicalSortUtil(i,visited,stack)
print (stack)
g= Graph(6)
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
g.topologicalSort()
Output:
[5, 4, 2, 3, 1, 0]
Dijkstra's Algorithm
Here we assume that 0 as a source vertex, and distance to all the other vertices is
infinity. Initially, we do not know the distances. First, we will find out the vertices
which are directly connected to the vertex 0. As we can observe in the above
graph that two vertices are directly connected to vertex 0.
Let's assume that the vertex 0 is represented by 'x' and the vertex 1 is
represented by 'y'. The distance between the vertices can be calculated by using
the below formula:
class Graph:
def __init__(self):
self.nodes = set()
self.edges = defaultdict(list)
self.distances = {}
def addNode(self,value):
self.nodes.add(value)
self.edges[fromNode].append(toNode)
visited = {initial : 0}
path = defaultdict(list)
nodes = set(graph.nodes)
while nodes:
minNode = None
if node in visited:
if minNode is None:
minNode = node
minNode = node
if minNode is None:
break
nodes.remove(minNode)
currentWeight = visited[minNode]
visited[edge] = weight
path[edge].append(minNode)
customGraph.addNode("A")
customGraph.addNode("B")
customGraph.addNode("C")
customGraph.addNode("D")
customGraph.addNode("E")
customGraph.addNode("F")
customGraph.addNode("G")
customGraph.addEdge("A", "B", 2)
customGraph.addEdge("A", "C", 5)
customGraph.addEdge("B", "C", 6)
customGraph.addEdge("B", "D", 1)
customGraph.addEdge("B", "E", 3)
customGraph.addEdge("C", "F", 8)
customGraph.addEdge("D", "E", 4)
customGraph.addEdge("E", "G", 9)
customGraph.addEdge("F", "G", 7)
print(dijkstra(customGraph, "A"))
self.V = vertices
self.graph = []
self.nodes = []
self.graph.append([s, d, w])
def addNode(self,value):
self.nodes.append(value)
dist[src] = 0
for _ in range(self.V-1):
for s, d, w in self.graph:
dist[d] = dist[s] + w
for s, d, w in self.graph:
return
self.print_solution(dist)
g = Graph(5)
g.addNode("A")
g.addNode("B")
g.addNode("C")
g.addNode("D")
g.addNode("E")
g.add_edge("A", "C", 6)
g.add_edge("A", "D", 6)
g.add_edge("B", "A", 3)
g.add_edge("C", "D", 1)
g.add_edge("D", "C", 2)
g.add_edge("D", "B", 1)
g.add_edge("E", "B", 4)
g.add_edge("E", "D", 2)
g.bellmanFord("E")
OUTPUT:
Vertex Distance from Source
A : 6
B : 3
C : 4
D : 2
E : 0
The Floyd Warshall Algorithm is for solving all pairs of shortest-path problems.
The problem is to find the shortest distances between every pair of vertices in a
given edge-weighted directed Graph.
Initialize the solution matrix same as the input graph matrix as a first step.
The idea is to one by one pick all vertices and updates all shortest paths
which include the picked vertex as an intermediate vertex in the shortest
path.
For every pair (i, j) of the source and destination vertices respectively, there
are two possible cases.
printSolution(nV, distance)
G = [[0, 8, INF,1],
[INF, 0, 1,INF],
[4, INF, 0,INF],
[INF, 2, 9,1]
]
floydWarshall(4, G)
OUTPUT:
0 3 4 1
5 0 1 6
4 7 0 5
7 2 3 1
Spanning Tree
A Spanning tree is a subset to a connected graph G, where all the edges are
connected, i.e, one can traverse to any edge from a particular edge with or
without intermediates. Also, a spanning tree must not have any cycle in it. Thus
we can say that if there are N vertices in a connected graph then the no. of edges
that a spanning tree may have is N-1.
1. Kruskal’s Algorithm
2. Prim’s Algorithm
Below are the steps for finding MST using Kruskal’s algorithm:
2. Pick the smallest edge. Check if it forms a cycle with the spanning tree
formed so far. If the cycle is not formed, include this edge. Else, discard it.
3. Repeat step#2 until there are (V-1) edges in the spanning tree.
Prim's Algorithm
Prim's Algorithm is a greedy algorithm that is used to find the minimum spanning
tree from a graph. Prim's algorithm finds the subset of edges that includes every
vertex of the graph such that the sum of the weights of the edges can be
minimized.
Prim's algorithm starts with the single node and explores all the adjacent nodes
with all the connecting edges at every step. The edges with the minimal weights
causing no cycles in the graph got selected.
o Now, we have to find all the edges that connect the tree in the above step
with the new vertices. From the edges found, select the minimum edge and
add it to the tree.
import sys
class Graph:
self.edges = edges
self.nodes = nodes
self.vertexNum = vertexNum
self.MST = []
def printSolution(self):
print("Edge : Weight")
for s, d, w in self.MST:
def primsAlgo(self):
visited = [0]*self.vertexNum
edgeNum=0
visited[0]=True
while edgeNum<self.vertexNum-1:
min = sys.maxsize
for i in range(self.vertexNum):
if visited[i]:
for j in range(self.vertexNum):
min = self.edges[i][j]
s=i
d=j
edgeNum += 1
self.printSolution()
[0, 0, 6, 8, 0]]
nodes = ["A","B","C","D","E"]
g.primsAlgo()
OUTPUT:
Edge : Weight
A -> B: 10
B -> D: 5
D -> E: 8
E -> C: 6