diff --git a/Exercise_1.py b/Exercise_1.py index 3e6adcf4..cb95cfc0 100644 --- a/Exercise_1.py +++ b/Exercise_1.py @@ -6,17 +6,27 @@ def binarySearch(arr, l, r, x): #write your code here + while l<=r: + mid = (l+r)//2 + if arr[mid] == x: + return mid + elif x>arr[mid]: + l = mid + 1 + else: + r = mid -1 + return -1 + # Test array arr = [ 2, 3, 4, 10, 40 ] -x = 10 +x = 40 # Function call result = binarySearch(arr, 0, len(arr)-1, x) if result != -1: - print "Element is present at index % d" % result + print(f"Element is present at index {result}") else: - print "Element is not present in array" + print("Element is not present in array") diff --git a/Exercise_2.py b/Exercise_2.py index 35abf0dd..ee7baf05 100644 --- a/Exercise_2.py +++ b/Exercise_2.py @@ -2,15 +2,33 @@ # give you explanation for the approach def partition(arr,low,high): + + i = ( low-1 ) # index of smaller element + pivot = arr[high] # pivot + for j in range(low , high): + # If current element is smaller than or + # equal to pivot + if arr[j] <= pivot: + # increment index of smaller element + i = i+1 + arr[i],arr[j] = arr[j],arr[i] - #write your code here + arr[i+1],arr[high] = arr[high],arr[i+1] + return ( i+1 ) # Function to do Quick sort def quickSort(arr,low,high): - - #write your code here + if low < high: + # pi is partitioning index, arr[p] is now + # at right place + pi = partition(arr,low,high) + + # Separately sort elements before + # partition and after partition + quickSort(arr, low, pi-1) + quickSort(arr, pi+1, high) # Driver code to test above arr = [10, 7, 8, 9, 1, 5] @@ -18,6 +36,4 @@ def quickSort(arr,low,high): quickSort(arr,0,n-1) print ("Sorted array is:") for i in range(n): - print ("%d" %arr[i]), - - + print ("%d" %arr[i]), diff --git a/Exercise_3.py b/Exercise_3.py index a26a69b8..5f25b18a 100644 --- a/Exercise_3.py +++ b/Exercise_3.py @@ -2,19 +2,33 @@ class Node: # Function to initialise the node object - def __init__(self, data): + def __init__(self, data): + self.data = data + self.next = None class LinkedList: def __init__(self): + self.head = None def push(self, new_data): + new_node = Node(new_data) + new_node.next = self.head + self.head = new_node # Function to get the middle of # the linked list - def printMiddle(self): + def printMiddle(self): + slow_ptr = self.head + fast_ptr = self.head + + if self.head is not None: + while (fast_ptr is not None and fast_ptr.next is not None): + fast_ptr = fast_ptr.next.next + slow_ptr = slow_ptr.next + print("The middle element is:", slow_ptr.data) # Driver code list1 = LinkedList() diff --git a/Exercise_4.py b/Exercise_4.py index 9bc25d3d..844fc3dd 100644 --- a/Exercise_4.py +++ b/Exercise_4.py @@ -1,18 +1,52 @@ # Python program for implementation of MergeSort def mergeSort(arr): - - #write your code here + if len(arr) > 1: + mid = len(arr)//2 #Finding the mid of the array + L = arr[:mid] # Dividing the elements into 2 halves + R = arr[mid:] + + # Sorting the first half + mergeSort(L) + + # Sorting the second half + mergeSort(R) + + i = j = k = 0 + + # Copy data to temp arrays L[] and R[] + while i < len(L) and j < len(R): + if L[i] < R[j]: + arr[k] = L[i] + i += 1 + else: + arr[k] = R[j] + j += 1 + k += 1 + + # Checking if any element was left + while i < len(L): + arr[k] = L[i] + i += 1 + k += 1 + + while j < len(R): + arr[k] = R[j] + j += 1 + k += 1 + +#write your code here # Code to print the list -def printList(arr): +def printList(arr): + print(arr) #write your code here # driver code to test the above code if __name__ == '__main__': - arr = [12, 11, 13, 5, 6, 7] - print ("Given array is", end="\n") - printList(arr) - mergeSort(arr) - print("Sorted array is: ", end="\n") - printList(arr) + arr = [12, 11, 13, 5, 6, 7] + print ("Given array is", end="\n") + printList(arr) + mergeSort(arr) + print("Sorted array is: ", end="\n") + printList(arr) diff --git a/Exercise_5.py b/Exercise_5.py index 1da24ffb..4ec47e2e 100644 --- a/Exercise_5.py +++ b/Exercise_5.py @@ -2,9 +2,148 @@ # This function is same in both iterative and recursive def partition(arr, l, h): - #write your code here + """ + Partition function using Lomuto partition scheme. + Takes last element as pivot, places the pivot element at its correct + position in sorted array, and places all smaller elements to left + of pivot and all greater elements to right of pivot. + + Time Complexity: O(n) + Space Complexity: O(1) + """ + # Choose the rightmost element as pivot + pivot = arr[h] + + # Index of smaller element indicates the right position of pivot found so far + i = l - 1 + + for j in range(l, h): + # If current element is smaller than or equal to pivot + if arr[j] <= pivot: + i += 1 # increment index of smaller element + arr[i], arr[j] = arr[j], arr[i] # swap elements + + # Place pivot at correct position + arr[i + 1], arr[h] = arr[h], arr[i + 1] + return i + 1 # return position of pivot def quickSortIterative(arr, l, h): - #write your code here + """ + Iterative implementation of QuickSort using only a stack. + + Time Complexity: + - Best/Average Case: O(n log n) + - Worst Case: O(n²) + Space Complexity: O(log n) for stack space + """ + # Create a stack to store pairs of (low, high) indices + stack = [] + + # Push initial values of l and h to stack as a tuple + stack.append((l, h)) + + # Keep processing while stack is not empty + while stack: + # Pop low and high indices from stack + low, high = stack.pop() + + # Only process if there are at least 2 elements + if low < high: + # Partition the array and get pivot index + pivot_index = partition(arr, low, high) + + # Push left subarray to stack (if it has more than 1 element) + if pivot_index - 1 > low: + stack.append((low, pivot_index - 1)) + + # Push right subarray to stack (if it has more than 1 element) + if pivot_index + 1 < high: + stack.append((pivot_index + 1, high)) + +def quickSortRecursive(arr, l, h): + """ + Recursive implementation of QuickSort. + + Time Complexity: + - Best/Average Case: O(n log n) + - Worst Case: O(n²) + Space Complexity: O(log n) for recursion stack + """ + if l < h: + # Partition the array and get pivot index + pi = partition(arr, l, h) + + # Recursively sort elements before and after partition + quickSortRecursive(arr, l, pi - 1) + quickSortRecursive(arr, pi + 1, h) + + +def print_array(arr): + """Helper function to print array""" + print(" ".join(map(str, arr))) + + +# Test the implementation +if __name__ == "__main__": + # Test case 1: Random array + print("=== QuickSort Implementation Test ===") + + arr1 = [64, 34, 25, 12, 22, 11, 90] + print(f"Original array: {arr1}") + + # Test iterative quicksort + arr1_copy = arr1.copy() + quickSortIterative(arr1_copy, 0, len(arr1_copy) - 1) + print(f"Sorted (Iterative): {arr1_copy}") + + # Test recursive quicksort + arr1_copy2 = arr1.copy() + quickSortRecursive(arr1_copy2, 0, len(arr1_copy2) - 1) + print(f"Sorted (Recursive): {arr1_copy2}") + + print() + + # Test case 2: Already sorted array + arr2 = [1, 2, 3, 4, 5] + print(f"Already sorted array: {arr2}") + arr2_copy = arr2.copy() + quickSortIterative(arr2_copy, 0, len(arr2_copy) - 1) + print(f"After quicksort: {arr2_copy}") + + print() + + # Test case 3: Reverse sorted array + arr3 = [5, 4, 3, 2, 1] + print(f"Reverse sorted array: {arr3}") + arr3_copy = arr3.copy() + quickSortRecursive(arr3_copy, 0, len(arr3_copy) - 1) + print(f"After quicksort: {arr3_copy}") + + print() + + # Test case 4: Array with duplicates + arr4 = [3, 6, 8, 10, 1, 2, 1] + print(f"Array with duplicates: {arr4}") + arr4_copy = arr4.copy() + quickSortIterative(arr4_copy, 0, len(arr4_copy) - 1) + print(f"After quicksort: {arr4_copy}") + + print() + + # Test case 5: Single element + arr5 = [42] + print(f"Single element array: {arr5}") + arr5_copy = arr5.copy() + quickSortRecursive(arr5_copy, 0, len(arr5_copy) - 1) + print(f"After quicksort: {arr5_copy}") + + print() + + # Test case 6: Empty array + arr6 = [] + print(f"Empty array: {arr6}") + if arr6: # Only sort if array is not empty + quickSortIterative(arr6, 0, len(arr6) - 1) + print(f"After quicksort: {arr6}")