Algorithm -- ten common sorting algorithms (implemented in python)

Posted by john87423 on Sun, 20 Feb 2022 19:25:05 +0100

1, Bubble sorting

Algorithm idea:

When sorting the i (i = 1, 2,...) pass, start from the first element of the first n - i + 1 element in the sequence, and compare the two adjacent elements. If the former is greater than the latter, the two exchange positions, otherwise they will not exchange.

Bubble sorting method is to make the elements with smaller values gradually move from the back to the front and the elements with larger values move from the front to the back through the comparison and exchange between adjacent elements, just like bubbles at the bottom of the water, so it is called bubble sorting method.

Algorithm steps:

  • First, compare the first element and the second element in the sequence. If the former is greater than the latter, the two exchange positions, otherwise they will not exchange;
  • Then compare the second element with the third element. If the former is greater than the latter, they will exchange positions, otherwise they will not exchange;
  • And so on until the nth - 1st element is compared (or exchanged) with the nth element. After such a sequence, the element with the largest value in n elements is placed at the nth position of the sequence.
  • After that, the same process is carried out for the first n - 1 element, so that the element with the largest value in the N - 1 element is placed at the N - 1 position.
  • Then repeat the above process for the first n - 2 elements until there is no action of element exchange position in a certain sorting process, and the sorting ends.

Algorithm analysis:

  • In the best case, when the initial sequence is in ascending order from small to large, the algorithm can end the sorting only after a comparison between n - 1 elements without moving the elements. At this time, the time complexity of the algorithm is O(n).
  • The worst case is that when the initial sequence participating in the sorting is in reverse order, or the minimum element is at the end of the sequence, n - 1 sorting is required, and a total of ∑ i=2n(i − 1)=n(n − 1) / 2 comparisons between elements are carried out. Therefore, the average time complexity of the bubble sorting algorithm is O(n**2).
  • Bubble sorting method needs to move elements more times in the sorting process. Therefore, the bubble sorting method is more suitable for the case that the amount of data participating in the sorting sequence is small, especially when the initial state of the sequence is basically orderly; In general, this method is the least efficient method in sorting time.
  • Because the element exchange is carried out between adjacent elements and will not change the relative position of elements with the same value, the bubble sorting method is a stable sorting method.

Code implementation:

class Solution:
  def bubbleSort(self,arr):
    for i in range(len(arr)):
      for j in range(len(arr)-i-1):
        if arr[j] > arr[j+1]:
          arr[j],arr[j+1] = arr[j+1],arr[j]
          
    return arr
  
  def sortArray(self,nums:List[int]) --> List[int]:
    return self.bubbleSort(nums)

Case realization:

[LeetCode]283. Move zero

Given an array num, write a function to move all zeros to the end of the array while maintaining the relative order of non-zero elements.

Note that you must operate on the array in place without copying it.

Example:

input: nums = [0,1,0,3,12]
output: [1,3,12,0,0]

Solve problems using bubble sorting:

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
      	for i in range(len(nums)):
            for j in range(len(nums)-i-1):
                if nums[j] < nums[j+1]:
                    nums[j],nums[j+1] = 								nums[j+1],nums[j]
      	return nums

The result is wrong:

Input:

[0,1,0,3,12]

Output:

[12,3,1,0,0]

Expected results:

[1,3,12,0,0]

The original requirement of our topic is to ensure that the relative position of the original number does not change, but the writing of our bubble algorithm is no problem, but it is not commonly used.

2, Select sort

Algorithm idea:

The ith sorting selects an element with the smallest value from the last n − i + 1 (i = 1, 2,..., n − 1) elements of the sequence, and exchanges the position with the front element of the n - i + 1 element, that is, with the element at the ith position of the whole sequence. This continues until i == n − 1 and the sorting ends.

It can be summarized as follows: in each sorting, select the smallest element from the remaining unordered elements and exchange positions with the first element of the unordered elements.

