How To Implement Merge Sort Algorithm In Python | by Richmond Alake | Towards Data Science

Learning algorithms can be tedious at times.
The algorithm topic domain is a branch of computer science that has a notorious perception of immense complexity. But algorithms come in different shapes, sizes, and levels of sophistication.
Suppose you aspire to get to a level where you can tackle complex algorithms and solve problems with them. In that case, you have to build a backlog of experience understanding and implementing less complex algorithms.
One of these ‘not too complex’ algorithms is Merge sort.
Introduction to divide and conquer approach to problem-solving
Explanation of the operations and intuitions involved in the merge sort algorithm
Python implementation of the merge sort algorithm
The Divide and conquer paradigm used in algorithm design incorporates recursion to solve small problems initially derived from a larger problem.
Recursion within the computer science domain is defined as the procedure whereby a function calls itself within its body.
Divide and conquer algorithms are recursive; this means that the initial problem is divided into associated subproblems where a solution can be applied recursively to each subproblem. The final solution to the larger problem is a combination of the solutions to the subproblems.
There are three main components of the divide-and-conquer approach to algorithm design:
Divide: continuously break down the larger problem into smaller parts.Conquer: solve each of the smaller parts by utilising a function that’s called recursively.Combine: merge all solutions for all smaller parts into a single unified solution, which becomes the solution to the starting problem.
Divide: continuously break down the larger problem into smaller parts.
Conquer: solve each of the smaller parts by utilising a function that’s called recursively.
Combine: merge all solutions for all smaller parts into a single unified solution, which becomes the solution to the starting problem.
The merge sort algorithm adopts the divide-and-conquer algorithm paradigm to sort elements within a list efficiently. The algorithm was developed in 1945 by John Von Neumann.
Merge sort operation follows the basis of dividing the list into halves and continuously dividing the new halves down to their individual component. Then there is a comparison between individual components, which are merged to form the final sorted list.
The approach to implementing the merge sort algorithm comes in two parts.
The first part will conduct the divide component of the divide-and-conquer paradigm. The code implementation within this section will divide the initial list into smaller components. The initial list division will stop only when each separated component can no longer be broken down.
Below is an illustration of the first part of the algorithm.
def merge_sort(list):    # 1. Store the length of the list    list_length = len(list)    # 2. List with length less than is already sorted    if list_length == 1:        return list    # 3. Identify the list midpoint and partition the list into a left_partition and a right_partition    mid_point = list_length // 2    # 4. To ensure all partitions are broken down into their individual components,    # the merge_sort function is called and a partitioned portion of the list is passed as a parameter    left_partition = merge_sort(list[:mid_point])    right_partition = merge_sort(list[mid_point:])    # 5. The merge_sort function returns a list composed of a sorted left and right partition.    return merge(left_partition, right_partition)
Store the length of the initial list to a variable list_length.The merge_sort function returns a sorted list. A list with the length of one is technically sorted; therefore, the list is returned.For any list with a length greater than 1, the next step is to partition the list into a left_partition and a right_partition. This is achieved by getting the midpoint of the initial list passed into the merge_sort function.Recall that divide and conquer algorithms are recursive. In the merge sort algorithm implementation, recursion occurs in the breaking down of lists. To ensure all partitions are broken down into their individual components, the merge_sort function is called, and a partitioned portion of the list is passed as a parameter.The merge_sort function returns a list composed of a sorted left and right partition.
Store the length of the initial list to a variable list_length.
The merge_sort function returns a sorted list. A list with the length of one is technically sorted; therefore, the list is returned.
For any list with a length greater than 1, the next step is to partition the list into a left_partition and a right_partition. This is achieved by getting the midpoint of the initial list passed into the merge_sort function.
Recall that divide and conquer algorithms are recursive. In the merge sort algorithm implementation, recursion occurs in the breaking down of lists. To ensure all partitions are broken down into their individual components, the merge_sort function is called, and a partitioned portion of the list is passed as a parameter.
The merge_sort function returns a list composed of a sorted left and right partition.
The second part of the merge sort algorithm is concerned with the actual sorting of the elements within the list in a specified order. For this use case, the sorting order of the elements in the original list is ascending.
# 6. takes in two lists and returns a sorted list made up of the content within the two listsdef merge(left, right):    # 7. Initialize an empty list output that will be populated with sorted elements.    # Initialize two variables i and j which are used pointers when iterating through the lists.    output = []    i = j = 0    # 8. Executes the while loop if both pointers i and j are less than the length of the left and right lists    while i < len(left) and j < len(right):        # 9. Compare the elements at every position of both lists during each iteration        if left[i] < right[j]:            # output is populated with the lesser value            output.append(left[i])            # 10. Move pointer to the right            i += 1        else:            output.append(right[j])            j += 1    # 11. The remnant elements are picked from the current pointer value to the end of the respective list    output.extend(left[i:])    output.extend(right[j:])    return output
6. The merge function takes in two lists and returns a sorted list made up of the elements within the two lists passed as parameters to the merge function.
7. An empty list called output is initialised. This list is populated with sorted elements. Also, two variables, i and j, are initialised, which are used as pointers when iterating through the lists.
8. We iterate through both lists with a while loop that executes its body when the condition that both pointers i and j are less than the length of the left and right lists, respectively.
9. Within the body of the while loop, compare the elements at every position of both lists during each iteration. The output list is populated with a lesser value at the current index of the list the corresponding pointer is located.
10. After the output list is populated, the appropriate pointer is to be incremented. Essentially we are moving the pointer to the right or the next value within the list.
11. After the while loop is completed, or if the whole loop condition is not met, the output list is populated with the content of the remnants of each list. The remnant elements are picked from the current pointer value to the end of the respective list.
def run_merge_sort():    unsorted_list = [2, 4, 1, 5, 7, 2, 6, 1, 1, 6, 4, 10, 33, 5, 7, 23]    print(unsorted_list)    sorted_list = merge_sort(unsorted_list)    print(sorted_list)run_merge_sort()
Lastly, we create a function that initialises an unordered list. The variable sorted_list holds the result of the merge_sort function.
Below is the full code.
In the worst performance scenario, the amount of computing time required to execute a merge sort algorithm has the time complexity of O(n * log(n)), where n is the number of elements within the list to be sorted.
Each broken down subpart of the initial list that’s sorted using the merge sort algorithm takes linear time to merge with another subpart. This article from Khanacademy goes into an in-depth explanation of the merge sort algorithm's time complexity and analysis.
Merge sort, when compared to other sorting algorithms, is generally faster on a larger dataset. This is because merge sort takes a reduced amount of step to sort a list. Most other sorting algorithms such as insertion sort (O(n2)) and bubble sort have to take many steps to sort a list. For example, sorting a list containing 40 elements will take 1600 steps (40*40) using the insertion sort algorithm, but with merge sort, approximately 64 steps to sort the list.
Tackling algorithms might seem daunting at first, especially for beginners. My approach is to understand simple algorithms such as insertion sort, merge sort and others; after that, tackle more complex algorithms.
There are tons of benefits to be realised for machine learning practitioners that embark on the journey of expanding their domain understanding of algorithms and their implementation. For starters, you begin to consider the execution time and performance of programs you write in your day to day role, which contributes to a generally positive end-user experience when interacting with products you’ve developed.
To connect with me or find more content similar to this article, do the following:
Follow me on TwitterSubscribe to my Email list for weekly newslettersFollow me on MediumConnect and reach me on LinkedIn
Follow me on Twitter
Subscribe to my Email list for weekly newsletters
Follow me on Medium
Connect and reach me on LinkedIn