Python十大排序算法源码

1. 前言

最近经常用到各种排序算法,但是网上的python排序源码质量参差不齐,因此就结合网上的资料和自己的理解,整理了一份可直接使用的排序算法python源码

2. 排序算法的选取规则

在网络上(此处)查到了一份排序算法的选取规则,复制到此。

(1)元素个数n大,排序码分布随机,稳定性不做要求 --------- 快速排序
(2)元素个数n大,内存空间允许, 要求稳定性 ------------- 归并排序
(3)元素个数n大,排序码可能正序或逆序,稳定性不做要求 --------- 堆排序、归并排序
(4)元素个数n小,排序码基本有序或随机,要求稳定性 ------------- 插入排序
(5)元素个数n小,稳定性不做要求 ------ 选择排序
(6)元素个数n小,排序码不接近逆序 ---- 插入排序
(7)冒泡排序一般很少用(时间空间成本都高)

因此,常使用的排序算法主要包括 快速排序,归并排序,堆排序

3.十大排序算法的python源码

我在网上(参考网址)找到的一个总结表,复制到此。
在这里插入图片描述

3.1冒泡排序

方法: 在无序区通过反复交换找到最大元素放在队首(比较次数多,交换次数多)
主要思想: 前后两两比较,大小顺序错误就交换位置

代码思路:
1.比较相邻元素,如果前者大于后者,就交换位置
2.从队首到队尾,每一对相邻元素都重复上述步骤,最后一个元素为最大元素
3.针对前n-1个元素重复

# 代码
def BubbleSort(arr):
    for i in range(len(arr) - 1):  # i 控制比较的轮数,减去1是除开自身(两两比较,下句相同)
        for j in range(len(arr) - i - 1):  # j表示每轮比较的元素范围,每经过一个大轮,就减少了待比较一个元素,减去i
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

if __name__ == "__main__":
    arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = BubbleSort(arr_in)
    print(arr_out)

时间复杂度 O(n^2)
空间复杂度 O(1)
稳定排序

3.2插入排序

方法:在无序区找到最下的元素放到有序区的队尾(比较次数多,交换次数少)
主要思想:水果摊挑苹果,先选出最大的,再选出次大的,直到最后
选择是对冒泡的优化,比较一轮只交换一次数据

代码思路
1.找到无序待排序列中最小的元素,和第一个元素交换位置
2.剩下的待排无序序列(2-n)选出最小的元素,和第二个元素交换位置
3.直到最后选择完成

# 代码
def SelectSort(arr):
    for i in range(len(arr)):  # 找到全局最小
        minIndex = i
        for j in range(i+1, len(arr)):
            if arr[j] < arr[minIndex]:
               minIndex = j
        arr[i], arr[minIndex] = arr[minIndex], arr[i]  # 把全局最小和待排序列首位交换
    return arr

if __name__ == "__main__":
    arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = SelectSort(arr_in)
    print(arr_out)

时间复杂度 O(n^2)
空间复杂度 O(1)
非稳定排序

3.3插入排序

方法: 把无序区的第一个元素插入到有序区的合适位置(比较次数少,交换次数多)
主要思想: 扑克牌打牌时候的插入思想,逐个插入到前面的有序数中

代码思路:
1.选择待排无序序列的第一个元素作为有序数列的第一个元素
2.把第2个元素到最后一个元素看做无序待排序列
3.依次从待排无序序列取出每一个元素,与有序序列的每个元素比较(从右向左扫描),符合条件交换元素位置

# 代码
# 方法一:
# def InsertSort(arr):
#     for i in range(1, len(arr)):
#         preindex = i - 1  # 有序序列最后一个元素位置
#         current = i  # 待排无序序列第一个元素位置
#         while preindex >= 0 and arr[preindex] > arr[current]:  # 对有序序列倒序遍历完成 & 待排元素 > 比较的有序元素
#             arr[preindex], arr[current] = arr[current], arr[preindex]  # 交换二者位置
#             preindex -= 1
#             current -= 1
#     return arr

# 方法二
def InsertSort(arr):
    for i in range(1, len(arr)):  # 控制循环的次数,从1开始,默认0位最小
        for j in range(i, 0, -1):  # 从右向左遍历已排有序序列
            if arr[j] < arr[j - 1]:  # 相邻两个数比较
                arr[j], arr[j - 1] = arr[j - 1], arr[j]
    return arr

if __name__ == "__main__":
    arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = InsertSort(arr_in)
    print(arr_out)

时间复杂度 O(n^2)
空间复杂度 O(1)
稳定排序

3.4希尔排序

方法:每一轮按照事先决定的间隔进行插入排序,间隔依次缩小,最后到 1
主要思想: 改进插入排序,从远距离到小距离进行比较-交换,类似冒泡和插入的联合