Algorithm steps:

  • Set the integer variable I in the algorithm, which can be used not only as the calculation of the number of sorting trips, but also as the position of the first element of the last n − i + 1 element participating in the sorting during the i-th sorting.
  • Integer variable min_i record the position of the smallest element in the n − i + 1 element.
  • At the beginning of each sequence, start another min first_ I = I (i.e. temporarily assume that the ith element of the sequence is the one with the lowest value, and then formally determine the position of the lowest value element according to the actual situation after comparison).
  • At the end of the i-th sorting comparison, the element with the lowest real value in the n − i + 1 element is the subscript min_i corresponding element. At this time, if any, min_i == i, indicating that the element with the smallest value is the first element of this n − i + 1 element, which means that element exchange is not necessary for this sorting.

Algorithm analysis:

For the sequence with n elements, the selective sorting method needs to go through n - 1 times of sorting.

  • When the original sequence is an increasing sequence by value (ascending order), the number of element moves is the least, which is 0 times. When the initial sequence is a decreasing sequence by value (in reverse order), the number of element moves is the most, which is 3(n − 1) (3 is the execution times of exchanging arr[i] and arr[min_i]).
  • However, no matter what the initial arrangement state of the elements in the sequence is, the comparison between n − i elements is required to find the element with the lowest value in the ith sorting. Therefore, the comparison times between elements in the whole sorting process are the same, which is Σ i=2n(i − 1)=n(n − 1) / 2 times.
  • This shows that the number of comparisons between elements by the selection sorting method is independent of the original state of the sequence, and the time complexity of the algorithm can be determined as O(n**2).
  • **Since the exchange action between the element with the lowest value and the first element in the element not in good order is carried out between non adjacent elements, it is likely to change the front and rear positions of the elements with the same value, * * therefore, the selective sorting method is an unstable sorting method.

Code implementation:

class Solution:
  def selectionSort(self,arr):
    for i in range(len(arr)-1):
      min_i = i
      for j in range(i+1,len(arr)):
        if arr[j] < arr[min_i]:
          min_i = j
      if i != min_i:
        arr[i],arr[min_i] = arr[min_i],arr[i]
   	return arr
  
  def sortArray(self,nums:List[int])-->List[int]:
    return self.selectionSort(nums)

Case realization:

[LeetCode]215. The kth largest element in the array

Given the integer array nums and integer k, please return the largest * * k * * element in the array.

Please note that you need to find the K largest element after the array is sorted, not the k different element.

Example:

input: [3,2,1,5,6,4] and k = 2
 output: 5

Solve problems using selection sorting:

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
      for i in range(len(nums)-1):
            min_i = i
            for j in range(i+1,len(nums)):
                if nums[j] < nums[min_i]:
                    min_i = j
            if i != min_i:
                nums[i],nums[min_i] = 								nums[min_i],nums[i]
      return nums[-k]

After sorting the array, take the k-largest element, but this result takes a long time and is not commonly used.

3, Insert sort

Algorithm idea:

The whole sequence is divided into two parts: the first i - 1 element is an ordered sequence, and the last n - i + 1 element is an unordered sequence. In each sorting, the first element of the unordered sequence is found in the ordered sequence and inserted.

It can be summarized as follows: in each sorting, the first element of the remaining unordered sequence is inserted into the appropriate position of the ordered sequence.

Algorithm steps:

  • Take the first element as an ordered sequence and the 2nd ~ n - 1 element as an unordered sequence.
  • Scan the unordered sequence from beginning to end, and insert each scanned element into the appropriate position of the ordered sequence.

Algorithm analysis:

  • For a sequence with n elements, the insertion sorting method requires a total of n - 1 times of sorting.
  • For the insertion sorting algorithm, the whole sorting process only needs an auxiliary space temp.
  • When the original sequence is an increasing sequence by value (ascending order), each corresponding i value only compares elements once, so the total number of comparisons is the least, which is Σ i=2n1=n − 1. There is no need to move elements (records), which is the best case.
  • In the worst case, the initial sequence is a decreasing sequence by value (in reverse order), then each corresponding i value must be compared between elements i - 1 times, and the total number of comparisons between elements reaches the maximum, which is Σ i=2n(i − 1)=n(n − 1) / 2.
  • If the initial situation of the sequence is random, that is, the probability of various permutations of elements in the sequence participating in the sorting is the same, the average of the above minimum and maximum values can be taken as the number of comparisons between elements during insertion sorting, which is about n2/4. Therefore, the time complexity of the insertion sorting algorithm is O(n**2).
  • The insertion sort method belongs to the stability sort method.

