[关闭]
@listenviolet 2019-06-27T02:36:41.000000Z 字数 15004 阅读 5076

十大经典排序算法总结(Python)


参考链接:
十大经典排序算法(动图演示)
基数排序
基数排序--MSD(桶排序)

0. 算法概述

0.1 算法分类

十种常见排序算法可以分为两大类:

  • 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
  • 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
排序算法
1.非线性时间比较类排序
1.1 交换排序: 冒泡排序;快速排序
1.2 插入排序: 简单插入排序;希尔排序
1.3 选择排序: 简单选择排序;堆排序
1.4 归并排序: 二路归并排序;多路归并排序
2.线性时间非比较类排序
2.1 基数排序;
2.2 桶排序;
2.3 计数排序

0.2 时间复杂度

![time_complexity][2]

0.3 相关概念

  • 稳定: 如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定: 如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度: 对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度: 是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

1. 冒泡排序 (Bubble Sort)

1.1 算法描述(升序)

  1. 从前向后遍历未排序序列,如果当前元素 > 后一个元素,则交换这两个元素
  2. 重复1,直到排序完成
  3. 注意:第i轮遍历后,数组的第i大的元素已移动到数组[-i]位,数组的倒数第i位至最后一位是已经排序好的最大的i个元素;那么下一轮遍历时,仅需遍历arr[:-i]中的元素即可。

2.2 例子

arr:
[4 3 2 1]
第1轮遍历
[3 4 2 1]
[3 2 4 1]
[3 2 1 4]
第2轮遍历
[2 3 1 4]
[2 1 3 4]
第3轮遍历
[1 2 3 4]

(其中粗体表示当前比较的两个元素,斜体表示已经排序好的元素)

1.3 代码实现

  1. def bubbleSort(arr):
  2. length = len(arr)
  3. for i in range(length - 1):
  4. for j in range(0, length - i - 1):
  5. if arr[j] > arr[j+1]:
  6. arr[j], arr[j+1] = arr[j+1], arr[j]
  7. return arr

1.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

2. 选择排序 (Selection Sort)

2.1 算法描述

  1. 有序区arr[:sorted]; 无序区arr[sorted:] (sorted为已排序部分最大index;初始为0)
  2. 在无序区中找到最小的元素,swap(arr[sorted], arr[minIndex])
  3. sorted += 1
  4. 重复1-3,直至所有元素排序完毕。

2.2 例子

arr:
[4 3 2 1]
第1轮遍历
[4 3 2 1]
[1 3 2 4]
第2轮遍历
[1 3 2 4]
[1 2 3 4]

2.3 代码实现

  1. def selectionSort(arr):
  2. length = len(arr)
  3. for i in range(length - 1): # the first ind of the unsorted
  4. minIdx = i # the minIdx of the unsorted
  5. for j in range(i+1, length):
  6. if arr[j] < arr[minIdx]:
  7. minIdx = j
  8. arr[i], arr[minIdx] = arr[minIdx], arr[i]
  9. return arr

2.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
不稳定

3. 插入排序 (Insertion Sort)

3.1 算法描述

  1. 有序区 arr[:sorted]; 无序区 arr[sorted:] (初始状态认为第0个元素为已排序的,sorted = 1)
  2. 取出无序区的第一个元素的值,temp = arr[sorted]
  3. 将该值从后向前依次与有序区元素arr[i]比较,若temp < arr[i],则将arr[i]后移;否则,将temp的值插入在(i+1)位置处。
  4. 重复1-3直至整个数组排序完成。

3.2 例子

arr
[1 3 2 5 4]
第1轮
[1 3 2 5 4] temp = 3
* 1为有序区第一个元素,3为无序区第一个元素。拿着数值3,从后向前遍历有序区。
* 因为3 > 1, 则将其放在有序区的后一位,即保持3的位置不变。
* 第1轮后,有序区为[1,3],无序区为[2 5 4])
第2轮
[1 3 2 5 4] temp = 2
* 2为无序区第一个元素,拿着数值2,从后向前遍历有序区
[1 3 _ 5 4] temp = 2
* 首先比较 3 > temp, 则将3后移一位,继续向前遍历有序区
[1 _ 3 5 4] temp = 2
* 比较 1 < temp, 则将temp插入在1的后一位
[1 2 3 5 4]
* 第2轮后,有序区为[1 2 3], 无序区为[5 4]
第3轮
[1 2 3 5 4] temp = 5
* 5为无序区第一个元素, 拿着数值5,,从后向前遍历有序区
* 因为3 < temp,则将temp插入在3的后一位,即保持5的位置不变
* 第3轮后,有序区变为[1 2 3 5],无序区为[4]
第4轮
[1 2 3 5 4] temp = 4
* 4为无序区第一个元素,拿着数值4,从后向前遍历有序区
* 因为 5 > temp, 将5后移一位,继续向前遍历有序区
[1 2 3 _ 5] temp = 4
* 因为3 < temp,则将temp插入在3的后一位。
[1 2 3 4 5]
* 排序完成

