【十种排序】python实现(附源码)

本文参考于 清华大学博士讲解Python数据结构与算法(排序介绍)

1、冒泡排序(bubble)

(1)思路

  • 如果前一个数比后一个数小则交换……
  • 时间复杂度为O(n2)

(2)代码

import random                                       #随机数库

def bubble_sort(list):                              #传入打乱的数据
    for i in range(len(list)-1):                    #最后一位不索引则减一
        flag = False                                #数据未排列好
        for j in range(len(list)-i-1):              #每过i趟则无序区减i
            if list[j] > list[j+1]:                 #前大于后则交换
                list[j], list[j+1] = list[j+1], list[j]
                flag = True                         #数据已排列好
        print(list)                                 #打印过程数据
        if not flag:                                #排列好则退出
            return
        
list = [random.randint(0,100) for i in range(10)]   #随机生成数据
print(list)
bubble_sort(list)

(3)运行结果

[8, 1, 5, 8, 9, 1, 9, 7, 3, 4]
[1, 5, 8, 8, 1, 9, 7, 3, 4, 9]
[1, 5, 8, 1, 8, 7, 3, 4, 9, 9]
[1, 5, 1, 8, 7, 3, 4, 8, 9, 9]
[1, 1, 5, 7, 3, 4, 8, 8, 9, 9]
[1, 1, 5, 3, 4, 7, 8, 8, 9, 9]
[1, 1, 3, 4, 5, 7, 8, 8, 9, 9]
[1, 1, 3, 4, 5, 7, 8, 8, 9, 9]

2、选择排序(select)

(1)思路

  • 一趟排序记录最小的数,放在第一个位置
  • 再一趟排序记录列表无序区最小的数,放在第二个位置
  • ……
  • 最后无序区只剩下最大的数,则无需遍历。
  • 简而言之,取列表最小的数放于首位,再取次小的数放于次位……
  • 时间复杂度为O(n2)

(2)代码

import random                                   #随机数库

def select_sort(list):
    for i in range(len(list) - 1):              #i是第几趟,最后只剩一个则无需遍历,则减一
        min_list = i                            #获取列表前面的数
        for j in range(i+1, len(list)):         #遍历无序区,从交换后的最小数开始
            if list[j] < list[min_list]:        #如果小于目前最小数则获取
                min_list = j
        if min_list != i:                       #找到最小值,则和无序区第一个数交换
            list[i], list[min_list] = list[min_list], list[i]
        print(list)                             #打印过程

list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
select_sort(list)

(3)运行结果

[2, 0, 7, 3, 1, 5, 7, 6, 5, 6]
[0, 2, 7, 3, 1, 5, 7, 6, 5, 6]
[0, 1, 7, 3, 2, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 7, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 7, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 5, 7, 7, 6, 5, 6]
[0, 1, 2, 3, 5, 5, 7, 6, 7, 6]
[0, 1, 2, 3, 5, 5, 6, 7, 7, 6]
[0, 1, 2, 3, 5, 5, 6, 6, 7, 7]
[0, 1, 2, 3, 5, 5, 6, 6, 7, 7]

3、插入排序(insert)

(1)思路

  • 初始时手里(有序区)只有一张牌
  • 每次(从无序区)摸一张牌,插入到手里已有牌的正确位置
  • 简而言之,如斗地主中的整牌。
  • 时间复杂度为O(n2) 具有稳定性

(2)代码

import random                                   #随机数库

def insert_sort(list):
    for i in range(1, len(list)):               #i表示摸到的牌的下标
        temp = list[i]
        j = i - 1                               #j指的是手里的牌的下标
        while j >= 0 and list[j] > temp:        #当有序区中的数大于无序区的数时
            list[j+1] = list[j]                 #有序区向后移一位
            j -= 1                              #j向前索引
        list[j+1] = temp                        #当有序区中的数小于无序区时,则置于有序区之后
        print(list)                             #将每一趟打印

list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
insert_sort(list)

(3)运行结果

[2, 7, 1, 8, 10, 9, 8, 9, 9, 8]
[2, 7, 1, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 9, 10, 8, 9, 9, 8]
[1, 2, 7, 8, 8, 9, 10, 9, 9, 8]
[1, 2, 7, 8, 8, 9, 9, 10, 9, 8]
[1, 2, 7, 8, 8, 9, 9, 9, 10, 8]
[1, 2, 7, 8, 8, 8, 9, 9, 9, 10]

4、快速排序(quick)

(1)思路

  • 取一个元素p(第一个元素),使元素p归位
  • 列表被p分为两部分,左边都比p小,右边都比p大
  • 递归完成排序
  • 类似二分查找
  • 一般 时间复杂度为O(nlogn)
  • 最坏 时间复杂度为O(n2) 不具有稳定性

(2)代码

