Data Structures with Python Lab Manual
Data Structures with Python Lab Manual
Asymptotic notations like Big O, Θ, and Ω are crucial in analyzing algorithm performance because they provide a high-level classification of an algorithm's efficiency without mechanical or platform constraints. For instance, for search algorithms like Linear and Binary Search, asymptotic notations help in understanding best, average, and worst-case scenarios. Linear Search has a time complexity of O(n) as it involves checking each element sequentially . In contrast, Binary Search has a time complexity of O(log n) because it divides the array into halves, significantly reducing the number of comparisons . For sorting algorithms, these notations help compare theoretical efficiency limits, such as Bubble Sort's O(n^2) versus more advanced algorithms like Merge Sort's O(n log n) (although Merge Sort is not covered in the document). Asymptotic notations are therefore vital for predicting and comparing performances theoretically independent of specific implementation details or hardware. .
The Tower of Hanoi is a classic problem involving three rods and a number of disks of different sizes. The goal is to move the entire stack of disks to another rod, following rules of moving only one disk at a time, placing only smaller disks on larger ones . The recursive solution involves moving n-1 disks from source to auxiliary using destination, then moving the nth disk to destination, and finally moving n-1 disks from auxiliary to destination using source. This divide-and-conquer strategy, implemented as towerofhanoi(n, source, destination, auxiliary), recursively resolves smaller subproblems, demonstrating the powerful use of recursion in problem-solving .
To implement an ADT in Python, such as a 'date' class, a variety of operations can be defined that manage the attributes of an object. The class can encapsulate attributes like day, month, and year, and offer methods to access these attributes. The example with class date defines operations such as day(), month(), year(), monthName(), and isLeapYear() that interact with the attributes to extract or compute necessary data . For example, the monthName() function translates a month number into its corresponding name, and isLeapYear() checks if a year is a leap year using the rules of the Gregorian calendar .
Recursive algorithms solve a problem by calling themselves with a subproblem. This concept applies to calculating factorials, where fact(n) returns n * fact(n-1) until n equals 1 . The recursive calculation of Fibonacci sequences operates similarly, where fib(n) = fib(n-1) + fib(n-2) continues until the base case n <= 1 . These implementations demonstrate how recursive solutions break down problems into smaller, more manageable parts, making them intuitive for operations involving sequences or hierarchical computations .
Python allows for easy manipulation of basic data structures such as lists, dictionaries, tuples, and sets. For example, a list can contain various data types and is mutable as shown with l1 = [1, 2, "ABC", 3, "xyz", 2.3] which adds multiple data types . A dictionary stores key-value pairs, exemplified with d1 = {"a": 134, "b": 266, "c": 343} . Tuples, such as t1 = (10, 20, 30, 40, 50, 40), are immutable and ordered collections . Sets, like s1 = {10, 30, 20, 40, 50}, automatically remove duplicates and store unordered collections of unique items .
In Python, a priority queue can be implemented using a wrapper class, such as PriorityQEntry, to keep items with associated priorities and a list to maintain the queue . Unlike a regular queue that serves the elements by order of insertion, a priority queue serves elements based on their priority. The enqueue(item, priority) operation inserts elements into the list according to their priority, using insertion sort techniques to maintain the list order, while the dequeue() operation removes the element with the highest priority (highest priority level denotes the peak position in the queue). This feature is essential for processes that require scheduling based on importance, rather than order of arrival .
The binary search algorithm is implemented by iteratively or recursively dividing the search interval in half. If the midpoint of the array is the target value, the search ends; otherwise, the interval is narrowed to the half that may contain the target . For its optimal performance, the array must be sorted. The key condition is that each iteration (or recursion) reduces the problem size by a factor of two, which ensures a time complexity of O(log n). However, its efficiency is contingent upon the sorted nature of the array, distinguishing it from other search algorithms which may not have this requirement .
A stack in Python can be implemented using a list where operations like push and pop follow Last In First Out (LIFO) protocol. Typical operations include isEmpty() to check if the stack is empty, push(item) to add an item, pop() to remove the most recently added item, peek() to view the top item without removing it, size() to get the number of items, and display() to print all elements . The implementation of the stack class in Python provided uses a list to maintain stack operations, illustrating each function's definition and how they interact with the data structure .
In Python, a queue can be implemented using a list with operations that follow the First In First Out (FIFO) principle. Fundamental operations include enqueue(item) to add an element at the end of the queue and dequeue() to remove the first element . Additional operations are isEmpty() to check if the queue is empty and display() to print the elements. The implementation of queues improves the efficient handling of dynamical data, such as waitlists and scheduling problems, characterized by these operations .
Bubble Sort, Selection Sort, and Insertion Sort can be compared based on their time and space complexities. Bubble Sort has a time complexity of O(n^2) because it repeatedly passes through the list, compares adjacent elements, and swaps them if they are in the wrong order . Selection Sort also has a time complexity of O(n^2), as it selects the smallest element from the unsorted portion and places it at the beginning, iterating over the list multiple times . Insertion Sort has a similar average and worst-case time complexity of O(n^2) but is more efficient when the list is partially sorted, as it builds the sorted array one item at a time . The space complexity for all three sorts is O(1) because they are in-place sorting algorithms .