3.3 代码实现

  1. def insertionSort(arr):
  2. length = len(arr)
  3. for i in range(1, length): # the current element index
  4. preIndex = i - 1 # the index to insert
  5. current = arr[i] # save the number otherwise it will be replace by others
  6. while(preIndex >= 0 and arr[preIndex] > current):
  7. arr[preIndex + 1] = arr[preIndex] # move -->
  8. preIndex -= 1
  9. arr[preIndex + 1] = current
  10. return arr

3.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

4. 希尔排序 (Shell Sort)

4.1 算法描述

  1. 设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔,将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中
  2. 在每一个子序列中分别实行直接插入排序。
  3. 然后缩小间隔increment,重复上述子序列划分和排序工作。
  4. 直到最后取increment=1,将所有元素放在同一个子序列中排序为止。

4.2 例子

arr
[21 25 49 25* 16 8] 初始增量: len(arr) // 3 + 1 = 3
增量为3
子序列1: [21 _ _ 25* _ _ ] => (21 < 25 直接插入排序无需交换)
子序列2: [ _ 25 _ _ 16 _ ] => (25 > 16 插入排序后交换) => [ _ 16 _ _ 25 _]
子序列3: [ _ _ 49 _ _ 8 ] => (49 > 8 插入排序后交换) => [ _ _ 8 _ _ 49 ]
合成序列: [21 16 8 25* 25 49]
增量为2
子序列1: [21 _ 8 _ 25] => (插入排序) => [8 _ 21 _ 25]
子序列2: [_ 16 _ 25* _ 49]
合成序列: [8 16 21 25* 25 49]
增量为1
当增量为1的时候,实际上就是把整个数列作为一个子序列进行插入排序
子序列: [8 16 21 25* 25 49]
合成序列: [8 16 21 25* 25 49]
排序完成

4.3 代码实现

  1. def shellSort(start, end, number):
  2. increment = end - start + 1
  3. while increment > 1:
  4. increment //= 3 + 1
  5. for i in range(start + increment, end + 1):
  6. if (number[i - increment] > number[i]):
  7. temp = number[i]
  8. j = i - increment
  9. while (j >= start and number[j] > temp):
  10. number[j + increment] = number[j]
  11. j -= increment
  12. number[j + increment] = temp
  13. return number

4.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
与增量选取有关 不稳定

5. 归并排序

5.1 算法描述

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。

5.2 例子

arr
[1 4 2 4 3 5]
left [1 4 2]
divide [1] [4 2]
divide [1] / [4] [3]
merge [1] / [2 4]
merge [1 2 4]
right[4 3 5]
divide [4] / [3 5]
divide [4] / [3] [5]
merge [4] / [3 5]
merge [3 4 5]
merge
[1 2 3 4 5 6]
* merge时,分别从前向后遍历left和right,比较left[i],right[j]的大小,将较小值插入到merge后的数组中。
* 当left(或right)遍历结束后,将right(或left)的剩余元素添加到merge后的数组中。

5.3 代码实现

  1. def mergeSort(arr):
  2. if len(arr) < 2:
  3. return arr
  4. middle = len(arr) // 2
  5. left_arr = arr[:middle]
  6. right_arr = arr[middle:]
  7. return merge(mergeSort(left_arr), mergeSort(right_arr))
  8. def merge(left_arr, right_arr):
  9. result = []
  10. i, j = 0, 0
  11. while i < len(left_arr) and j < len(right_arr):
  12. if left_arr[i] < right_arr[j]:
  13. result.append(left_arr[i])
  14. i += 1
  15. else:
  16. result.append(right_arr[j])
  17. j += 1
  18. if i == len(left_arr):
  19. result.extend(right_arr[j:])
  20. elif j == len(right_arr):
  21. result.extend(left_arr[i:])
  22. return result

