• 算法篇之(排序)


    前言:算法中排序也是经常用到的,今天学习排序的几大经典算法(python语言实现)。

    目录

    冒泡排序

    代码解读

    时间复杂度分析

    选择排序

    代码解读

    时间复杂度分析

    插入排序

    代码解读

    时间复杂度分析

    希尔排序

    代码解读

    时间复杂度分析

    归并排序

    代码解读

    时间复杂度分析

    快速排序

    代码解读

    时间复杂度分析

    桶排序

    代码解读

    时间复杂度分析

    计数排序

    代码解读

    时间复杂度分析

    基数排序

    代码解读

    时间复杂度分析

    堆排序

    代码解读

    时间复杂度分析

    总结


    冒泡排序

    1.1 冒泡排序 | 菜鸟教程 (runoob.com)

    代码:

    1. def bubble_sort(alist):
    2. n = len(alist)
    3. for i in range(n - 1, -1, -1):
    4. for j in range(0, i):
    5. if alist[j] > alist[j + 1]:
    6. alist[j], alist[j + 1] = alist[j + 1], alist[j]

    代码解读

    case:[22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70]

    1. len函数获取列表的长度,例子中数组长度为14,下标范围为:0-13
    2. 第一层for循环,倒着取出下标,13-0
    3. 第二层for循环,取出range(0,i)范围内的数,i是第一层循环取出的下标,即第二层循环,分别取出range(0,13)、range(0,12)……range(0,1),range(0,0)
    4. 依次比较取出的数作为下标,在列表里对应的值,如果前一个数比后面的数大,则将两个数互换,alist[ j ], alist[ j + 1 ] = alist[ j + 1 ], alist[ j ],这行代码是python语言特有的写法,表示将两个数进行交换

    时间复杂度分析

    O(N^2)

    有两层for循环, 看一下最差的情况

    外层for循环,执行次数为N

    内层for循环,第一次比较了n-1,第二次比较了n-2,……,第N-1次比较了1次,加起来,总的执行次数复杂度为N

    复杂度为N*2

    选择排序

    1.2 选择排序 | 菜鸟教程 (runoob.com)

    1. def select_sort(alist):
    2. n = len(alist)
    3. for j in range(n - 1):
    4. min_index = j
    5. for i in range(j + 1, n):
    6. if alist[min_index] > alist[i]:
    7. min_index = i
    8. alist[j], alist[min_index] = alist[min_index], alist[j]

    代码解读

    case:[22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70]

    1. len函数获取列表的长度,例子中数组长度为14,下标范围为:0-13
    2. 第一层for循环,取出列表的每一个下标
    3. 第二层for循环,从每个下标,往后一直找到结束
    4. 分为两个部分:所在的第一个下标和未排好序的部分,未排好序的部分的所有数,与第一个下标所在的值,依次进行对比,如果找到最小的数,则将两者进行交换

    时间复杂度分析

    时间复杂度:N^2

    循环了两次,外层循环和内层循环,时间复杂度为N^2

    插入排序

    1.3 插入排序 | 菜鸟教程 (runoob.com)

    1. def InsertionSort(aList):
    2. n = len(aList)
    3. for i in range(1, n):
    4. x = aList[i]
    5. j = i - 1
    6. while j >= 0:
    7. if x <= aList[j]:
    8. aList[j + 1] = aList[j]
    9. j -= 1
    10. else:
    11. break
    12. aList[j + 1] = x

    代码解读

    case:[22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70]

    1. len函数获取列表的长度,例子中数组长度为14,下标范围为:0-13
    2. 第一层for循环,取出列表的每一个下标(不包括下标为0的),取出每个下标对应的数
    3. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。

    时间复杂度分析

    N^2

    需要循环两次,时间复杂度为N^2

    希尔排序

    1.4 希尔排序 | 菜鸟教程 (runoob.com)

    1. def ShellSort(aList):
    2. n = len(aList)
    3. gap = n // 2
    4. while gap > 0:
    5. for i in range(gap, n):
    6. x = aList[i]
    7. j = i
    8. while j >= gap:
    9. if x < aList[j - gap]:
    10. aList[j] = aList[j - gap]
    11. else:
    12. break
    13. j -= gap
    14. aList[j] = x
    15. print(aList)
    16. gap = gap // 2

    代码解读

    1. 选择一个增量序列
    2. 按增量序列个数 k,对序列进行 k 趟排序;
    3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

    时间复杂度分析

    希尔排序时间复杂度有点复杂,排序的效率是取决于它的增量序列。时间复杂度在O(nlogn~n^2)之间

    归并排序

    1.5 归并排序 | 菜鸟教程 (runoob.com)

    1. def Merge(aList, start, mid, end):
    2. tmp = []
    3. L = start
    4. r = mid + 1
    5. while L <= mid and r <= end:
    6. if aList[L] <= aList[r]:
    7. tmp.append(aList[L])
    8. L += 1
    9. else:
    10. tmp.append(aList[r])
    11. r += 1
    12. tmp.extend(aList[L:mid + 1])
    13. tmp.extend(aList[r:end + 1])
    14. for i in range(start, end + 1):
    15. aList[i] = tmp[i - start]
    16. print(start, end, tmp)
    17. def MergeSort(aList, start, end):
    18. if start == end:
    19. return
    20. mid = (start + end) // 2
    21. MergeSort(aList, start, mid)
    22. MergeSort(aList, mid + 1, end)
    23. Merge(aList, start, mid, end)

    代码解读

    1. 申请空间,大小为为两个已经排序序列之和,该空间用来存放合并后的序列
    2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
    3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
    4. 重复步骤 3 直到某一指针达到序列尾
    5. 将另一序列剩下的所有元素直接复制到合并序列尾

    时间复杂度分析

    O(NlogN)

    归并排序用到了递归和分治的思想,列表长度为N,用二分法分解,为logN,合在一起,复杂度是O(NlogN)

    快速排序

    1.6 快速排序 | 菜鸟教程 (runoob.com)

    1. def QucikSortPivot(aList, start, end):
    2. pivot = start
    3. j = start + 1
    4. for i in range(start + 1, end + 1):
    5. if aList[i] <= aList[pivot]:
    6. aList[i], aList[j] = aList[j], aList[i]
    7. j += 1
    8. aList[pivot], aList[j - 1] = aList[j - 1], aList[pivot]
    9. pivot = j - 1
    10. print(aList[pivot], aList[start:pivot], aList[pivot + 1:end + 1])
    11. return pivot
    12. def QuickSort(aList, start, end):
    13. if start >= end:
    14. return
    15. pivot = QucikSortPivot(aList, start, end)
    16. QuickSort(aList, start, pivot - 1)
    17. QuickSort(aList, pivot + 1, end)

    随机快速排序

    1. import random
    2. def QucikSortPivot(aList, start, end):
    3. randIndex = random.randint(start,end)
    4. aList[start],aList[randIndex] = aList[randIndex],aList[start]
    5. pivot = start
    6. j = start + 1
    7. for i in range(start + 1, end + 1):
    8. if aList[i] <= aList[pivot]:
    9. aList[i], aList[j] = aList[j], aList[i]
    10. j += 1
    11. aList[pivot], aList[j - 1] = aList[j - 1], aList[pivot]
    12. pivot = j - 1
    13. print(aList[pivot], aList[start:pivot], aList[pivot + 1:end + 1])
    14. return pivot
    15. def QuickSort(aList, start, end):
    16. if start >= end:
    17. return
    18. pivot = QucikSortPivot(aList, start, end)
    19. QuickSort(aList, start, pivot - 1)
    20. QuickSort(aList, pivot + 1, end)

    代码解读

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

    时间复杂度分析

    最坏时间复杂度:O(N^2)

    桶排序

    1.9 桶排序 | 菜鸟教程 (runoob.com)

    1. def SelectSort(aList):
    2. n = len(aList)
    3. for i in range(0, n):
    4. min = i
    5. for j in range(i + 1, n):
    6. if aList[j] < aList[min]:
    7. min = j
    8. aList[i], aList[min] = aList[min], aList[i]
    9. def BucketSort(aList):
    10. minV = min(aList)
    11. maxV = max(aList)
    12. bucketCount = 3
    13. bucket = [[], [], []]
    14. perBucket = (maxV - minV + bucketCount) // bucketCount
    15. for num in aList:
    16. buketIndex = (num - minV) // perBucket
    17. bucket[buketIndex].append(num)
    18. for i in range(bucketCount):
    19. SelectSort(bucket[i])
    20. index = 0
    21. for i in range(bucketCount):
    22. for v in bucket[i]:
    23. aList[index] = v
    24. index += 1

    代码解读

    • 桶排序的核心思路是:先给数分配桶,元素在每个桶内排序,再按桶的顺序,进行所有数据的排序
    • 先看一下分配桶的思路:找到数组里的最大最小值,需要分配的桶的数量,数组里的每个数与最小数的差值与计算的值( perBucket = (maxV - minV + bucketCount) // bucketCount)向下取整,得到一个值,为桶的索引,生成新的列表,列表里嵌套多个列表
    • 每个桶内的元素,进行排序,规则是,遍历数组里的数,按插入排序规则,进行排序
    • 桶是有顺序的,桶内的元素也是有顺序的,最终得到新的有序列表

    时间复杂度分析

    O(N^2)

    最坏情况时间复杂度是O(N^2)

    计数排序

    1.8 计数排序 | 菜鸟教程 (runoob.com)

    1. def CountingSort(aList):
    2. n = len(aList)
    3. countLen = max(aList) + 1
    4. count = [0] * countLen
    5. for val in aList:
    6. count[val] += 1
    7. print(count)
    8. n = 0
    9. for val in range(0, countLen):
    10. while count[val] > 0:
    11. count[val] -= 1
    12. aList[n] = val
    13. n += 1

    代码解读

    • 创建一个数组,数组的长度为原列表中最大值+1
    • 遍历列表,新数组对应的下标,在列表里查找到数,就进行计数+1
    • 遍历新数组,当上一步的计数大于0时,计数-1,修改原列表的值,为新数组索引的值,每次索引+1

    时间复杂度分析

    O(n+k)

    第一次遍历数组,时间复杂度:O(n),第二次遍历计数器,(如果有k个计数器)时间复杂度O(K),加起来,时间复杂度为:O(n+k)

    基数排序

    1.10 基数排序 | 菜鸟教程 (runoob.com)

    1. def RadixSort(aList):
    2. base = 1
    3. maxv = max(aList)
    4. while base < maxv:
    5. bucket = []
    6. for index in range(10):
    7. bucket.append([])
    8. for num in aList:
    9. index = num // base % 10
    10. bucket[index].append(num)
    11. L = 0
    12. for index in range(10):
    13. for v in bucket[index]:
    14. aList[L] = v
    15. L += 1
    16. print(aList)
    17. base *= 10

    代码解读

    • 本质上也像前面的桶排序、计数排序一样,都用到了桶,计数排序:每个桶只存储单一键值;桶排序:每个桶存储一定范围的数值;基数排序:根据键值的每位数字来分配桶;
    • 创建一个桶
    • 遍历数组,将数据放入桶内
    • 再从桶内将数据取出来

    时间复杂度分析

    O(n*k)

    时间复杂度:O(n*k),其中n是输入元素的数量,k是数字的最大位数。

    堆排序

    1.7 堆排序 | 菜鸟教程 (runoob.com)

    1. def maxHeapify(heap, start, end):
    2. son = start * 2
    3. while son <= end:
    4. if son + 1 <= end and heap[son + 1] > heap[son]:
    5. son += 1
    6. if heap[son] > heap[start]:
    7. heap[start], heap[son] = heap[son], heap[start]
    8. start, son = son, son * 2
    9. else:
    10. break
    11. def HeapSort(aList):
    12. heap = [None] + aList
    13. root = 1
    14. L = len(heap)
    15. for i in range(L // 2, root - 1, -1):
    16. maxHeapify(heap, i, L - 1)
    17. for i in range(L - 1, root, -1):
    18. heap[i], heap[root] = heap[root], heap[i]
    19. maxHeapify(heap, root, i - 1)
    20. return heap[root:]

    代码解读

    先来看看什么是堆,推荐阅读下面的这篇博客

    大顶堆,小顶堆_这瓜保熟么的博客-CSDN博客

    时间复杂度分析

    建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),总的时间复杂度为O(nlogn)

    总结

    针对具体的序列,每种排序算法复杂度都不同,具体要用到哪种排序算法,要结合实际案例,针对每种算法的时间复杂度的分析和应用,后面再做一下总结,此外,每种排序算法的代码要加以理解巩固!多练习几遍排序算法,达到熟练使用的程度。“无他,唯手熟尔”!

  • 相关阅读:
    电脑换cpu要重装系统吗
    ROS环境下使用WHEELTEC N100惯导模块
    3C数字钥匙技术规范解读
    学习c++的第十三天
    Vue学习第20天——Vue中常用的ajax请求库(axios与vue-rouserce)
    理解交叉熵(Cross Entropy)
    3000帧动画图解MySQL为什么需要binlog、redo log和undo log
    数学建模__线性规划Python实现
    MySQL学习笔记22
    Vue源码探秘(二)—— Vue 响应式原理模拟
  • 原文地址:https://blog.csdn.net/MRJJ_9/article/details/133321770