├── .gitignore ├── Algorithms ├── Kadane's-Algorithm.py ├── Searching │ ├── binary-search.py │ └── linear-search.py ├── Sorting │ ├── bubble-sort.py │ ├── bucket-sort.py │ ├── insertion-sort.py │ ├── mergeSort.py │ ├── selection-sort.py │ └── shell-sort.py └── Strings │ └── KMP.py ├── Data-Structures ├── Graphs │ ├── bfs.py │ ├── dfs.py │ └── graphs-using-adjacency-list.py ├── Linked-List │ ├── Doubly-Linked-List.py │ └── Singly-Linked-List.py ├── Queue │ ├── Priority-Queues │ │ └── binary-heaps.py │ ├── queue-using-linked-list.py │ └── queue.py ├── Stack │ ├── stack-using-linked-list.py │ └── stack.py ├── Tree │ └── Binary-Search-Tree.py ├── Trie │ └── trie.py └── array.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Data-Structures/.DS_Store 3 | -------------------------------------------------------------------------------- /Algorithms/Kadane's-Algorithm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kadane’s algorithm is a Dynamic Programming approach to solve “the largest contiguous elements in an array” with runtime of O(n). 3 | """ 4 | def maxSubArray(nums): 5 | if len(nums) == 0: 6 | return 0 7 | Sum = nums[0] 8 | result = Sum 9 | for i in range(1,len(nums)): 10 | Sum = (Sum + nums[i]) if(Sum+nums[i]) >= nums[i] else nums[i] 11 | result = Sum if Sum > result else result 12 | return result 13 | -------------------------------------------------------------------------------- /Algorithms/Searching/binary-search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Binary search is the most popular Search algorithm.It is efficient and also one of the most commonly used techniques 3 | that is used to solve problems. 4 | Binary search works only on a sorted set of elements. To use binary search on a collection, the collection must first be sorted. 5 | 6 | Binary search looks for a particular item by comparing the middle most item of the collection. If a match occurs, 7 | then the index of item is returned. If the middle item is greater than the item, then the item is searched in the sub-array 8 | to the left of the middle item. Otherwise, the item is searched for in the sub-array to the right of the middle item. 9 | This process continues on the sub-array as well until the size of the subarray reduces to zero. 10 | 11 | Time Complexity: 12 | 13 | Best Case O(1) 14 | Average Case O(log n) 15 | Worst Case O(log n) 16 | """ 17 | 18 | def binarySearch(arr,target): 19 | l = len(arr) - 1 20 | start,end = 0,l 21 | mid = (start+end)//2 22 | while start <= end: 23 | if arr[mid] == target: 24 | return mid 25 | elif arr[mid] < target: 26 | start = mid + 1 27 | else: 28 | end = mid - 1 29 | mid = (start+end)//2 30 | return -1 31 | 32 | a = [1,2,4,5,8] 33 | print(binarySearch(a,5)) # it will return 3 as 5 is present at 3rd index 34 | print(binarySearch(a,9)) # it will return -1 since 9 is not present in the list -------------------------------------------------------------------------------- /Algorithms/Searching/linear-search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Problem Statement: 3 | 4 | Given a list of n elements with values and target element, find the index/position of the target in list. 5 | 6 | The linear search is a basic search algorithm which searches all the elements in the list and finds the required value. 7 | This is also known as sequential search. 8 | 9 | Time Complexity: 10 | 11 | Best Case: O(1) 12 | Average Case: O(n) 13 | Worst Case: O(n) 14 | """ 15 | def linearSearch(arr,target): 16 | for i in range(len(arr)): # traversing the list 17 | if arr[i] == target: # comparing the list element with the target value 18 | return i # return the index if list element is equal to the target element 19 | return -1 # if target is not present in the list it will return -1 20 | 21 | a = [1,2,4,5,8] 22 | print(linearSearch(a,5)) # it will return 3 as 5 is present at 3rd index 23 | print(linearSearch(a,9)) # it will return -1 since 9 is not present in the list -------------------------------------------------------------------------------- /Algorithms/Sorting/bubble-sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bubble Sort 3 | Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in wrong order. 4 | 5 | Example: 6 | First Pass: 7 | ( 5 1 4 2 8 ) –> ( 1 5 4 2 8 ), Here, algorithm compares the first two elements, and swaps since 5 > 1. 8 | ( 1 5 4 2 8 ) –> ( 1 4 5 2 8 ), Swap since 5 > 4 9 | ( 1 4 5 2 8 ) –> ( 1 4 2 5 8 ), Swap since 5 > 2 10 | ( 1 4 2 5 8 ) –> ( 1 4 2 5 8 ), Now, since these elements are already in order (8 > 5), algorithm does not swap them. 11 | 12 | Second Pass: 13 | ( 1 4 2 5 8 ) –> ( 1 4 2 5 8 ) 14 | ( 1 4 2 5 8 ) –> ( 1 2 4 5 8 ), Swap since 4 > 2 15 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 16 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 17 | Now, the array is already sorted, but our algorithm does not know if it is completed. The algorithm needs one whole pass without 18 | any swap to know it is sorted. 19 | 20 | Third Pass: 21 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 22 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 23 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 24 | ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 ) 25 | """ 26 | 27 | def bubbleSort(arr): 28 | n = len(arr) 29 | for i in range(n-1): 30 | for j in range(n-i-1): 31 | if arr[j] > arr[j+1]: 32 | arr[j],arr[j+1] = arr[j+1],arr[j] 33 | return arr 34 | # Worst and Average Case Time Complexity: O(n*n). 35 | # Best Case Time Complexity: O(n*n). 36 | 37 | def optimizedBubbleSort(arr): 38 | n = len(arr) 39 | for i in range(n-1): 40 | swap = False 41 | for j in range(n-i-1): 42 | if arr[j] > arr[j+1]: # Swap if the element found is greater than the next element. 43 | arr[j],arr[j+1] = arr[j+1],arr[j] 44 | swap = True 45 | if swap == False: # IF no two elements were swapped by inner loop, then break. (It is the case when the array is already sorted) 46 | break 47 | return arr 48 | # Worst and Average Case Time Complexity: O(n*n). 49 | # Best Case Time Complexity: O(n). 50 | 51 | a = [1,2,4,5,8] 52 | print(*bubbleSort(a)) 53 | print(*optimizedBubbleSort(a)) -------------------------------------------------------------------------------- /Algorithms/Sorting/bucket-sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bucket sort is a comparison sort algorithm that operates on elements by dividing them 3 | into different buckets and then sorting these buckets individually. Each bucket is 4 | sorted individually using a separate sorting algorithm or by applying the bucket 5 | sort algorithm recursively. Bucket sort is mainly useful when the input is uniformly 6 | distributed over a range. 7 | 8 | Average & Best Case Time Complexity: O(n+k) 9 | Worst Case Time Complexity: O(n*n) 10 | 11 | Space complexity: O(n+k). 12 | """ 13 | 14 | import math 15 | def insertionSort(arr): 16 | for i in range(1, len(arr)): 17 | temp = arr[i] 18 | j = i - 1 19 | while j >=0 and arr[j] > temp: 20 | arr[j+1] = arr[j] 21 | j -= 1 22 | arr[j+1] = temp 23 | return arr 24 | 25 | def bucketSort(arr): 26 | buckets = [[] for _ in range(len(arr))] # Creating Buckets 27 | divider = math.ceil((max(max(arr), len(arr))+1)/len(buckets)) 28 | # Adding numbers in buckets 29 | for i in arr: 30 | j = i//divider 31 | buckets[j].append(i) 32 | # Sorting the buckers 33 | for i in range(len(buckets)): 34 | buckets[i] = insertionSort(buckets[i]) 35 | 36 | # Merging Buckets 37 | k = 0 38 | for i in range(len(buckets)): 39 | for j in range(len(buckets[i])): 40 | arr[k] = buckets[i][j] 41 | k += 1 42 | 43 | arr = [10,9,8,7] 44 | bucketSort(arr) 45 | print(arr) -------------------------------------------------------------------------------- /Algorithms/Sorting/insertion-sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------ INSERTION SORT ------------------------- 3 | Insertion sort works the way many people sort a hand of playing cards. 4 | We start with an empty left hand and the cards face down on the table. 5 | We then remove one card at a time from the table and insert it into the 6 | correct position in the left hand. To find the correct position for a card, 7 | we compare it with each of the cards already in the hand, from right to left. 8 | At all times, the cards held in the left hand are sorted, 9 | and these cards were originally the top cards of the pile on the table. 10 | We present our pseudocode for insertion sort as a procedure called INSERTION- SORT, 11 | which takes as a parameter an array A[1..n] containing a sequence of length n that is to be sorted. 12 | The algorithm sorts the input numbers in place: it rearranges the numbers within the array A, 13 | with at most a constant number of them stored outside the array at any time. The input array A 14 | contains the sorted output sequence when the INSERTION-SORT procedure is finished. 15 | 16 | Time Complexity: 17 | Best Case: O(n) 18 | Average Case: O(n*n) 19 | Worst Case: O(n*n) 20 | """ 21 | def insertion_sort(arr): 22 | for i in range(0,len(arr)): 23 | temp = arr[i] 24 | j = i-1 25 | while j>=0 and arr[j]>temp: 26 | arr[j+1] = arr[j] 27 | j-=1 28 | arr[j+1] = temp 29 | print ("Sorted array: ",arr) 30 | 31 | arr = [2, 6, 1, 3, 4, 10] 32 | insertion_sort(arr) -------------------------------------------------------------------------------- /Algorithms/Sorting/mergeSort.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Merge Sort (Jon von Neumann) 3 | * ➤ Divide and Conquer 4 | * ➤ Recursive 5 | * 6 | * Steps: 7 | * → Divide an array into 2 halves. 8 | * → Recursively sort 2 halves. 9 | * → Merge 2 halves. 10 | * 11 | * SORTING HAPPENS IN THE MERGE OPERATION ONLY! 12 | * ➤ Since it's a Divide and Conquer method(Recursive), we break down our array into sub arrays till 13 | * each subarray is has length equal to 1. (N single item sub-arrays) 14 | * Consider this array: [E, A, M, A, R, X, C, K, O, L] 15 | * After we are done with 'sub-arraying' this array, we get: 16 | * [E] [A] [M] [A] [R] [X] [C] [K] [O] [L] 17 | * 18 | * We take 1st two element -> Sort and Merge. 19 | * If you read 'HOW MERGE OPERATION WORKS', you see we have 3 conditions. That's were you sort them. And, 20 | * when you put them in the arr (See, arr[k] = aux[i or j]) => That's where you merge them. 21 | * 22 | * Let's see this in more detail now :- 23 | * 24 | * So, How MERGE OPERATION works? 25 | * Consider this array: [E, E, G, M, R, A, C, E, R, T] 26 | * → Break it in 2 part by finding the 'mid' of the array. (mid = low + (high - low) / 2) 27 | * → Initialize an auxilliary array (aux) and copy the content of 'arr' into 'aux' 28 | * → Let consider these 2 sub arrays which we got after finding the 'mid' to be sorted sub-arrays. 29 | * In this case if you notice, two sub-arrays are sorted. 30 | * 31 | * Objective: Merge this 2 sorted sub-arrays. 32 | * arr = [E, E, G, M, R, A, C, E, R, T] 33 | * k ==> pointer 'k' which operates on our original array and mutate it 34 | * based on the conditions described below 35 | * aux = [E, E, G, M, R, A, C, E, R, T] 36 | * i 🔺j ==> 2 pointers i & j. Both starts from the 1st element of two sub-arrays. 37 | * I've used 🔺 to show the separation of sub-arrays. 38 | * 39 | * Conditions: 40 | * 1. aux[i] > aux[j] ➤ arr[k] = aux[j] and also increment 'k' and 'j' 41 | * 2. aux[i] < aux[j] ➤ arr[k] = aux[i] and also increment 'k' and 'i' 42 | * 3. aux[i] === aux[j] ➤ arr[k] = aux[i] and also increment 'k' and 'i' 43 | * 44 | * Let's start: 45 | * → compare aux[i] and aux[j] 46 | * ➤ aux[i], which is E, is GREATER than aux[j] which is A. This satisfies our Condition 1. 47 | * Resulting arrays: 48 | * arr = [A, E, G, M, R, A, C, E, R, T] 49 | * k => arr[k] = aux[j] & 'k' is incremented by 1 50 | * aux = [E, E, G, M, R, A, C, E, R, T] 51 | * i 🔺 j => 'i' remains the same, 'j' is incremented 52 | * 53 | * ➤ aux[i], which is E, is GREATER than aux[j] which is C. This satisfies our Condition 1. 54 | * Resulting arrays: 55 | * arr = [A, C, G, M, R, A, C, E, R, T] 56 | * k => arr[k] = aux[j] & 'k' is incremented by 1. 57 | * aux = [E, E, G, M, R, A, C, E, R, T] 58 | * i 🔺 j => 'i' remains the same, 'j' is incremented. 59 | * 60 | * ➤ aux[i], which is E, is EQUAL to aux[j] which is also E. This satisfies our Condition 3. 61 | * Resulting arrays: 62 | * arr = [A, C, E, M, R, A, C, E, R, T] 63 | * k => arr[k] = aux[i] & 'k' is incremented by 1 64 | * aux = [E, E, G, M, R, A, C, E, R, T] 65 | * i 🔺 j => 'i' is incremented, 'j' remains the same. 66 | * 67 | * ➤ Again, aux[i], which is E, is EQUAL to aux[j] which is also E. This satisfies our Condition 3. 68 | * Resulting arrays: 69 | * arr = [A, C, E, E, R, A, C, E, R, T] 70 | * k => arr[k] = aux[i] & 'k' is incremented by 1 71 | * aux = [E, E, G, M, R, A, C, E, R, T] 72 | * i 🔺 j => 'i' is incremented, 'j' remains the same. 73 | * 74 | * ➤ aux[i], which is G, is GREATER than aux[j] which is E. This satisfies our Condition 1. 75 | * Resulting arrays: 76 | * arr = [A, C, E, E, E, A, C, E, R, T] 77 | * k => arr[k] = aux[j] & 'k' is incremented by 1. 78 | * aux = [E, E, G, M, R, A, C, E, R, T] 79 | * i 🔺 j => 'i' remains the same, 'j' is incremented. 80 | * 81 | * ➤ aux[i], which is G, is SMALLER than aux[j] which is R. This satisfies our Condition 2. 82 | * Resulting arrays: 83 | * arr = [A, C, E, E, E, G, C, E, R, T] 84 | * k => arr[k] = aux[i] & 'k' is incremented by 1. 85 | * aux = [E, E, G, M, R, A, C, E, R, T] 86 | * i 🔺 j => 'i' is incremented, 'j' remains the same. 87 | * 88 | * ➤ aux[i], which is M, is SMALLER than aux[j] which is R. This satisfies our Condition 2. 89 | * Resulting arrays: 90 | * arr = [A, C, E, E, E, G, M, E, R, T] 91 | * k => arr[k] = aux[i] & 'k' is incremented by 1. 92 | * aux = [E, E, G, M, R, A, C, E, R, T] 93 | * i🔺 j => 'i' is incremented, 'j' remains the same. 94 | * 95 | * ➤ aux[i], which is R, is EQUAL to aux[j] which is R. This satisfies our Condition 3. 96 | * Resulting arrays: 97 | * arr = [A, C, E, E, E, G, M, R, R, T] 98 | * k => arr[k] = aux[i] & 'k' is incremented by 1. 99 | * aux = [E, E, G, M, R, A, C, E, R, T] 100 | * 🔺 i j => 'i' is incremented, 'j' remains the same. 101 | * 102 | * Since our 1st sub array is exhausted, we can simply merge the remaining part of the 2nd subarray 103 | * to our MUTATING ORIGINAL ARRAY(arr) 104 | * 105 | * How can we simple merge the remaining part without comparing? Because 2 sub arrays were already sorted 106 | * before we started merging two sub arrays. 107 | * 108 | * Since one of our array is exhausted, we don't need to check/compare anything. 109 | * 110 | * Do we really need to merge the remaining part? No! Because we copied all the content before we started 111 | * merging, so it's already there if you notice. In this case, R & T are already present in 'arr' because of 112 | * the copy OPERATION we performed in the beginning. 😅 113 | * 114 | * arr = [A, C, E, E, E, G, M, R, R, T] is our SORTED ARRAY after MERGE OPERATION. 115 | * 116 | * 117 | * --------------- T H A T 'S H O W M E R G E O P E R A T I O N W O R K S ----------------- 118 | * 119 | * Complexity: N Log N 120 | * Stability: Stable. Why? 121 | * ➤ Unlike Quick Sort, there are no long exchanges. 122 | * ➤ If two items are equal we choose from 1st array thereby preserving the order in original array. 123 | * 124 | * Improvements: 125 | * ➤ Use insertion sort for small arrays. 126 | * ➤ Don't perform MERGE OPERATION if last item of the 1st sub array is smaller than or equal to the first item of the 2nd subarray. 127 | * 128 | * Problem: Extra space for auxilliary array, N. 129 | """ 130 | def merge(left,right): 131 | result = [] 132 | i = j = 0 133 | while i < len(left) and j < len(right): 134 | if left[i] < right[j]: 135 | result.append(left[i]) 136 | i += 1 137 | else: 138 | result.append(right[j]) 139 | j += 1 140 | result += left[i:] 141 | result += right[j:] 142 | return result 143 | 144 | def mergeSort(arr): 145 | if len(arr) <= 1: 146 | return arr 147 | mid = len(arr)//2 148 | left = mergeSort(arr[:mid]) 149 | right = mergeSort(arr[mid:]) 150 | return merge(left,right) 151 | 152 | arr = [4,2,7,1,100,26,9,1,0] 153 | print(mergeSort(arr)) -------------------------------------------------------------------------------- /Algorithms/Sorting/selection-sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Selection sort: 3 | One of the simplest sorting algorithms works as follows: First, find 4 | the smallest item in the array and exchange it with the first entry (itself if the first entry 5 | is already the smallest). Then, find the next smallest item and exchange it with the sec- 6 | ond entry. Continue in this way until the entire array is sorted. This method is called 7 | selection sort because it works by repeatedly selecting the smallest remaining item. 8 | Running time is insensitive to input. 9 | The process of finding the smallest item on one 10 | pass through the array does not give much information about where the smallest item 11 | might be on the next pass. This property can be disadvantageous in some situations. 12 | For example, the person using the sort client might be surprised to realize that it takes 13 | about as long to run selection sort for an array that is already in order or for an array 14 | with all keys equal as it does for a randomly-ordered array! As we shall see, other algo- 15 | rithms are better able to take advantage of initial order in the input. 16 | Data movement is minimal. 17 | Each of the N exchanges changes the value of two array 18 | entries, so selection sort uses N exchanges—the number of array accesses is a linear 19 | function of the array size. None of the other sorting algorithms that we consider have 20 | this property (most involve linearithmic or quadratic growth). 21 | 22 | Time Complexity: 23 | Best Case: O(n*n) 24 | Average Case: O(n*n) 25 | Worst Case: O(n*n) 26 | """ 27 | 28 | class selection_sort: 29 | def selectionSort(self,arr): 30 | for i in range(len(arr)-1): 31 | minn = i 32 | for j in range(i+1,len(arr)): 33 | if arr[j] < arr[minn]: 34 | minn = j #finding the minimum element 35 | arr[i],arr[minn] = arr[minn],arr[i] #swapping the minimum element with the first element 36 | return arr 37 | 38 | s = selection_sort() 39 | arr = [1,2,7,5,3,6,0] 40 | print(s.selectionSort(arr)) -------------------------------------------------------------------------------- /Algorithms/Sorting/shell-sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------ SHELL SORT ------------------------- 3 | ShellSort is mainly a variation of Insertion Sort. In insertion sort, we move elements only one position ahead. 4 | When an element has to be moved far ahead, many movements are involved. The idea of shellSort is to allow exchange of far items. 5 | In shellSort, we make the array h-sorted for a large value of h. We keep reducing the value of h until it becomes 1. 6 | An array is said to be h-sorted if all sublists of every h’th element is sorted. 7 | 8 | Time Complexity: O(n**3/2) 9 | """ 10 | def shell_sort(arr): 11 | n = len(arr) 12 | h = 1 13 | while h<(n//3): 14 | h= 3 * h + 1 15 | while h >=1: 16 | for i in range(h,n): 17 | temp = arr[i] 18 | j = i 19 | while j>=h and arr[j-h]>temp: 20 | arr[j] = arr[j-h] 21 | j-=h 22 | arr[j] = temp 23 | h = h//3 24 | print ("Sorted array: ",arr) 25 | 26 | arr = [2, 6, 1, 3, 4, 10] 27 | shell_sort(arr) -------------------------------------------------------------------------------- /Algorithms/Strings/KMP.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pattern searching is an important problem in computer science. When we do search for a string in notepad/word file or 3 | browser or database, pattern searching algorithms are used to show the search results. 4 | 5 | The KMP matching algorithm uses degenerating property (pattern having same sub-patterns appearing more than once in 6 | the pattern) of the pattern and improves the worst case complexity to O(n). The basic idea behind KMP’s algorithm is: 7 | whenever we detect a mismatch (after some matches), we already know some of the characters in the text of the next window. 8 | We take advantage of this information to avoid matching the characters that we know will anyway match. 9 | """ 10 | def shiftTable(s): 11 | shiftT = [0] * (len(s)) 12 | index = 0 13 | i = 1 14 | while i < len(s): 15 | if s[i] == s[index]: 16 | shiftT[i] = index + 1 17 | index += 1 18 | i += 1 19 | else: 20 | if index != 0: 21 | index = shiftT[index-1] 22 | else: 23 | shiftT[i] = 0 24 | i += 1 25 | return shiftT 26 | 27 | def KMP(text,pattern): 28 | shiftT = shiftTable(pattern) 29 | i = j = 0 30 | while i < len(text) and j < len(pattern): 31 | if text[i] == pattern[j]: 32 | i += 1 33 | j += 1 34 | else: 35 | if j != 0: 36 | j = shiftT[j-1] 37 | else: 38 | i += 1 39 | if j == len(pattern): 40 | return True 41 | return False 42 | 43 | print(KMP("acacabacacabacacac","ccacac")) # return False 44 | print(KMP("acacabacacabacacac","bacacac")) # return True -------------------------------------------------------------------------------- /Data-Structures/Graphs/bfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Graph traversal means visiting every vertex and edge exactly once in a well-defined order. 3 | While using certain graph algorithms, you must ensure that each vertex of the graph is visited exactly once. 4 | The order in which the vertices are visited are important and may depend upon the algorithm or question that 5 | you are solving. 6 | During a traversal, it is important that you track which vertices have been visited. 7 | The most common way of tracking vertices is to mark them. 8 | 9 | Breadth First Search (BFS) 10 | There are many ways to traverse graphs. BFS is the most commonly used approach. 11 | BFS is a traversing algorithm where you should start traversing from a selected node (source or starting node) 12 | and traverse the graph layerwise thus exploring the neighbour nodes (nodes which are directly connected to source node). 13 | You must then move towards the next-level neighbour nodes. 14 | 15 | As the name BFS suggests, you are required to traverse the graph breadthwise as follows: 16 | - First move horizontally and visit all the nodes of the current layer 17 | - Move to the next layer 18 | """ 19 | def bfs(graph, root): 20 | visited, queue = set(), [root] 21 | visited.add(root) 22 | while queue: 23 | vertex = queue.pop(0) 24 | print(vertex) 25 | for neighbour in graph[vertex]: 26 | if neighbour not in visited: 27 | visited.add(neighbour) 28 | queue.append(neighbour) 29 | 30 | graph = {0: [2], 1: [0,2], 2: [3], 3: [1,2]} 31 | bfs(graph, 2) # return 2 3 1 0 -------------------------------------------------------------------------------- /Data-Structures/Graphs/dfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | The DFS algorithm is a recursive algorithm that uses the idea of backtracking. It involves exhaustive searches of all 3 | the nodes by going ahead, if possible, else by backtracking. 4 | Here, the word backtrack means that when you are moving forward and there are no more nodes along the current path, 5 | you move backwards on the same path to find nodes to traverse. All the nodes will be visited on the current path 6 | till all the unvisited nodes have been traversed after which the next path will be selected. 7 | This recursive nature of DFS can be implemented using stacks. The basic idea is as follows: 8 | Pick a starting node and push all its adjacent nodes into a stack. 9 | Pop a node from stack to select the next node to visit and push all its adjacent nodes into a stack. 10 | Repeat this process until the stack is empty. However, ensure that the nodes that are visited are marked. 11 | This will prevent you from visiting the same node more than once. If you do not mark the nodes that are visited 12 | and you visit the same node more than once, you may end up in an infinite loop. 13 | """ 14 | def dfs(graph, start, visited=None): 15 | if visited is None: 16 | visited = set() 17 | visited.add(start) 18 | print(start) 19 | for i in graph[start]: 20 | if i not in visited: 21 | dfs(graph, i, visited) 22 | return visited 23 | 24 | graph = {0: [2], 1: [0,2], 2: [3], 3: [1,2]} 25 | dfs(graph, 1) # 1 0 2 3 -------------------------------------------------------------------------------- /Data-Structures/Graphs/graphs-using-adjacency-list.py: -------------------------------------------------------------------------------- 1 | """ 2 | Graph is a data structure that consists of following two components: 3 | 1. A finite set of vertices also called as nodes. 4 | 2. A finite set of ordered pair of the form (u, v) called as edge. The pair is ordered because (u, v) is not same as (v, u) in case of a directed graph(di-graph). 5 | The pair of the form (u, v) indicates that there is an edge from vertex u to vertex v. The edges may contain weight/value/cost. 6 | 7 | Graphs are used to represent many real-life applications: Graphs are used to represent networks. The networks may include paths in a city or telephone network 8 | or circuit network. Graphs are also used in social networks like linkedIn, Facebook. For example, in Facebook, each person is represented with a vertex(or node). 9 | Each node is a structure and contains information like person id, name, gender and locale. 10 | 11 | Following two are the most commonly used representations of a graph. 12 | 1. Adjacency Matrix 13 | 2. Adjacency List 14 | There are other representations also like, Incidence Matrix and Incidence List. The choice of the graph representation is situation specific. 15 | It totally depends on the type of operations to be performed and ease of use. 16 | 17 | Implementation of Graphs using Adjacency List: 18 | 19 | An array/dictionary of lists is used. Size of the array/dictionary is equal to the number of vertices. Let the array be array[]. 20 | An entry array[i] represents the list of vertices adjacent to the ith vertex. This representation can also be used to represent a weighted graph. 21 | The weights of edges can be represented as lists of pairs. 22 | """ 23 | class Vertex: 24 | def __init__(self,key): 25 | self.id = key 26 | self.connectedTo = {} 27 | 28 | def addNeighbor(self,nbr,weight=0): 29 | self.connectedTo[nbr] = weight 30 | 31 | def getConnections(self): 32 | return self.connectedTo.keys() 33 | 34 | def getId(self): 35 | return self.id 36 | 37 | def getWeight(self,nbr): 38 | return self.connectedTo[nbr] 39 | 40 | class Graph: 41 | def __init__(self): 42 | self.vertList = {} 43 | self.numVertices = 0 44 | 45 | def addVertex(self,key): 46 | if key not in self.vertList: 47 | self.numVertices = self.numVertices + 1 48 | newVertex = Vertex(key) 49 | self.vertList[key] = newVertex 50 | 51 | def getVertex(self,n): 52 | if n in self.vertList: 53 | return self.vertList[n] 54 | else: 55 | return None 56 | 57 | def addEdge(self,f,t,cost=0): 58 | if f not in self.vertList: 59 | self.addVertex(f) 60 | if t not in self.vertList: 61 | self.addVertex(t) 62 | self.vertList[f].addNeighbor(self.vertList[t], cost) 63 | 64 | def getVertices(self): 65 | return list(self.vertList.keys()) 66 | 67 | g = Graph() 68 | for i in range(6): 69 | g.addVertex(i) 70 | g.addEdge(0,1,5) 71 | g.addEdge(0,5,2) 72 | g.addEdge(1,2,4) 73 | g.addEdge(2,3,9) 74 | g.addEdge(3,4,7) 75 | g.addEdge(3,5,3) 76 | g.addEdge(4,0,1) 77 | g.addEdge(5,4,8) 78 | g.addEdge(5,2,1) 79 | print(g.getVertices()) 80 | for v in g.vertList.values(): 81 | for w in v.getConnections(): 82 | print("( %s , %s )" % (v.getId(), w.getId())) -------------------------------------------------------------------------------- /Data-Structures/Linked-List/Doubly-Linked-List.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------ Doubly Linked List ------------------------- 3 | """ 4 | class Node: 5 | def __init__(self, data = None): 6 | self.data = data 7 | self.next = None 8 | self.prev = None 9 | 10 | class doublyLinkedList: 11 | def __init__(self): 12 | self.head = None 13 | self.tail = None 14 | 15 | def length(self): 16 | count = 0 17 | cur = self.head 18 | while cur: 19 | count += 1 20 | cur = cur.next 21 | return count 22 | 23 | def get(self, index): 24 | if index >= self.length(): 25 | return "ERROR : index out of range!" 26 | idx = 0 27 | cur = self.head 28 | while cur: 29 | if idx == index: 30 | return cur 31 | idx += 1 32 | cur = cur.next 33 | 34 | def insertAtBeg(self, data): 35 | newNode = Node(data) 36 | if self.head: 37 | newNode.next = self.head 38 | self.head.prev = newNode 39 | self.head = newNode 40 | else: 41 | self.head = self.tail = newNode 42 | 43 | def insertAtEnd(self, data): 44 | newNode = Node(data) 45 | if self.tail: 46 | self.tail.next = newNode 47 | newNode.prev = self.tail 48 | self.tail = newNode 49 | else: 50 | self.head = self.tail = newNode 51 | 52 | def insertAfter(self, refNode, data): 53 | if not refNode: #if reference node does not exist 54 | return 55 | newNode = Node(data) 56 | newNode.prev = refNode 57 | if refNode.next is None: 58 | self.tail = newNode 59 | else: 60 | newNode.next = refNode.next 61 | newNode.next.prev = newNode 62 | refNode.next = newNode 63 | 64 | def insertBefore(self, refNode, data): 65 | if not refNode: #if reference node does not exist 66 | return 67 | newNode = Node(data) 68 | newNode.next = refNode 69 | if refNode.prev is None: 70 | self.head = newNode 71 | else: 72 | newNode.prev = refNode.prev 73 | refNode.prev.next = newNode 74 | refNode.prev = newNode 75 | 76 | def remove(self, node): 77 | if not node: #if reference node does not exist 78 | return 79 | if not node.prev: 80 | self.head = node.next 81 | else: 82 | node.prev.next = node.next 83 | if not node.next: 84 | self.tail = node.prev 85 | else: 86 | node.next.prev = node.prev 87 | 88 | def display(self): 89 | elements = [] 90 | cur = self.head 91 | while cur: 92 | elements.append(cur.data) 93 | cur = cur.next 94 | return elements 95 | 96 | x = doublyLinkedList() # creating object of doublyLinkedList 97 | x.insertAtBeg(1) # Inserting 1 at the begining 98 | x.insertAtEnd(2) # Inserting 2 at the end 99 | x.insertAtEnd(6) # Inserting 6 at the end 100 | x.insertAfter(x.get(1),5) # Inserting 5 after 1st(according to index) node 101 | x.insertBefore(x.get(3),10)# Inserting 10 before 3rd(according to index) node 102 | x.remove(x.get(1)) # Removing 1st(according to index) node 103 | print(x.length()) # Print the length of the Linked List 104 | print(x.display()) # Print the Linked List i.e. [1,5,10,6] -------------------------------------------------------------------------------- /Data-Structures/Linked-List/Singly-Linked-List.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------ Singly Linked List ------------------------- 3 | """ 4 | class node: 5 | def __init__(self, data = None): 6 | self.data = data 7 | self.next = None 8 | 9 | class singly_linked_list: 10 | def __init__(self): 11 | self.head = node() 12 | 13 | def append(self, data): 14 | new_node = node(data) 15 | cur = self.head 16 | while cur.next != None: 17 | cur = cur.next 18 | cur.next = new_node 19 | 20 | def length(self): 21 | cur = self.head 22 | count = 0 23 | while cur.next != None: 24 | count += 1 25 | cur = cur.next 26 | return count 27 | 28 | def display(self): 29 | elements = [] 30 | cur = self.head 31 | while cur.next != None: 32 | cur = cur.next 33 | elements.append(cur.data) 34 | return elements 35 | 36 | def get(self, index): 37 | if index >= self.length(): 38 | return "ERROR : index out of range!" 39 | idx = 0 40 | cur = self.head 41 | while cur.next != None: 42 | cur = cur.next 43 | if idx == index: 44 | return cur.data 45 | idx += 1 46 | 47 | def delete(self, index): 48 | if index >= self.length(): 49 | return "ERROR : index out of range!" 50 | idx = 0 51 | cur = self.head 52 | while cur.next != None: 53 | last_node = cur 54 | cur = cur.next 55 | if idx == index: 56 | last_node.next = cur.next 57 | return "Node Deleted" 58 | idx += 1 59 | 60 | def insert(self, index, value): 61 | if index > self.length(): 62 | return "ERROR : index out of range!" 63 | elif index == self.length(): 64 | self.append(value) 65 | return "Node Added" 66 | new_node = node(value) 67 | idx = 0 68 | cur = self.head 69 | while cur.next != None: 70 | last_node = cur 71 | cur = cur.next 72 | if idx == index: 73 | last_node.next = new_node 74 | new_node.next = cur 75 | return "Node Added" 76 | idx += 1 -------------------------------------------------------------------------------- /Data-Structures/Queue/Priority-Queues/binary-heaps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Priority Queue: 3 | Priority Queue is an extension of queue with following properties. 4 | 1) Every item has a priority associated with it. 5 | 2) An element with high priority is dequeued before an element with low priority. 6 | 3) If two elements have the same priority, they are served according to their order in the queue. 7 | 8 | A typical priority queue supports following operations. 9 | ➤ insert(item, priority): Inserts an item with given priority. 10 | ➤ getHighestPriority(): Returns the highest priority item. 11 | ➤ deleteHighestPriority(): Removes the highest priority item. 12 | 13 | Applications of Priority Queue: 14 | 1) CPU Scheduling 15 | 2) Graph algorithms like Dijkstra’s shortest path algorithm, Prim’s Minimum Spanning Tree, etc 16 | 3) All queue applications where priority is involved. 17 | 4) Data Compression (Huffman codes) 18 | 5) Find the largest M items in a stream of N items. 19 | 20 | Ways to implement Priority Queue 21 | ➤ Arrays - Insertion and Deletion is expensive in order to maintain the priority. 22 | ➤ LinkedList -> Same as array. But deletion is fast. 23 | ➤ Binary Heap -> Best 24 | 25 | Binary Heap (Min Heap or Max Heap) 26 | Based on the idea of Complete Binary Tree. 27 | Binary Tree -> Empty or Nodes to left and right binary tree. 28 | Complete Binary Tree -> Perfectly Balanced, except for the bottom level and the bottom level has all keys as left as possible. 29 | 30 | o <- Level 0 31 | / \ 32 | o o <- Level 1 33 | / \ / \ 34 | o o o o <- Level 2 35 | / \ 36 | o o <- Level 3 37 | 38 | ✔︎ Perfectly Balanced, except for Level 3 39 | ✔︎ Height of a Complete Binary Tree of N node is Log N. 40 | In above tree, there are 9 nodes ==> Log 9 = Log 3^2 = 2 Log 3. (3 Levels) 41 | 42 | 43 | Implementation - Array representation of the heap ordered complete binary tree. 44 | Head Ordered Binary Tree: 45 | ✬ Keys in nodes. 46 | ✬ Parent's key is smaller than children's keys. This is important. ✅ 47 | 48 | Properties of Binary Heap: 49 | ➤ Smallest key is arr[1], which is the root of the binary tree. 50 | ➤ Parent of node at 'k' index is at k/2 index. (It's integer divide. No floats) 51 | ➤ Children of a node 'k' are at index '2k' and '2k + 1', given we start indexing from 1 instead of 0. ✅ 52 | We don't need actual tree to represent these data structures. Array indices are sufficient. 53 | 54 | Time complexity for Building a Binary Heap is O(N). 55 | """ 56 | # This is the example Min Heap 57 | class binHeap: 58 | def __init__(self): 59 | self.heapList = [0] 60 | self.curSize = 0 61 | 62 | def percUp(self,i): 63 | while i // 2 > 0: 64 | if self.heapList[i] < self.heapList[i//2]: 65 | self.heapList[i], self.heapList[i//2] = self.heapList[i//2], self.heapList[i] 66 | i = i // 2 67 | 68 | def insert(self, n): 69 | self.heapList.append(n) 70 | self.curSize += 1 71 | self.percUp(self.curSize) 72 | 73 | def percDown(self, i): 74 | while i * 2 <= self.curSize: 75 | minn = self.minChild(i) 76 | if self.heapList[i] > self.heapList[minn]: 77 | self.heapList[i], self.heapList[minn] = self.heapList[minn], self.heapList[i] 78 | i = minn 79 | 80 | def minChild(self, i): 81 | if (i * 2) + 1 > self.curSize: 82 | return i * 2 83 | else: 84 | if self.heapList[i*2] < self.heapList[i*2+1]: 85 | return i * 2 86 | return i * 2 + 1 87 | 88 | def delMin(self): 89 | val = self.heapList[1] 90 | self.heapList[1] = self.heapList[self.curSize] 91 | self.curSize -= 1 92 | self.heapList.pop() 93 | self.percDown(1) 94 | return val 95 | 96 | def buildHeap(self, alist): 97 | i = len(alist)//2 98 | self.curSize = len(alist) 99 | self.heapList = [0] + alist[:] 100 | while i > 0: 101 | self.percDown(i) 102 | i -= 1 103 | 104 | def getMin(self): 105 | return self.heapList[1] 106 | 107 | x = binHeap() 108 | x.buildHeap([9, 6, 5, 2, 3]) 109 | print(x.heapList) # Print [0, 2, 3, 5, 6, 9] 110 | print(x.delMin()) # Print 2 111 | print(x.heapList) # Print [0, 3, 6, 5, 9] 112 | x.insert(2) 113 | print(x.heapList) # Print [0, 2, 3, 5, 9, 6] 114 | print(x.getMin()) # Print 2 -------------------------------------------------------------------------------- /Data-Structures/Queue/queue-using-linked-list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data = None): 3 | self.data = data 4 | self.next = None 5 | 6 | class Queue: 7 | def __init__(self): 8 | self.head = None 9 | self.last = None 10 | 11 | def enqueue(self, data): 12 | if not self.last: 13 | self.head = Node(data) 14 | self.last = self.head 15 | else: 16 | self.last.next = Node(data) 17 | self.last = self.last.next 18 | 19 | def dequeue(self): 20 | if not self.head: 21 | return None 22 | val = self.head.data 23 | self.head = self.head.next 24 | return val 25 | 26 | def display(self): 27 | temp = self.head 28 | while temp != None: 29 | print(temp.data) 30 | temp = temp.next 31 | 32 | x = Queue() # Creating object of queue class 33 | x.enqueue(1) # Add 1 to the queue 34 | x.enqueue(2)# Add 2 to the queue 35 | x.display() # 1 => 2 36 | print(x.dequeue()) # Deleting the first element of the queue. 37 | x.display() # 2 38 | print(x.dequeue()) # 2 39 | print(x.dequeue()) # None(because queue is already empty) 40 | -------------------------------------------------------------------------------- /Data-Structures/Queue/queue.py: -------------------------------------------------------------------------------- 1 | class Queue: 2 | arr = [] 3 | def enqueue(self,item): 4 | self.arr.append(item) 5 | 6 | def dequeue(self): 7 | if len(self.arr) > 0: 8 | ditem = self.arr[0] 9 | del self.arr[0] 10 | return ditem 11 | else: 12 | return #queue is empty 13 | 14 | def dispaly(self): 15 | print(self.arr) 16 | 17 | x = Queue() # Creating object of queue class 18 | x.enqueue(1) 19 | x.enqueue(2) 20 | x.dispaly() # arr = [1,2] 21 | x.dequeue() # Deleting the first element of the queue. 22 | x.dispaly() # arr = [2] 23 | print(x.dequeue()) # 2 24 | print(x.dequeue()) # None(because queue is already empty) -------------------------------------------------------------------------------- /Data-Structures/Stack/stack-using-linked-list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | class Stack: 7 | def __init__(self): 8 | self.head = None 9 | 10 | def push(self, data): 11 | if not self.head: 12 | self.head = Node(data) 13 | else: 14 | newNode = Node(data) 15 | newNode.next = self.head 16 | self.head = newNode 17 | 18 | def pop(self): 19 | if not self.head: 20 | return None 21 | popped = self.head.data 22 | self.head = self.head.next 23 | return popped 24 | 25 | def display(self): 26 | temp = self.head 27 | while temp != None: 28 | print(temp.data) 29 | temp = temp.next 30 | 31 | x = Stack() # Creating object of Stack class 32 | x.push(1) # Pushing 1 to stack 33 | x.push(2) # Pushing 2 to stack 34 | x.display() # 2 1 35 | print(x.pop()) # Deleting the last element(2) from the stack. 36 | x.display() # 1 37 | print(x.pop()) # 1 38 | print(x.pop()) # None(because stack is already empty) -------------------------------------------------------------------------------- /Data-Structures/Stack/stack.py: -------------------------------------------------------------------------------- 1 | class Stack: 2 | arr = [] 3 | def push(self,item): 4 | self.arr.append(item) 5 | 6 | def spop(self): 7 | if len(self.arr) > 0: 8 | return self.arr.pop() 9 | else: 10 | return #stack is empty 11 | 12 | def display(self): 13 | print(self.arr) 14 | 15 | x = Stack() # Creating object of Stack class 16 | x.push(1) # Pushing 1 to stack 17 | x.push(2) # Pushing 2 to stack 18 | x.display() # arr = [1,2] 19 | x.spop() # Deleting the last element from the stack. 20 | x.display() # arr = [1] 21 | print(x.spop()) # 1 22 | print(x.spop()) # None(because stack is already empty) -------------------------------------------------------------------------------- /Data-Structures/Tree/Binary-Search-Tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | ----------------------- Binary Search Tree ------------------------ 3 | """ 4 | class Node: 5 | def __init__(self, val): 6 | self.left = None 7 | self.right = None 8 | self.data = val 9 | 10 | class Binary_Tree: 11 | def __init__(self): 12 | self.root = None 13 | 14 | def getRoot(self): 15 | return self.root.data 16 | 17 | def add(self, val): 18 | if self.root == None: 19 | self.root = Node(val) 20 | else: 21 | self._add(val, self.root) 22 | 23 | def _add(self, val, node): 24 | if val < node.data: 25 | if node.left != None: 26 | self._add(val, node.left) 27 | else: 28 | node.left = Node(val) 29 | else: 30 | if node.right != None: 31 | self._add(val, node.right) 32 | else: 33 | node.right = Node(val) 34 | 35 | def printTree(self, trav = "in"): 36 | if self.root != None: 37 | if trav == "in": 38 | self.printTree_in(self.root) 39 | elif trav == "pre": 40 | self.printTree_pre(self.root) 41 | elif trav == "post": 42 | self.printTree_post(self.root) 43 | 44 | def printTree_in(self, node): 45 | if node == None: 46 | return 47 | self.printTree_in(node.left) 48 | print(node.data) 49 | self.printTree_in(node.right) 50 | 51 | def printTree_pre(self, node): 52 | if node == None: 53 | return 54 | print(node.data) 55 | self.printTree_pre(node.left) 56 | self.printTree_pre(node.right) 57 | 58 | def printTree_post(self, node): 59 | if node == None: 60 | return 61 | self.printTree_post(node.left) 62 | self.printTree_post(node.right) 63 | print(node.data) 64 | 65 | bTree = Binary_Tree() 66 | bTree.add(6) 67 | bTree.add(4) 68 | bTree.add(8) 69 | bTree.add(10) 70 | bTree.add(3) 71 | bTree.add(5) 72 | bTree.printTree("pre") # 6 4 3 5 8 10 73 | print("----------------") 74 | bTree.printTree("in") # 3 4 5 6 8 10 75 | print("----------------") 76 | bTree.printTree("post") # 3 5 4 10 8 6 77 | -------------------------------------------------------------------------------- /Data-Structures/Trie/trie.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Trie is a special data structure used to store strings that can be visualized like a graph. It consists of nodes and edges. 3 | Each node consists of at max 26 children and edges connect each parent node to its children. 4 | These 26 pointers are nothing but pointers for each of the 26 letters of the English alphabet A separate edge is maintained for every edge. 5 | Strings are stored in a top to bottom manner on the basis of their prefix in a trie. All prefixes of length 1 are stored at until level 1, 6 | all prefixes of length 2 are sorted at until level 2 and so on. 7 | Now, one would be wondering why to use a data structure such as a trie for processing a single string? Actually, Tries are generally used on groups of strings, 8 | rather than a single string. When given multiple strings , we can solve a variety of problems based on them. For example, consider an English dictionary and a 9 | single string , find the prefix of maximum length from the dictionary strings matching the string . Solving this problem using a naive approach would require us 10 | to match the prefix of the given string with the prefix of every other word in the dictionary and note the maximum. The is an expensive process considering the 11 | amount of time it would take. Tries can solve this problem in much more efficient way. 12 | Before processing each Query of the type where we need to search the length of the longest prefix, we first need to add all the existing words into the dictionary. 13 | A Trie consists of a special node called the root node. This node doesn't have any incoming edges. It only contains 26 outgoing edfes for each letter in the 14 | alphabet and is the root of the Trie. 15 | So, the insertion of any string into a Trie starts from the root node. All prefixes of length one are direct children of the root node. 16 | In addition, all prefixes of length 2 become children of the nodes existing at level one. 17 | """ 18 | class Trie: 19 | 20 | def __init__(self): 21 | self.dic = {} 22 | 23 | def insert(self, word): 24 | cur = self.dic 25 | for c in word: 26 | if c not in cur: 27 | cur[c] = {} 28 | cur = cur[c] 29 | cur['end'] = {} 30 | 31 | def search(self, word): 32 | cur = self.dic 33 | for c in word: 34 | if c in cur: 35 | cur = cur[c] 36 | else: 37 | return False 38 | if 'end' in cur: 39 | return True 40 | return False 41 | 42 | 43 | def startsWith(self, prefix): 44 | cur = self.dic 45 | for c in prefix: 46 | if c in cur: 47 | cur = cur[c] 48 | else: 49 | return False 50 | return True 51 | 52 | obj = Trie() # Creating object of Trie Class 53 | obj.insert("hello") # Insert "hello" into Trie 54 | obj.insert("hi") # Insert "hi" into Trie 55 | obj.insert("hey") # Insert "hey" into Trie 56 | print(obj.search("hi")) # return True 57 | print(obj.startsWith("he")) # return True 58 | print(obj.startsWith("heyy")) # return False -------------------------------------------------------------------------------- /Data-Structures/array.py: -------------------------------------------------------------------------------- 1 | import array 2 | class myArray: 3 | def newArray(self, ar_type): 4 | self.arr = array.array(ar_type, []) # new array created 5 | 6 | def append(self,val): 7 | self.arr.append(val) # value added to the array 8 | return self.arr 9 | 10 | def insert(self,pos,val): 11 | self.arr.insert(pos,val) # value add at "pos" location 12 | return self.arr 13 | 14 | def pop(self): 15 | if len(self.arr)!=0: 16 | return self.arr.pop() # removing the last element 17 | return -1 # if array is empty 18 | 19 | def remove(self, val): 20 | if val in self.arr: 21 | self.arr.remove(val) # removing the first occurence of "val" 22 | return self.arr 23 | 24 | def index(self, val): 25 | return self.arr.index(val) # finding the first index of "val" 26 | 27 | def reverse(self): 28 | self.arr.reverse() # reversing the array 29 | return self.arr -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anant Kaushik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Structures and Algorithms 2 | Data Structures and Algorithms implementation in Python. 3 | 4 | ### Topics covered: 5 | 6 | - [x] [Data Structures](Data-Structures) 7 | - [x] [Array](Data-Structures/array.py) 8 | - [x] [Stack](Data-Structures/Stack) 9 | - [x] [Stack Using Array](Data-Structures/Stack/stack.py) 10 | - [x] [Stack Using Linked List](Data-Structures/Stack/stack-using-linked-list.py) 11 | - [x] [Queue](Data-Structures/Queue) 12 | - [x] [Queue Using Array](Data-Structures/Queue/queue.py) 13 | - [x] [Queue Using Linked List](Data-Structures/Queue/queue-using-linked-list.py) 14 | - [x] [Priority Queue](Data-Structures/Priority-Queues) 15 | - [x] [Binary Heaps](Data-Structures/Queue/Priority-Queues/binary-heaps.py) 16 | - [x] [Linked List](Data-Structures/Linked-List) 17 | - [x] [Singly Linked List](Data-Structures/Linked-List/Singly-Linked-List.py) 18 | - [x] [Doubly Linked List](Data-Structures/Linked-List/Doubly-Linked-List.py) 19 | - [x] [Tree](Data-Structures/Tree) 20 | - [x] [Binary Search Tree](Data-Structures/Tree/Binary-Search-Tree.py) 21 | - [x] [Trie](Data-Structures/Trie/trie.py) 22 | - [x] [Graphs](Data-Structures/Graphs) 23 | - [x] [Graph(Using Adjacency List)](Data-Structures/Graphs/graphs-using-adjacency-list.py) 24 | - [x] [Breadth First Search](Data-Structures/Graphs/bfs.py) 25 | - [x] [Depth First Search](Data-Structures/Graphs/dfs.py) 26 | 27 | - [x] [Algorithms](Algorithms) 28 | - [x] [Searching](Algorithms/Searching) 29 | - [x] [Linear Search](Algorithms/Searching/linear-search.py) 30 | - [x] [Binary Search](Algorithms/Searching/binary-search.py) 31 | - [x] [Sorting](Algorithms/Sorting) 32 | - [x] [Bubble Sort(optimized)](Algorithms/Sorting/bubble-sort.py) 33 | - [x] [Selection Sort](Algorithms/Sorting/selection-sort.py) 34 | - [x] [Insertion Sort](Algorithms/Sorting/insertion-sort.py) 35 | - [x] [Shell Sort](Algorithms/Sorting/shell-sort.py) 36 | - [x] [Bucket Sort](Algorithms/Sorting/bucket-sort.py) 37 | - [x] [Merge Sort](Algorithms/Sorting/mergeSort.py) 38 | - [ ] [Quick Sort](Algorithms/Sorting/) 39 | - [x] [Strings](Algorithms/Strings) 40 | - [x] [KMP (Knuth Morris Pratt) Pattern Searching](Algorithms/Strings/KMP.py) 41 | - [x] [Kadane's Algorithm](Algorithms/Kadane's-Algorithm.py) 42 | 43 | ### Why this repository? 44 | This is mostly for my personal learning and future references, but anyone can use it for learning purpose. 🍻 45 | --------------------------------------------------------------------------------