import random                                   #随机数库

def partition(list, left, right):
    temp = list[left]
    while left < right:
        while left < right and list[right] >= temp: #从右边找比temp小的数
            right -= 1                          #往左走一步
        list[left] = list[right]                #把右边的值写到左边空位上
        while left < right and list[left] <= temp: #从左边找比temp大的数
            left += 1                           #往右走一步
        list[right] = list[left]                #把左边的值写到右边空位上
    list[left] = temp                           #把temp归位

    return left

def quick_sort(list, left, right):
    if left < right:                            #至少两个元素
        mid = partition(list, left, right)      #获取第一个值
        quick_sort(list, left, mid-1)           #左边区间
        quick_sort(list, mid+1, right)          #右边区间
        print(list)

list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
quick_sort(list, 0, len(list)-1)

(3)运行结果

[3, 8, 2, 9, 4, 8, 3, 4, 8, 2]
[2, 2, 3, 9, 4, 8, 3, 4, 8, 8]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]

5、堆排序(heap)

(1)思路

  • :一种特殊的完全二叉树(即最后一行的叶子节点可以不全但需左对齐)结构。
  • 大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
  • 小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小在这里插入图片描述

  • 堆排序过程:
  • 1、建立堆;
  • 2、得到堆顶元素,为最大元素;
  • 3、去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序;
  • 4、堆顶元素为第二大元素;
  • 5、重复步骤3,直至堆为空。

  • 大根堆得到的是升序;小根堆得到的是降序
  • 时间复杂度为O(nlogn) 不具有稳定性

(2)大根堆代码

import random                                   #随机数库

def sift(list, low, high):
    '''
    :param list: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    '''
    i = low                                     #i最开始指向根节点
    j = 2 * i + 1                               #j开始是左孩子
    temp = list[low]                            #把堆顶存起来
    while j <= high:                            #只要j位置有数
        if j+1 <= high and list[j+1] > list[j]: #如果右孩子有并且比较大
            j = j + 1                           #j指向右孩子
        if list[j] > temp:
            list[i] = list[j]                   #大值放于堆顶
            i = j                               #往下一层
            j = 2 * i +1
    #     else:                                 #两种方式均可
    #         break
    # else:
    #     list[i] = temp
        else:                                   #temp更大,把temp放到i的位置上
            list[i] = temp                      #把temp放到某一级领导位置上
            break
    else:                                       #到最后都没有比temp大的数
        list[i] = temp                          #把temp放到叶子节点上

