diff --git a/Arrays/Arrays.ipynb b/Arrays/Arrays.ipynb index 955a607..3c33a1c 100644 --- a/Arrays/Arrays.ipynb +++ b/Arrays/Arrays.ipynb @@ -26,7 +26,7 @@ "metadata": {}, "source": [ "* Array is a data structure used to store homogeneous elements at contiguous locations.\n", - "* One memory block is allocated for the entire array Lo hold the clements of the arTny. The array elements can be accessed in constant time by using the index of the parliculnr element as the subscript." + "* One memory block is allocated for the entire array to hold the elements of the array. The array elements can be accessed in constant time by using the index of the particular element as the subscript." ] }, { @@ -348,4 +348,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/Arrays/P05_CheckForPairSum.py b/Arrays/P05_CheckForPairSum.py index 45b42e5..d71e6db 100644 --- a/Arrays/P05_CheckForPairSum.py +++ b/Arrays/P05_CheckForPairSum.py @@ -4,8 +4,10 @@ # in S whose sum is exactly x. def checkSum(array, sum): - # sort the array in descending order - array = quickSort(array) + # sort the array in ascending order + # new changes : made use of Python's inbuilt Merge Sort method + # Reason for such change : Worst case Time complexity of Quick Sort is O(n^2) whereas Worst Case Complexity of Merge Sort is O(nlog(n)) + array = sorted(array) leftIndex = 0 rightIndex = len(array) - 1 @@ -20,14 +22,14 @@ def checkSum(array, sum): return False, False -def quickSort(array): - if len(array) <= 1: - return array - pivot = array[len(array) // 2] - left = [x for x in array if x < pivot] - middle = [x for x in array if x == pivot] - right = [x for x in array if x > pivot] - return quickSort(left) + middle + quickSort(right) +##def quickSort(array): +## if len(array) <= 1: +## return array +## pivot = array[len(array) // 2] +## left = [x for x in array if x < pivot] +## middle = [x for x in array if x == pivot] +## right = [x for x in array if x > pivot] +## return quickSort(left) + middle + quickSort(right) if __name__ == '__main__': myArray = [10, 20, 30, 40, 50] @@ -37,4 +39,4 @@ def quickSort(array): if(number1 and number2): print('Array has elements:', number1, 'and', number2, 'with sum:', sum) else: - print('Array doesn\'t has elements with the sum:', sum) + print('Array doesn\'t have elements with the sum:', sum) diff --git a/Dynamic Programming/LongestDecreasingSubsequence.py b/Dynamic Programming/LongestDecreasingSubsequence.py new file mode 100644 index 0000000..e3381e9 --- /dev/null +++ b/Dynamic Programming/LongestDecreasingSubsequence.py @@ -0,0 +1,27 @@ +def lds(arr, n): + + lds = [0] * n + max = 0 + + # Initialize LDS with 1 for all index + # The minimum LDS starting with any + # element is always 1 + for i in range(n): + lds[i] = 1 + + # Compute LDS from every index + # in bottom up manner + for i in range(1, n): + for j in range(i): + if (arr[i] < arr[j] and + lds[i] < lds[j] + 1): + lds[i] = lds[j] + 1 + + # Select the maximum + # of all the LDS values + for i in range(n): + if (max < lds[i]): + max = lds[i] + + # returns the length of the LDS + return max diff --git a/Dynamic Programming/P01_Fibonnaci.py b/Dynamic Programming/P01_Fibonnaci.py index e3da115..53d1745 100644 --- a/Dynamic Programming/P01_Fibonnaci.py +++ b/Dynamic Programming/P01_Fibonnaci.py @@ -14,11 +14,20 @@ def fibonacci(number): # traditional recursive fibonacci function def fibonacciRec(number): - if number <= 1: + if number == 1 or number == 0: return number else: return (fibonacciRec(number - 1) + fibonacciRec(number - 2)) +# improved recursive fibonacci function +def fib_memoization(n, lookup): + if n == 0 or n == 1 : + lookup[n] = n + if lookup[n] is None: + lookup[n] = fib(n-1 , lookup) + fib(n-2 , lookup) + return lookup[n] + + if __name__ == '__main__': userInput = int(input('Enter the number: ')) @@ -37,3 +46,9 @@ def fibonacciRec(number): result = fibonacciRec(userInput) stopTime = time.time() print('Time:', (stopTime - startTime), 'Result:', result) + + startTime = time.time() + lookup=[None]*(101) + result = fib_memoization(userInput,lookup) + stopTime = time.time() + print('Time:', (stopTime - startTime), 'Result:', result) diff --git a/Dynamic Programming/P04_SieveOfEratosthenes.py b/Dynamic Programming/P04_SieveOfEratosthenes.py new file mode 100644 index 0000000..4c980fd --- /dev/null +++ b/Dynamic Programming/P04_SieveOfEratosthenes.py @@ -0,0 +1,32 @@ + +# Python program to print all primes smaller than or equal to +# n using Sieve of Eratosthenes + +def sieve_of_eratosthenes(n): + + # Create a boolean array "prime[0..n]" and initialize + # all entries it as true. A value in prime[i] will + # finally be false if i is Not a prime, else true. + prime = [True for i in range(n+1)] + p = 2 + while (p * p <= n): + + # If prime[p] is not changed, then it is a prime + if (prime[p] == True): + + # Update all multiples of p + for i in range(p * 2, n+1, p): + prime[i] = False + p += 1 + + # Print all prime numbers + for p in range(2, n): + if prime[p]: + print (p), + +# driver program +if __name__=='__main__': + n = 30 + print ("Following are the prime numbers smaller"), + print ("than or equal to", n ) + sieve_of_eratosthenes(n) diff --git a/Dynamic Programming/mincoin.py b/Dynamic Programming/mincoin.py new file mode 100644 index 0000000..da90708 --- /dev/null +++ b/Dynamic Programming/mincoin.py @@ -0,0 +1,29 @@ +import sys + +def min_coins(coins,sum): + + # dp[i] will be storing the minimum + dp = [0 for i in range(sum + 1)] + + # Base case + dp[0] = 0 + + # Initialize values as Infinite + for i in range(1, sum + 1): + dp[i] = sys.maxsize + + # for all values from 1 to sum + for i in range(1, sum + 1): + for j in range(len(coins)): + if (coins[j] <= i): + res = dp[i - coins[j]] + if (res != sys.maxsize and res + 1 < dp[i]): + dp[i] = res + 1 + return dp[sum] + + +if __name__ == "__main__": + coins = [9, 6, 5, 1] + m = len(coins) + amount = 11 + print("Minimum coins:",min_coins(coins,amount)) \ No newline at end of file diff --git a/Linked Lists/circular_linked_list.py b/Linked Lists/circular_linked_list.py new file mode 100644 index 0000000..30de914 --- /dev/null +++ b/Linked Lists/circular_linked_list.py @@ -0,0 +1,55 @@ +#Represents the node of list. +class Node: + def __init__(self,data): + self.data = data; + self.next = None; + +class CreateList: + #Declaring head and tail pointer as null. + def __init__(self): + self.head = Node(None); + self.tail = Node(None); + self.head.next = self.tail; + self.tail.next = self.head; + + #This function will add the new node at the end of the list. + def add(self,data): + newNode = Node(data); + #Checks if the list is empty. + if self.head.data is None: + #If list is empty, both head and tail would point to new node. + self.head = newNode; + self.tail = newNode; + newNode.next = self.head; + else: + #tail will point to new node. + self.tail.next = newNode; + #New node will become new tail. + self.tail = newNode; + #Since, it is circular linked list tail will point to head. + self.tail.next = self.head; + + #Displays all the nodes in the list + def display(self): + current = self.head; + if self.head is None: + print("List is empty"); + return; + else: + print("Nodes of the circular linked list: "); + #Prints each node by incrementing pointer. + print(current.data), + while(current.next != self.head): + current = current.next; + print(current.data), + + +class CircularLinkedList: + cl = CreateList(); + #Adds data to the list + cl.add(1); + cl.add(2); + cl.add(3); + cl.add(4); + #Displays all the nodes present in the list + cl.display(); diff --git a/Queue/CicularQueue.py b/Queue/CicularQueue.py index 43f1086..59785c5 100644 --- a/Queue/CicularQueue.py +++ b/Queue/CicularQueue.py @@ -2,41 +2,49 @@ class CircularQueue(object): def __init__(self, limit = 10): - self.front = None - self.rear = None self.limit = limit - self.queue = [] + self.queue = [None for i in range(limit)] + self.front = self.rear = -1 # for printing the queue def __str__(self): - return ' '.join([str(i) for i in self.queue]) + if (self.rear >= self.front): + return ' '.join([str(self.queue[i]) for i in range(self.front, self.rear + 1)]) + + else: + q1 = ' '.join([str(self.queue[i]) for i in range(self.front, self.limit)]) + q2 = ' '.join([str(self.queue[i]) for i in range(0, self.rear + 1)]) + return q1 + ' ' + q2 # for checking if queue is empty def isEmpty(self): - return self.queue == [] + return self.front == -1 # for checking if the queue is full def isFull(self): - return len(self.queue) == self.limit + return (self.rear + 1) % self.limit == self.front - # for adding an element at the rear end + # for adding an element to the queue def enqueue(self, data): if self.isFull(): print('Queue is Full!') elif self.isEmpty(): - self.front = self.rear = 0 - self.queue.append(data) + self.front = 0 + self.rear = 0 + self.queue[self.rear] = data else: - self.rear += 1 - self.queue.append(data) + self.rear = (self.rear + 1) % self.limit + self.queue[self.rear] = data - # for deleting the deleting an element from front end + # for removing an element from the queue def dequeue(self): if self.isEmpty(): print('Queue is Empty!') + elif (self.front == self.rear): + self.front = -1 + self.rear = -1 else: - self.front += 1 - return self.queue.pop(0) + self.front = (self.front + 1) % self.limit if __name__ == '__main__': myCQ = CircularQueue(5) diff --git a/Queue/Queue.py b/Queue/Queue.py index 0305c4f..38edddc 100644 --- a/Queue/Queue.py +++ b/Queue/Queue.py @@ -35,7 +35,7 @@ def dequeue(self): if self.isEmpty(): return -1 # queue underflow else: - self.queue.pop() + self.queue.pop(0) self.size -= 1 if self.size == 0: self.front = self.rear = 0 diff --git a/README.md b/README.md index fb30c5b..a19c7d8 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,12 @@ Pune, Maharashtra, India.
* [Fibonacci Series](Dynamic%20Programming/P01_Fibonnaci.py) * [Longest Increasing Subsequence](Dynamic%20Programming/P02_LongestIncreasingSubsequence.py) * [Longest Continuous Odd Subsequence](Dynamic%20Programming/P03_LongestContinuousOddSubsequence.py) +* [Count Minimum Number of Coins](Dynamic%20Programming/mincoin.py) + +# Donation + +If you have found my softwares to be of any use to you, do consider helping me pay my internet bills. This would encourage me to create many such softwares :) + +| PayPal | Donate via PayPal! | +|:-------------------------------------------:|:-------------------------------------------------------------:| +| ₹ (INR) | Donate via Instamojo | diff --git a/Segment Tree/SegmentTree.py b/Segment Tree/SegmentTree.py new file mode 100644 index 0000000..0c0faba --- /dev/null +++ b/Segment Tree/SegmentTree.py @@ -0,0 +1,66 @@ +class SegmentTree: + def __init__(self, values): + self.valarr = values + self.arr = dict() + + #start is the starting index of node. + #end is the ending index of node. + #l is the lower bound of given query. + #r is the upper bound of given query. + + def buildTree(self, start, end, node): + if start == end: + self.arr[node] = self.valarr[start] + return + mid = (start+end)//2 + #Building the left subtree of the node. + self.buildTree(start, mid, node*2) + #Building the right subtree of the node. + self.buildTree(mid+1, end, node*2+1) + #Assign the value of node as the sum of its children. + self.arr[node] = self.arr[node*2]+self.arr[node*2+1] + + def rangeQuery(self, node, start, end, l, r): + #When start and end index of the given node lies between the query range[l, r]. + if (l <= start and r >= end): + return self.arr[node] + #When the start and end index of the given node lies completely outside of the query range[l, r]. + if (end < l or start > r): + return 0 + #In case of overlapping of the regions of the start and end index of node and query range[l, r]. + mid = (start+end)//2 + return self.rangeQuery(2*node, start, mid, l, r) + self.rangeQuery(2*node+1, mid+1, end, l, r) + + def update(self, node, newvalue, oldvalue, position, start, end): + #If position where the given value to be inserted lies within start and end index of the node. + if start <= position <= end: + self.arr[node] += (newvalue-oldvalue) + #Updating all those nodes where position lies within its start and end index. + if start != end: + mid = (start+end)//2 + self.update(node*2, newvalue, oldvalue, position, start, mid) + self.update(node*2+1, newvalue, oldvalue, position, mid+1, end) + +#Code to run the above functions +if __name__ == '__main__': + l = list(map(int, input("Enter the elements of the array separated by space:\n").split())) + st = SegmentTree(l) + st.buildTree(0, len(l)-1, 1) + + #I have assumed 1 as the base index instead of 0. + baseindex = 1 + endindex = len(l) + + #To print the constructed segment tree. + print(st.arr) + + #To print the sum of numbers between index 3 and 5. + print("Sum of numbers from index 3 and 5 is: ", st.rangeQuery(1, baseindex, endindex, 3, 5)) + + #Updating 3rd element of the array to 10. + updateindex = 3 + updatevalue = 10 + st.update(1, updatevalue, l[updateindex-1], updateindex, baseindex, endindex) + + #To print the sum of numbers between index 3 and 5 after updation + print("Updated sum of numbers from index 3 and 5 is: ", st.rangeQuery(1, baseindex, endindex, 3, 5)) \ No newline at end of file diff --git a/Segment Tree/SegmentTree2.py b/Segment Tree/SegmentTree2.py new file mode 100644 index 0000000..0b9291a --- /dev/null +++ b/Segment Tree/SegmentTree2.py @@ -0,0 +1,56 @@ +import sys + +class SegmentTree: + def __init__(self, values): + self.minarr = dict() + #Storing the original array for later use. + self.originalarr = values[:] + + # start is the starting index of node. + # end is the ending index of node. + # l is the lower bound of given query. + # r is the upper bound of given query. + + def buildminTree(self, start, end, node): + if start == end: + self.minarr[node] = self.originalarr[start] + return + mid = (start + end) // 2 + # Building the left subtree of the node. + self.buildminTree(start, mid, node*2) + # Building the right subtree of the node. + self.buildminTree(mid + 1, end, node*2 + 1) + # Assign the value of node as the sum of its children. + self.minarr[node] = min(self.minarr[node*2], self.minarr[node*2 + 1]) + + def minRangeQuery(self, node, start, end, l, r): + if l <= start and end <= r: + return self.minarr[node] + if r < start or l > end: + return sys.maxsize + mid = (start+end)//2 + return min(self.minRangeQuery(node*2, start, mid, l, r), self.minRangeQuery(node*2+1, mid+1, end, l, r)) + + def update(self, node, newvalue, position, start, end): + if start <= position <= end: + self.minarr[node] = min(self.minarr[node], newvalue) + # Updating all those nodes where position lies within its start and end index. + if start != end: + mid = (start + end) // 2 + self.update(node * 2, newvalue, position, start, mid) + self.update(node * 2 + 1, newvalue, position, mid + 1, end) + +arr = [10, 5, 9, 3, 4, 8, 6, 7, 2, 1] +st = SegmentTree(arr) +st.buildminTree(0, len(arr)-1, 1) +print("Segment Tree for given array", st.minarr) + +#Minimum from index 6 to 9. +print("Minimum of numbers from index 6 to 9 is: ", st.minRangeQuery(1, 1, len(arr), 6, 9)) +st.update(1, 2, 4, 1, len(arr)) + +print(st.minarr) + +print("Updated minimum of numbers from index 2 to 9 is: ", st.minRangeQuery(1, 1, len(arr), 2, 6)) + +#ALl the operations are same for max range Query. Just replace each min() with max and sys.maxsize with -(sys.maxsize). \ No newline at end of file diff --git a/Sorting/.ipynb_checkpoints/5. Quick_Sort-checkpoint.ipynb b/Sorting/.ipynb_checkpoints/5. Quick_Sort-checkpoint.ipynb new file mode 100644 index 0000000..9e2186d --- /dev/null +++ b/Sorting/.ipynb_checkpoints/5. Quick_Sort-checkpoint.ipynb @@ -0,0 +1,210 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quick Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def partition(array, low, high):\n", + " i = low - 1\n", + " pivot = array[high]\n", + " \n", + " for j in range(low, high):\n", + " if array[j] < pivot:\n", + " i += 1\n", + " array[i], array[j] = array[j], array[i]\n", + " \n", + " array[i + 1], array[high] = array[high], array[i + 1]\n", + " return i + 1\n", + "\n", + "def quick_sort(array, low, high):\n", + " if low < high:\n", + " temp = partition(array, low, high)\n", + " \n", + " quick_sort(array, low, temp - 1)\n", + " quick_sort(array, temp + 1, high)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Time Complexity:\n", + "\n", + "- Best Case: O(n log2(n))\n", + "- Average Case: O(n log2(n))\n", + "- Worst Case: O(n * n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are already sorted\n", + "array = [i for i in range(1, 20)]\n", + "\n", + "print(array)\n", + "# 20 ALREADY sorted elements need 18 iterations approx = n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 3, 2, 13, 6, 17, 5, 7, 9, 11, 8, 19, 18, 4, 16, 10, 14, 12, 15]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "import random\n", + "# elements are randomly shuffled\n", + "array = [i for i in range(1, 20)]\n", + "random.shuffle(array)\n", + "print(array)\n", + "# 20 shuffled elements need 324 iterations approx = n * n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are reverse sorted\n", + "array = [i for i in range(1, 20)]\n", + "# reversing the array\n", + "array = array[::-1]\n", + "\n", + "print(array)\n", + "# 20 REVERSE sorted elements need 324 iterations approx = n * n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why Quick Sort is preferred over MergeSort for sorting Arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Quick Sort in its general form is an in-place sort (i.e. it doesn’t require any extra storage) whereas merge sort requires O(N) extra storage, N denoting the array size which may be quite expensive. Allocating and de-allocating the extra space used for merge sort increases the running time of the algorithm. Comparing average complexity we find that both type of sorts have O(NlogN) average complexity but the constants differ. For arrays, merge sort loses due to the use of extra O(N) storage space.\n", + "\n", + "Most practical implementations of Quick Sort use randomized version. The randomized version has expected time complexity of O(nLogn). The worst case is possible in randomized version also, but worst case doesn’t occur for a particular pattern (like sorted array) and randomized Quick Sort works well in practice.\n", + "\n", + "Quick Sort is also a cache friendly sorting algorithm as it has good locality of reference when used for arrays.\n", + "\n", + "Quick Sort is also tail recursive, therefore tail call optimizations is done." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why MergeSort is preferred over QuickSort for Linked Lists?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case of linked lists the case is different mainly due to difference in memory allocation of arrays and linked lists. Unlike arrays, linked list nodes may not be adjacent in memory. Unlike array, in linked list, we can insert items in the middle in O(1) extra space and O(1) time. Therefore merge operation of merge sort can be implemented without extra space for linked lists.\n", + "\n", + "In arrays, we can do random access as elements are continuous in memory. Let us say we have an integer (4-byte) array A and let the address of A[0] be x then to access A[i], we can directly access the memory at (x + i*4). Unlike arrays, we can not do random access in linked list. Quick Sort requires a lot of this kind of access. In linked list to access i’th index, we have to travel each and every node from the head to i’th node as we don’t have continuous block of memory. Therefore, the overhead increases for quick sort. Merge sort accesses data sequentially and the need of random access is low." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/1. Bubble_Sort.ipynb b/Sorting/1. Bubble_Sort.ipynb new file mode 100644 index 0000000..f8e6807 --- /dev/null +++ b/Sorting/1. Bubble_Sort.ipynb @@ -0,0 +1,205 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bubble Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def bubble_sort(array):\n", + " n=len(array)\n", + " for i in range(0,n):\n", + " for j in range(0,n-i-1):\n", + " if array[j] > array[j + 1]:\n", + " array[j], array[j + 1] = array[j + 1], array[j] # swap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimized Bubble Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# optimized bubble sort does not increase or decrease asymtotic notations\n", + "# however number of iterations can be reduced to some extent\n", + "def optimized_bubble_sort(array):\n", + " global iterations\n", + " iterations = 0\n", + " for i in range(len(array) - 1):\n", + " swapped = False\n", + " for j in range(len(array) - 1):\n", + " iterations += 1\n", + " if array[j] > array[j + 1]:\n", + " array[j], array[j + 1] = array[j + 1], array[j]\n", + " swapped = True\n", + " # if no swapping is performed that means sorting is complete\n", + " # hence break out of the loop\n", + " if not swapped: \n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Time Complexity:\n", + "\n", + "- Best Case: O(n)\n", + "- Average Case: O(n * n)\n", + "- Worst Case: O(n * n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18\n" + ] + } + ], + "source": [ + "# elements are already sorted\n", + "array = [i for i in range(1, 20)]\n", + "\n", + "optimized_bubble_sort(array)\n", + "# 20 ALREADY sorted elements need 18 iterations approx = n\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "324\n" + ] + } + ], + "source": [ + "import random\n", + "# elements are randomly shuffled\n", + "array = [i for i in range(1, 20)]\n", + "random.shuffle(array)\n", + "\n", + "optimized_bubble_sort(array)\n", + "# 20 shuffled elements need 324 iterations approx = n * n\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "324\n" + ] + } + ], + "source": [ + "# elements are reverse sorted\n", + "array = [i for i in range(1, 20)]\n", + "# reversing the array\n", + "array = array[::-1]\n", + "\n", + "optimized_bubble_sort(array)\n", + "# 20 REVERSE sorted elements need 324 iterations approx = n * n\n", + "\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Applications" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you're doing something quick and dirty and for some reason you can't just use the standard library's sorting algorithm. The only advantage this has over insertion sort is being slightly easier to implement." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/2. Selection_Sort.ipynb b/Sorting/2. Selection_Sort.ipynb new file mode 100644 index 0000000..189abe7 --- /dev/null +++ b/Sorting/2. Selection_Sort.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Selection Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def selection_sort(array):\n", + " global iterations\n", + " iterations = 0\n", + " for i in range(len(array)):\n", + " minimum_index = i\n", + " for j in range(i + 1, len(array)):\n", + " iterations += 1\n", + " if array[minimum_index] > array[j]:\n", + " minimum_index = j\n", + " \n", + " # Swap the found minimum element with \n", + " # the first element\n", + " if minimum_index != i:\n", + " array[i], array[minimum_index] = array[minimum_index], array[i]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Time Complexity:\n", + "\n", + "- Best Case: O(n * n)\n", + "- Average Case: O(n * n)\n", + "- Worst Case: O(n * n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "190\n" + ] + } + ], + "source": [ + "# When array is already sorted\n", + "array = [i for i in range(20)]\n", + "selection_sort(array)\n", + "\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "190\n" + ] + } + ], + "source": [ + "# when array is shuffled\n", + "import random\n", + "array = [i for i in range(20)]\n", + "random.shuffle(array)\n", + "\n", + "selection_sort(array)\n", + "\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "190\n" + ] + } + ], + "source": [ + "# when array is reverse sorted\n", + "array = [i for i in range(20)]\n", + "array = array[::-1]\n", + "\n", + "selection_sort(array)\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Applications:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "No real life applications as such because of O(n * n) behaviour. However, bubble sort, selection sort and insertion sort are the basic sorting algorithm taught for every beginner" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/3. Insertion_Sort.ipynb b/Sorting/3. Insertion_Sort.ipynb new file mode 100644 index 0000000..3018325 --- /dev/null +++ b/Sorting/3. Insertion_Sort.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Insertion Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def insertion_sort(array):\n", + " global iterations\n", + " iterations = 0\n", + " for i in range(1, len(array)):\n", + " current_value = array[i]\n", + " for j in range(i - 1, -1, -1):\n", + " iterations += 1\n", + " if array[j] > current_value:\n", + " array[j], array[j + 1] = array[j + 1], array[j] # swap\n", + " else:\n", + " array[j + 1] = current_value\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Time Complexity:\n", + "\n", + "- Best Case: O(n)\n", + "- Average Case: O(n * n)\n", + "- Worst Case: O(n * n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "18\n" + ] + } + ], + "source": [ + "# elements are already sorted\n", + "array = [i for i in range(1, 20)]\n", + "\n", + "insertion_sort(array)\n", + "# 20 ALREADY sorted elements need 18 iterations approx = n\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "89\n" + ] + } + ], + "source": [ + "import random\n", + "# elements are randomly shuffled\n", + "array = [i for i in range(1, 20)]\n", + "random.shuffle(array)\n", + "\n", + "insertion_sort(array)\n", + "# 20 shuffled elements need 324 iterations approx = n * n\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "171\n" + ] + } + ], + "source": [ + "# elements are reverse sorted\n", + "array = [i for i in range(1, 20)]\n", + "# reversing the array\n", + "array = array[::-1]\n", + "\n", + "insertion_sort(array)\n", + "# 20 REVERSE sorted elements need 324 iterations approx = n * n\n", + "\n", + "print(array)\n", + "print(iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Applications" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "More efficient that other sorts when N is comparatively smaller, say < 30." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/4. Merge_Sort.ipynb b/Sorting/4. Merge_Sort.ipynb new file mode 100644 index 0000000..4baecb8 --- /dev/null +++ b/Sorting/4. Merge_Sort.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Merge Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "def merge_sort(array):\n", + " if len(array) < 2:\n", + " return array\n", + " \n", + " mid = len(array) // 2\n", + " left = merge_sort(array[:mid])\n", + " right = merge_sort(array[mid:])\n", + " \n", + " return merge(left, right)\n", + "\n", + "def merge(left, right):\n", + " result = []\n", + " i, j = 0, 0\n", + " while i < len(left) or j < len(right):\n", + " if left[i] <= right[j]:\n", + " result.append(left[i])\n", + " i += 1\n", + " else:\n", + " result.append(right[j])\n", + " j += 1\n", + " if i == len(left) or j == len(right):\n", + " result.extend(left[i:] or right[j:])\n", + " break\n", + " \n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Time Complexity:\n", + "\n", + "- Best Case: O(n log2(n))\n", + "- Average Case: O(n log2(n))\n", + "- Worst Case: O(n log2(n))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why O(n log n) ?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are given two sorted arrays(say A & B) of length n/2 then it will take O(n) time to merge and make a sorted array of length n.\n", + "\n", + "But if A and B are not sorted then we need to sort them first. For this we first divide array A and B of length n/2 each into two arrays of length n/4 and suppose these two arrays are already sorted.\n", + "\n", + "Now to merge two sorted array of length n/4 to make array A of length n/2 will take O(n/2) time and similarly array B formation will also take O(n/2) time.\n", + "\n", + "So total time to make array A and B both also took O(n). So at every stage it is taking O(n) time. So the total time for merge sort will be O(no. of stages * n).\n", + "\n", + "Here we are dividing array into two parts at every stage and we will continue dividing untill length of two divided array is one.\n", + "\n", + "So if length of array is eight then we need to divide it three times to get arrays of length one like this\n", + "\n", + "8 = 4+4 = 2+2+2+2 = 1+1+1+1+1+1+1+1\n", + "\n", + "So\n", + "\n", + "no. of stages = log2(8) = 3\n", + "\n", + "That is why merge sort is O(nlog(n)) with log2(n) iteration.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are already sorted\n", + "array = [i for i in range(1, 20)]\n", + "\n", + "print(array)\n", + "# 20 ALREADY sorted elements need 18 iterations approx = n\n", + "print(merge_sort(array))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[5, 2, 17, 15, 3, 13, 9, 12, 7, 19, 11, 18, 14, 10, 1, 16, 4, 8, 6]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "import random\n", + "# elements are randomly shuffled\n", + "array = [i for i in range(1, 20)]\n", + "random.shuffle(array)\n", + "print(array)\n", + "# 20 shuffled elements need 324 iterations approx = n * n\n", + "print(merge_sort(array))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are reverse sorted\n", + "array = [i for i in range(1, 20)]\n", + "# reversing the array\n", + "array = array[::-1]\n", + "\n", + "print(array)\n", + "# 20 REVERSE sorted elements need 324 iterations approx = n * n\n", + "print(merge_sort(array))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/5. Quick_Sort.ipynb b/Sorting/5. Quick_Sort.ipynb new file mode 100644 index 0000000..cc2d444 --- /dev/null +++ b/Sorting/5. Quick_Sort.ipynb @@ -0,0 +1,218 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quick Sort" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def partition(array, low, high):\n", + " i = low - 1 # index of smaller element\n", + " pivot = array[high] # pivot \n", + " \n", + " for j in range(low, high):\n", + " # If current element is smaller than the pivot\n", + " \n", + " if array[j] < pivot:\n", + " # increment index of smaller element\n", + " \n", + " i += 1\n", + " array[i], array[j] = array[j], array[i]\n", + " \n", + " array[i + 1], array[high] = array[high], array[i + 1]\n", + " return i + 1\n", + "\n", + "def quick_sort(array, low, high):\n", + " if low < high:\n", + " # pi is partitioning index, arr[p] is now\n", + " # at right place \n", + " temp = partition(array, low, high)\n", + " \n", + " # Separately sort elements before\n", + " # partition and after partition \n", + " quick_sort(array, low, temp - 1)\n", + " quick_sort(array, temp + 1, high)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Time Complexity:\n", + "\n", + "- Best Case: O(n log2(n))\n", + "- Average Case: O(n log2(n))\n", + "- Worst Case: O(n * n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code for executing and seeing the difference in time complexities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Best Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are already sorted\n", + "array = [i for i in range(1, 20)]\n", + "\n", + "print(array)\n", + "# 20 ALREADY sorted elements need 18 iterations approx = n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Average Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 3, 2, 13, 6, 17, 5, 7, 9, 11, 8, 19, 18, 4, 16, 10, 14, 12, 15]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "import random\n", + "# elements are randomly shuffled\n", + "array = [i for i in range(1, 20)]\n", + "random.shuffle(array)\n", + "print(array)\n", + "# 20 shuffled elements need 324 iterations approx = n * n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Worst Case Performance:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n" + ] + } + ], + "source": [ + "# elements are reverse sorted\n", + "array = [i for i in range(1, 20)]\n", + "# reversing the array\n", + "array = array[::-1]\n", + "\n", + "print(array)\n", + "# 20 REVERSE sorted elements need 324 iterations approx = n * n\n", + "quick_sort(array, 0, len(array) - 1)\n", + "print(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why Quick Sort is preferred over MergeSort for sorting Arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Quick Sort in its general form is an in-place sort (i.e. it doesn’t require any extra storage) whereas merge sort requires O(N) extra storage, N denoting the array size which may be quite expensive. Allocating and de-allocating the extra space used for merge sort increases the running time of the algorithm. Comparing average complexity we find that both type of sorts have O(NlogN) average complexity but the constants differ. For arrays, merge sort loses due to the use of extra O(N) storage space.\n", + "\n", + "Most practical implementations of Quick Sort use randomized version. The randomized version has expected time complexity of O(nLogn). The worst case is possible in randomized version also, but worst case doesn’t occur for a particular pattern (like sorted array) and randomized Quick Sort works well in practice.\n", + "\n", + "Quick Sort is also a cache friendly sorting algorithm as it has good locality of reference when used for arrays.\n", + "\n", + "Quick Sort is also tail recursive, therefore tail call optimizations is done." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why MergeSort is preferred over QuickSort for Linked Lists?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case of linked lists the case is different mainly due to difference in memory allocation of arrays and linked lists. Unlike arrays, linked list nodes may not be adjacent in memory. Unlike array, in linked list, we can insert items in the middle in O(1) extra space and O(1) time. Therefore merge operation of merge sort can be implemented without extra space for linked lists.\n", + "\n", + "In arrays, we can do random access as elements are continuous in memory. Let us say we have an integer (4-byte) array A and let the address of A[0] be x then to access A[i], we can directly access the memory at (x + i*4). Unlike arrays, we can not do random access in linked list. Quick Sort requires a lot of this kind of access. In linked list to access i’th index, we have to travel each and every node from the head to i’th node as we don’t have continuous block of memory. Therefore, the overhead increases for quick sort. Merge sort accesses data sequentially and the need of random access is low." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Sorting/Counting_sort.py b/Sorting/Counting_sort.py new file mode 100644 index 0000000..f537321 --- /dev/null +++ b/Sorting/Counting_sort.py @@ -0,0 +1,32 @@ +def countSort(arr): + + # The output character array that will have sorted arr + output = [0 for i in range(256)] + + # Create a count array to store count of inidividul + # characters and initialize count array as 0 + count = [0 for i in range(256)] + + # For storing the resulting answer since the + # string is immutable + ans = ["" for _ in arr] + + # Store count of each character + for i in arr: + count[ord(i)] += 1 + + # Change count[i] so that count[i] now contains actual + # position of this character in output array + for i in range(256): + count[i] += count[i-1] + + # Build the output character array + for i in range(len(arr)): + output[count[ord(arr[i])]-1] = arr[i] + count[ord(arr[i])] -= 1 + + # Copy the output array to arr, so that arr now + # contains sorted characters + for i in range(len(arr)): + ans[i] = output[i] + return ans diff --git a/Sorting/Radix_Sort.py b/Sorting/Radix_Sort.py new file mode 100644 index 0000000..f0c4184 --- /dev/null +++ b/Sorting/Radix_Sort.py @@ -0,0 +1,43 @@ +# Sorting Type: Radix +# +# Description: Radix sort is a sorting technique that +# sorts the elements by first grouping the individual +# digits of same place value. Then, sort the elements +# according to their increasing/decreasing order. + +from math import log10 +from random import randint + +def get_num(num, base, pos): + return (num // base ** pos) % base + +def prefix_sum(array): + for i in range(1, len(array)): + array[i] = array[i] + array[i-1] + return array + +def radixsort(l, base=10): + passes = int(log10(max(l))+1) + output = [0] * len(l) + + for pos in range(passes): + count = [0] * base + + for i in l: + digit = get_num(i, base, pos) + count[digit] +=1 + + count = prefix_sum(count) + + for i in reversed(l): + digit = get_num(i, base, pos) + count[digit] -= 1 + new_pos = count[digit] + output[new_pos] = i + + l = list(output) + return output + +l = [ randint(1, 99999) for x in range(100) ] +sorted = radixsort(l) +print(sorted) diff --git a/Sorting/heapsort.py b/Sorting/heapsort.py new file mode 100644 index 0000000..37b469c --- /dev/null +++ b/Sorting/heapsort.py @@ -0,0 +1,32 @@ +def heapify(nums, heap_size, root_index): + # Assume the index of the largest element is the root index + largest = root_index + left_child = (2 * root_index) + 1 + right_child = (2 * root_index) + 2 + + if left_child < heap_size and nums[left_child] > nums[largest]: + largest = left_child + + if right_child < heap_size and nums[right_child] > nums[largest]: + largest = right_child + + if largest != root_index: + nums[root_index], nums[largest] = nums[largest], nums[root_index] + # Heapify the new root element to ensure it's the largest + heapify(nums, heap_size, largest) + + +def heap_sort(nums): + n = len(nums) + + for i in range(n, -1, -1): + heapify(nums, n, i) + + # Move the root of the max heap to the end of + for i in range(n - 1, 0, -1): + nums[i], nums[0] = nums[0], nums[i] + heapify(nums, i, 0) + +random_list_of_nums = [35, 12, 43, 8, 51] +heap_sort(random_list_of_nums) +print(random_list_of_nums) diff --git a/Stack/P05_QueueImplementationUsingTwoStacks.py b/Stack/P05_QueueImplementationUsingTwoStacks.py new file mode 100644 index 0000000..22afe9b --- /dev/null +++ b/Stack/P05_QueueImplementationUsingTwoStacks.py @@ -0,0 +1,49 @@ +class StackedQueue: + + def __init__(self): + self.stack = Stack() + self.alternateStack = Stack() + + def enqueue(self, item): + while(not self.stack.is_empty()): + self.alternateStack.push(self.stack.pop()) + + self.alternateStack.push(item) + + while(not self.alternateStack.is_empty()): + self.stack.push(self.alternateStack.pop()) + + def dequeue(self): + return self.stack.pop() + + def __repr__(self): + return repr(self.stack) + +class Stack: + def __init__(self): + self.items = [] + + def push(self, item): + self.items.append(item) + + def pop(self): + return self.items.pop() + + def size(self): + return len(self.items) + + def is_empty(self): + return self.items == [] + + def __repr__(self): + return str(self.items) + +if __name__ == "__main__": + structure = StackedQueue() + structure.enqueue(4) + structure.enqueue(3) + structure.enqueue(2) + structure.enqueue(1) + print(structure) + structure.dequeue() + print(structure) \ No newline at end of file diff --git a/Stack/Stack.ipynb b/Stack/Stack.ipynb index d494183..5fb3412 100644 --- a/Stack/Stack.ipynb +++ b/Stack/Stack.ipynb @@ -107,7 +107,7 @@ " \n", " # to check if stack is empty\n", " def isEmpty(self):\n", - " return len(self.stack) == []\n", + " return len(self.stack) == 0\n", " \n", " # for checking the size of stack\n", " def size(self):\n", diff --git a/Trees/BinarySearchTree.py b/Trees/BinarySearchTree.py index 7992615..ea0e15a 100644 --- a/Trees/BinarySearchTree.py +++ b/Trees/BinarySearchTree.py @@ -44,23 +44,45 @@ def minValueNode(self, node): return current - def delete(self, data): + def maxValueNode(self, node): + current = node + + # loop down to find the leftmost leaf + while(current.rightChild is not None): + current = current.rightChild + + return current + + + def delete(self, data,root): ''' For deleting the node ''' if self is None: - return root + return None # if current node's data is less than that of root node, then only search in left subtree else right subtree if data < self.data: - self.leftChild = self.leftChild.delete(data) + self.leftChild = self.leftChild.delete(data,root) elif data > self.data: - self.rightChild = self.rightChild.delete(data) + self.rightChild = self.rightChild.delete(data,root) else: # deleting node with one child if self.leftChild is None: + + if self == root: + temp = self.minValueNode(self.rightChild) + self.data = temp.data + self.rightChild = self.rightChild.delete(temp.data,root) + temp = self.rightChild self = None return temp elif self.rightChild is None: + + if self == root: + temp = self.maxValueNode(self.leftChild) + self.data = temp.data + self.leftChild = self.leftChild.delete(temp.data,root) + temp = self.leftChild self = None return temp @@ -69,7 +91,7 @@ def delete(self, data): # first get the inorder successor temp = self.minValueNode(self.rightChild) self.data = temp.data - self.rightChild = self.rightChild.delete(temp.data) + self.rightChild = self.rightChild.delete(temp.data,root) return self @@ -128,7 +150,7 @@ def insert(self, data): def delete(self, data): if self.root is not None: - return self.root.delete(data) + return self.root.delete(data,self.root) def find(self, data): if self.root: @@ -189,4 +211,4 @@ def postorder(self): print('\n\nAfter deleting 10') tree.delete(10) tree.inorder() - tree.preorder() + tree.preorder() \ No newline at end of file diff --git a/Trees/ListViewUsingTree.py b/Trees/ListViewUsingTree.py new file mode 100644 index 0000000..5f12065 --- /dev/null +++ b/Trees/ListViewUsingTree.py @@ -0,0 +1,67 @@ +# Author: Parameswaran + +# sample object +class Sample: + def __init__(self, data_description, node_id, parent_id=""): + self.data_description = data_description + self.node_id = node_id + self.parent_id = parent_id + + +# Node structure (Basically N-ary Tree) +class Node: + def __init__(self, data): + self.data = Sample(data['data_description'], data['node_id'], data['parent_id']) + self.children = [] + + +class Tree: + def __init__(self, data): + self.Root = data + + def insert_child(self, root, new_node): + + # if the list item's parent_id is equal to the current node it will append the node in their child array. + + if root.data.node_id == new_node.data.parent_id: + root.children.append(new_node) + + # else it will check all the node and their children list whether the parent_id is same. + + elif len(root.children) > 0: + for each_child in root.children: + # it will create a recursive call for all nodes to treate as a root and search for all its child_list nodes + self.insert_child(each_child, new_node) + + def print_tree(self, root, point): + print(point, root.data.node_id, root.data.parent_id, root.data.data_description) + if len(root.children) > 0: + point += "_" + for each_child in root.children: + self.print_tree(each_child, point) + + +data = {'data_description': 'Sample_root_1', 'node_id': '1', 'parent_id': ''} +data1 = {'data_description': 'Sample_root_2', 'node_id': '2', 'parent_id': '1'} +data2 = {'data_description': 'Sample_root_3', 'node_id': '3', 'parent_id': '1'} +data3 = {'data_description': 'Sample_root_4', 'node_id': '4', 'parent_id': '2'} +data4 = {'data_description': 'Sample_root_5', 'node_id': '5', 'parent_id': '3'} +data5 = {'data_description': 'Sample_root_6', 'node_id': '6', 'parent_id': '4'} +data6 = {'data_description': 'Sample_root_7', 'node_id': '7', 'parent_id': '4'} + +a = Tree(Node(data)) +a.insert_child(a.Root, Node(data1)) +a.insert_child(a.Root, Node(data2)) +a.insert_child(a.Root, Node(data3)) +a.insert_child(a.Root, Node(data4)) +a.insert_child(a.Root, Node(data5)) +a.insert_child(a.Root, Node(data6)) +a.print_tree(a.Root, "|_") + +# |_ 1 Sample_root_1 +# |__ 2 1 Sample_root_2 +# |___ 4 2 Sample_root_4 +# |____ 6 4 Sample_root_6 +# |____ 7 4 Sample_root_7 +# |__ 3 1 Sample_root_3 +# |___ 5 3 Sample_root_5 diff --git a/Trees/TreeTravesal.py b/Trees/TreeTravesal.py new file mode 100644 index 0000000..8f8ef4c --- /dev/null +++ b/Trees/TreeTravesal.py @@ -0,0 +1,59 @@ + + +class Node(object): + """docstring for Node""" + def __init__(self, val): + self.key = val + self.left=None + self.right=None + + +class Tree: + """docstring for Tree""" + def __init__(self,val): + self.root = Node(val) + + def insertNode(root,val): + if(root==None): + root=Node(val) + elif(root.key Node: + root = Node(1) + root.left = Node(2) + root.right = Node(3) + root.left.left = Node(4) + root.left.right = Node(5) + return root + +def zigzag_iterative(root: Node): + """ + ZigZag traverse by iterative method: Print node left to right and right to left, alternatively. + """ + if root == None: + return + + # two stacks to store alternate levels + s1 = [] # For levels to be printed from right to left + s2 = [] # For levels to be printed from left to right + + # append first level to first stack 's1' + s1.append(root) + + # Keep printing while any of the stacks has some nodes + while not len(s1) == 0 or not len(s2) == 0: + + # Print nodes of current level from s1 and append nodes of next level to s2 + while not len(s1) == 0: + temp = s1[-1] + s1.pop() + print(temp.data, end = " ") + + # Note that is left is appended before right + if temp.left: + s2.append(temp.left) + if temp.right: + s2.append(temp.right) + + # Print nodes of current level from s2 and append nodes of next level to s1 + while not len(s2) == 0: + temp = s2[-1] + s2.pop() + print(temp.data, end = " ") + + # Note that is rightt is appended before left + if temp.right: + s1.append(temp.right) + if temp.left: + s1.append(temp.left) + +def main(): # Main function for testing. + """ + Create binary tree. + """ + root = make_tree() + print("\nZigzag order traversal(iterative) is: ") + zigzag_iterative(root) + print() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() +