Sorting Algorithms
Sorting Algorithms (Brief intro)
In this article, I will describe the most popular sorting algorithms and implement them from their definitions. The little background story of my burgeoning interest in data structures and algorithms generally lies in the fact that I like hacking. However my decision to formally acquaint myself with the subject was precipitated by a potential job interview and I have not looked back since. Now lets get to it.
Types of sort algorithms
- In place algorithms: these algorithms don't need additional memory.
- Stable algorithms: these algorithms don't change the original order of things after sorting.
Bogo sort
This algorithm is basically not your go to algorithm except you have a super computer. It basically gets all the possible permutations of the list to be sorted and then checks if they are sorted. The implementation can be seen below.
xxxxxxxxxx# Here you need to sort with the permutation of all possibilities# Basically has variety of possible implementations.class BogoSort(Sort): def sort(self): p = self.arr found = False count = 0 while found is not True: # Implement permutation n! # Fisher Yates approach for i in range(len(p)): r = random.randint(i, len(p) -1) p[i], p[r] = p[r], p[i] found = self.check(p) return p def check(self, p): for i in range(len(p)-1): if p[i] > p[i + 1]: return False return TrueTime complexity of this algorithm is basically:
This algorithm is not a stable algorithm but it is an inplace algorithm.
Bubble sort
For some weird reason I like this algorithm. It basically bubbles the largest value to the end of the list. It does this by binary comparison starting with the first and second element. If the first is greater than the second you move the first to the second position and then proceed to compare the second and the third till you get to the end of the list and start all over. This is a stable and an inplace algorithm which means that if there is sorted subsection, the algorithm will leave them that way. The implementation can be seen below.
xxxxxxxxxx# this can be done with recursion and loop. I have implemented the recursion before will do the loop hereclass BubbleSort(Sort): def sort(self): for i in range(len(self.arr)-1): for j in range(len(self.arr)-1-i): if self.arr[j] > self.arr[j+1]: self.arr[j], self.arr[j+1] = self.arr[j+1], self.arr[j] return self.arrFrom the implementation, this algorithm has an complexity because you have to iterate twice over a list of size n.
Selection sort
This algorithm finds the largest(smallest) item and moves it over to the first index(last unsorted index) till the whole list is sorted. It is a stable and an inplace algorithm. The implementation can be seen below.
xxxxxxxxxxclass SelectionSort(Sort): def sort(self): for i in range(len(self.arr)): min = i for j in range(i, len(self.arr)-1): if self.arr[min] > self.arr[j+1]: min = j+1 self.arr[i], self.arr[min] = self.arr[min], self.arr[i] return self.arrThe time complexity is .
Insertion sort
This is very similar to selection sort with the exception that it makes many insertions and inserts can be costly depending on the type of memory we are looking at. It is an inplace and stable algorithm. Basically you compare an element in the list with the preceding ones and move it as required. In selection sort you had to check the elements in front for the smallest one but here you look at the preceding ones. The implementation can be seen below.
xxxxxxxxxxclass InsertionSort(Sort): def sort(self): for i in range(len(self.arr)): for j in range(i, 0, -1): if self.arr[j] < self.arr[j-1]: self.arr[j], self.arr[j-1] = self.arr[j-1], self.arr[j] return self.arrThe time complexity is .
Quick sort
This is one of the most popular sort algorithms. And arguably one of the fastest. It is a divide and conquer algorithm. In the first iteration you divide the list into two and move elements bigger than the middle element to right and does smaller to the left. You repeat the process recursively till the list is sorted. This algorithm is an in place algorithm but not stable.
xxxxxxxxxxclass QuickSort(Sort): def sort(self): self.pivot_and_check(0, len(self.arr) - 1) return self.arr def pivot_and_check(self, l_lim, r_lim): if l_lim >= r_lim: return pivot = (l_lim + r_lim) // 2 # swap the pivot with the last item self.arr[pivot], self.arr[r_lim] = self.arr[r_lim], self.arr[pivot] mid = l_lim for i in range(l_lim, r_lim): if self.arr[i] < self.arr[r_lim]: self.arr[mid], self.arr[i] = self.arr[i], self.arr[mid] mid += 1 self.arr[mid], self.arr[r_lim] = self.arr[r_lim], self.arr[mid] self.pivot_and_check(l_lim, mid-1) self.pivot_and_check(mid + 1, r_lim)This algorithm has an time complexity.
Merge sort
This algorithm is another divide and conquer algorithm. It breaks the list down to a single unit and begins joining them in sorted order by making comparisms till the whole list is sorted. It is a stable algorithm but not in place. The implementation can be seen below.
xxxxxxxxxxclass MergeSort(Sort): def sort(self): return self.recursion(self.arr) def recursion(self, arr): # base case if len(arr) == 1: return arr midpoint = len(arr) // 2 left = arr[:midpoint] right = arr[midpoint:] left_arr = self.recursion(left) right_arr = self.recursion(right) r_index = 0 l_index = 0 new_arr = [] while l_index < len(left_arr) and r_index < len(right_arr): if left_arr[l_index] < right_arr[r_index]: new_arr.append(left_arr[l_index]) l_index += 1 else: new_arr.append(right_arr[r_index]) r_index += 1 if r_index < len(right_arr): for i in range(r_index, len(right_arr)): new_arr.append(right_arr[i]) if l_index < len(left_arr): for i in range(l_index, len(left_arr)): new_arr.append(left_arr[i]) return new_arrThis algorithm has an time complexity.
Competition time
I executed the code below
xxxxxxxxxxp = [1, 8, -50, 100, 5, 7, -1, 49] # bogo print("bogo sort") bogo = BogoSort(p) tic = timer() result = bogo.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # bubble sort print("bubble sort") bubble = BubbleSort(p) tic = timer() result = bubble.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # selection sort print("selection sort") selection = SelectionSort(p) tic = timer() result = selection.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # insertion sort print("insertion sort") insertion = InsertionSort(p) tic = timer() result = insertion.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # quick sort print("quick sort") quick = QuickSort(p) tic = timer() result = quick.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # merge sort print("merge sort") merge = MergeSort(p) tic = timer() result = merge.sort() print(result) toc = timer() total_time = toc - tic print(total_time)Result:
Bogo sort - 1.2 secs. Don't use this for a large list. I tried it and it took forever.
Bubble sort - 2.0 secs.
Selection sort - 1.8 secs. Has a reputation of being efficient for small lists like the one used above.
Insertion sort - 1.9 secs. Not far away from selection sort.
Quick sort - 3.5 secs.
Merge sort - 3.19 secs.
Bonus
Sorting an object. I implemented an obect to test the sorting algorithms.
xxxxxxxxxxclass Object(): def __init__(self, name, age): self.name = name self.age = age def __lt__(self, other): return self.age < other.age def __repr__(self): return str(self.name) p = [Object('Object1', 31), Object('Object2', 29), Object('Object3', 24), Object('Object4', 33)] # bogo print("bogo sort") bogo = BogoSort(p) tic = timer() result = bogo.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # bubble sort print("bubble sort") bubble = BubbleSort(p) tic = timer() result = bubble.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # selection sort print("selection sort") selection = SelectionSort(p) tic = timer() result = selection.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # insertion sort print("insertion sort") insertion = InsertionSort(p) tic = timer() result = insertion.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # quick sort print("quick sort") quick = QuickSort(p) tic = timer() result = quick.sort() print(result) toc = timer() total_time = toc - tic print(total_time) print("-----------------------") # merge sort print("merge sort") merge = MergeSort(p) tic = timer() result = merge.sort() print(result) toc = timer() total_time = toc - tic print(total_time)Viola!!! Your object is sorted.
Thanks for reading.
Comments
Post a Comment