Code implementation:

class Solution:
  def insertSort(self,arr):
    for i in range(1,len(arr)):
      temp = arr[i]
      j = i
      while j > 0 and arr[j-1] > temp:
        arr[j] = arr[j-1]
        j -= 1
      arr[j] = temp
    return arr
  
  def sortArray(self,nums:List[int])-->List[int]:
    return self.insertionSort(nums)
  

Case realization:

[LeetCode]75. Color classification

Given an array num containing n * elements in red, white and blue, they are sorted in place so that the elements of the same color are adjacent and arranged in the order of red, white and blue.

We use integers 0, 1, and 2 to represent red, white, and blue, respectively.

You must solve this problem without using the sort function of the library.

Example:

Input: nums = [2,0,2,1,1,0]
Output:[0,0,1,1,2,2]

Use insert sort to solve problems:

class Solution:
    def sortColors(self, nums: List[int]) -> None:
      for i in range(1,len(nums)):
         temp = nums[i]
         j = i
         while j > 0 and nums[j-1] > temp:
             nums[j] = nums[j-1]
             j -= 1
         nums[j] = temp
      return nums

Insertion sorting is also a time-consuming method. Generally, there are few such problems, which are not very common.

4, Hill sort

Algorithm idea:

The whole sequence is divided into several subsequences according to a certain interval, and each subsequence is inserted and sorted respectively. Then gradually reduce the interval for the next round of molecular sequence and insertion sequencing. Insert and sort the whole sequence until the last round of sorting interval is 1.

Algorithm steps:

  • Firstly, determine an element interval number gap, and then divide the sequence participating in the sorting into several subsequences from the first element according to this interval number, that is, all elements separated by gap are regarded as a subsequence, and some sort method is adopted for insertion sorting in each subsequence.

  • Then reduce the number of intervals, and re divide the whole sequence into several subsequences according to the new number of intervals, and then sort each subsequence respectively, so on until the number of intervals gap = 1.

Algorithm analysis:

  • The speed of Hill ranking method is a function of a series of interval number gapi. It is not easy to find out the dependence between the number of comparisons and gap, and give a complete mathematical analysis.

  • In the above algorithm, since the method of gapi = ⌊ gapi − 1 / 2 ⌋ is used to reduce the number of intervals, for the sequence with n elements, if gap1 = ⌊ n/2 ⌋, there will be gapp=1 after p = ⌊ log2n ⌋ times of sorting. Therefore, the total number of sorting by Hill sorting method is ⌊ log2n ⌋.

  • It can also be seen from the algorithm that the while loop in the outermost layer is of the order of log2n, and the do while loop in the middle layer is of the order of n. The more subsequences are divided, the fewer elements in the subsequence and the fewer for loops in the innermost layer; On the contrary, when the number of sub sequences is reduced, the elements in the sub sequence are also increased, but the whole sequence is gradually close to order, but the number of cycles will not increase. Therefore, the time complexity of hill sorting algorithm is between O(nlog2n) and O(n2).

  • Hill sorting method is an unstable sorting algorithm.

Code implementation:

class Solution:
    def shellSort(self, arr):
        size = len(arr)
        gap = size // 2

        while gap > 0:
            for i in range(gap, size):
                temp = arr[i]
                j = i
                while j >= gap and arr[j - gap] > temp:
                    arr[j] = arr[j - gap]
                    j -= gap
                arr[j] = temp
            gap = gap // 2
        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.shellSort(nums)

Case realization:

Understand that the time complexity is slightly faster than the previous sorting method, but it is almost not used.

5, Merge sort

Algorithm idea:

Using the classical divide and conquer strategy, first recursively divide the current sequence into two halves until all subsequences have a length of 1, and then merge the ordered sequences into one ordered sequence.

