0% found this document useful (0 votes)
11 views37 pages

Final - Lab Practical

The document is a laboratory manual for advanced data structures, specifically focusing on Binary Search Trees (BST) and AVL Trees. It includes various programs for insertion, searching, and deletion operations in these data structures, along with algorithms and implementations in Python. The manual is structured into practical sections with objectives, algorithms, and example outputs for each operation.

Uploaded by

productionsankit
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views37 pages

Final - Lab Practical

The document is a laboratory manual for advanced data structures, specifically focusing on Binary Search Trees (BST) and AVL Trees. It includes various programs for insertion, searching, and deletion operations in these data structures, along with algorithms and implementations in Python. The manual is structured into practical sections with objectives, algorithms, and example outputs for each operation.

Uploaded by

productionsankit
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 37

LABORATORY MANUAL

ADVANCED DATA STRUCTURE

Govind Ballabh Pant University of


Agriculture and Technology, Pantnagar
Department of Computer Engineering

SUBMITTED TO : Dr. Sunita Jalal

SUBMITTED BY : Akanksha Joshi

ID NO: 62626
INDEX

S. NAME OF PROGRAM PAGE DATE


NO. NO.
1. Program for Insertion in Binary Search Tree (BST). 01-04 14/09/24
2. Program for Searching in a Binary Search Tree (BST). 05-06 20/09/24
3. Program for Deletion in a Binary Search Tree (BST). 07-08 25/09/24
4. Program for Insertion in an AVL Tree. 09-12 05/10/24
5. Program for Searching in an AVL Tree. 13-15 11/10/24
6. Program for Deletion in an AVL Tree. 16-19 16/10/24
7. Program for Insertion in a Skip List. 20-22 22/10/24
8. Program for Searching in a Skip List. 23-25 01/11/24
9. Program for Deletion in a Skip List. 26-28 05/11/24
10. Program for Naïve String Matching Algorithm. 29 11/11/24
11. Program for Rabin-Karp String Matching Algorithm. 30-31 21/11/24
12. Program for Hashing using Open Addressing. 32-34 28/11/24
LAB PRACTICAL-1
Objective: Program for Insertion in Binary Search Tree.
Binary Search Tree is a node-based binary tree data structure which has the following
properties:
 The left subtree of a node contains only nodes with keys lesser than the node’s
key.
 The right subtree of a node contains only nodes with keys greater than the node’s
key.
 The left and right subtree each must also be a binary search tree.

Insertion in Binary Search Tree (BST):-A new key is always inserted at the leaf by
maintaining the property of the binary search tree. We start searching for a key from the root
until we hit a leaf node. Once a leaf node is found, the new node is added as a child of the
leaf node. The below steps are followed while we try to insert a node into a binary search
tree:
Check the value to be inserted (X) with the value of the current node (Y) we are at
 If X is less than val move to the left subtree.
 Otherwise, move to the right subtree.
 Once the leaf node is reached, insert X to its right or left based on the relation
between X and the leaf node’s value.
ALGORITHM

Step 1: IF TREE = NULL


Allocate memory for TREE
SET TREE -> DATA = ITEM
SET TREE -> LEFT = TREE -> RIGHT = NULL
ELSE
IF ITEM < TREE -> DATA
Insert(TREE -> LEFT, ITEM)
ELSE
Insert(TREE -> RIGHT, ITEM)
[END OF IF]
[END OF IF]
Step 2: END

Inorder Traversal
The inorder traversal operation in a Binary Search Tree visits all its nodes in the following
order −
 Firstly, we traverse the left child of the root node/current node, if any.
 Next, traverse the current node.
 Lastly, traverse the right child of the current node, if any.
Algorithm:
1. START
2. Traverse the left subtree, recursively
3. Then, traverse the root node
4. Traverse the right subtree, recursively.
5. END
Preorder Traversal
The preorder traversal operation in a Binary Search Tree visits all its nodes. However, the
root node in it is first printed, followed by its left subtree and then its right subtree.

Algorithm:
1. START
2. Traverse the root node first.
3. Then traverse the left subtree, recursively
4. Later, traverse the right subtree, recursively.
5. END
Postorder Traversal
Like the other traversals, postorder traversal also visits all the nodes in a Binary Search Tree
and displays them. However, the left subtree is printed first, followed by the right subtree and
lastly, the root node.
Algorithm
1. START
2. Traverse the left subtree, recursively
3. Traverse the right subtree, recursively.
4. Then, traverse the root node
5. END
IMPLEMENTATION class Node:
def __init__(self, key):
self.left = None
self.right = None
self.key = key
def insert(root, key):
temp = Node(key)
if root is None:
return temp
parent = None
curr = root
while curr is not None:
parent = curr
if curr.key > key:
curr = curr.left
elif curr.key < key:
curr = curr.right
else:
return root
if parent.key > key:
parent.left = temp
else:
parent.right = temp
return root
def inorder(root):
if root:
inorder(root.left)
print(root.key, end=" ")
inorder(root.right)
def preorder(root):
if root:
print(root.key, end=" ")
preorder(root.left)
preorder(root.right)
def postorder(root):
if root:
postorder(root.left)
postorder(root.right)
print(root.key, end=" ")
r = Node(50)
r = insert(r, 10)
r = insert(r, 20)
r = insert(r, 30)
r = insert(r, 80)
r = insert(r, 60)
r = insert(r, 40)
print("Inorder traversal (Left, Root, Right):")
inorder(r)
print("\n\nPreorder traversal (Root, Left, Right):")
preorder(r)
print("\n\nPostorder traversal (Left, Right, Root):")
postorder(r)
OUTPUT

