Summary of Eight Common Sorting Algorithms

Keywords: less

summary

Stable Sorting: Bubble Sorting, Insert Sorting, Merge Sorting, Count Sorting, Cardinal Sorting
Unstable Sorting: Selective Sorting, Quick Sorting, Heap Sorting

In terms of efficiency, fast sorting is the fastest method in comparison-based method. The average time complexity is O(nlogn), while the time complexity of base sorting is O(n), which is not based on comparison. Although it looks faster, in fact, fast sorting can adapt to more randomized data and occupies less memory than base sorting, so fast sorting is more popular.

Exchange sort

I. Bubble Sorting

  • Main idea: It compares two adjacent numbers, and if it finds that their sizes are contrary to the ordering requirements, it interchanges them to achieve the effect of bringing out smaller (larger) numbers.

  • Features: Stable sorting (as can be seen from examples, the order of two duplicate numbers will not be changed in the sorting process), does not occupy additional memory

  • Code:

    def BubbleSort(alist):
        for i in range(len(alist)):
            for j in range(len(alist) - 1, i, -1):
                if alist[j] < alist[j - 1]:
                    alist[j], alist[j - 1] = alist[j - 1], alist[j]
        return alist

II. Quick Sorting

  • Main ideas:

    1. First select a base element (usually the first or last element)

    2. Find the correct position of the reference position after its ordering

    3. The primitive component is divided into two parts with the correct position of the reference element as the dividing line, and then sorted in the same way until all the elements are sorted.

    Then according to the above ideas, the main code of quick sorting is as follows:

    def QuickSort(alist):
        if len(alist) <= 1: return alist  # If there is no element or only one element, return directly
        privot = getPartition(alist)  # Get the base element index
        left = QuickSort(alist[:privot])  # According to the index of the base element, the elements on the left continue to search for the base element.
        right = QuickSort(alist[privot + 1:])  # According to the index partition of the base element, the elements on the right continue to search for the base element.

    Now the question is how to get the correct position of the benchmark element, that is, how to implement the getPartition function. The idea is as follows: using two indexes i and j to search from both sides of the array to the middle, i = 0, j = len(input_array) - 1, the purpose of i index is to find elements larger than the base element, and j index is to find elements smaller than the base element. Whenever i or j finds its own target, it exchanges the position of i and j until i = J

  • Features: Unstable sorting (as you can see from the example above, in the process of locating the base element, the latter one ends up ahead of the former one), without additional memory (regardless of recursive stack memory)

  • Code:

    def QuickSort(alist):
        """
        //There are three steps:
            1. Find the index of the current benchmark element
            2. Calculate the index of the left element of the base element
            3. Calculate the index of the right element of the base element
        :param alist: 
        :return:
        """
        if len(alist) <= 1: return alist  # If there is no element or only one element, return directly
        privot = getPartition(alist)  # Get the base element index
        left = QuickSort(alist[:privot])  # According to the index of the base element, the elements on the left continue to search for the base element.
        right = QuickSort(alist[privot + 1:])  # According to the index partition of the base element, the elements on the right continue to search for the base element.
        return left + [alist[privot]] + right  # Returns the sorted result
    
    
    def getPartition(alist):
        """
        //Get the base element index
        :param alist:
        :return:
        """
        privotKey = alist[0]  # Take the first element as the reference point
        i = 0  # Find elements smaller than the base point
        j = len(alist) - 1  # Looking for elements larger than the reference point
        while(i < j):
            while(alist[j] >= privotKey  and i < j): j -= 1  # j goes from right to left until it is smaller than the base point
            alist[i], alist[j] = alist[j], alist[i]  # Exchange i, j elements
            while(alist[i] <= privotKey  and i < j): i += 1  # i go from left to right until it is larger than the datum point.
            alist[i], alist[j] = alist[j], alist[i]  # Exchange i, j elements
        return i

Selection sort

I. Direct Selection Sorting

  • Main idea: Record the index of the smallest (largest) element of the unordered element each time, and then exchange it with the element after the sorted array

  • Features: Unstable sorting (from the above example, the first 5 to the second 5 after), does not require additional memory

  • Code:

    def SelectionSort(alist):
        for i in range(len(alist)):
            min_index = i
            for j in range(i + 1, len(alist)):
                if alist[j] < alist[min_index]:
                    min_index = j
            alist[i], alist[min_index] = alist[min_index], alist[i]
        return alist