Algorithm steps:

  • Initially, the n records in the sequence to be sorted are regarded as n ordered subsequences (each subsequence is always ordered), and the length of each subsequence is 1.
  • Merge the ordered subsequences in the current sequence group in pairs. After completion, halve the number of sorting sequences in the sequence group and double the length of each subsequence.
  • Repeat the above operation for the ordered subsequence with double length, and finally get an ordered sequence with length n.

Algorithm analysis:

  • The time complexity of merging sorting algorithm is equal to the number of merging trips and the time complexity of each merging trip. The time complexity of the sub algorithm merge(left_arr, right_arr): is O(n). Therefore, the total time complexity of the merge sorting algorithm is O(nlog2n).
  • The merge sort method requires the same auxiliary space as the sequence participating in the sort. Therefore, the spatial complexity of the algorithm is O(n).
  • Because in the merging process of two ordered subsequences, if the same element appears in the two ordered sequences, merge(left_arr, right_arr): the algorithm can make the same element in the previous sequence be copied first, so as to ensure that the relative order of the two elements does not change. So merge sort algorithm is a stable sort algorithm.

Code implementation:

class Solution:
  #Subsequence sorting function
  def merge(self,left_arr,right_arr):
    arr = []
    while left_arr and right_arr:
      if left_arr[0] <= right_arr[0]:   
        arr.append(left_arr.pop(0))   #If the left is less than the right, the less than will be pushed into the arr for cyclic comparison until all the subsequences are sorted
      else:
        arr.append(right_arr.pop(0))
    while left_arr:
      arr.append(left_arr.pop(0))
    while right_arr:
      arr.append(right_arr.pop(0))
    return arr
  
  def mergeSort(self,arr):
    size = len(arr)
    if size < 2:
      return arr
    mid = size//2
    left_arr,right_arr = arr[0:mid],arr[mid:]   #Divide the sequence into two halves
    return self.merge(self.mergeSort(left_arr),self.mergeSort(right_arr))
  
  def sortArray(self,nums:List[int])-->List[int]:
    return self.mergeSort(nums)

Case realization:

An array called merge sort is made. The two arrays are sorted after merging, but it is not made by merging. It is not because of the problem of the two arrays, but because the algorithm idea of merge array is divide and rule and the idea of recursion. Under the two arrays, I didn't understand how to recurse, so I couldn't write it in the end.

6, Quick sort

Algorithm idea:

Quick sort, also known as division exchange sort, picks an element from the unordered queue and divides the unordered queue into two independent parts. All data in one part is smaller than all data in the other part, and then quickly sort the two parts of data respectively according to this method. The whole sorting process can be carried out recursively, In this way, the whole data becomes an ordered sequence.

Algorithm steps:

  • Randomly find a reference number from the array.
  • Then move the elements larger than the benchmark number to the right of the benchmark number and the elements smaller than him to the left of the benchmark number, so as to split the array into left and right parts.
  • Repeat the second step until the left and right parts are sorted separately.

Algorithm analysis:

  • The quick sort method takes the longest time when the elements participating in the sorting are already in order at the beginning. At this time, after n - 1 comparison, the first element is still determined in the original position, and a subsequence with a length of N - 1 is obtained; After n - 2 comparisons, the second element is determined in its original position, and a subsequence with a length of N - 2 is obtained; By analogy, the final total number of comparisons is (n − 1)+(n − 2) +... + 1=n(n − 1) / 2. Therefore, the time complexity is O(n2).

  • In another case, if the dividing element is located in the middle of the sequence after each sorting, so that the current sequence participating in the sorting is divided into two subsequences of equal size, the time required for quick sorting of sequences with length n is:

    T(n)≤ n+2T(n/2) ≤ 2n+4T(n/2) ≤ 3n+8T(n/8) ...... ≤ (log2n)n+nT(1)=O(nlog2n)

    Therefore, the time complexity of quick sorting method is O(nlog2n), and the time performance is obviously better than several sorting algorithms discussed above.

  • Whether the quick sort algorithm is recursive or not, the auxiliary space of stack or other structures is needed to store the first and last positions of the current sequence to be sorted. In the worst case, the spatial complexity is O(n).

  • If the algorithm is rewritten, compare the length of the two sub sequences obtained after one sorting, and first quickly sort the sub sequences with shorter length. At this time, the required spatial complexity can reach O(log2n).

  • Quick sort is not only an unstable sort algorithm, but also a sort algorithm that is not suitable for the implementation of linked list structure.

