当前位置:   article > 正文

算法(Python)——经典排序算法_python 排序算法

python 排序算法

0.算法概述

(1)分类

常见的经典排序算法有10种,可以分为两大类:

  1. 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  2. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

在这里插入图片描述

(2)时间复杂度
排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
插入排序O(n^2)O(n^2)O(n)O(1)稳定
冒泡排序O(n^2)O(n^2)O(n)O(1)稳定
希尔排序O(n^1.3)O(n^2)O(n)O(1)不稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
快速排序O(nlogn)O(n^2)O(nlogn)O(nlogn)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
计数排序O(n+k)O(n+k)O(n+k)O(n+k)稳定
桶排序O(n+k)O(n^2)O(n)O(n+k)稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定

注:

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

1.插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

1.1算法步骤

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
1.2动图描述

在这里插入图片描述

1.3代码实现
# 直接插入排序
def Direct_Insertion_Sort(ary):

    for i in range(1, len(ary)):  # 第一个元素认为已排序,从第二个元素开始遍历
        if ary[i] < ary[i-1]:  # 取出已经排好序的元素的下一个元素,与排好序的最后一个元素对比
            tmp = ary[i]   # 如果小于最后一个元素,则将待排序元素的值赋给临时变量tmp
            index = i      # 下标赋给index,待插入下标
            for j in range(i-1, -1, -1):  # 从排好序的元素的最后一个元素开始向前遍历,循环到0
                if ary[j] > tmp:  # 若之前有序的元素大于待排序元素
                    ary[j+1] = ary[j]  # 将该元素后移
                    index = j  # 待插入位置为j
                else:  # 若大于之前排好序的元素则终止循环
                    break
            ary[index] = tmp

    return ary

if __name__ == '__main__':
    ary = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44]  # 待排序数组
    correct_ary = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64]  # 排好序数组
    print("正确排序后的结果:")
    print(correct_ary)
    # 直接插入排序结果
    insert_sort_result = Direct_Insertion_Sort(ary)
    print("直接插入排序后的结果:")
    print(insert_sort_result)