Inorder traversal (Left, Root, Right):


10 20 30 40 50 60 80

Preorder traversal (Root, Left, Right):


50 10 20 30 40 80 60

Postorder traversal (Left, Right, Root):


40 30 20 10 60 80 50
LAB PRACTICAL-2
Objective: Program for Searching in Binary Search Tree.
Searching means finding or locating some specific element or node within a data structure.
However, searching for some specific node in binary search tree is pretty easy due to the fact
that, element in BST are stored in a particular order.
1. Compare the element with the root of the tree.
2. If the item is matched then return the location of the node.
3. Otherwise check if item is less than the element present on root, if so then move to the
left sub-tree.
4. If not, then move to the right sub-tree.
5. Repeat this procedure recursively until match found.
6. If element is not found then return NULL.
Algorithm:
Search (ROOT, ITEM)
Step 1: IF ROOT -> DATA = ITEM OR ROOT = NULL
Return ROOT
ELSE
IF ITEM < ROOT -> DATA
Return search(ROOT -> LEFT, ITEM)
ELSE
Return search(ROOT -> RIGHT,ITEM)
[END OF IF]
[END OF IF]

Step 2: END
IMPLENENTATION

class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def search(root, key):
if root is None:
return False
if root.key == key:
return True
if root.key < key:
return search(root.right, key)
return search(root.left, key)
root = Node(50)
root.left = Node(30)
root.right = Node(70)
root.left.left = Node(20)
root.left.right = Node(40)
root.right.left = Node(60)
root.right.right = Node(80)
print(" Key 29 Found" if search(root, 29) else "Key 29 Not Found")
print("Key 70 Found" if search(root, 80) else " Key 70 not Found")
OUTPUT

Key 29 Not Found

Key 70 Found
LAB PRACTICAL-3
Objective: Program for Deletion in Binary Search Tree
Delete function is used to delete the specified node from a binary search tree. However, we
must delete a node from a binary search tree in such a way, that the property of binary search
tree doesn't violate. There are three situations of deleting a node from binary search tree.
1. The node to be deleted is a leaf node: It is the simplest case, in this case, replace the leaf
node with the NULL and simple free the allocated space.
2. The node to be deleted has only one child: In this case, replace the node with its child
and delete the child node, which now contains the value which is to be deleted. Simply
replace it with the NULL and free the allocated space.
3. The node to be deleted has two children: It is a bit complexed case compare to other two
cases. However, the node which is to be deleted, is replaced with its in-order successor or
predecessor recursively until the node value (to be deleted) is placed on the leaf of the tree.
After the procedure, replace the node with NULL and free the allocated space.
ALGORITHM
Delete (TREE, ITEM)

Step 1: IF TREE = NULL


Write "item not found in the tree"
ELSE IF ITEM < TREE -> DATA
Delete(TREE->LEFT, ITEM)
ELSE IF ITEM > TREE -> DATA
Delete(TREE -> RIGHT, ITEM)
ELSE IF TREE -> LEFT AND TREE -> RIGHT
SET TEMP = findLargestNode(TREE -> LEFT)
SET TREE -> DATA = TEMP -> DATA
Delete(TREE -> LEFT, TEMP -> DATA)
ELSE
SET TEMP = TREE
IF TREE -> LEFT = NULL AND TREE -> RIGHT = NULL
SET TREE = NULL
ELSE IF TREE -> LEFT != NULL
SET TREE = TREE -> LEFT
ELSE
SET TREE = TREE -> RIGHT
[END OF IF]
FREE TEMP
[END OF IF]

Step 2: END
IMPLEMENTATION

class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def get_successor(curr):
curr = curr.right
while curr is not None and curr.left is not None:
curr = curr.left
return curr
def del_node(root, x):
if root is None:
return root
if root.key > x:
root.left = del_node(root.left, x)
elif root.key < x:
root.right = del_node(root.right, x)
else:
if root.left is None:
return root.right
if root.right is None:
return root.left
succ = get_successor(root)
root.key = succ.key
root.right = del_node(root.right, succ.key)
return root
def inorder(root):
if root is not None:
inorder(root.left)
print(root.key, end=" ")
inorder(root.right)
if __name__ == "__main__":
root = Node(10)
root.left = Node(5)
root.right = Node(15)
root.right.left = Node(12)
root.right.right = Node(18)
print("Initial BST (Inorder Traversal):")
inorder(root)
print()
x = 15
root = del_node(root, x)
print(f"BST after deleting {x} (Inorder Traversal):")
inorder(root)
print()

OUTPUT:

Initial BST (Inorder Traversal):


