DATA STRUCTURES USING PYTHON
List of Experiments:
1. Write a Python program for class, Flower, that has three instance variables of type str,
int, and float that respectively represent the name of the flower, its number of petals,
and its price. Your class must include a constructor method that initializes each
variable to an appropriate value, and your class should include methods for setting the
value of each type, and retrieving the value of each type.
Program:
class Flower:
def __init__(self, name: str, petals: int, price: float):
"""Constructor to initialize the flower's name, number of petals, and price."""
self._name = name
self._petals = petals
self._price = price
# Getter methods
def get_name(self) -> str:
"""Returns the name of the flower."""
return self._name
def get_petals(self) -> int:
"""Returns the number of petals."""
return self._petals
def get_price(self) -> float:
"""Returns the price of the flower."""
return self._price
# Setter methods
def set_name(self, name: str):
"""Sets the name of the flower."""
self._name = name
def set_petals(self, petals: int):
"""Sets the number of petals."""
self._petals = petals
def set_price(self, price: float):
"""Sets the price of the flower."""
self._price = price
# Example usage:
flower = Flower("Rose", 32, 2.5)
# Accessing values using getter methods
print(f"Name: {flower.get_name()}")
print(f"Petals: {flower.get_petals()}")
print(f"Price: {flower.get_price()}")
# Modifying values using setter methods
flower.set_name("Tulip")
flower.set_petals(25)
flower.set_price(3.0)
# Accessing updated values
print(f"Updated Name: {flower.get_name()}")
print(f"Updated Petals: {flower.get_petals()}")
print(f"Updated Price: {flower.get_price()}")
Output:
Name: Rose
Petals: 32
Price: 2.5
Updated Name: Tulip
Updated Petals: 25
Updated Price: 3.0
2. Develop an inheritance hierarchy based upon a Polygon class that has abstract
methods area( ) and perimeter( ). Implement classes Triangle, Quadrilateral,
Pentagon, that extend this base class, with the obvious meanings for the area( ) and
perimeter( ) methods. Write a simple program that allows users to create polygons of
the various types and input their geometric dimensions, and the program then outputs
their area and perimeter
Program:
from abc import ABC, abstractmethod
import math
# Base Polygon class with abstract methods
class Polygon(ABC):
@abstractmethod
def area(self):
"""Calculate the area of the polygon."""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter of the polygon."""
pass
# Triangle class extending Polygon
class Triangle(Polygon):
def __init__(self, a: float, b: float, c: float, height: float):
self.a = a
self.b = b
self.c = c
self.height = height
def area(self):
"""Area of a triangle = 0.5 * base * height"""
return 0.5 * self.a * self.height
def perimeter(self):
"""Perimeter of a triangle = sum of all sides"""
return self.a + self.b + self.c
# Quadrilateral class extending Polygon
class Quadrilateral(Polygon):
def __init__(self, a: float, b: float, c: float, d: float, height: float):
self.a = a
self.b = b
self.c = c
self.d = d
self.height = height
def area(self):
"""Area of a simple quadrilateral can be calculated with base * height"""
return self.a * self.height
def perimeter(self):
"""Perimeter of a quadrilateral = sum of all sides"""
return self.a + self.b + self.c + self.d
# Pentagon class extending Polygon
class Pentagon(Polygon):
def __init__(self, side: float):
self.side = side
def area(self):
"""Area of a regular pentagon = (sqrt(5(5 + 2sqrt(5))) * side^2) / 4"""
return (math.sqrt(5 * (5 + 2 * math.sqrt(5))) * self.side ** 2) / 4
def perimeter(self):
"""Perimeter of a regular pentagon = 5 * side"""
return 5 * self.side
# Function to create polygon and display area and perimeter
def create_polygon():
print("Choose a polygon:")
print("1. Triangle")
print("2. Quadrilateral")
print("3. Pentagon")
choice = int(input("Enter the number of the polygon type: "))
if choice == 1:
a = float(input("Enter side a of the triangle: "))
b = float(input("Enter side b of the triangle: "))
c = float(input("Enter side c of the triangle: "))
height = float(input(f"Enter the height corresponding to side a: "))
triangle = Triangle(a, b, c, height)
print(f"Triangle Area: {triangle.area():.2f}")
print(f"Triangle Perimeter: {triangle.perimeter():.2f}")
elif choice == 2:
a = float(input("Enter side a of the quadrilateral: "))
b = float(input("Enter side b of the quadrilateral: "))
c = float(input("Enter side c of the quadrilateral: "))
d = float(input("Enter side d of the quadrilateral: "))
height = float(input(f"Enter the height corresponding to side a: "))
quadrilateral = Quadrilateral(a, b, c, d, height)
print(f"Quadrilateral Area: {quadrilateral.area():.2f}")
print(f"Quadrilateral Perimeter: {quadrilateral.perimeter():.2f}")
elif choice == 3:
side = float(input("Enter the side length of the pentagon: "))
pentagon = Pentagon(side)
print(f"Pentagon Area: {pentagon.area():.2f}")
print(f"Pentagon Perimeter: {pentagon.perimeter():.2f}")
else:
print("Invalid choice.")
# Run the program
if __name__ == "__main__":
create_polygon()
Output:
Choose a polygon:
1. Triangle
2. Quadrilateral
3. Pentagon
Enter the number of the polygon type: 3
Enter the side length of the pentagon: 5
Pentagon Area: 43.01
Pentagon Perimeter: 25.00
Choose a polygon:
1. Triangle
2. Quadrilateral
3. Pentagon
Enter the number of the polygon type: 1
Enter side a of the triangle: 3
Enter side b of the triangle: 4
Enter side c of the triangle: 5
Enter the height corresponding to side a: 2
Triangle Area: 3.00
Triangle Perimeter: 12.00
3. Write a python program to implement Method Overloading and Method Overriding.
Program:
# Method Overloading simulation using default arguments
class Calculator:
def add(self, a=None, b=None, c=None):
"""Simulate method overloading by using default arguments."""
if a is not None and b is not None and c is not None:
return a + b + c # If three arguments are provided
elif a is not None and b is not None:
return a + b # If two arguments are provided
elif a is not None:
return a # If only one argument is provided
else:
return 0 # If no arguments are provided
# Method Overriding example
class Animal:
def sound(self):
"""Base class method."""
print("This animal makes a sound.")
class Dog(Animal):
def sound(self):
"""Overriding the base class method."""
print("The dog barks.")
class Cat(Animal):
def sound(self):
"""Overriding the base class method."""
print("The cat meows.")
# Testing Method Overloading
calc = Calculator()
# Different usages of the overloaded 'add' method
print(f"Sum (one arg): {calc.add(10)}") # Only one argument
print(f"Sum (two args): {calc.add(10, 20)}") # Two arguments
print(f"Sum (three args): {calc.add(10, 20, 30)}") # Three arguments
# Testing Method Overriding
dog = Dog()
cat = Cat()
# Each subclass overrides the 'sound' method of the Animal class
dog.sound() # The dog barks
cat.sound() # The cat meows
Output:
Sum (one arg): 10
Sum (two args): 30
Sum (three args): 60
The dog barks.
The cat meows.
4. Write a Python program to illustrate the following comprehensions:
a) List Comprehensions
Program:
input_list = [1, 2, 3, 4, 4, 5, 6, 7, 7]
output_list = []
for var in input_list:
if var % 2 == 0:
output_list.append(var)
print("Output List using for loop:", output_list)
Output:
Output List using for loop: [2, 4, 4, 6]
# Example 1: Create a list of squares of numbers from 1 to 10
squares = [x**2 for x in range(1, 11)]
print(f"Squares of numbers from 1 to 10: {squares}")
Output:
Squares of numbers from 1 to 10: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Example 2: Create a list of even numbers from 1 to 20
evens = [x for x in range(1, 21) if x % 2 == 0]
print(f"Even numbers from 1 to 20: {evens}")
Output:
Even numbers from 1 to 20: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Example 3: Create a list of the first letters of each word in a list
words = ["apple", "banana", "cherry", "date"]
first_letters = [word[0] for word in words]
print(f"First letters of each word: {first_letters}")
Output:
First letters of each word: ['a', 'b', 'c', 'd']
# Example 4: Create a list of numbers divisible by both 3 and 5 from 1 to 50
divisible_by_3_and_5 = [x for x in range(1, 51) if x % 3 == 0 and x % 5 == 0]
print(f"Numbers divisible by 3 and 5 from 1 to 50: {divisible_by_3_and_5}")
Output:
Numbers divisible by 3 and 5 from 1 to 50: [15, 30, 45]
# Example 5: Convert a list of strings to uppercase
fruits = ["apple", "banana", "cherry"]
uppercase_fruits = [fruit.upper() for fruit in fruits]
print(f"Fruits in uppercase: {uppercase_fruits}")
Output:
Fruits in uppercase: ['APPLE', 'BANANA', 'CHERRY']
# Example 6: Create a list of tuples where each tuple contains a number and its square
num_square_tuples = [(x, x**2) for x in range(1, 6)]
print(f"List of tuples (number, square): {num_square_tuples}")
Output:
List of tuples (number, square): [(1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
# Example 7: Flatten a 2D matrix into a single list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened_matrix = [element for row in matrix for element in row]
print(f"Flattened matrix: {flattened_matrix}")
Output:
Flattened matrix: [1, 2, 3, 4, 5, 6, 7, 8, 9]
b) Dictionary Comprehensions
# Example 1: Create a dictionary where keys are numbers and values are their squares
squares_dict = {x: x**2 for x in range(1, 6)}
print(f"Squares dictionary: {squares_dict}")
Output
Squares dictionary: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Example 2: Create a dictionary from two lists of the same length (keys and values)
keys = ['name', 'age', 'city']
values = ['Alice', 25, 'New York']
person_dict = {keys[i]: values[i] for i in range(len(keys))}
print(f"Dictionary from lists: {person_dict}")
Output:
Dictionary from lists: {'name': 'Alice', 'age': 25, 'city': 'New York'}
# Example 3: Create a dictionary of numbers and their cubes, but only for even numbers
even_cubes_dict = {x: x**3 for x in range(1, 11) if x % 2 == 0}
print(f"Even cubes dictionary: {even_cubes_dict}")
Output:
Even cubes dictionary: {2: 8, 4: 64, 6: 216, 8: 512, 10: 1000}
# Example 4: Convert a list of tuples (key, value) into a dictionary
tuple_list = [('apple', 3), ('banana', 5), ('cherry', 7)]
fruit_dict = {key: value for (key, value) in tuple_list}
print(f"Dictionary from tuple list: {fruit_dict}")
Output:
Dictionary from tuple list: {'apple': 3, 'banana': 5, 'cherry': 7}
# Example 5: Swap the keys and values in a given dictionary
original_dict = {'a': 1, 'b': 2, 'c': 3}
swapped_dict = {value: key for key, value in original_dict.items()}
print(f"Swapped dictionary (keys and values swapped): {swapped_dict}")
Output:
Swapped dictionary (keys and values swapped): {1: 'a', 2: 'b', 3: 'c'}
# Example 6: Create a dictionary with only items from an existing dictionary where the value is greater than 1
filtered_dict = {key: value for key, value in original_dict.items() if value > 1}
print(f"Filtered dictionary (values > 1): {filtered_dict}")
Output:
Filtered dictionary (values > 1): {'b': 2, 'c': 3}
c) Set Comprehensions
# Example 1: Create a set of squares of numbers from 1 to 10
squares_set = {x**2 for x in range(1, 11)}
print(f"Set of squares from 1 to 10: {squares_set}")
Output:
Set of squares from 1 to 10: {64, 1, 4, 36, 100, 9, 16, 49, 81, 25}
# Example 2: Create a set of even numbers from 1 to 20
evens_set = {x for x in range(1, 21) if x % 2 == 0}
print(f"Set of even numbers from 1 to 20: {evens_set}")
Output:
Set of even numbers from 1 to 20: {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
# Example 3: Create a set of unique characters from a string (excluding spaces)
string = "hello world"
unique_chars = {char for char in string if char != ' '}
print(f"Set of unique characters from the string: {unique_chars}")
Output:
Set of unique characters from the string: {'r', 'd', 'e', 'o', 'l', 'h', 'w'}
# Example 4: Create a set of numbers divisible by 3 or 5 from 1 to 30
divisible_by_3_or_5 = {x for x in range(1, 31) if x % 3 == 0 or x % 5 == 0}
print(f"Set of numbers divisible by 3 or 5 from 1 to 30: {divisible_by_3_or_5}")
Output:
Set of numbers divisible by 3 or 5 from 1 to 30: {3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30}
# Example 5: Convert a list of words to lowercase and remove duplicates using a set comprehension
words = ["Apple", "Banana", "Cherry", "apple", "banana", "CHERRY"]
lowercase_words = {word.lower() for word in words}
print(f"Set of lowercase words: {lowercase_words}")
Output:
Set of lowercase words: {'apple', 'cherry', 'banana'}
# Example 6: Create a set of cubes of odd numbers from 1 to 10
odd_cubes_set = {x**3 for x in range(1, 11) if x % 2 != 0}
print(f"Set of cubes of odd numbers from 1 to 10: {odd_cubes_set}")
Output:
Set of cubes of odd numbers from 1 to 10: {1, 343, 729, 27, 125}
d) Generator Comprehensions
# Example 1: Generator for squares of numbers from 1 to 10
squares_generator = (x**2 for x in range(1, 11))
print("Squares of numbers from 1 to 10:")
for square in squares_generator:
print(square)
Output:
Squares of numbers from 1 to 10:
16
25
36
49
64
81
100
# Example 2: Generator for even numbers from 1 to 20
evens_generator = (x for x in range(1, 21) if x % 2 == 0)
print("\nEven numbers from 1 to 20:")
for even in evens_generator:
print(even)
Output:
Even numbers from 1 to 20:
10
12
14
16
18
20
# Example 3: Generator for unique characters in a string (excluding spaces)
string = "hello world"
unique_chars_generator = (char for char in string if char != ' ')
print("\nUnique characters in the string 'hello world':")
for char in unique_chars_generator:
print(char)
Output:
Unique characters in the string 'hello world':
# Example 4: Generator for numbers divisible by 3 or 5 from 1 to 30
divisible_by_3_or_5_generator = (x for x in range(1, 31) if x % 3 == 0 or x % 5 == 0)
print("\nNumbers divisible by 3 or 5 from 1 to 30:")
for number in divisible_by_3_or_5_generator:
print(number)
Output:
Numbers divisible by 3 or 5 from 1 to 30:
3
10
12
15
18
20
21
24
25
27
30
# Example 5: Generator for cubes of odd numbers from 1 to 10
odd_cubes_generator = (x**3 for x in range(1, 11) if x % 2 != 0)
print("\nCubes of odd numbers from 1 to 10:")
for cube in odd_cubes_generator:
print(cube)
Output:
Cubes of odd numbers from 1 to 10:
27
125
343
729
5. Write a Python program to generate the combinations of n distinct objects taken from the elements of a
given list. Example: Original list: [1, 2, 3, 4, 5, 6, 7, 8, 9] Combinations of 2 distinct objects: [1, 2] [1, 3] [1, 4]
[1, 5] .... [7, 8] [7, 9] [8, 9].
Program:
import itertools
# Define the original list
original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Number of distinct objects to choose (in this case, 2)
n=2
# Generate combinations of n distinct objects
combinations = itertools.combinations(original_list, n)
# Print the combinations
print(f"Combinations of {n} distinct objects from the list:")
for combination in combinations:
print(combination)
Output:
Combinations of 2 distinct objects from the list:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
(1, 7)
(1, 8)
(1, 9)
(2, 3)
(2, 4)
(2, 5)
(2, 6)
(2, 7)
(2, 8)
(2, 9)
(3, 4)
(3, 5)
(3, 6)
(3, 7)
(3, 8)
(3, 9)
(4, 5)
(4, 6)
(4, 7)
(4, 8)
(4, 9)
(5, 6)
(5, 7)
(5, 8)
(5, 9)
(6, 7)
(6, 8)
(6, 9)
(7, 8)
(7, 9)
(8, 9)
6. Write a program for Linear Search and Binary search.
Program:
def linear_search(arr, target):
"""Perform a linear search on the array."""
for index, value in enumerate(arr):
if value == target:
return index # Return the index where the target is found
return -1 # Return -1 if the target is not found
def binary_search(arr, target):
"""Perform a binary search on the sorted array."""
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2 # Find the middle index
if arr[mid] == target:
return mid # Return the index if the target is found
elif arr[mid] < target:
left = mid + 1 # Adjust the left boundary
else:
right = mid - 1 # Adjust the right boundary
return -1 # Return -1 if the target is not found
# Test the search functions
if __name__ == "__main__":
# Sample data
numbers = [4, 2, 7, 1, 3, 5, 6]
sorted_numbers = sorted(numbers) # Sort the list for binary search
target = 3
# Linear Search
print("Linear Search:")
result = linear_search(numbers, target)
if result != -1:
print(f"Target {target} found at index: {result}")
else:
print(f"Target {target} not found.")
# Binary Search
print("\nBinary Search:")
result = binary_search(sorted_numbers, target)
if result != -1:
print(f"Target {target} found at index: {result} in the sorted array.")
else:
print(f"Target {target} not found in the sorted array.")
Output:
Linear Search:
Target 3 found at index: 4
Binary Search:
Target 3 found at index: 2 in the sorted array.
7. Write a program to implement Bubble Sort and Selection Sort.
Program:
def bubble_sort(arr):
"""Perform Bubble Sort on the given array."""
n = len(arr)
for i in range(n):
# Track if any swapping occurs
swapped = False
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]: # Compare adjacent elements
arr[j], arr[j + 1] = arr[j + 1], arr[j] # Swap if out of order
swapped = True
if not swapped: # No swaps means the array is sorted
break
return arr
def selection_sort(arr):
"""Perform Selection Sort on the given array."""
n = len(arr)
for i in range(n):
min_index = i # Assume the minimum is the first element
for j in range(i + 1, n):
if arr[j] < arr[min_index]: # Find the minimum element
min_index = j
# Swap the found minimum element with the first element
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# Test the sorting functions
if __name__ == "__main__":
# Sample data
numbers = [64, 34, 25, 12, 22, 11, 90]
# Bubble Sort
print("Original list:", numbers)
sorted_numbers_bubble = bubble_sort(numbers.copy()) # Use copy to retain original list
print("Sorted list using Bubble Sort:", sorted_numbers_bubble)
# Selection Sort
sorted_numbers_selection = selection_sort(numbers.copy()) # Use copy to retain original list
print("Sorted list using Selection Sort:", sorted_numbers_selection)
Output:
Original list: [64, 34, 25, 12, 22, 11, 90]
Sorted list using Bubble Sort: [11, 12, 22, 25, 34, 64, 90]
Sorted list using Selection Sort: [11, 12, 22, 25, 34, 64, 90]
8. Write a program to implement Merge sort and Quick sort.
Program:
def merge_sort(arr):
"""Perform Merge Sort on the given array."""
if len(arr) > 1:
mid = len(arr) // 2 # Find the middle of the array
left_half = arr[:mid] # Divide the array elements into 2 halves
right_half = arr[mid:]
merge_sort(left_half) # Sort the first half
merge_sort(right_half) # Sort the second half
i = j = k = 0 # Initialize index variables
# Copy data to temp arrays L[] and R[]
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
# Checking if any element was left
while i < len(left_half):
arr[k] = left_half[i]
i += 1
k += 1
while j < len(right_half):
arr[k] = right_half[j]
j += 1
k += 1
return arr
def quick_sort(arr):
"""Perform Quick Sort on the given array."""
if len(arr) <= 1: # Base case for recursion
return arr
else:
pivot = arr[len(arr) // 2] # Choosing the middle element as the pivot
left = [x for x in arr if x < pivot] # Elements less than the pivot
middle = [x for x in arr if x == pivot] # Elements equal to the pivot
right = [x for x in arr if x > pivot] # Elements greater than the pivot
return quick_sort(left) + middle + quick_sort(right) # Recursive sorting
# Test the sorting functions
if __name__ == "__main__":
# Sample data
numbers = [64, 34, 25, 12, 22, 11, 90]
# Merge Sort
print("Original list:", numbers)
sorted_numbers_merge = merge_sort(numbers.copy()) # Use copy to retain original list
print("Sorted list using Merge Sort:", sorted_numbers_merge)
# Quick Sort
sorted_numbers_quick = quick_sort(numbers.copy()) # Use copy to retain original list
print("Sorted list using Quick Sort:", sorted_numbers_quick)
Output:
Original list: [64, 34, 25, 12, 22, 11, 90]
Sorted list using Merge Sort: [11, 12, 22, 25, 34, 64, 90]
Sorted list using Quick Sort: [11, 12, 22, 25, 34, 64, 90]
9. Write a program to implement Stacks and Queues.
Program:
class Stack:
"""A simple implementation of a Stack."""
def __init__(self):
self.items = [] # Initialize an empty list to store stack elements
def push(self, item):
"""Push an item onto the stack."""
self.items.append(item)
print(f"Pushed {item} onto the stack.")
def pop(self):
"""Pop an item off the stack."""
if not self.is_empty():
popped_item = self.items.pop()
print(f"Popped {popped_item} from the stack.")
return popped_item
else:
print("Stack is empty. Cannot pop.")
return None
def peek(self):
"""Return the top item of the stack without removing it."""
if not self.is_empty():
return self.items[-1]
else:
print("Stack is empty. Nothing to peek.")
return None
def is_empty(self):
"""Check if the stack is empty."""
return len(self.items) == 0
def size(self):
"""Return the size of the stack."""
return len(self.items)
class Queue:
"""A simple implementation of a Queue."""
def __init__(self):
self.items = [] # Initialize an empty list to store queue elements
def enqueue(self, item):
"""Add an item to the end of the queue."""
self.items.append(item)
print(f"Enqueued {item} into the queue.")
def dequeue(self):
"""Remove and return the front item from the queue."""
if not self.is_empty():
dequeued_item = self.items.pop(0)
print(f"Dequeued {dequeued_item} from the queue.")
return dequeued_item
else:
print("Queue is empty. Cannot dequeue.")
return None
def front(self):
"""Return the front item of the queue without removing it."""
if not self.is_empty():
return self.items[0]
else:
print("Queue is empty. Nothing at the front.")
return None
def is_empty(self):
"""Check if the queue is empty."""
return len(self.items) == 0
def size(self):
"""Return the size of the queue."""
return len(self.items)
# Test the Stack and Queue implementations
if __name__ == "__main__":
# Stack operations
print("Stack Operations:")
stack = Stack()
stack.push(10)
stack.push(20)
stack.push(30)
print(f"Top item is: {stack.peek()}")
stack.pop()
print(f"Stack size is: {stack.size()}")
stack.pop()
stack.pop()
stack.pop() # Attempt to pop from an empty stack
# Queue operations
print("\nQueue Operations:")
queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
print(f"Front item is: {queue.front()}")
queue.dequeue()
print(f"Queue size is: {queue.size()}")
queue.dequeue()
queue.dequeue()
queue.dequeue() # Attempt to dequeue from an empty queue
Output:
Stack Operations:
Pushed 10 onto the stack.
Pushed 20 onto the stack.
Pushed 30 onto the stack.
Top item is: 30
Popped 30 from the stack.
Stack size is: 2
Popped 20 from the stack.
Popped 10 from the stack.
Stack is empty. Cannot pop.
Queue Operations:
Enqueued 1 into the queue.
Enqueued 2 into the queue.
Enqueued 3 into the queue.
Front item is: 1
Dequeued 1 from the queue.
Queue size is: 2
Dequeued 2 from the queue.
Dequeued 3 from the queue.
Queue is empty. Cannot dequeue.
10. Write a program to implement Singly Linked List.
Program:
class Node:
"""A Node of a Singly Linked List."""
def __init__(self, data):
self.data = data # Store the data
self.next = None # Pointer to the next node
class SinglyLinkedList:
"""A Singly Linked List."""
def __init__(self):
self.head = None # Initialize the head of the list
def append(self, data):
"""Add a new node at the end of the list."""
new_node = Node(data)
if not self.head:
self.head = new_node # If the list is empty, set head to the new node
print(f"Appended {data} as the head node.")
return
last_node = self.head
while last_node.next: # Traverse to the last node
last_node = last_node.next
last_node.next = new_node # Link the last node to the new node
print(f"Appended {data} to the list.")
def delete(self, key):
"""Delete the first occurrence of a node with the given key."""
current = self.head
previous = None
# If the head node itself holds the key to be deleted
if current and current.data == key:
self.head = current.next # Change head
print(f"Deleted {key} from the list.")
return
# Search for the key to be deleted
while current and current.data != key:
previous = current
current = current.next
# If key was not present in the list
if not current:
print(f"{key} not found in the list.")
return
# Unlink the node from the list
previous.next = current.next
print(f"Deleted {key} from the list.")
def search(self, key):
"""Search for a node with the given key."""
current = self.head
while current:
if current.data == key:
print(f"{key} found in the list.")
return True
current = current.next
print(f"{key} not found in the list.")
return False
def display(self):
"""Display the entire list."""
current = self.head
if not current:
print("The list is empty.")
return
print("Singly Linked List:")
while current:
print(f"{current.data} -> ", end="")
current = current.next
print("None") # Indicate the end of the list
# Test the Singly Linked List implementation
if __name__ == "__main__":
linked_list = SinglyLinkedList()
# Append nodes
linked_list.append(10)
linked_list.append(20)
linked_list.append(30)
# Display the list
linked_list.display()
# Search for a node
linked_list.search(20)
linked_list.search(40)
# Delete a node
linked_list.delete(20)
linked_list.display()
# Attempt to delete a node that does not exist
linked_list.delete(40)
# Display the final state of the list
linked_list.display()
Output:
Appended 10 as the head node.
Appended 20 to the list.
Appended 30 to the list.
Singly Linked List:
10 -> 20 -> 30 -> None
20 found in the list.
40 not found in the list.
Deleted 20 from the list.
Singly Linked List:
10 -> 30 -> None
40 not found in the list.
Singly Linked List:
10 -> 30 -> None
11. Write a program to implement Doubly Linked list.
Program:
class Node:
"""A Node of a Doubly Linked List."""
def __init__(self, data):
self.data = data # Store the data
self.next = None # Pointer to the next node
self.prev = None # Pointer to the previous node
class DoublyLinkedList:
"""A Doubly Linked List."""
def __init__(self):
self.head = None # Initialize the head of the list
def append(self, data):
"""Add a new node at the end of the list."""
new_node = Node(data)
if not self.head:
self.head = new_node # If the list is empty, set head to the new node
print(f"Appended {data} as the head node.")
return
last_node = self.head
while last_node.next: # Traverse to the last node
last_node = last_node.next
last_node.next = new_node # Link the last node to the new node
new_node.prev = last_node # Link the new node back to the last node
print(f"Appended {data} to the list.")
def delete(self, key):
"""Delete the first occurrence of a node with the given key."""
current = self.head
# If the head node itself holds the key to be deleted
if current and current.data == key:
self.head = current.next # Change head
if self.head: # If there's a new head, set its prev to None
self.head.prev = None
print(f"Deleted {key} from the list.")
return
# Search for the key to be deleted
while current and current.data != key:
current = current.next
# If key was not present in the list
if not current:
print(f"{key} not found in the list.")
return
# Unlink the node from the list
if current.next: # If the node to be deleted is not the last node
current.next.prev = current.prev
if current.prev: # If the node to be deleted is not the first node
current.prev.next = current.next
print(f"Deleted {key} from the list.")
def search(self, key):
"""Search for a node with the given key."""
current = self.head
while current:
if current.data == key:
print(f"{key} found in the list.")
return True
current = current.next
print(f"{key} not found in the list.")
return False
def display_forward(self):
"""Display the entire list in forward direction."""
current = self.head
if not current:
print("The list is empty.")
return
print("Doubly Linked List (Forward):")
while current:
print(f"{current.data} <-> ", end="")
current = current.next
print("None") # Indicate the end of the list
def display_backward(self):
"""Display the entire list in backward direction."""
current = self.head
if not current:
print("The list is empty.")
return
# Move to the last node
while current.next:
current = current.next
print("Doubly Linked List (Backward):")
while current:
print(f"{current.data} <-> ", end="")
current = current.prev
print("None") # Indicate the start of the list
# Test the Doubly Linked List implementation
if __name__ == "__main__":
doubly_linked_list = DoublyLinkedList()
# Append nodes
doubly_linked_list.append(10)
doubly_linked_list.append(20)
doubly_linked_list.append(30)
# Display the list forward and backward
doubly_linked_list.display_forward()
doubly_linked_list.display_backward()
# Search for a node
doubly_linked_list.search(20)
doubly_linked_list.search(40)
# Delete a node
doubly_linked_list.delete(20)
doubly_linked_list.display_forward()
doubly_linked_list.display_backward()
# Attempt to delete a node that does not exist
doubly_linked_list.delete(40)
# Display the final state of the list
doubly_linked_list.display_forward()
doubly_linked_list.display_backward()
Output:
Appended 10 as the head node.
Appended 20 to the list.
Appended 30 to the list.
Doubly Linked List (Forward):
10 <-> 20 <-> 30 <-> None
Doubly Linked List (Backward):
30 <-> 20 <-> 10 <-> None
20 found in the list.
40 not found in the list.
Deleted 20 from the list.
Doubly Linked List (Forward):
10 <-> 30 <-> None
Doubly Linked List (Backward):
30 <-> 10 <-> None
40 not found in the list.
Doubly Linked List (Forward):
10 <-> 30 <-> None
Doubly Linked List (Backward):
30 <-> 10 <-> None
12. Write a program to implement Binary Search Tree.
Program:
class TreeNode:
"""A Node of a Binary Search Tree."""
def __init__(self, key):
self.left = None # Pointer to the left child
self.right = None # Pointer to the right child
self.val = key # Store the value of the node
class BinarySearchTree:
"""A Binary Search Tree."""
def __init__(self):
self.root = None # Initialize the root of the tree
def insert(self, key):
"""Insert a new node with the specified key."""
if self.root is None:
self.root = TreeNode(key) # If the tree is empty, set root to the new node
print(f"Inserted {key} as the root node.")
else:
self._insert_recursively(self.root, key)
def _insert_recursively(self, node, key):
"""Helper method to insert a new node recursively."""
if key < node.val: # If the key is smaller, go to the left subtree
if node.left is None:
node.left = TreeNode(key) # Insert the new node
print(f"Inserted {key} to the left of {node.val}.")
else:
self._insert_recursively(node.left, key) # Continue searching
else: # If the key is greater or equal, go to the right subtree
if node.right is None:
node.right = TreeNode(key) # Insert the new node
print(f"Inserted {key} to the right of {node.val}.")
else:
self._insert_recursively(node.right, key) # Continue searching
def search(self, key):
"""Search for a node with the specified key."""
result = self._search_recursively(self.root, key)
if result:
print(f"{key} found in the BST.")
else:
print(f"{key} not found in the BST.")
return result
def _search_recursively(self, node, key):
"""Helper method to search for a key recursively."""
if node is None or node.val == key:
return node # Return the node if found or None if not found
if key < node.val: # Search in the left subtree
return self._search_recursively(node.left, key)
# Search in the right subtree
return self._search_recursively(node.right, key)
def delete(self, key):
"""Delete a node with the specified key."""
self.root = self._delete_recursively(self.root, key)
def _delete_recursively(self, node, key):
"""Helper method to delete a node recursively."""
if node is None:
print(f"{key} not found in the BST. Cannot delete.")
return node # Node not found
if key < node.val: # Go to the left subtree
node.left = self._delete_recursively(node.left, key)
elif key > node.val: # Go to the right subtree
node.right = self._delete_recursively(node.right, key)
else:
# Node with the key found
print(f"Deleting {key}.")
# Node with only one child or no child
if node.left is None:
return node.right
elif node.right is None:
return node.left
# Node with two children, get the inorder successor (smallest in the right subtree)
min_larger_node = self._min_value_node(node.right)
node.val = min_larger_node.val # Copy the inorder successor's value to this node
node.right = self._delete_recursively(node.right, min_larger_node.val) # Delete the inorder successor
return node
def _min_value_node(self, node):
"""Helper method to find the node with the minimum value greater than the given node."""
current = node
while current.left:
current = current.left
return current
def in_order_traversal(self):
"""Perform in-order traversal of the tree."""
print("In-order traversal of the BST:")
self._in_order_traversal_recursively(self.root)
def _in_order_traversal_recursively(self, node):
"""Helper method for in-order traversal recursively."""
if node:
self._in_order_traversal_recursively(node.left)
print(node.val, end=" ")
self._in_order_traversal_recursively(node.right)
# Test the Binary Search Tree implementation
if __name__ == "__main__":
bst = BinarySearchTree()
# Insert nodes
bst.insert(50)
bst.insert(30)
bst.insert(20)
bst.insert(40)
bst.insert(70)
bst.insert(60)
bst.insert(80)
# Display in-order traversal
bst.in_order_traversal()
# Search for nodes
bst.search(40)
bst.search(100)
# Delete nodes
bst.delete(20)
bst.in_order_traversal()
bst.delete(30)
bst.in_order_traversal()
bst.delete(50)
bst.in_order_traversal()
# Attempt to delete a node that does not exist
bst.delete(100)
Output:
Inserted 50 as the root node.
Inserted 30 to the left of 50.
Inserted 20 to the left of 30.
Inserted 40 to the right of 30.
Inserted 70 to the right of 50.
Inserted 60 to the left of 70.
Inserted 80 to the right of 70.
In-order traversal of the BST:
20 30 40 50 60 70 80 40 found in the BST.
100 not found in the BST.
Deleting 20.
In-order traversal of the BST:
30 40 50 60 70 80 Deleting 30.
In-order traversal of the BST:
40 50 60 70 80 Deleting 50.
Deleting 60.
In-order traversal of the BST:
40 60 70 80 100 not found in the BST. Cannot delete.
Example 2 for BST
lass BinaryTreeNode:
def __init__(self, data):
self.data = data
self.leftChild = None
self.rightChild=None
def insert(root,newValue):
#if binary search tree is empty, make a new node and declare it as root
if root is None:
root=BinaryTreeNode(newValue)
return root
#binary search tree is not empty, so we will insert it into the tree
#if newValue is less than value of data in root, add it to left subtree and proceed recursively
if newValue<root.data:
root.leftChild=insert(root.leftChild,newValue)
else:
#if newValue is greater than value of data in root, add it to right subtree and proceed recursively
root.rightChild=insert(root.rightChild,newValue)
return root
root= insert(None,15)
insert(root,10)
insert(root,25)
insert(root,6)
insert(root,14)
insert(root,20)
insert(root,60)
a1=root
a2=a1.leftChild
a3=a1.rightChild
a4=a2.leftChild
a5=a2.rightChild
a6=a3.leftChild
a7=a3.rightChild
print("Root Node is:")
print(a1.data)
print("left child of node is:")
print(a1.leftChild.data)
print("right child of node is:")
print(a1.rightChild.data)
print("Node is:")
print(a2.data)
print("left child of node is:")
print(a2.leftChild.data)
print("right child of node is:")
print(a2.rightChild.data)
print("Node is:")
print(a3.data)
print("left child of node is:")
print(a3.leftChild.data)
print("right child of node is:")
print(a3.rightChild.data)
print("Node is:")
print(a4.data)
print("left child of node is:")
print(a4.leftChild)
print("right child of node is:")
print(a4.rightChild)
print("Node is:")
print(a5.data)
print("left child of node is:")
print(a5.leftChild)
print("right child of node is:")
print(a5.rightChild)
print("Node is:")
print(a6.data)
print("left child of node is:")
print(a6.leftChild)
print("right child of node is:")
print(a6.rightChild)
print("Node is:")
print(a7.data)
print("left child of node is:")
print(a7.leftChild)
print("right child of node is:")
print(a7.rightChild)
Output:
Root Node is:
15
left child of node is:
10
right child of node is:
25
Node is:
10
left child of node is:
right child of node is:
14
Node is:
25
left child of node is:
20
right child of node is:
60
Node is:
left child of node is:
None
right child of node is:
None
Node is:
14
left child of node is:
None
right child of node is:
None
Node is:
20
left child of node is:
None
right child of node is:
None
Node is:
60
left child of node is:
None
right child of node is:
None