5.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

6. 快速排序(Quick Sort)

6.1 算法描述

  1. 从数列中挑出一个元素,称为“基准”(pivot)
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2 例子

取数组第0个元素的值作为pivot
初始化两个位置标记left = 0; right = len(arr) - 1
1. 比较pivot和arr[right]的大小,若pivot <= arr[right],则right递减1;
2. 直到 pivot > arr[right] 且 left < right: 将 arr[right] 的值赋给 arr[left]。right 向前移动结束,至第 4 步,left 向后移动。
3. 若 left == right,则将pivot的值赋给arr[left],本轮排序结束。此时,pivot之前的元素均小于pivot; pivot之后的元素均大于pivot。递归地用快速排序算法对上一轮pivot前后的两个子序列分别排序。
4. 比较pivot和arr[left]的大小,若pivot >= arr[left],则left递增1;
5. 直到 pivot > arr[left] 且 left < right: 将 arr[left] 的值赋给 arr[right]。left 向前移动结束,至第 1 步,right 向前移动。
6. 若 left == right,转至第 3 步。

arr: [3 1 2 6 4 5] pivot = 3
<-- right
1. [3 1 2 6 4 5] pivot = 3; left = 0; right = 5
2. [3 1 2 6 4 5] pivot = 3; left = 0; right = 2
3. [2 1 2 6 4 5] pivot = 3; left = 0; right = 2; arr[left] = arr[right]

left-->
4. [2 1 2 6 4 5] pivot = 3; left = 2; right = 2;
5. [2 1 3 6 4 5] pivot = 3; left = 2; right = 2; arr[left] = pivot

对pivot前后的两个子序列递归快排:[2 1], [6 4 5],略。

6.3 代码实现

  1. # Python
  2. def quickSort(arr):
  3. if len(arr) == 0: return []
  4. if len(arr) == 1: return arr
  5. left = 0
  6. right = len(arr) - 1
  7. base = arr[left]
  8. while(left < right):
  9. while(arr[right] > base and right > left):
  10. right -= 1
  11. if right > left:
  12. arr[left] = arr[right]
  13. left += 1
  14. while(arr[left] < base and left < right):
  15. left += 1
  16. if right > left:
  17. arr[right] = arr[left]
  18. right -= 1
  19. if left == right:
  20. arr[left] = base
  21. result = quickSort(arr[:left]) + [arr[left]]
  22. if left < len(arr) - 1:
  23. result = result + quickSort(arr[left + 1:])
  24. return result
  1. // C++
  2. int RandomInRange(int start, int end)
  3. {
  4. return rand() % (end - start + 1) + start;
  5. }
  6. void Swap(int *num1, int *num2)
  7. {
  8. int temp = *num1;
  9. *num1 = *num2;
  10. *num2 = temp;
  11. }
  12. int Partition(vector<int> &data, int start, int end)
  13. {
  14. int index = RandomInRange(start, end);
  15. Swap(&data[index], &data[end]);
  16. int small = start - 1;
  17. for(index = start; index < end; ++index)
  18. {
  19. if(data[index] < data[end])
  20. {
  21. ++small;
  22. if(small != index) Swap(&data[index], &data[small]);
  23. }
  24. }
  25. ++small;
  26. Swap(&data[small], &data[end]);
  27. return small;
  28. }
  29. void QuickSort(vector<int> &data, int start, int end)
  30. {
  31. int index = Partition(data, start, end);
  32. if(index < end) QuickSort(data, index + 1, end);
  33. if(index > start) QuickSort(data, start, index - 1);
  34. }

6.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

7. 堆排序

7.1 数组与二叉树

  • 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:
  • 大顶堆满足:子结点的键值或索引总是小于它的父节点;
  • 小顶堆满足:子结点的键值或索引总是大于它的父节点。
    数组索引与二叉树中节点的关系:
    当前node的值在数组中的索引为
    其左孩子节点对应数组索引为
    其右孩子节点对应数组索引为
    其父节点对应数组索引为
   4
  /   \
  5    3
 / \   /
2  6  1

7.2 堆的创建(初始化)

给定一棵二叉树,将其调整为堆有自上而下调整方式,以使其满足最大堆/最小堆的性质。