Code implementation:

class Solution:
  def sortArray(self,nums:List[int])-->List[int]:
    return self.quick_sort(nums,0,len(nums)-1)
  
  def quick_sort(self,arr,low,high):
    # Divide one into two
    # start=end, which proves that there is only one data to be processed
    # Start > end to prove that there is no data on the right
    if low >= high:
        return
    # Define two cursors, pointing to 0 and the end position respectively
    left = low
    right = high
    # Take the data at position 0 as the intermediate value
    mid = arr[left]
    while left < right:
        # Move the right cursor to the left in order to find a value less than mid and put it in the left cursor position
        while left < right and arr[right] >= mid:
            right -= 1
        arr[left] = arr[right]
        # Move the left cursor to the right in order to find a value greater than mid and put it at the right cursor position
        while left < right and arr[left] < mid:
            left += 1
        arr[right] = arr[left]
    # After the while is finished, put the mid in the middle, left=right
    arr[left] = mid
    # Recursive processing of data on the left
    quick_sort(arr, low, left-1)
    # Recursively process the data on the right
    quick_sort(li, left+1, high)

Case realization:

[LeetCode]912. Sort array

Give you an integer array nums, please arrange the array in ascending order.

Example:

Input: nums = [5,2,3,1]
Output:[1,2,3,5]