代码思路:
1.先对数据进行间隔分组,分组数依此指数型减小 (/2)
2.组内元素进行比较交换位置
3.直到最后分组为1

# 方法一: 调用插入排序
# # 1.先写插入排序
# def InsertSort(arr):
#     for i in range(0, len(arr)):  # 待排无序序列
#         for j in range(i, -1, -1):  # 已排有序序列,从右向左
#             if arr[j] > arr[i]:
#                 arr[i], arr[j] = arr[j], arr[i]
#                 i -= 1
#                 j -= 1  # j会取到-1,如果要保证代码没bug,if中限制j>=0
#     return arr
# # 2.再写希尔排序
# def ShellSort(arr):
#     gap = int(len(arr) / 2)  # 增量最大长度
#     while gap > 0:
#         for m in range(0, len(arr), gap):
#             InsertSort(arr)
#             gap = int(gap / 2)
#     return arr

# 方法二:直接写希尔排序
# 希尔排序
def ShellSort(arr):
    gap = len(arr)  # 计算排序序列长度
    while gap > 1:
        gap = gap // 2 # 分组在减少,组内比较次数在增多
        for i in range(gap, len(arr)):  # 后半部分遍历
            for j in range(i % gap, i, gap):  # 前半部分遍历
                if arr[i] < arr[j]:
                    arr[i], arr[j] = arr[j], arr[i]
    return arr

if __name__ == "__main__":
    arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17]
    print(arr_in)
    arr_out = ShellSort(arr_in)
    print(arr_out)

时间复杂度 O(n^2)
空间复杂度 O(1)
非稳定排序

3.5归并排序

方法:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序
主要思想: 分治法:自上而下的递归和自下而上的迭代

代码思路
1.将待排序序列从中间无限二分划分,直至最小为1
2.再从小到大合并,合并组内重复比较大小
3.直到最后合并完成

def Merge(left, right):
    result = []
    while len(left) and len(right):
        if left[0] <= right[0]:   # 如果左边小于右边
            result.append(left.pop(0))  # 就把左边取出来放到有序序列
        else:
            result.append(right.pop(0))
    if len(left) != 0:
        result += left
    if len(right) != 0:
        result += right
    return result

def MergeSort(arr):
    if len(arr) <= 1:
        return arr
    num = len(arr) // 2  # 二分序列
    left = MergeSort(arr[:num])
    right = MergeSort(arr[num:])
    return Merge(left, right)  # 不断递归

if __name__ == "__main__":
    arr_in = [6, 5, 18, 2, 16, 15]
    print(arr_in)
    arr_out = MergeSort(arr_in)
    print(arr_out)

时间复杂度 O(n log n)
空间复杂度 O(n)
稳定排序

3.6快速排序

方法:选取一个基准值,小的在前,大的在后
代码思路
1.从数列挑选一个基准值
2.根据基准值大小,小于基准值得放在后面,小于基准值的放在前面
3.递归,直到所有子集只剩下一个元素
4.分解完成再一层一层返回,返回规则是:左边分区+基准值+右边分区

def QuickSort(arr):
    if len(arr) <= 1:
        return arr  # 递归跳出的条件
    else:
        mid = arr[0]  # 取第一个数为基准值
        # 列表解析式,先执行中间的for,再执行后面if或者for的条件语句,最后执行前面的目的语句
        left = QuickSort([l for l in arr[1:] if l < mid])  # 从第二个数据开始,遍历arr,如果小于mid,就递归
        right = QuickSort([m for m in arr[1:] if m >= mid])  #
        return left + [mid] + right

arr_in = [6, 5, 18, 2, 16, 15]
print(arr_in)
arr_out = QuickSort(arr_in)
print(arr_out)

时间复杂度 O(n log n)
空间复杂度 O(n log n)
不稳定排序

3.7堆排序

方法: 二叉堆本来就是有序的大顶堆或者小顶堆(存放在列表中的)
主要思想:将待排序列构成一个二叉堆

二叉堆的基本代码:参考文献网址

二叉堆的节点计算(列表从0开始索引) 给出任意结点下标 r 父节点 : (r -1) // 2, (0号元素的父节点还是0,-1/2 =
0) 左子节点: 2 * r + 1 右子节点: 2 * r + 2

代码思路
1.根据输入的无序序列,构建局部大顶堆:依次 倒序自下向上 遍历所有父节点,把每个父节点和其子节点比较,更大的交换到父节点,构成局部大顶堆
2.构建大顶堆:依次遍历树,倒序把末节点和顶节点交换位置,然后再次构建大顶堆
3.循环完成就结束