7.2.1 算法描述(例子:调整为小顶堆)

  1. 所有的叶子节点均可以看作已经调整为最小堆的结构;故从第一个有孩子节点的节点开始向前遍历调整,直到遍历调整根结点后停止;
  2. 若当前节点node(i)的值不大于其左右孩子节点的值,则继续遍历前一个节点(即节点序号 i - 1);
  3. 若当前节点node(i)的值大于其孩子节点的值,则选择值最小的孩子节点与该节点交换,直至调整到以node(i)为根结点的子树满足最小堆的性质;继续遍历前一个节点node(i-1)。

7.2.2 例子

   98
  /   \
  86    68
 / \   /
58  42 42*

7.2.3 代码实现

自上而下调整子树为最小堆:(见算法描述3)

  1. # Adjust from top to bottom
  2. # a: the arr
  3. # i: adjust from node i
  4. # n: the total number of nodes in heap
  5. # j: the child of i
  6. def MinHeapFixdown(a, i, n):
  7. temp = a[i]
  8. j = 2 * i + 1
  9. while j < n:
  10. if j + 1 < n and a[j + 1] < a[j]: # find the min of left and right child
  11. j += 1
  12. if a[j] < temp:
  13. a[i] = a[j]
  14. i = j
  15. j = 2 * i + 1
  16. else: break
  17. a[i] = temp