5 10 12 15 18
BST after deleting 15 (Inorder Traversal):
5 10 12 18
LAB PRACTICAL-4
Objective: Program for Insertion in AVL Tree.
Theory:-The AVL TREE is a height-balanced binary search tree which means it is also a
binary tree that is balanced by the left and right subtree of a node. The tree is said to be
balanced when the balance factor of each node is either -1, 0, or +1.
The Balance factor of a tree can be defined as:
Balanced Factor(X) = height(left Subtree (X)) – height(right Subtree(X))
Example of AVL Tree:-

INSERTION IN AVL TREE

 Insert as in a Binary Search Tree (BST):


Insert the new node in the correct position based on BST properties.

 Update Heights:
After the insertion, update the height of the ancestor nodes.

 Calculate Balance Factor:


For each node, compute the balance factor. A balance factor of −1, 0 or 1 is considered
balanced.

 Check for Imbalance:


If the balance factor is outside the range [−1,1], the tree is unbalanced, and rotations are
needed to restore balance.

Types of Rotations (Cases)

Case 1: Left-Left (LL) Rotation

Occurs when a node is inserted into the left subtree of the left child of the unbalanced node.
Perform a Right Rotation on the unbalanced node.

Steps for Right Rotation:


1. The left child becomes the new root.
2. The original root becomes the right child of the new root.
3. The right subtree of the new root becomes the left subtree of the original root.

Case 2: Right-Right (RR) Rotation

Occurs when a node is inserted into the right subtree of the right child of the unbalanced
node. Perform a Left Rotation on the unbalanced node.

Steps for Left Rotation:

1. The right child becomes the new root.


2. The original root becomes the left child of the new root.
3. The left subtree of the new root becomes the right subtree of the original root.

Case 3: Left-Right (LR) Rotation

Occurs when a node is inserted into the right subtree of the left child of the unbalanced
node. Perform a Left Rotation on the left child, followed by a Right Rotation on the
unbalanced node.

Steps:

1. Perform a Left Rotation on the left child of the unbalanced node to convert it into a
Left-Left case.
2. Perform a Right Rotation on the unbalanced node.

Case 4: Right-Left (RL) Rotation

Occurs when a node is inserted into the left subtree of the right child of the unbalanced node.
Perform a Right Rotation on the right child, followed by a Left Rotation on the unbalanced node.

Steps:

1. Perform a Right Rotation on the right child of the unbalanced node to convert it into
a Right-Right case.
2. Perform a Left Rotation on the unbalanced node.