def heap_sort(list):
    for i in range((len(list)-2) // 2, -1, -1):
        # i表示建堆的时候调整的部分的根的下标
        sift(list, i, len(list)-1)
    print(list)
    # 建堆完成了
    for i in range(len(list)-1 , -1, -1):
        # i指向当前堆的最后一个元素
        list[0], list[i] = list[i], list[0]
        sift(list, 0, i-1)                      #i-1是新的high

list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
heap_sort(list)
print(list)

运行结果

[5, 4, 6, 3, 7, 1, 10, 0, 2, 5]
[10, 7, 6, 3, 5, 1, 5, 0, 2, 4]
[0, 1, 2, 3, 4, 5, 5, 6, 7, 10]

(3)小根堆代码

import random                                   #随机数库

def sift(list, low, high):
    '''
    :param list: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    '''
    i = low                                     #i最开始指向根节点
    j = 2 * i + 1                               #j开始是左孩子
    temp = list[low]                            #把堆顶存起来
    while j <= high:                            #只要j位置有数
        if j+1 <= high and list[j+1] < list[j]: #如果右孩子有并且比较小
            j = j + 1                           #j指向右孩子
        if list[j] < temp:
            list[i] = list[j]                   #小值放于堆顶
            i = j                               #往下一层
            j = 2 * i +1
    #     else:                                 #两种方式均可
    #         break
    # else:
    #     list[i] = temp
        else:                                   #temp更小,把temp放到i的位置上
            list[i] = temp                      #把temp放到某一级领导位置上
            break
    else:                                       #到最后都没有比temp小的数
        list[i] = temp                          #把temp放到叶子节点上

def heap_sort(list):
    for i in range((len(list)-2) // 2, -1, -1):
        # i表示建堆的时候调整的部分的根的下标
        sift(list, i, len(list)-1)
    print(list)
    # 建堆完成了
    for i in range(len(list)-1 , -1, -1):
        # i指向当前堆的最后一个元素
        list[0], list[i] = list[i], list[0]
        sift(list, 0, i-1)                      #i-1是新的high

list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
heap_sort(list)
print(list)
#print(list[::-1])								#倒序

运行结果

[9, 7, 9, 5, 8, 10, 3, 5, 9, 5]
[3, 5, 9, 5, 5, 10, 9, 7, 9, 8]
[10, 9, 9, 9, 8, 7, 5, 5, 5, 3]

(4)内置函数(小根堆)

import heapq                                #q->queue 优先队列
import random

list = list(range(10))                      #随机生成数据
random.shuffle(list)

print(list)

heapq.heapify(list)                         #建堆

for i in range(len(list)):
    print(heapq.heappop(list), end=',')     #排序输出

运行结果

[5, 2, 1, 0, 3, 7, 4, 8, 6, 9]
0,1,2,3,4,5,6,7,8,9,

6、归并排序(merge)

(1)思路

  • 分解:将列表越分越小,直至分成一个元素;
  • 终止条件:一个元素是有序的;
  • 合并:将两个有序列表归并,列表越来越大。
  • 不具有稳定性
  • 图示在这里插入图片描述

  • 简而言之,就是将乱序list分解成有序的子list,然后再合并成一个list。
  • 时间复杂度为O(nlogn)
  • (归并排序需要格外一个列表)
  • 空间复杂度为O(n)

(2)代码

def merge(list, low, mid, high):                # 一次归并
    i = low                                     # 第一个有序列表的首值
    j = mid + 1                                 # 第二个有序列表的首值
    temp = []                                   # 临时储存的列表
    while i <= mid and j <= high:               # 当两个列表都有数据才有效
        if list[i] < list[j]:                   # 小的先出
            temp.append(list[i])
            i += 1                              # 位置后移
        else:
            temp.append(list[j])
            j += 1
                                                # while执行完后,或许其中一个列表有部分数据还没处理
    while i <= mid:
        temp.append(list[i])                    # 因为为有序列表,所以直接追加即可
        i += 1
    while j <= high:
        temp.append(list[j])
        j += 1
    list[low: high+1] = temp                    # 重新传回list

def merge_sort(list, low, high):
    if low < high:                              # 至少有两个元素,递归
        mid = (low + high) // 2
        merge_sort(list, low, mid)              # mid左边先分解
        merge_sort(list, mid+1, high)           # mid右边再分解
        print(list[low: high+1])                # 打印分解过程
        merge(list, low, mid, high)             # 归并
        print(list[low: high+1])                # 打印归并过程

list = list(range(10))                          # 长度为10的列表
import random                                   # 随机函数库
random.shuffle(list)                            # 随机排列list
print(list)                                     # 打印乱序list
merge_sort(list, 0, len(list) - 1)              # 归并排序
#print(list)                                    # 打印有序list

(3)运行结果

[0, 1, 2, 6, 9, 8, 4, 7, 5, 3]		# 乱序数据
[0, 1]								# 一次左分解
[0, 1]								# 一次合并
[0, 1, 2]							# 一次左分解
[0, 1, 2]							# 一次合并
[6, 9]								# 一次左分解
[6, 9]								# 一次合并
[0, 1, 2, 6, 9]						# 一次左分解
[0, 1, 2, 6, 9]						# 一次合并
[8, 4]								# 一次右分解
[4, 8]								# 一次合并
[4, 8, 7]							# 一次右分解
[4, 7, 8]							# 一次合并
[5, 3]								# 一次右分解
[3, 5]								# 一次合并
[4, 7, 8, 3, 5]						# 一次右分解
[3, 4, 5, 7, 8]						# 一次合并
[0, 1, 2, 6, 9, 3, 4, 5, 7, 8]		# 两个有序的列表
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]		# 有序数据

(1~6总结)

  • 前三种排序算法的时间复杂度均为O(n2)
  • 后三种排序算法的时间复杂度均为O(nlogn)
  • 一般情况就运行时间而言,快速排序 < 归并排序 < 堆排序
  • 后三种排序的缺点:
    • 快速排序:极端情况下排序效率低;
    • 归并排序:需要额外的内存开销;
    • 堆排序:在后三种排序中运行速度慢。
  • 总结在这里插入图片描述

7、希尔排序(sher)

(1)思路

  • 其是一种分组插入排序算法。
    • 1、首先取一个整数d1=n/2,将元素分为d1个组,每组相邻元素之间的距离为d1,在各组内进行直接插入排序;
    • 2、取第二个整数d2=d1/2,重复上述分组排序过程,直到d1=1,即所有元素在同一组内进行直接插入排序。
  • 希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
  • 简而言之,就是按步长(递减)进行分组,各组进行插入排序,直到整体有序。
  • 时间复杂度: 比较复杂,与选取的gap(组距)有关。

(2)代码

def insert_sort_gap(list, gap):                 # 插入排序加入组距gap
    for i in range(gap, len(list)):             # i表示摸到牌的下标
        tmp = list[i]
        j = i - gap                             # j值手里牌的下标
        while j >= 0 and list[j] > tmp:
            list[j + gap] = list[j]
            j -= gap
        list[j + gap] = tmp

def sher_sort(list):                            # 希尔排序
    d = len(list) // 2                          # 组距
    while d >= 1:                               # 不到一时,一直处于接近有许的操作
        insert_sort_gap(list, d)
        d //= 2

list = list(range(10))                          # 长度为10的列表
import random                                   # 随机函数库
random.shuffle(list)                            # 随机排列list
print(list)                                     # 打印乱序list
sher_sort(list)
print(list)                                     # 打印有序list

(3)运行结果

[0, 5, 3, 2, 4, 8, 1, 9, 7, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

8、计数排序(count)

(1)思路

  • 已知列表的数范围在0到n之间。
  • 优点:
    • 运行速度快,比快速排序还快。
  • 缺点:
    • 需提前知道n大小;
    • 需新建n+1个列表,空间复杂度高。
  • 简而言之,就是乱序中每个数值统计入对应的列表中。
  • 时间复杂度O(n)
  • 空间复杂度O(n+2)

(2)代码

def count_sort(list, max_count = 100):              # max_count为需要的新创的列表数
    count = [0 for _ in range(max_count + 1)]       # 每个新创列表值为0
    for val in list:                                # 将list中存在的值计入对应下标的列表中
        count[val] += 1
    list.clear()                                    # 原列表清空
    for ind, val in enumerate(count): # enumerate多用于在for循环中得到计数,同时获得索引和值
        for i in range(val):                        # 值为多少就追加多少个i
            list.append(ind)

import random
list = [random.randint(0, 100) for _ in range(10)]  # 随机生成10个0到100的数
print(list)                                         # 打印乱序list
count_sort(list)
print(list)                                         # 打印有序list

(3)运行结果

[54, 84, 13, 27, 80, 38, 21, 68, 48, 68]
[13, 21, 27, 38, 48, 54, 68, 68, 80, 84]

9、桶排序(bucket)

(1)思路

  • 基于计数排序,如果元素范围过大,可使用桶排序。
  • 桶排序:首先将元素分在不同的桶中,再对每个桶中元素进行排序。
  • 优点:
    • 可减少计数排序的空间。
  • 缺点:
    • 需提前知道n大小。
  • 桶排序的表现:取决于数据的分步。也就是需要对不同数据排序时采取不同的分桶策略。
  • 平均时间复杂度O(n+k)
  • 最坏时间复杂度O(n2k)
  • 空间复杂度O(nk)

(2)代码

def bucket_sort(list, n=5, max_num=10):             # n为桶的数量,max_num为需要新创的列表数
    bucket = [[] for _ in range(n)]                 # 创建桶
    for var in list:
        i = min(var // (max_num // n), n-1)         # i表示var放在几号桶
        bucket[i].append(var)
        for j in range(len(bucket[i])-1, 0, -1):    # 进行桶内排序,插入排序
            if bucket[i][j] < bucket[i][j-1]:
                bucket[i][j], bucket[i][j-1] = bucket[i][j-1], bucket[i][j]
            else:
                break
    sorted_list = []                                # 新建存储排好序的列表
    for buc in bucket:
        sorted_list.extend(buc)                     # 用新列表扩展原来的列表
    return sorted_list

import random
list = [random.randint(1,100) for i in range(10)]   # 随机生成10个0到100的数
print(list)                                         # 打印乱序list
list = bucket_sort(list)
print(list)                                         # 打印有序list

(3)运行结果

[50, 21, 51, 56, 81, 75, 44, 77, 31, 96]
[21, 31, 44, 50, 51, 56, 75, 77, 81, 96]

10、基数排序(radix)

(1)思路

  • 又称多关键字排序:假如现在有一个员工表,要求按照薪资排序,年龄相同的按照年龄排序。
    • 先按照年龄进行排序,再按照薪资进行稳定的排序。
  • 简而言之,就是相当于对一个数进行每一位的位数排序。
  • 时间复杂度O(kn) (仅次取计数排序)
  • 空间复杂度O(k + n) (k表示数字位数)

(2)代码

def radix_sort(list):
    max_num = max(list)                     # 乱序中最大的数
    k = 0                                   # 最大的数的位数
    while 10 ** k <= max_num:
        buckets = [[] for _ in range(10)]   # 每一位都进行一次桶排序
        for var in list:
            digit = (var // 10 ** k) % 10   # 取对应位数的值
            buckets[digit].append(var)
        list.clear()                        # 分桶完成
        for buc in buckets:
            list.extend(buc)                # 把数写回list
        k += 1                              # 位数加一

import random
list = list(range(10))
random.shuffle(list)
print(list)                                 # 打印乱序list
radix_sort(list)
print(list)                                 # 打印有序list

(3)运行结果

[4, 1, 0, 6, 5, 8, 9, 7, 3, 2]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

应用数学

只要想学即可

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值