Quick sort method:

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        def quicksort(nums,left,right):
            flag=nums[(left+right)//2] # initialize the sentry position from the middle every time
            i,j=left,right                          #Set the pointer i from left to right and the pointer j from right to left
            while i<=j:
                while nums[i]<flag: i+=1            #i scan from left to right to find the number greater than or equal to flag.
                while nums[j]>flag: j-=1            #j scan from right to left to find the number less than or equal to flag.
                if i<=j:
                    nums[i],nums[j]=nums[j],nums[i] #Exchange the values corresponding to the left and right pointer subscripts
                    i+=1                            #The left pointer continues to go right
                    j-=1                            #The right pointer continues to go left
            if i<right: quicksort(nums,i,right)     #Recursively sort the low order array on the left of flag
            if j>left:  quicksort(nums,left,j)      #Recursively sort the low order array on the right of flag
        quicksort(nums,0,len(nums)-1)               #Function entry to pass in the information of the whole array
        return nums                                 #Returns the modified num

Fast scheduling mainly deals with the understanding of range determination and recursive process

7, Heap sort

Algorithm idea:

The element at the top of the heap (small top heap) is the smallest element in the whole heap. Exchange the top of the heap element with the last element, and then use a "down filter" to move the new top of the heap element to the correct position in the heap: that is, compare the size of the top of the heap element with its two left and right child nodes. If the top of the heap element is the smallest, keep it at the top of the heap and stop; If the left or right child node is the smallest, exchange the values of the heap top element and the left or right child node, and then continuously compare them along the current path until the initial heap top element is the smallest value in a comparison or reaches the leaf node position.

In addition, if it is a small top pile, the descending sequence is obtained; If it is a large top heap, the result is an ascending sequence.

The heap can be regarded as a complete binary tree, which satisfies that the value of any non leaf node is not greater than (or less than) the value of its left and right child nodes

Algorithm steps:

Heap sorting needs to solve two problems:

  • How to build a heap from an unordered sequence
  • How do we adjust the remaining elements into a new heap after the top elements are exported?

To solve the second problem: generally, after outputting the heap top element, it is regarded as excluding this element, and then fill its position with the last element in the table, and adjust it from top to bottom: first, compare the heap top element with the root nodes of its left and right subtrees, and exchange the smallest element to the heap top; Then adjust it all the way along the damaged path to the leaf node to get a new heap.

Step 1: construct initial heap

When initializing the heap, all non leaf nodes are filtered
The subscript of the last non terminal element is rounded down [n/2], so the filtering only needs to start with the whole element taken down [n/2] and adjust from back to front.

Step 2: sort the heap

Heap sorting is a sort of selection. The initial heap is the initial unordered area.

At the beginning of sorting, first output the heap top element (because it is the highest value), and exchange the heap top element with the last element. In this way, the nth position (i.e. the last position) is used as the ordered area, and the first n-1 position is still the disordered area. Adjust the disordered area to get the heap, and then exchange the heap top and the last element, so that the length of the ordered area becomes 2...

Continue this operation, readjust the remaining elements to the heap, and then output the elements at the top of the heap to the ordered area. Each exchange leads to disordered region-1 and ordered region + 1. This process is repeated until the length of the ordered area increases to n-1 and the sorting is completed.

Algorithm analysis:

  • Because the time complexity of heap building is O (n) O (n) O (n), and the time complexity of adjusting heap is O (l o g n) O (logn) O (logn), the time complexity of heap sorting is O (n l o g n) O (nlogn) O (nlogn)
  • Since only one auxiliary space of record size is required in stacking sort, the space complexity of stacking sort is O(1).
  • Heap sorting is an unstable sorting algorithm. Heap sorting is also a sort that is not suitable for implementation on linked lists.
  • Heap sorting method is not worth advocating for files with fewer records, but it is still very effective for n larger files. Because its running time is mainly spent on the repeated "screening" during the construction of the initial heap and the adjustment of the construction of a new heap.

Code implementation:

Case realization:

8, Count sort

Algorithm idea:

Use an additional array counts, where the ith element counts[i] is the number of elements whose value in the array to be sorted is equal to I. Then arrange the elements in the arr to the correct position according to the array counts.

Counting sort is a sort algorithm that does not compare the size of data. It is a sort algorithm that sacrifices space for time.

Counting sorting is suitable for sorting data with large amount of data and small data range, such as sorting people's age, sorting test scores, etc.

Algorithm steps:

  • Find the maximum value k in the list to be sorted, and open up a count list with a length of k+1. The values in the count list are 0.
  • Visit the list to be sorted. If the visited element value is i, the value of index i in the count list will be increased by 1.
  • Visit the complete list to be sorted. The value J of index i in the count list indicates that the number of i is j, and count the number of each value in the list to be sorted.
  • Create a new list, traverse the count list, and add j i's to the new list in turn. The new list is the ordered list. The data size in the list to be sorted is not compared in the whole process.

Algorithm analysis:

  • When the input element is n integers between 0 and K, the time complexity of counting sorting is O(n+k).
  • Because the length of the array counts used for counting depends on the range of data in the array to be sorted (equal to the maximum value of the array to be sorted minus the minimum value plus 1). Therefore, counting sorting requires a lot of time and memory for arrays with a large data range.
  • Count sorting is generally used to sort integers, not to sort people's names alphabetically.
  • Counting sorting is a stable sorting algorithm.

Code implementation:

class Solution:
    def countingSort(self, arr):
        arr_min, arr_max = min(arr), max(arr)
        size = arr_max - arr_min + 1
        counts = [0 for _ in range(size)]   #Generate a list of all zeros for counting

        for num in arr:
            counts[num - arr_min] += 1
        for j in range(1, size):
            counts[j] += counts[j - 1]

        res = [0 for _ in range(len(arr))]   #Create a new list to store the newly sorted elements
        for i in range(len(arr) - 1, -1, -1):
            res[counts[arr[i] - arr_min] - 1] = arr[i]
            counts[arr[i] - arr_min] -= 1
        return res

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.countingSort(nums)

Case realization:

[LeetCode]1122. Relative sorting of arrays

Give you two arrays, arr1 and arr2. The elements in arr2 are different, and each element in arr2 appears in arr1.

Sort the elements in arr1 so that the relative order of items in arr1 is the same as that in arr2. Elements that do not appear in arr2 need to be placed at the end of arr1 in ascending order.

Example:

Input: arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
Output:[2,2,2,1,4,3,3,9,6,7,19]

9, Bucket sorting

Algorithm idea:

The unordered array is divided into several "buckets", and the elements of each bucket are sorted separately.

Algorithm steps:

  • The interval is divided into n sub intervals of the same size, and each interval is called a bucket.
  • Traverse the array and load each element into the corresponding bucket.
  • The elements in each bucket are sorted separately (using algorithms such as insertion, merging, fast sorting, etc.).
  • Finally, combine the elements in the bucket in order.

Algorithm analysis:

  • Bucket sorting can be completed in linear time. When the number of input elements is n and the number of buckets is m, the data in each bucket is k = n / m. The time complexity of sorting in each bucket is O(k * log2k). M barrels m ∗ O ( k ∗ l o g 2 k ) = m ∗ O ( ( n / m ) ∗ l o g 2 ( n / m ) ) = O ( n ∗ l o g 2 ( n / m ) ) m * O(k * log_2k) = m * O((n/m)*log_2(n/m)) = O(n*log_2(n/m)) m∗O(k∗log2​k)=m∗O((n/m)∗log2​(n/m))=O(n∗log2​(n/m)). When the number of buckets m is close to the number of data n, log2(n/m) is a small constant, so the sorting time complexity of sorting buckets is close to O(n).
  • Because bucket sorting uses auxiliary space, the spatial complexity of bucket sorting is o(n+m).
  • If stable sorting algorithms such as insertion sorting algorithm are used in the bucket, bucket sorting is also a stable sorting algorithm.

Code implementation:

class Solution:
    def insertionSort(self, arr):
        for i in range(1, len(arr)):
            temp = arr[i]
            j = i
            while j > 0 and arr[j - 1] > temp:
                arr[j] = arr[j - 1]
                j -= 1
            arr[j] = temp

        return arr

    def bucketSort(self, arr, bucket_size=5):
        arr_min, arr_max = min(arr), max(arr)    #Find the maximum and minimum number
        bucket_count = (arr_max - arr_min) // bucket_size + 1 # delimit molecular interval
        buckets = [[] for _ in range(bucket_count)]  #Create bucket

        for num in arr:
            buckets[(num - arr_min) // bucket_size].append(num) # places the element in the corresponding bucket

        res = []   
        for bucket in buckets: 
            self.insertionSort(bucket)  #Sort the elements in the bucket using select sort
            res.extend(bucket)

        return res

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.bucketSort(nums)

Case realization:

10, Cardinality sort

Algorithm idea:

Cut the integer into different numbers according to the number of digits, and then compare and sort them according to each number of digits.

Algorithm steps:

The radix sorting algorithm can adopt "least significant digital first" or "most significant digital first". The most commonly used is the "lowest priority method".

Let's take the lowest order first method as an example to explain the algorithm steps.

  • Traverse the array elements, get the maximum element of the array, and get the number of bits.
  • Sort the array elements with the bit elements as the index.
  • Merge arrays.
  • Then, take the ten bits, hundred bits,..., and the highest value of the maximum value element as the index, sort, and merge the array to finally complete the sorting.

Algorithm analysis:

  • The time complexity of cardinality sorting is O(k * n). Where n is the number of elements to be sorted and K is the number of digits. The size of K depends on the selection of digital bits (decimal bits, binary bits) and the size of the complete set of data types to which the elements to be sorted belong.
  • The spatial complexity of cardinality sorting is O(n+k).
  • Cardinality sorting is a stable sorting algorithm.

Code implementation:

class Solution:
    def radixSort(self, arr):
        size = len(str(max(arr)))    #Gets the number of bits of the largest element

        for i in range(size):
            buckets = [[] for _ in range(10)]
            for num in arr:
                buckets[num // (10 ** i) % 10].append(num) # sorts from a single bit
            arr.clear()
            for bucket in buckets:
                for num in bucket:
                    arr.append(num)

        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.radixSort(nums)

Case realization:

Topics: Python Algorithm