IMPLEMENTATION
class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
def height(node):
if not node:
return 0
return node.height
# Function to calculate the balance factor of a node
def get_balance(node):
if not node:
return 0
return height(node.left) - height(node.right)
# Function for right rotation
def right_rotate(y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
y.height = 1 + max(height(y.left), height(y.right))
x.height = 1 + max(height(x.left), height(x.right))
return x
# Function for left rotation
def left_rotate(x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
x.height = 1 + max(height(x.left), height(x.right))
y.height = 1 + max(height(y.left), height(y.right))
return y
# Function to insert a node into the AVL Tree
def insert(node, key):
# Perform normal BST insertion
if not node:
return AVLNode(key)
if key < node.key:
node.left = insert(node.left, key)
elif key > node.key:
node.right = insert(node.right, key)
else: # Duplicate keys are not allowed
return node
# Update the height of the ancestor node
node.height = 1 + max(height(node.left), height(node.right))
# Get the balance factor
balance = get_balance(node)
# Perform rotations to balance the tree
# Case 1: Left-Left (LL)
if balance > 1 and key < node.left.key:
return right_rotate(node)
# Case 2: Right-Right (RR)
if balance < -1 and key > node.right.key:
return left_rotate(node)
# Case 3: Left-Right (LR)
if balance > 1 and key > node.left.key:
node.left = left_rotate(node.left)
return right_rotate(node)
# Case 4: Right-Left (RL)
if balance < -1 and key < node.right.key:
node.right = right_rotate(node.right)
return left_rotate(node)
return node
# Function to perform inorder traversal
def inorder(node):
if node:
inorder(node.left)
print(node.key, end=" ")
inorder(node.right)
# Main Program
if __name__ == "__main__":
# Create an empty AVL Tree
root = None
# Harcoded set of values for the AVL Tree
values = [20, 10, 30, 25, 40, 22, 50]
# Insert each value into the AVL Tree
for value in values:
root = insert(root, value)
# Display the AVL Tree using inorder traversal
print("Inorder traversal of the AVL Tree:")
inorder(root)
print()

OUTPUT

Inorder traversal of the AVL Tree: 10 20 22 25 30 40 50


LAB PRACTICAL-5
Objective: Program for Searching in AVL Tree
Searching in an AVL Tree follows the same process as searching in a Binary Search Tree
(BST), with the added benefit that the AVL Tree is balanced. This balance ensures that the
height of the tree is minimized, leading to efficient search operations.
The process involves comparing the target value with the current node's value and moving to
the left or right subtree based on the comparison.
ALGORITHM
1. Start at the root of the AVL Tree.
2. Compare the target value with the current node's value:
o If the target matches the current node's value, the search is successful.
o If the target is less than the current node's value, move to the left subtree.
o If the target is greater than the current node's value, move to the right subtree.
3. Repeat the process until the target value is found or a null node is reached (indicating
the value is not present).
IMPLEMENTATION

class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
def height(node):
if not node:
return 0
return node.height
def get_balance(node):
if not node:
return 0
return height(node.left) - height(node.right)
def right_rotate(y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
y.height = 1 + max(height(y.left), height(y.right))
x.height = 1 + max(height(x.left), height(x.right))
return x
def left_rotate(x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
x.height = 1 + max(height(x.left), height(x.right))
y.height = 1 + max(height(y.left), height(y.right))
return y
def insert(node, key):
if not node:
return AVLNode(key)
if key < node.key:
node.left = insert(node.left, key)
elif key > node.key:
node.right = insert(node.right, key)
else: # Duplicate keys are not allowed
return node
node.height = 1 + max(height(node.left), height(node.right))
balance = get_balance(node)
if balance > 1 and key < node.left.key: # Left-Left (LL)
return right_rotate(node)
if balance < -1 and key > node.right.key: # Right-Right (RR)
return left_rotate(node)
if balance > 1 and key > node.left.key: # Left-Right (LR)
node.left = left_rotate(node.left)
return right_rotate(node)
if balance < -1 and key < node.right.key: # Right-Left (RL)
node.right = right_rotate(node.right)
return left_rotate(node)
return node
def search(node, key):
if not node:
return False
if node.key == key:
return True
elif key < node.key:
return search(node.left, key)
else:
return search(node.right, key)
def inorder(node):
if node:
inorder(node.left)
print(node.key, end=" ")
inorder(node.right)
if __name__ == "__main__":
# Create an empty AVL Tree
root = None
values = [20, 10, 30, 25, 40, 22, 50]
for value in values:
root = insert(root, value)
print("Inorder traversal of the AVL Tree:")
inorder(root)
print()
keys_to_search = [25, 15, 50, 60]
for key in keys_to_search:
result = "Found" if search(root, key) else "Not Found"
print(f"Search for {key}: {result}")

OUTPUT

Inorder traversal of the AVL Tree: 10 20 22 25 30 40 50


Search for 25: Found
Search for 15: Not Found
Search for 50: Found
Search for 60: Not Found
LAB PRACTICAL-6
Objective: Program for Deletion in Binary Search Tree
THEORY:
Deletion in an AVL Tree involves removing a node while ensuring the tree remains balanced.
This process includes three steps:
1. Perform the standard BST deletion to remove the node.
2. Update the height of the affected nodes.
3. Rebalance the tree by applying rotations (left, right, left-right, or right-left) based on
the balance factor.
The AVL Tree's self-balancing nature ensures that search, insertion, and deletion operations
remain efficient.
ALGORITHM
1. Locate the node to be deleted using standard BST traversal.
2. Remove the node:
o If it is a leaf node, delete it directly.
o If it has one child, replace it with its child.
o If it has two children, replace it with its in-order successor (smallest node in
the right subtree).
3. Update the height of ancestor nodes.
4. Check the balance factor of each node:
o If the balance factor exceeds the range (-1 to 1), apply appropriate rotations to
rebalance the tree.
IMPLEMENTATION

class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
def height(node):
if not node:
return 0
return node.height
def get_balance(node):
if not node:
return 0
return height(node.left) - height(node.right)
def right_rotate(y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
y.height = 1 + max(height(y.left), height(y.right))
x.height = 1 + max(height(x.left), height(x.right))
return x

def left_rotate(x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
x.height = 1 + max(height(x.left), height(x.right))
y.height = 1 + max(height(y.left), height(y.right))
return y
def get_min_value_node(node):
if node is None or node.left is None:
return node
return get_min_value_node(node.left)
def delete_node(root, key):
if not root:
return root
if key < root.key:
root.left = delete_node(root.left, key)
elif key > root.key:
root.right = delete_node(root.right, key)
else:
if not root.left:
return root.right
elif not root.right:
return root.left
temp = get_min_value_node(root.right)
root.key = temp.key
root.right = delete_node(root.right, temp.key)
root.height = 1 + max(height(root.left), height(root.right))
balance = get_balance(root)
# Left-Left (LL)
if balance > 1 and get_balance(root.left) >= 0:
return right_rotate(root)
# Left-Right (LR)
if balance > 1 and get_balance(root.left) < 0:
root.left = left_rotate(root.left)
return right_rotate(root)
# Right-Right (RR)
if balance < -1 and get_balance(root.right) <= 0:
return left_rotate(root)
# Right-Left (RL)
if balance < -1 and get_balance(root.right) > 0:
root.right = right_rotate(root.right)
return left_rotate(root)
return root
# Function to insert a node into the AVL Tree
def insert(root, key):
if not root:
return AVLNode(key)
if key < root.key:
root.left = insert(root.left, key)
elif key > root.key:
root.right = insert(root.right, key)
else:
return root
root.height = 1 + max(height(root.left), height(root.right))
balance = get_balance(root)
if balance > 1 and key < root.left.key:
return right_rotate(root)
if balance < -1 and key > root.right.key:
return left_rotate(root)
if balance > 1 and key > root.left.key:
root.left = left_rotate(root.left)
return right_rotate(root)
if balance < -1 and key < root.right.key:
root.right = right_rotate(root.right)
return left_rotate(root)
return root
def inorder(root):
if root:
inorder(root.left)
print(root.key, end=" ")
inorder(root.right)
if __name__ == "__main__":
root = None
# Insert nodes into the AVL Tree
values = [20, 10, 30, 5, 15, 25, 35]
for value in values:
root = insert(root, value)
print("Inorder traversal of the AVL Tree before deletion:")
inorder(root)
print()
# Delete a node from the AVL Tree
key_to_delete = 10
root = delete_node(root, key_to_delete)
print(f"Inorder traversal of the AVL Tree after deleting {key_to_delete}:")
inorder(root)
print()
OUTPUT

Inorder traversal of the AVL Tree before deletion: 5 10 15 20 25 30 35

Inorder traversal of the AVL Tree after deleting 10: 5 15 20 25 30 35


LAB PRACTICAL-7
Objective: Program for Insertion in Skip List
A skip list is a probabilistic data structure. The skip list is used to store a sorted list of
elements or data with a linked list. It allows the process of the elements or data to view
efficiently.
The skip list is an extended version of the linked list. It allows the user to search, remove, and
insert the element very quickly. It consists of a base list that includes a set of elements which
maintains the link hierarchy of the subsequent elements.
It is built in two layers: The lowest layer and Top layer.

Working of Skip List


This example has 14 nodes, such that these nodes are divided into two layers, as shown in the
diagram. The lower layer is a common line that links all nodes, and the top layer is an express
line that links only the main nodes, as you can see in the diagram. Suppose we want to find
47 in this example. We will start the search from the first node of the express line and
continue running on the express line until we find a node that is equal a 47 or more than 47.
We see in the example that 47 does not exist in the express line, so we search for a node of
less than 47, which is 40. Now, we go to the normal line with the help of 40, and search the
47.

ALGORITHM
Insertion (L, Key)
local update [0...Max_Level + 1]
a = L → header
for i = L → level down to 0 do.
while a → forward[i] → key forward[i]
update[i] = a
a = a → forward[0]
lvl = random_Level()
if lvl > L → level then
for i = L → level + 1 to lvl do
update[i] = L → header
L → level = lvl

a = makeNode(lvl, Key, value)


for i = 0 to level do
a → forward[i] = update[i] → forward[i]
update[i] → forward[i] = a
IMPLEMENTATION

import random
# Node class for the Skip List
class Node:
def __init__(self, key, level):
self.key = key # The value of the node
self.forward = [None] * (level + 1) # Pointers to next nodes at different levels
# Skip List class
class SkipList:
def __init__(self, max_level, p):
self.max_level = max_level # Maximum level of the Skip List
self.p = p # Probability for random level generation
self.header = self.create_node(None, max_level) # Create a header node
self.level = 0 # Current level of the Skip List
# Create a node with a given key and level
def create_node(self, key, level):
return Node(key, level)
# Generate a random level for a new node
def random_level(self):
level = 0
while random.random() < self.p and level < self.max_level:
level += 1
return level
# Insert a key into the Skip List
def insert(self, key):
# Create an update array to keep track of nodes at each level
update = [None] * (self.max_level + 1)
current = self.header
# Traverse the Skip List from top to bottom
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
update[i] = current
print(f"Inserted
# Move to the nextkey
node {key} at level {new_level}")
# Print
current the=Skip List for visualization
current.forward[0]
def# display(self):
If the key does not already exist
print("\nSkip
if current is None List:")
or current.key != key:
for#iGenerate
in range(self.level
a random +level1): for the new node
current = self.header.forward[i]
new_level = self.random_level()
#print(f"Level
If the random {i}: ", end="")
level is greater than the current level of the list
while current:
if new_level > self.level:
print(current.key,
for end="+")1, new_level + 1):
i in range(self.level
current = current.forward[i]
update[i] = self.header
print()
self.level = new_level
# Main #Function
Create a new node with the key and random level
if __name__
new_node== "__main__":
= self.create_node(key, new_level)
max_level = 4 # Maximum
# Adjust pointers for eachlevels in the Skip List
level
p = 0.5for i in range(new_level + 1): generation
# Probability factor for level
skip_listnew_node.forward[i]
= SkipList(max_level, p)
= update[i].forward[i]
# Insertupdate[i].forward[i]
keys into the Skip List = new_node
keys = [3, 6, 7, 9, 12, 19, 17, 26, 21]
for key in keys:
skip_list.insert(key)
# Displaying the Skip List
skip_list.display()
OUTPUT

Inserted key 3 at level 2


Inserted key 6 at level 2
Inserted key 7 at level 0
Inserted key 9 at level 2
Inserted key 12 at level 0
Inserted key 19 at level 1
Inserted key 17 at level 1
Inserted key 26 at level 1
Inserted key 21 at level 1

Skip List:
Level 0: 3 6 7 9 12 17 19 21 26
Level 1: 3 6 9 17 19 21 26
Level 2: 3 6 9

LAB PRACTICAL-8
Objective: Program for Searching in Skip List.
Searching an element is very similar to approach for searching a spot for inserting an element
in Skip list. The basic idea is if –
1. Key of next node is less than search key then we keep on moving forward on the same
level.
2. Key of next node is greater than the key to be inserted then we store the pointer to
current node i at update[i] and move one level down and continue our search.
At the lowest level (0), if the element next to the rightmost element (update[0]) has key equal
to the search key, then we have found key otherwise failure.
ALGORITHM
Search(list, searchKey)
x := list -> header
-- loop invariant: x -> key level downto 0 do
while x -> forward[i] -> key forward[i]
x := x -> forward[0]
if x -> key = searchKey then return x -> value
else return failure

IMPLEMENTATION

import random
# Node class for the Skip List
class Node:
def __init__(self, key, level):
self.key = key # The value of the node
self.forward = [None] * (level + 1) # Pointers to next nodes at different levels
# Skip List class
class SkipList:
def __init__(self, max_level, p):
self.max_level = max_level # Maximum level of the Skip List
def insert(self, key):
update = [None] * (self.max_level + 1)
current = self.header
# Traverse the Skip List to find the position to insert
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
update[i] = current
current = current.forward[0]
if current is None or current.key != key:
new_level = self.random_level()
if new_level > self.level:
for i in range(self.level + 1, new_level + 1):
update[i] = self.header
self.level = new_level
new_node = self.create_node(key, new_level)
for i in range(new_level + 1):
new_node.forward[i] = update[i].forward[i]
update[i].forward[i] = new_node
print(f"Inserted key {key} at level {new_level}")
# Search for a key in the Skip List
def search(self, key):
current = self.header
# Traverse the Skip List from top to bottom
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
# Move to the next node
current = current.forward[0]
# If the current node's key matches the search key
if current and current.key == key:
print(f"Key {key} found in the Skip List")
return True
else:
print(f"Key {key} not found in the Skip List")
return False
# Print the Skip List
def display(self):
print("\nSkip List:")
for i in range(self.level + 1):
current = self.header.forward[i]
print(f"Level {i}: ", end="")
while current:
print(current.key, end=" ")
current = current.forward[i]
print()
if __name__ == "__main__":
max_level = 4 # Maximum levels in the Skip List
p = 0.5 # Probability factor for level generation
skip_list = SkipList(max_level, p)
# Insert keys into the Skip List
keys = [3, 6, 7, 9, 12, 19, 17, 26, 21]
for key in keys:
skip_list.insert(key)
# Display the Skip List
skip_list.display()
# Search for keys in the Skip List
search_keys = [3, 19, 50]
for key in search_keys:
skip_list.search(key)

OUTPUT
PS C:\Users\joshi\Desktop\mtech\ADS Codes> python -u "c:\Users\joshi\Desktop\mtech\ADS
Codes\skiplistsearch.py"
Inserted key 3 at level 0
Inserted key 6 at level 2
Inserted key 7 at level 1
Inserted key 9 at level 1
Inserted key 12 at level 1
Inserted key 19 at level 0
Inserted key 17 at level 0
Inserted key 26 at level 0
Inserted key 21 at level 1
Skip List:
Level 0: 3 6 7 9 12 17 19 21 26
Level 1: 6 7 9 12 21
Level 2: 6

Key 3 found in the Skip List

Key 19 found in the Skip List

Key 50 not found in the Skip List


LAB PRACTICAL-9
Objective: Program for Deletion in Skip List
Deletion in a skip list involves removing a target element while maintaining the structural
properties of the skip list. A skip list is composed of multiple levels, with each level
containing nodes that form a subset of the nodes in the level below. Each node has forward
pointers that link to other nodes at the same level.

ALGORITHM
Deletion (L, Key)
local update [0... Max_Level + 1]
a = L → header
for i = L → level down to 0 do.
while a → forward[i] → key forward[i]
update[i] = a
a = a → forward[0]
if a → key = Key then
for i = 0 to L → level do
if update[i] → forward[i] ? a then break
update[i] → forward[i] = a → forward[i]
free(a)
while L → level > 0 and L → header → forward[L → level] = NIL do
L → level = L → level - 1
IMPLEMENTATION:

import random
# Node class for the Skip List
class Node:
def __init__(self, key, level):
self.key = key # The value of the node
self.forward = [None] * (level + 1) # Pointers to next nodes at different levels
# Skip List class
class SkipList:
def __init__(self, max_level, p):
self.max_level = max_level # Maximum level of the Skip List
self.p = p # Probability factor for level generation
self.header = self.create_node(None, max_level) # Create a header node
self.level = 0 # Current level of the Skip List
# Create a node with a given key and level
def create_node(self, key, level):
return Node(key, level)
# Generate a random level for a new node
def random_level(self):
level = 0
while random.random() < self.p and level < self.max_level:
level += 1
return level
# Insert a key into the Skip List
def insert(self, key):
current = self.header
# Traverse the Skip List to find the position to insert
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
update[i] = current
current = current.forward[0]
if current is None or current.key != key:
new_level = self.random_level()
if new_level > self.level:
for i in range(self.level + 1, new_level + 1):
update[i] = self.header
self.level = new_level
new_node = self.create_node(key, new_level)
for i in range(new_level + 1):
new_node.forward[i] = update[i].forward[i]
update[i].forward[i] = new_node
print(f"Inserted key {key} at level {new_level}")
# Search for a key in the Skip List
def search(self, key):
current = self.header
# Traverse the Skip List from top to bottom
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
current = current.forward[0]
# If the current node's key matches the search key
if current and current.key == key:
print(f"Key {key} found in the Skip List")
return True
else:
print(f"Key {key} not found in the Skip List")
return False
# Delete a key from the Skip List
def delete(self, key):
update = [None] * (self.max_level + 1)
current = self.header
# Traverse the Skip List to find the key
for i in range(self.level, -1, -1):
while current.forward[i] and current.forward[i].key < key:
current = current.forward[i]
update[i] = current
current = current.forward[0]
# If the key is found
if current and current.key == key:
for i in range(self.level + 1):
if update[i].forward[i] != current:
break
update[i].forward[i] = current.forward[i]
# Remove levels that are now empty
while self.level > 0 and self.header.forward[self.level] is None:
self.level -= 1
print(f"Key {key} deleted from the Skip List")
else:
print(f"Key {key} not found in the Skip List")
def display(self):
print("\nSkip List:")
for i in range(self.level + 1):
current = self.header.forward[i]
print(f"Level {i}: ", end="")
while current:
print(current.key, end=" ")
current = current.forward[i]
print()
if __name__ == "__main__":
max_level = 4 # Maximum levels in the Skip List
p = 0.5 # Probability factor for level generation
skip_list = SkipList(max_level, p)
keys = [3, 6, 7, 9, 12, 19, 17, 26, 21]
for key in keys:
skip_list.insert(key)
skip_list.display()
delete_keys = [3, 19, 50]
for key in delete_keys:
skip_list.delete(key)
skip_list.display()

OUTPUT:

Inserted key 3 at level 0


Inserted key 6 at level 1
Inserted key 7 at level 2
Inserted key 9 at level 2
Inserted key 12 at level 1
Inserted key 19 at level 0
Inserted key 17 at level 3
Inserted key 26 at level 0
Inserted key 21 at level 0
Skip List:
Level 0: 3 6 7 9 12 17 19 21 26
Level 1: 6 7 9 12 17
Level 2: 7 9 17
Level 3: 17
Key 3 deleted from the Skip List
Key 19 deleted from the Skip List
Key 50 not found in the Skip List
Skip List:
Level 0: 6 7 9 12 17 21 26
Level 1: 6 7 9 12 17
Level 2: 7 9 17
Level 3: 17
LAB PRACTICAL-10
Objective: Program for Naïve String Matching Algorithm
The naive approach tests all the possible placement of Pattern P [1.......m] relative to text T
[1......n]. We try shift s = 0, 1.......n-m, successively and for each shift s. Compare T
[s+1.......s+m] to P [1......m].

The naive algorithm finds all valid shifts using a loop that checks the condition P [1.......m] =
T [s+1.......s+m] for each of the n - m +1 possible value of s.

ALGORITHM
NAIVE-STRING-MATCHER (T, P)

1. n ← length [T]
2. m ← length [P]
3. for s ← 0 to n -m
4. do if P [1.....m] = T [s + 1....s + m]
5. then print "Pattern occurs with shift" s

IMPLEMENTATION

def search_pattern(pattern, text):


m = len(pattern)
n = len(text)
for i in range(n - m + 1):
j=0
while j < m and text[i + j] == pattern[j]:
j += 1
if j == m:
print(f"Pattern found at index {i}")
if __name__ == "__main__":
text = input("Enter the text: ")
pattern = input("Enter the pattern: ")
print("\nSearching for the pattern in the text...")
search_pattern(pattern, text)

OUTPUT

Enter the text: jjjjjkkjkjljkkkaaaaa

Enter the pattern: jkjljkkk

Searching for the pattern in the text...

Pattern found at index 7


LAB PRACTICAL-11
Objective: Program for Rabin Karp String Matching Algorithm
In the Naive string matching algorithm, we check whether every substring of the text of the
pattern’s size is equal to the pattern or not one by one.
The Rabin-Karp algorithm also check every substring. But unlike the Naive algorithm, the
Rabin Karp algorithm matches the hash value of the pattern with the hash value of the
current substring of text, and if the hash values match then only it starts matching individual
characters. So Rabin Karp algorithm needs to calculate hash values for the following strings.
 Pattern itself
 All the substrings of the text of length m which is the size of pattern.
Here’s how the hash value is typically calculated in Rabin-Karp:
Step 1: Choose a suitable base and a modulus:
 Select a prime number ‘p‘ as the modulus. This choice helps avoid overflow issues
and ensures a good distribution of hash values.
 Choose a base ‘b‘ (usually a prime number as well), which is often the size of the
character set (e.g., 256 for ASCII characters).
Step 2: Initialize the hash value:
 Set an initial hash value ‘hash‘ to 0.
Step 3: Calculate the initial hash value for the pattern:
 Iterate over each character in the pattern from left to right.
 For each character ‘c’ at position ‘i’, calculate its contribution to the hash value as
‘c * (bpattern_length – i – 1) % p’ and add it to ‘hash‘.
 This gives you the hash value for the entire pattern.
Step 4:Slide the pattern over the text:
 Start by calculating the hash value for the first substring of the text that is the same
length as the pattern.
Step 5: Update the hash value for each subsequent substring:
To slide the pattern one position to the right, you remove the contribution of the leftmost
character and add the contribution of the new character on the right.
 The formula for updating the hash value when moving from position ‘i’ to ‘i+1’ is:
hash = (hash - (text[i - pattern_length] * (bpattern_length - 1)) % p) * b + text[i]
Step 6: Compare hash values:
 When the hash value of a substring in the text matches the hash value of the
pattern, it’s a potential match.
 If the hash values match, we should perform a character-by-character comparison
to confirm the match, as hash collisions can occur.

IMPLEMENTATION
def rabin_karp_search(text, pattern, q):
M = len(pattern)
N = len(text)
d = 256
p=0
t=0
h=1
# h = (d^(M-1)) % q
for i in range(M-1):
h = (h * d) % q
for i in range(M):
p = (d * p + ord(pattern[i])) % q
t = (d * t + ord(text[i])) % q
# Slide the pattern over the text one by one
for i in range(N - M + 1):
if p == t:
for j in range(M):
if text[i + j] != pattern[j]:
break
if j == M - 1:
print(f"Pattern found at index {i}")
if i < N - M:
t = (d * (t - ord(text[i]) * h) + ord(text[i + M])) % q
if t < 0:
t=t+q
if __name__ == "__main__":
text = "AAAAKANKSHAJOSHIJKJK"
pattern = "JOSHIJKJK"
q = 101
rabin_karp_search(text, pattern, q)

OUTPUT

Enter the text: jjjjjkkjkjljkkkaaaaa

Enter the pattern: jkjljkkk

Searching for the pattern in the text...

Pattern found at index 7

LAB PRACTICAL-12
Objective: Program for Hashing using Open Addressing.
THEORY:
Hashing is a technique used to store and retrieve data efficiently. In open addressing, when
a collision occurs (i.e., two keys hash to the same index), the ALGORITHM searches for the
next available slot in the hash table using a probing sequence. There are several probing
techniques, including:
1. Linear Probing: In the case of a collision, check the next slot sequentially (i.e., index
+ 1).
2. Quadratic Probing: In the case of a collision, check the next slot with a quadratic
step (i.e., index + 1², index + 2², etc.).
3. Double Hashing: Use a second hash function to determine the step size when a
collision occurs.
For this practical, we will implement Linear Probing as a method of open addressing.
ALGORITHM for Hashing using Linear Probing
1. Initialize the hash table with a fixed size, all set to None (empty).
2. Insertion:
o Calculate the hash value of the key.
o If the slot is occupied, use linear probing to find the next available slot.
3. Search:
o Calculate the hash value of the key.
o If the slot is empty or contains the desired key, return the result. If not,
continue checking subsequent slots using linear probing.
4. Deletion:
o Find the key using linear probing.
o Once found, remove the key and shift the subsequent keys, if necessary, to
maintain the probing sequence.
IMPLEMENTATION

class HashTable:
def _init_(self, size):
self.size = size
self.table = [None] * self.size
def hash_function(self, key):
return key % self.size
def linear_probe(self, key):
index = self.hash_function(key)
original_index = index
while self.table[index] is not None and self.table[index] != "DELETED":
if self.table[index] == key:
return index # If the key is found, return the index
index = (index + 1) % self.size
return None (key not found)
if index == original_index:
return None
return index # If an empty slot is found, return that index
def insert(self, key):
index = self.linear_probe(key)
if index is not None:
self.table[index] = key
else:
print("Hash Table is full! Cannot insert key.")
def delete(self, key):
index = self.linear_probe(key)
if index is not None:
self.table[index] = "DELETED"
else:
print("Key not found in the hash table.")
def search(self, key):
index = self.linear_probe(key)
if index is None:
return None # Key not found
return self.table[index]
def display(self):
for index in range(self.size):
if self.table[index] is None:
print(f"Index {index}: None")
elif self.table[index] == "DELETED":
print(f"Index {index}: DELETED")
else:
print(f"Index {index}: {self.table[index]}")
if _name_ == "_main_":
hash_table = HashTable(7) # Create a hash table with size 7
# Insert some keys
hash_table.insert(10)
hash_table.insert(20)
hash_table.insert(15)
hash_table.insert(7)
print("Hash Table after insertions:")
hash_table.display()
print("\nSearch for key 15:", hash_table.search(15)) # Should return 15
print("Search for key 30:", hash_table.search(30)) # Should return None
hash_table.delete(20)
print("\nHash Table after deletion of key 20:")
hash_table.display()
print("\nSearch for deleted key 20:", hash_table.search(20)) # Should return None
OUTPUT

Inserted key 10 at index 0


Inserted key 20 at index 1
Inserted key 30 at index 2
Inserted key 40 at index 3
Inserted key 50 at index 4
Inserted key 45 at index 5
Inserted key 65 at index 6
Inserted key 67 at index 7
Inserted key 36 at index 8
Inserted key 44 at index 9
Hash Table:
Index 0: 10
Index 1: 20
Index 2: 30
Index 3: 40
Index 4: 50
Index 5: 45
Index 6: 65
Index 7: 67
Index 8: 36
Index 9: 44
Searching for key 20: 1
Searching for key 60: -1

You might also like