# 调整大顶堆
def heap_adjust(heap, heap_size, root):
    # 计算节点下标
    larger = root  # 父节点
    left = 2 * root + 1  # 左节点
    right = 2 * root + 2  # 右节点

    if left < heap_size and heap[larger] < heap[left]:  # 找出左右结点更大的位置
        larger = left
    if right < heap_size and heap[larger] < heap[right]:  # 找出左右结点更大的位置
        larger = right
    if larger != root:  # 最大值结点  不是  父节点
        heap[larger], heap[root] = heap[root], heap[larger]  # 交换父节点和找到的最大值叶子节点
        heap_adjust(heap, heap_size, larger)

# 建立大顶堆
def build_max_heap(heap):
    heap_size = len(heap)
    for i in range((heap_size-2)//2, -1, -1):  # 自低向顶构建局部大顶堆
        heap_adjust(heap, heap_size, i)

    for j in range(heap_size-1, -1, -1):
        heap[j], heap[0] = heap[0], heap[j]  # 从最后一个节点开始,依此把最后一个节点放到第一个节点位置,再把大节点上浮(为了下一次交换到堆底)
        heap_adjust(heap, j, 0)  # 大数上浮
    return heap

if __name__ == '__main__':
    arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = build_max_heap(arr_in)
    print(arr_out)

时间复杂度 O(n log n)
空间复杂度 O(1)
不稳定排序

3.8计数排序

方法:统计每个元素出现的次数
明显问题: 只能最大最小值相差不是太大的整数数组排序

代码思路
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入计数数组的第 i-min_value 项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:根据计数数组位置索引,将对应位置的 索引值+ min_value 放入输出数组,每放一个元素就将conter该位置计数值减去1

def CountSort(arr):
    max_value = max(arr)  # 待排序列的最大值
    min_value = min(arr)  # 待排序列的最小值
    arr_out = []  # 待输出序列
    # 创建一个全0列表,计数待排序列中的每个数出现的次数
    conter = [0] * (max_value - min_value + 1)
    # 计数
    for one in arr:
        conter[one - min_value] += 1
    # 回填
    # 遍历计数数组
    for index in range(0, len(conter)):
        # 当计数数组某位不为零时,证明该位置有数
        while conter[index] > 0:
            # 添加到输出数组,其值大小为(index + min_value)
            arr_out.append(index + min_value)
            # 每添加一次,该值的计数减1
            conter[index] -= 1
    return arr_out

if __name__ == '__main__':
    arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = CountSort(arr_in)
    print(arr_out)

时间复杂度 O(n + m)
空间复杂度 O(n + m)
稳定排序

3.9桶排序

方法:计数排序的升级版,依次将一定范围内的数放进一个桶,再分别进行排序

代码思路
1.找出待排序的数组中最大和最小的元素
2.计算每个桶的存储数据范围
3.建立空桶
4.根据桶的存数范围,分配进相应的桶
5.对桶内数据进行排序
6.依次将桶内数据取出来组成新的有序序列

def BucketSort(arr):
    max_value = max(arr)  # 待排序列的最大值
    min_value = min(arr)  # 待排序列的最小值
    arr_out = []  # 待输出数组
    bucket_size = (max_value - min_value) / len(arr)    # 根据桶的数量找到每个桶的取值范围
    count_list = [[] for i in range(len(arr) + 1)]  # 建立桶
    for one in arr:  # 向桶内分配数字
        count_list[int((one - min_value) // bucket_size)].append(one)  # 取整,然后存到对应位置
    for i in count_list:
        for j in sorted(i):  # 对桶内的数字进行排序
            arr_out.append(j)  # 回填
    return arr_out

if __name__ == '__main__':
    arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
    print(arr_in)
    arr_out = BucketSort(arr_in)
    print(arr_out)

时间复杂度 O(n + m)
空间复杂度 O(n + m)
稳定排序

3.10基数排序

方法:先依据个位排序,再依据十位排序,再依据百位排序,最后的数据就是有序的
主要思想:反复进行同位相比较
负数不能参与排序

代码思路
1.建立10个桶(后续存放0-9)
2.计算待排序列中最大数的位数
3.个位相同的数放进同一个桶,并桶内排序
4.取出来组成新序列,再依据十位相同的放进一个桶,桶内排序
5.依此循环直到最高位
6.组成新序列

def RadixSort(arr):
    max_value = max(arr)  # 待排序列的最大值
    arr_out = []  # 待输出数组
    max_digits = len(str(max_value))  # 计算序列中最大数的位数
    for count_digits in range(max_digits):
        bucket_list = [[] for i in range(10)]  # 每一轮建立10个桶
        for one in arr:
            bucket_list[(one // (10 ** count_digits) % 10)].append(one)  # 按照位来放入不同的桶
        arr = [j for one in bucket_list for j in one]  # 按当前桶的顺序重排列表
    return arr

if __name__ == '__main__':
    arr_in = [16, 5, 1835, 20, 6, 150]
    print(arr_in)
    arr_out = RadixSort(arr_in)
    print(arr_out)

时间复杂度 O(n * m)
空间复杂度 O(n ^ 2)
稳定排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值