最小堆的创建(初始化)

  1. # Create the Min Heap
  2. # Adjust from the first node which is not leaf
  3. # Make root(i) whose heap to be the min heap
  4. # then make the root(i - 1) whose heap to be the min heap...
  5. def createMinHeap(a, n):
  6. for i in range(n // 2 - 1, -1, -1):
  7. MinHeapFixdown(a, i, n)
  8. return a

7.3 堆中元素的删除 与 堆排序

7.3.1 算法描述

  1. 取出最小堆的根的值;将当前最小堆的根的值与最后一个元素交换;
  2. 利用7.1.2中"自上而下调整子树为最小堆"的方法,将前n - 1 个元素构成的树重新调整为最小堆结构。

7.3.2 例子

   42
  /   \
  58   42*
 / \   /
98  86 68
1. 取出根结点的值,加入到已排序的数组arr中,并交换42与最后一个节点68的值。
   68
  /   \
  58   42*
 / \   /
98  86 42
arr = [42]
2. 此时认为42已被删除(被删除元素用斜体标注);对剩余的前5个元素进行调整,自上而下的调整为最小堆;
   42*
  /   \
  58   68
 / \   /
98  86 42
arr = [42]
3. 最小堆调整完毕;取出根结点的值42*,加入到已排序的数组arr中,并交换42*与最后一个节点86的值。(注意:42已被删除,最后一个节点为86.)
   86
  /   \
  58   68
 / \   /
98  42 42
arr = [42, 42*]
4. 此时认为42*已被删除;对剩余的前4个元素进行调整,自上而下的调整为最小堆;
   58
  /   \
 86   68
 / \   /
98 42* 42
5. 最小堆调整完毕;取出根结点的值58,加入到已排序的数组arr中,并交换58与最后一个节点98的值。
   98
  /   \
 86   68
 / \   /
58 42* 42
arr = [42, 42*, 58]
6. 同理进行调整,直至取出堆中最后一个元素,堆排序完成。arr = [42, 42*, 58, 68, 86, 98]

7.3.3 代码实现

删除当前根结点,并调整剩余元素重新构成最小堆

  1. # Delete from the heap
  2. def MinHeapDeleteNumber(a, n):
  3. a[0], a[n - 1] = a[n - 1], a[0]
  4. MinHeapFixdown(a, 0, n - 1)

堆排序

  1. def MinHeapSortToDescendArray(a, n):
  2. for i in range(n - 1, 0, -1):
  3. a[0], a[i] = a[i], a[0]
  4. MinHeapFixdown(a, 0, i)
  5. return a

7.3.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
不稳定

7.4 堆中添加元素

7.4.1 算法描述

  1. 在一个最小堆中添加新元素,将该元素添加为该树的最后的一个节点。
  2. 比较新节点的值与其父节点的值大小;若该节点的值大于父节点的值,则该子结构满足最小堆性质,可以停止调整;若该节点的值小于父节点的值,则交换该结点与其父节点,直到子结构满足最小堆性质。

7.4.2 例子

   42
  /   \
  58   42*
 / \   /  \
98  86 68  36

1.添加新元素36到已创建的最小堆中。
2.比较新节点的值36与其父节点的值; 36 < 42 不满足最小堆的性质了,需要进行调整; 交换36和其父节点42*。
   42
  /   \
  58    36
 / \   /  \
98  86 68  42*
3. 比较36与其父节点的值的大小; 36 < 42; 不满足最小堆的性质,需要进行调整; 交换36和其父节点42; 调整完毕。
   36
  /   \
  58    42
 / \   /  \
98  86 68  42*

7.4.3 代码实现

自下而上调整为最小堆

  1. # Adjust from bottom to top
  2. # a: the arr
  3. # i: adjust from node i
  4. # j: the parent of i
  5. def MinHeapFixup(a, i):
  6. temp = a[i]
  7. j = (i - 1) // 2 # adjust from the first node which is not leaf
  8. while j >= 0 and i != 0: # if the parent > current: current <= parent
  9. if a[j] < temp:
  10. a[i] = a[j]
  11. i = j
  12. j = (i - 1) // 2
  13. else: break
  14. a[i] = temp

向堆中添加元素

  1. # Add a number to the heap
  2. # a: the arr
  3. # n: the total number of the heap
  4. def MinHeapAddNumber(a, n, number):
  5. a[n] = number
  6. MinHeapFixup(a, n)

8. 计数排序(Counting Sort)

8.1 算法描述

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

8.2 例子

arr = [2, 3, 2, 5, 3, 1]
min = 1
max = 5
C[1]: 1
C[2]: 2
C[3]: 2
C[4]: 0
C[5]: 1
C数组的下标即为arr数组中元素的值,arr数组中元素出现的次数,即为对应下标下C数组的值。比如arr中3出现了2次,C[3] = 2.
那么将C数组的下标按计数大小依次读出,即可获得arr中元素的值从小到大的排列。
得到:arr = [1, 2, 2, 3, 3, 5]

8.3 代码实现

  1. def countSort(arr, Max):
  2. bucket = [0] * (Max + 1)
  3. for i in range(len(arr)):
  4. bucket[arr[i]] += 1
  5. idx = 0
  6. for i in range(len(bucket)):
  7. while(bucket[i] > 0):
  8. arr[idx] = i
  9. idx += 1
  10. bucket[i] -= 1
  11. return arr

8.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

9.桶排序(Bucket Sort)

9.1 算法描述

  1. 根据所给数据的大小范围,划定合理的所分的桶数,则第i桶中所盛放的数据的大小范围为:[i * bucketsize, (i + 1) * bucketsize]
  2. 遍历输入数组,并把数据一个个放到所对应的桶中
  3. 对每个不是空的桶进行排序(本例子中用的插入排序)
  4. 从不是空的桶中把排好序的数据拼接起来

9.2 例子

设有数组 array = [63, 157, 189, 51, 101, 47, 141, 121, 157, 156, 194, 117, 98, 139, 67, 133, 181, 13, 28, 109],对其进行桶排序
1. 设定分5个桶,每个桶的大小为36.4
2. 那么所分的桶的区间分别为:[13,49.4), [49.4, 85.8), [85.8, 122.2), [122.2, 158.6), [158.6, 195)
3. 遍历输入数据,将数据一个个放入对应的桶中。之后对每个桶中的数据进行插入排序。
index = 0
[13,49.4) => 13 -> 28 -> 47

index = 1
[49.4, 85.8)=> 51 -> 63 -> 67

index = 2
[85.8, 122.2) => 98 -> 101 -> 109 -> 117 -> 121

index = 3
[122.2, 158.6) => 133 -> 139 -> 141 -> 156 -> 157 -> 157

[158.6, 195) => 181 -> 189 -> 194
4. 合并数据。bucket 0 -> bucket 1 -> bucket 2 -> bucket3 -> bucket 4
5. 最终数据。 [13, 28, 47, 51, 63, 67, 98, 101, 109, 117, 121, 133, 139, 141, 156, 157, 157]

9.3 代码实现

  1. def bucketSort(arr, bucketSize):
  2. if len(arr) == 0: return arr
  3. minValue = min(arr)
  4. maxValue = max(arr)
  5. bucketsCount = (maxValue - minValue) // bucketSize + 1
  6. buckets = [[]] * bucketsCount
  7. for i in range(len(arr)):
  8. ind = (arr[i] - minValue) // bucketSize
  9. buckets[ind] = buckets[ind] + [arr[i]]
  10. arr = []
  11. for i in range(len(buckets)):
  12. buckets[i] = insertSort(buckets[i])
  13. for j in range(len(buckets[i])):
  14. arr.append(buckets[i][j])
  15. return arr
  16. def insertSort(bucket):
  17. for i in range(1, len(bucket)):
  18. temp = bucket[i]
  19. j = i - 1
  20. while j >= 0 and bucket[j] > temp:
  21. bucket[j + 1] = bucket[j]
  22. j -= 1
  23. bucket[j + 1] = temp
  24. return bucket

9.4 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

10.基数排序(Radix Sort)

LSD 最低位优先(Least Significant Digit first)法
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
MSD 最高位优先(Most Significant Digit first)法
先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

10.1 算法描述

10.1.1 LSD算法描述

  1. 取得数组中的最大数,并取得位数;
  2. 以个位元素为索引,对数组元素进行计数排序
  3. 合并数组;
  4. 之后,依次以十位,...,最大元素的最大位数处的值为索引,进行计数排序,并合并数组;
  5. 最后可得完全排序后的数组。

10.1.2 MSD算法描述

  1. 先根据最高位关键码K1排序,得到若干对象组,对象组中每个对象都有相同关键码K1。
  2. 再分别对每组中对象根据关键码K2进行排序,按K2值的不同,再分成若干个更小的子组,每个子组中的对象具有相同的K1和K2值。
  3. 依此重复,直到对关键码Kd完成排序为止。
  4. 最后,把所有子组中的对象依次连接起来,就得到一个有序的对象序列。

10.2 例子

10.2.1 LSD举例

arr = [73, 22, 93, 43, 55, 14, 28, 65, 39, 81]
1. 首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0:
1: 81
2: 22
3: 73 93 43
4: 14
5: 55 65
6:
7:
8: 28
9: 39
2. 接下来将这些桶子中的数值重新串接起来,成为数列:arr = [81, 22, 73, 93, 43, 14, 55, 65, 28, 39]
3. 根据十位数来分配:
0:
1: 14
2: 22 28
3: 39
4: 43
5: 55
6: 65
7: 73
8: 81
9: 93
4. 接下来将这些桶子中的数值重新串接起来,成为数列:arr = [14, 22, 28, 39, 43, 55, 65, 73, 81, 93], 整个数列已经排序完毕。

10.2.2 MSD举例

arr = [12, 14, 54, 5, 6, 3, 9, 8, 47, 89]
1. 按照最高位十位遍历并分桶:
0: 5 6 3 9 8
1: 12 14
2:
3:
4: 47
5: 54
6:
7:
8: 89
9:
2. 然后再将每个桶分0-9号小桶
例如,对于0号桶,分小桶后:
0
1
2
3: 3
4
5: 5
6: 6
7
8: 8
9: 9
3. 分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下的数据序列:arr = [3, 5, 6, 8, 9, 12, 14, 47, 54, 89]

10.3 代码实现

10.3.1 LSD代码

  1. import math
  2. def LSDRadixSort(arr):
  3. Max = max(arr)
  4. radix = 0
  5. while(Max != 0):
  6. Max /= 10
  7. radix += 1
  8. for i in range(1, radix + 1):
  9. bukcet = [[] for i in range(10)]
  10. mod = pow(10, i)
  11. for j in range(len(arr)):
  12. base = arr[j] % mod
  13. base = base // pow(10, i - 1)
  14. bukcet[base].append(arr[j])
  15. arr = []
  16. for k in range(10):
  17. for ele in bukcet[k]:
  18. arr.append(ele)
  19. return arr

10.3.2 MSD代码

  1. def MSDRadixSort(arr,radix):
  2. n = len(arr)
  3. Max = max(arr)
  4. k = pow(10, radix - 1)
  5. bucket = [[] for i in range(10)]
  6. num = [0 for i in range(10)]
  7. for a in arr:
  8. index = (a // k) % 10
  9. bucket[index].append(a)
  10. num[index] += 1
  11. cnt = 0
  12. arr = []
  13. for i in range(10):
  14. if num[i] == 1:
  15. arr.append(bucket[i][0])
  16. elif num[i] > 1:
  17. B = bucket[i][:]
  18. B = MSDRadixSort(B, radix - 1)
  19. arr.extend(B)
  20. cnt += len(B)
  21. return arr

10.4 LSD 算法复杂度

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
稳定

LSD时间效率: 设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。
LSD与MSD比较: LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注