九种排序汇总 python实现

947 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

用python实现常见的九种排序

冒泡排序

每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。 而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。

from cal_time import cal_time
@cal_time
def bubble_sort(li):
    for i in range(len(li)-1):
        exchange = False
        for j in range(len(li)-i-1):
            if li[j] > li[j+1]:
                li[j], li[j+1] = li[j+1], li[j]
                exchange = True
        if not exchange:
            return

选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

算法关键: 有序区和无序区, 记录无序区最小数的位置

def select_sort(li):
    for i in range(len(li)-1):
        min_loc = i
        for j in range(i+1,len(li)):
            if li[j] < li[min_loc]:
                min_loc = j
        li[i], li[min_loc] = li[min_loc], li[i]

插入排序

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

def insert_sort(li):
    for i in range(1, len(li)):
        tmp = li[i]
        j = i-1
        # 找插入的位置
        # 从右往左比较,如果tmp小于l[j],l[j]他的位置就向右移一个位置,
        while j >= 0 and li[j] > tmp:
            li[j+1] = li[j]
            j = j-1
        li[j+1] = tmp

快速排序

从数列中挑出一个元素,称为 "基准"(pivot);

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

def partition(li, left, right):
    # 将最左边的元素给tmp
    tmp = li[left]
    while left < right:
        while left < right and li[right]>=tmp:  # 从右面找出比tmp小的数
            right -= 1
        li[left] = li[right]    # 将右边第一个小于tmp的数放到左边的空位上
        while left < right and li[left] <= tmp:   # 从左边找出比tmp大的数
            left += 1
        li[right] = li[left]   # 将左边第一个大于tmp的数放到右边刚才腾出来的空位上
    li[left] = tmp       # 将tmp归位,放到了左边全比它小,右面全比它大的位置,也就是它最后应在的位置
    return left      # 这个left代表的就是tmp位置的索引


def quick_sort(li, left, right):
    if left < right:  # 至少有两个元素,是判断递归是否停止的条件
        mid = partition(li, left, right)
        quick_sort(li,left, mid-1)
        quick_sort(li, mid+1,right)

堆排序

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

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

①将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
② 依次将根节点与待排序序列的最后一个元素交换
③ 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low      # 最开始指向根节点的
    j = 2*i+1   # j 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    while j <= high:
        if j + 1 <= high and li[j+1] > li[j]:   # 如果右孩子有且比较大
            j = j+1   # j指向右孩子
        if li[j] > tmp:
            li[i] = li[j]
            i = j
            j = 2*i+1
        else:         # tmp更大把tmp放到i的位置吧
            li[i] = tmp     # 把tmp放到某一领导位置
            break
    else:
        li[i] = tmp   #  把tmp放到叶子节点


def heap_sort(li):
    n = len(li)
    for i in range((n-2)//2-1, -1, -1):
        # i 标示建堆的时候调整部分的根下标
        sift(li, i, n-1)
    # 堆建造完成

    for i in range(n-1,-1, -1):
        # i 指向当前堆的最后一个元素
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i-1)    # i-1 是新的high

归并排序

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。
def marge(li, low,mid,high):
    """
    :param li:  列表
    :param low:  第一个有序列表的起始位置
    :param mid: 分界位置的索引
    :param high:   第二个有序列表的结尾位置
    :return:
    """
    i = low
    j = mid+1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] <= li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    # while执行完,肯定有一部分没有数了
    # print(li)
    # print(len(li))
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1

    li[low:high+1] = ltmp     # 给切片赋值

# li = [1,2,5,8,9,4,5,6,7,8]
# print((len(li)-1)//2)
# marge(li, 0,4,len(li)-1)
# # print(li)

def merge_sort(li,low,high):
    if high > low:    # 至少有两个元素,递归
        mid = (low+high) // 2
        merge_sort(li, low, mid)
        merge_sort(li, mid+1, high)
        marge(li, low, mid, high)


希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

按增量序列个数 k,对序列进行 k 趟排序;

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

def insert_sort_gap_(li,gap):
    for i in range(gap,len(li)):    # i 代表摸到的牌的下标
        tmp = li[i]
        j = i-gap        # j指的是手中的牌的下标
        while j >= 0 and li[j] > tmp:
            li[j+gap] = li[j]     # 将大的数放到后面
            j -= gap
        li[j+gap] = tmp


def shell_sort(li):
    d = len(li)//2
    while d >= 1:
        insert_sort_gap_(li,d)
        d //= 2

计数排序

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

  • 找出待排序的数组中最大和最小的元素()
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
def count_sort(li,max_count=100): 
    # 在本算法中默认最小值为0,排序的数据只能是正整数,如果想对负数进行排序可以考虑使用字典进行计数
    count = [0 for _ in range(max_count+1)]
    for val in li:
        count[val] += 1
    li.clear()
    for ind, val in enumerate(count):
        for i in range(val):
            li.append(ind)

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。 可以拓展为其他的排序

def radix_sort(li):
    max_num = max(li)
    it = 0
    while 10**it <= max_num:
        buckets = [[] for _ in range(10)]
        # 分桶
        for var in li:
            digit = (var // 10**it) % 10
            buckets[digit].append(var)
        # 将数重新放回li
        li.clear()
        for buc in buckets:
            li.extend(buc)
        it += 1

写在最后: 这几种排序算法主要是要理解算法思想而不是模仿代码,代码是的实现方式有很多,但是都围绕着主题的思想。