原始待排序数组:
[2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44]
正确排序后的结果:
[2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64]
直接插入排序后的结果:
[2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
1.4算法分析

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

1.5扩展——折半插入排序
1.5.1算法描述

折半插入排序是在一组有序数列中插入新的元素,找到插入位置是高半区还是低半区。折半插入排序所需附加存储空间和直接插入排序相同,从时间上比较,折半插入排序减少了关键字间的比较次数,而记录的移动次数不变。

区别:在插入到已排序的数据时采用来折半查找(二分查找),取已经排好序的数组的中间元素,与插入的数据进行比较,如果比插入的数据大,那么插入的数据肯定属于前半部分,否则属于后半部分,依次不断缩小范围,确定要插入的位置。

1.5.2算法步骤
  1. 分别指向数列的第一位和末位,下标为low和high
  2. m = (low + high)/2
  3. 如果要插入的数小于m位置的数,说明要在低半区查找,high = m - 1
  4. 如果要插入的数大于m位置的数,说明要在高半区查找,low = m + 1
  5. 如果要插入的数等于m位置的数,直接退出,high=m
  6. 当low > high时,停止查找
  7. 插入的位置为high+1
1.5.3代码实现
# 折半插入排序
def Binary_Insertion_Sort(ary):
    for i in range(1, len(ary)):  # 从数组第二个元素开始
        if ary[i] < ary[i - 1]:   # 如果小于前一个元素
            x = ary[i]   # 值赋给临时变量x
            low = 0
            high = i - 1
            while low <= high:
                m = int((low + high) / 2)
                if x < ary[m]:   # 与排好序的中间值比较,找到需要插入的位置
                    high = m - 1
                else:
                    low = m + 1
            for j in range(i - 1, high, -1):
                ary[j + 1] = ary[j]  # 统一移动该移动元素
            ary[high + 1] = x   # 插入到正确位置
    return ary

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
1.5.4算法分析

先折半查找元素的应该插入的位置,然后统一移动应该移动的元素,再将这个元素插入到正确的位置。

优点 : 稳定,相对于直接插入排序元素减少了比较次数;

缺点 : 相对于直接插入排序元素的移动次数不变;

时间复杂度:可以看出,折半插入排序减少了比较元素的次数,约为O(nlogn),比较的次数取决于表的元素个数n。因此,折半插入排序的时间复杂度仍然为O(n²),但它的效果还是比直接插入排序要好。

空间复杂度:排序只需要一个位置来暂存元素,因此空间复杂度为O(1)。

2.冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

2.1算法步骤
  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。
2.2动图描述

在这里插入图片描述

2.3代码实现

某一趟遍历如果没有数据交换,则说明已经排好序了,因此不用再进行迭代了。用一个标记记录这个状态即可。

# 冒泡排序
def Bubble_Sort(ary):
    for i in range(len(ary) - 1, 0, -1):
        flag = 1   # 用状态判断本次循环是否改变,flag=1表示没有改变
        for j in range(0, i):
            if ary[j] > ary[j + 1]:  # 如果前者比后者大
                ary[j], ary[j + 1] = ary[j + 1], ary[j]  # 则交换位置
                flag = 0
        if flag:
            break
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

进一步优化:
记录某次遍历时最后发生数据交换的位置,这个位置之后的数据显然已经有序了。因此通过记录最后发生数据交换的位置就可以确定下次循环的范围了。

def bubble_sort(ary):
    n = len(ary)
    k = n                           #k为循环的范围,初始值n
    for i in range(n):
        flag = 1
        for j in range(1,k):        #只遍历到最后交换的位置即可
            if  ary[j-1] > ary[j] :
                ary[j-1],ary[j] = ary[j],ary[j-1]
                k = j               #记录最后交换的位置
                flag = 0
        if flag :
            break
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

3.1算法步骤

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  1. 初始状态:无序区为R[1…n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了。
3.2动图描述

在这里插入图片描述

3.3代码实现
# 选择排序
def Selection_Sort(ary):
    n = len(ary)
    for i in range(0, n):
        min = i  # 最小元素下标标记
        for j in range(i + 1, n):
            if ary[j] < ary[min]:
                min = j  # 找到最小值的下标
        ary[min], ary[i] = ary[i], ary[min]  # 交换两者
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.希尔排序(Shell Sort)

希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。希尔排序是非稳定排序算法。

希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。

【从大范围到小范围进行比较-交换】类似冒泡和插入的联合

4.1算法步骤

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

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

在这里插入图片描述

注:步长分别为5,3,1

4.3代码实现
# 希尔排序
def Shell_Sort(ary):
    n = len(ary)
    gap = round(n / 2)  # 初始步长 , 用round四舍五入取整
    while gap > 0:
        for i in range(gap, n):  # 每一列进行插入排序 , 从gap 到 n-1
            temp = ary[i]
            j = i
            while (j >= gap and ary[j - gap] > temp):  # 插入排序
                ary[j] = ary[j - gap]  # 交换位置
                j = j - gap   # 改变下标
            ary[j] = temp  # 交换位置
        gap = round(gap / 2)  # 重新设置步长
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

注:上面源码的步长的选择是从n/2开始,每次再减半,直至为0。步长的选择直接决定了希尔排序的复杂度。

5.归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

归并排序的思想就是先递归分解数组,再合并数组。

5.1算法步骤
  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。
5.2动图描述

分治法-2-4-8插入排序

在这里插入图片描述

5.3代码实现
# 归并排序
def Merge_Sort(ary):
    if len(ary) <= 1:
        return ary
    num = int(len(ary) / 2)  # 二分分解
    left = Merge_Sort(ary[:num])  # 递归
    right = Merge_Sort(ary[num:])
    return merge(left, right)  # 合并数组


def merge(left, right):
    # 合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组
    l, r = 0, 0           # left与right数组的下标指针
    result = []
    while l < len(left) and r < len(right):
        if left[l] < right[r]:  # 按顺序加入到最终数组
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += left[l:]
    result += right[r:]
    return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

6.快排序(Quick Sort)

快速排序通常明显比同为Ο(n log n)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

6.1算法步骤

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

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

在这里插入图片描述

6.3代码实现
# 快排序
def Quick_Sort(ary):
    return qsort(ary, 0, len(ary)-1)

def qsort(ary, left, right):
    # 快排函数,ary为待排序数组,left为待排序的左边界,right为右边界
    if left >= right: 
        return ary
    key = ary[left]     # 取最左边的为基准数
    lp = left           # 左指针
    rp = right          # 右指针
    while lp < rp:
        while ary[rp] >= key and lp < rp:
            rp -= 1
        while ary[lp] <= key and lp < rp:
            lp += 1
        ary[lp], ary[rp] = ary[rp], ary[lp]  # 交换位置
    ary[left], ary[lp] = ary[lp], ary[left]
    qsort(ary, left, lp-1)  # 递归左部分
    qsort(ary, rp+1, right)  # 递归右部分
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

7.堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序在 top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是一维数组。

二叉堆具有以下性质:

  1. 父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
  2. 每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)。
7.1算法步骤
  1. 构造最大堆(Build_Max_Heap):若数组下标范围为0~n,考虑到单独一个元素是大根堆,则从下标n/2开始的元素均为大根堆。于是只要从n/2-1开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。

  2. 堆排序(HeapSort):由于堆是用数组模拟的。得到一个大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点,并做最大堆调整的递归运算。第一次将heap[0]与heap[n-1]交换,再对heap[0…n-2]做最大堆调整。第二次将heap[0]与heap[n-2]交换,再对heap[0…n-3]做最大堆调整。重复该操作直至heap[0]和heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。

  3. 最大堆调整(Max_Heapify):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整,使得子节点永远小于父节点 。

7.2动图描述

在这里插入图片描述

7.3代码实现
# 堆排序
def Heap_Sort(ary):
    n = len(ary)
    first = int(n / 2 - 1)  # 最后一个非叶子节点
    for start in range(first, -1, -1):  # 构造大根堆
        max_heapify(ary, start, n - 1)
    for end in range(n - 1, 0, -1):  # 堆排,将大根堆转换成有序数组
        ary[end], ary[0] = ary[0], ary[end]
        max_heapify(ary, 0, end - 1)
    return ary

# 最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
# start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary, start, end):
    root = start
    while True:
        child = root*2 + 1               # 调整节点的子节点
        if child > end:
            break
        if child+1 <= end and ary[child] < ary[child+1]:
            child = child+1             # 取较大的子节点
        if ary[root] < ary[child]:     # 较大的子节点成为父节点
            ary[root], ary[child] = ary[child], ary[root]     # 交换
            root = child
        else:
            break
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

8.计数排序(Counting Sort)

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。

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

在这里插入图片描述

8.3代码实现
# 计数排序
def Count_Sort(ary):

    max = 0  # 最大值
    min = 0  # 最小值
    for i in ary:  # 计算最值
        if max < i:
            max = i
        if min > i:
            min = i
    dict_value = {}  # 字典存储次数
    for i in ary:
        if i in dict_value.keys():
            dict_value[i] += 1
        else:
            dict_value[i] = 1
    result = []  # 存储排序结果
    for i in range(min, max+1):
        if i in dict_value.keys():
            for j in range(dict_value[i]):
                result.append(i)

    return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
8.4算法分析

计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。

计数排序的缺点:

当数值中有非整数时,计数数组的索引无法分配

计数排序是以牺牲空间换时间,虽然很快,但由于可能产生大量的空位置导致内存增大。

9.桶排序(Bucket Sort)

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

桶排序与计数排序类似,但可以解决非整数的排序。

9.1算法步骤
  1. 设置一个定量的数组当作空桶;
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  3. 对每个不是空的桶进行排序;
  4. 从不是空的桶里把排好序的数据拼接起来。
9.2图片展示

《算法导论》的描述图:
在这里插入图片描述

9.3代码实现
# 桶排序(可以处理非整数)
def Bucket_Sort(ary):

    min_num = min(ary)
    max_num = max(ary)
    # 桶的大小
    bucket_range = (max_num - min_num) / len(ary)
    # 桶数组
    count_list = [[] for i in range(len(ary) + 1)]
    # 向桶数组填数
    for i in ary:
        count_list[int((i - min_num) // bucket_range)].append(i)

    result = []
    # 回填,这里桶内部排序直接调用了sorted,可以选用不同的排序方法
    for i in count_list:
        for j in sorted(i):
            result.append(j)

    return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
9.4算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

桶排序中尽量使每个桶中的元素个数均匀分布最好。

10.基数排序(Radix Sort)

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

10.1算法步骤
  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
10.2动图描述

在这里插入图片描述

10.3代码实现

这里将列表进行基数排序,默认列表中的元素都是正整数

# 基数排序
def Radix_Sort(ary):

    n = len(str(max(ary)))  # 记录最大值的位数
    for k in range(n):  # n轮排序
        # 每一轮生成10个列表
        bucket_list = [[] for i in range(10)]  # 因为每一位数字都是0~9,故建立10个桶
        for i in ary:
            # 按第k位放入到桶中
            bucket_list[i // (10 ** k) % 10].append(i)
        # 按当前桶的顺序重排列表
        ary = [j for i in bucket_list for j in i]
        
    return ary
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
10.4算法分析

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。

基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。

桶排序与基数排序常作为桶式排序出现,基数排序进行了多轮的桶排序。桶式排序不再是一种基于比较的排序方法,它是一种比较巧妙的排序方式,但这种排序方式需要待排序的序列满足以下两个特征:待排序列所有的值处于一个可枚举的范围之类;待排序列所在的这个可枚举的范围不应该太大,否则排序开销太大。

参考文档:
十大经典排序算法(动图演示)
经典排序算法总结与实现—Python
计数排序与桶排序的python实现
十大经典排序算法(python实现)(原创)

附录:
完整代码


"""
下面是10种经典排序的简单python实现
Date: 2020/11/1
By: Zhugangya
"""

# 直接插入排序
def Direct_Insertion_Sort(ary):

    for i in range(1, len(ary)):  # 第一个元素认为已排序,从第二个元素开始遍历
        if ary[i] < ary[i-1]:  # 取出已经排好序的元素的下一个元素,与排好序的最后一个元素对比
            tmp = ary[i]   # 如果小于最后一个元素,则将待排序元素的值赋给临时变量tmp
            index = i      # 下标赋给index,待插入下标
            for j in range(i-1, -1, -1):  # 从排好序的元素的最后一个元素开始向前遍历,循环到0
                if ary[j] > tmp:  # 若之前有序的元素大于待排序元素
                    ary[j+1] = ary[j]  # 将该元素后移
                    index = j  # 待插入位置为j
                else:  # 若大于之前排好序的元素则终止循环
                    break
            ary[index] = tmp

    return ary

# 折半插入排序
def Binary_Insertion_Sort(ary):
    for i in range(1, len(ary)):  # 从数组第二个元素开始
        if ary[i] < ary[i - 1]:   # 如果小于前一个元素
            x = ary[i]   # 值赋给临时变量x
            low = 0
            high = i - 1
            while low <= high:
                m = int((low + high) / 2)
                if x < ary[m]:   # 与排好序的中间值比较,找到需要插入的位置
                    high = m - 1
                else:
                    low = m + 1
            for j in range(i - 1, high, -1):
                ary[j + 1] = ary[j]  # 统一移动该移动元素
            ary[high + 1] = x   # 插入到正确位置
    return ary

# 冒泡排序
def Bubble_Sort(ary):
    for i in range(len(ary) - 1, 0, -1):
        flag = 1   # 用状态判断本次循环是否改变,flag=1表示没有改变
        for j in range(0, i):
            if ary[j] > ary[j + 1]:  # 如果前者比后者大
                ary[j], ary[j + 1] = ary[j + 1], ary[j]  # 则交换位置
                flag = 0
        if flag:
            break
    return ary

# 选择排序
def Selection_Sort(ary):
    n = len(ary)
    for i in range(0, n):
        min = i  # 最小元素下标标记
        for j in range(i + 1, n):
            if ary[j] < ary[min]:
                min = j  # 找到最小值的下标
        ary[min], ary[i] = ary[i], ary[min]  # 交换两者
    return ary

# 希尔排序
def Shell_Sort(ary):
    n = len(ary)
    gap = round(n / 2)  # 初始步长 , 用round四舍五入取整
    while gap > 0:
        for i in range(gap, n):  # 每一列进行插入排序 , 从gap 到 n-1
            temp = ary[i]
            j = i
            while (j >= gap and ary[j - gap] > temp):  # 插入排序
                ary[j] = ary[j - gap]  # 交换位置
                j = j - gap   # 改变下标
            ary[j] = temp  # 交换位置
        gap = round(gap / 2)  # 重新设置步长
    return ary

# 归并排序
def Merge_Sort(ary):
    if len(ary) <= 1:
        return ary
    num = int(len(ary) / 2)  # 二分分解
    left = Merge_Sort(ary[:num])  # 递归
    right = Merge_Sort(ary[num:])
    return merge(left, right)  # 合并数组

def merge(left, right):
    # 合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组
    l, r = 0, 0           # left与right数组的下标指针
    result = []
    while l < len(left) and r < len(right):
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += left[l:]
    result += right[r:]
    return result

# 快排序
def Quick_Sort(ary):
    return qsort(ary, 0, len(ary)-1)

def qsort(ary, left, right):
    # 快排函数,ary为待排序数组,left为待排序的左边界,right为右边界
    if left >= right:
        return ary
    key = ary[left]     # 取最左边的为基准数
    lp = left           # 左指针
    rp = right          # 右指针
    while lp < rp:
        while ary[rp] >= key and lp < rp:
            rp -= 1
        while ary[lp] <= key and lp < rp:
            lp += 1
        ary[lp], ary[rp] = ary[rp], ary[lp]  # 交换位置
    ary[left], ary[lp] = ary[lp], ary[left]
    qsort(ary, left, lp-1)  # 递归左部分
    qsort(ary, rp+1, right)  # 递归右部分
    return ary

# 堆排序
def Heap_Sort(ary):
    n = len(ary)
    first = int(n / 2 - 1)  # 最后一个非叶子节点
    for start in range(first, -1, -1):  # 构造大根堆
        max_heapify(ary, start, n - 1)
    for end in range(n - 1, 0, -1):  # 堆排,将大根堆转换成有序数组
        ary[end], ary[0] = ary[0], ary[end]
        max_heapify(ary, 0, end - 1)
    return ary

# 最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
# start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary, start, end):
    root = start
    while True:
        child = root*2 + 1               # 调整节点的子节点
        if child > end:
            break
        if child+1 <= end and ary[child] < ary[child+1]:
            child = child+1             # 取较大的子节点
        if ary[root] < ary[child]:     # 较大的子节点成为父节点
            ary[root], ary[child] = ary[child], ary[root]     # 交换
            root = child
        else:
            break

# 计数排序
def Count_Sort(ary):

    max = 0  # 最大值
    min = 0  # 最小值
    for i in ary:  # 计算最值
        if max < i:
            max = i
        if min > i:
            min = i
    dict_value = {}  # 字典存储次数
    for i in ary:
        if i in dict_value.keys():
            dict_value[i] += 1
        else:
            dict_value[i] = 1
    result = []  # 存储排序结果
    for i in range(min, max+1):
        if i in dict_value.keys():
            for j in range(dict_value[i]):
                result.append(i)

    return result

# 计数排序升级版桶排序
def bucketSort(nums):
    # 选择一个最大的数
    max_num = max(nums)
    # 创建一个元素全是0的列表, 当做桶
    bucket = [0] * (max_num + 1)
    # 把所有元素放入桶中, 即把对应元素个数加一
    for i in nums:
        bucket[i] += 1

    # 存储排序好的元素
    sort_nums = []
    # 取出桶中的元素
    for j in range(len(bucket)):
        if bucket[j] != 0:
            for y in range(bucket[j]):
                sort_nums.append(j)

    return sort_nums


# 桶排序(可以处理非整数)
def Bucket_Sort(ary):

    min_num = min(ary)
    max_num = max(ary)
    # 桶的大小
    bucket_range = (max_num - min_num) / len(ary)
    # 桶数组
    count_list = [[] for i in range(len(ary) + 1)]
    # 向桶数组填数
    for i in ary:
        count_list[int((i - min_num) // bucket_range)].append(i)

    result = []
    # 回填,这里桶内部排序直接调用了sorted,可以选用不同的排序方法
    for i in count_list:
        for j in sorted(i):
            result.append(j)

    return result

# 基数排序
def Radix_Sort(ary):

    n = len(str(max(ary)))  # 记录最大值的位数
    for k in range(n):  # n轮排序
        # 每一轮生成10个列表
        bucket_list = [[] for i in range(10)]  # 因为每一位数字都是0~9,故建立10个桶
        for i in ary:
            # 按第k位放入到桶中
            bucket_list[i // (10 ** k) % 10].append(i)
        # 按当前桶的顺序重排列表
        ary = [j for i in bucket_list for j in i]

    return ary


if __name__ == '__main__':
    ary = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44]  # 待排序数组
    correct_ary = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64]  # 排好序数组
    print("原始待排序数组:")
    print(ary)
    print("正确排序后的结果:")
    print(correct_ary)
    # 直接插入排序结果
    #insert_sort_result = Direct_Insertion_Sort(ary)
    print("直接插入排序后的结果:")
    #print(insert_sort_result)
    # 折半插入排序结果
    #binary_insert_sort_result = Binary_Insertion_Sort(ary)
    print("折半插入排序后的结果:")
    #print(binary_insert_sort_result)
    # 冒泡排序结果
    #bubble_sort_result = Bubble_Sort(ary)
    print("冒泡排序后的结果:")
    #print(bubble_sort_result)
    # 选择排序结果
    #selection_sort_result = Selection_Sort(ary)
    print("选择排序后的结果:")
    #print(selection_sort_result)
    # 希尔排序结果
    #shell_sort_result = Shell_Sort(ary)
    print("希尔排序后的结果:")
    #print(shell_sort_result)
    # 归并排序结果
    #merge_sort_result = Merge_Sort(ary)
    print("归并排序后的结果:")
    #print(merge_sort_result)
    # 快速排序结果
    #quick_sort_result = Quick_Sort(ary)
    print("快速排序后的结果:")
    #print(quick_sort_result)
    # 堆排序结果
    #heap_sort_result = Heap_Sort(ary)
    print("堆排序后的结果:")
    #print(heap_sort_result)
    # 计数排序结果
    #count_sort_result = Count_Sort(ary)
    print("计数排序后的结果:")
    #print(count_sort_result)
    # 计数排序结果
    #bucket_sort_result = Bucket_Sort(ary)
    print("桶排序后的结果:")
    #print(bucket_sort_result)
    # 计数排序结果
    radix_sort_result = Radix_Sort(ary)
    print("基数排序后的结果:")
    print(radix_sort_result)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/407369
推荐阅读
相关标签
  

闽ICP备14008679号