II. Heap Sorting

  • Main idea: First, construct the largest (small) heap by filtering, then exchange the root node and the last element, rebuild the heap for the first n - 1 (only need to filter the new root node once), until all the elements are sorted.

    • Considering that the heap is a complete binary tree, if we store the values of each node in an array, we assume that the index of the parent node is i, the index of its left son is 2*i+1, and the index of its right son is 2*i+2. Assuming that the minimum heap is constructed, the parent node needs to be compared with the smaller son in the filtering process. If its value is greater than that of the son, the value of the son needs to be moved up. It continues to compare with the next son until its value is greater than that of the son.

      def percDown(alist, root, n):
          """
          //In the range of [root:n], filter the root node
          """
          len_list = len(alist)
          father = root
          child = root * 2 + 1
          while child < n:
              if child + 1 < n and alist[child] > alist[child + 1]:  # Find smaller subnodes
                  child += 1
              if alist[father] > alist[child]:  # Swap if parent node is larger than child node
                  alist[father], alist[child] = alist[child], alist[father]
              else: break  # If the parent node is smaller than the child node, exit directly
              father = child
              child = father * 2 + 1
          return alist
  • Features: Unstable sorting (as you can see from the example above, after the first 5 to the second 5), does not require additional memory

  • Code:

    def HeapSort(alist):
        alist = buildHeap(alist)
        for i in range(len(alist) - 1, -1, -1):
            alist[0], alist[i] = alist[i], alist[0]
            percDown(alist, 0, i)
        return alist
    
    
    def buildHeap(alist):
        """
        //Build a minimal heap
        """
        # Filtration begins at the last node with child nodes
        for i in range(int(len(alist) / 2) - 1, -1, -1):
            percDown(alist, i, len(alist))
        return alist
    
    
    def percDown(alist, root, n):
        len_list = len(alist)
        father = root
        child = root * 2 + 1
        while child < n:
            if child + 1 < n and alist[child] > alist[child + 1]:  # Find smaller subnodes
                child += 1
            if alist[father] > alist[child]:  # Swap if parent node is larger than child node
                alist[father], alist[child] = alist[child], alist[father]
            else: break  # If the parent node is smaller than the child node, exit directly
            father = child
            child = father * 2 + 1
        return alist

Insertion sort

  • Main idea: For each new element, insert it into the sorted ordered table. In the insertion process, the elements behind it move backwards one grid.

  • Features: Stable sorting, no additional memory required

  • Code:

    def InsertionSort(alist):
        for i in range(1, len(alist)):
            # Save the current element to insert and free up the current space
            current_index = i
            current_value = alist[i]
            # Comparing with the previous elements
            for j in range(i - 1, 0, -1):
                # If the preceding element is larger than it, move the preceding element one grid to the right
                if alist[j] > current_value:
                    alist[j + 1] = alist[j]
                    current_index = j
                else: break
            # Insert the current element into the space
            alist[current_index] = current_value
        return alist

Merge sort

  • Main idea: Merge sorting mainly uses the idea of divide and conquer, dividing the array to be sorted into two parts. If the left part has been sorted and the right part has been sorted well, then it is only necessary to combine the two parts in order.

  • Features: Stable sorting, requiring additional memory (when merging operations)

  • Code:

    def MergeSort(alist):
        if len(alist) <= 1: return alist
        middle = int(len(alist) / 2)
        left = MergeSort(alist[:middle])
        right = MergeSort(alist[middle:])
        return merge(left, right)
    
    
    def merge(left, right):
        left_index = 0
        right_index = 0
        res = []
        while left_index < len(left) and right_index < len(right):
            if left[left_index] < right[right_index]:
                res.append(left[left_index])
                left_index += 1
            else:
                res.append((right[right_index]))
                right_index += 1
        if left_index < len(left):
            res += left[left_index:]
        if right_index < len(right):
            res += right[right_index:]
        return res

Counting sort

  • Main idea: Non-comparison-based sorting algorithm (because array subscript index is ordered by default, so as long as the number corresponding to put in there will be ordered). For each input element x, determine the number of elements less than x (that is, its location), and then put them in the output array. In the last traversal, you must traverse from the back to the front, otherwise the order will be unstable.

  • Features: Stable sorting, requiring additional memory

  • Code:

    def CountingSort(alist, k):
        temp = [0 for _ in range(k + 1)]  # Store the location of each element in alist
        res = [0 for _ in alist]  # Save the sorted results
        for i in alist:  # Number of elements with value I stored in temp[i]
            temp[i] += 1
        for t in range(1, len(temp)):  # temp[i] stores numbers less than or equal to the number of elements I
            temp[t] += temp[t - 1]
        # Place the elements in the input array in the corresponding position in the output array
        for i in range(len(alist) - 1, -1, -1):  # Output from back to front to ensure that repetitive elements are output in the original order
            res[temp[alist[i]] - 1] = alist[i]  # temp[alist[i]] represents the position of the first element of alist
            temp[alist[i]] -= 1  # Ensure that when duplicate numbers are encountered, the corresponding position is output
        return res

Radix sorting

  • The main idea: cardinal sorting starts from each bit (first from one bit, then ten, hundred, etc.) For example, 12, 34, 7, first get 12, 34, 7 by bit, and then get 7, 12, 34 by 10 bit (7's 10 bits are 0). It uses counting sort for each bit, because the range of each bit is only 0-9, and counting sort is stable sort, so cardinal sort is also stable sort.

  • Features: Stable sorting requires additional memory (because counting sorting requires additional memory)

  • Code:

    def RadixSort(alist, d):
        for i in range(1, d + 1):
            alist = CountingSort(alist, 9, i)  # Sort in the first place each time, because the counting order is stable, so you can sort multiple times.
        return alist
    
    
    def CountingSort(alist, k, d):
        temp = [0] * (k + 1)
        res = [0] * len(alist)
        for a in alist:
            a = getBitData(a, d)  # Get the number d
            temp[a] += 1
        for i in range(1, len(temp)):
            temp[i] += temp[i - 1]
        for i in range(len(alist) - 1, -1, -1):
            a = getBitData(alist[i], d)
            res[temp[a] - 1] = alist[i]
            temp[a] -= 1
        return res
    
    
    def getBitData(e, d):
        """
        //Get the number d
        :param e:
        :param d:
        :return:
        """
        res = e
        for _ in range(d):
            res = e % 10
            e //= 10
        return res

Posted by genericnumber1 on Sun, 31 Mar 2019 13:36:28 -0700