十大经典排序算法-python


---------本文以最终结果为升序编写

1、冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

(1)算法步骤

比较相邻的两个元素,如果第一个数比第二个元素大,就另其进行交换。当对应下标到达结尾时,也是全部数据的最大值了。在进行下一层循环时剔除最后一个元素也可以减少一小点的数据比较。直到循环结束,所得也即为排序结果。

(2)动图演示

冒泡排序

(3)代码实现

def bubble_sort(li):
    for i in range(len(li) - 1):  # 第i趟
        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

2、选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

(1)算法步骤

首先在列表数据中一遍排序找到最小的元素
然后使其与未排序的数据中的第一个元素进行互换,依次类推直到所有元素都排序完毕。

(2)动图演示

选择排序

(3)代码实现

def select_sort(li):
    for i in range(len(li) - 1):  # i是第几趟
        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]
    return

3、插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

(1)算法步骤

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。

(2)动图演示

插入排序

(3)代码实现

def insert_sort(li):
    for i in range(1, len(li)):  # i 表示摸到的牌的下标
        tmp = li[i]
        j = i - 1  # j指的是手里的牌的下标
        while j >= 0 and li[j] > tmp:
            li[j + 1] = li[j]
            j -= 1
        li[j + 1] = tmp
    return

4、快速排序(重要)

快速排序(Quicksort)是对冒泡排序算法的一种改进。
快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

(1)算法步骤

以未排序的数据的第一个数为基准,依次向后遍历,将比基准数小的元素放在左边,同样的将比基准数大的元素放在右边,直到遍历完成,将基准数放在中间
然后对基准数左右两边的数据分别进行同样的操作,此过程可以使用递归来完成,而结束条件可以为:其相对应部分区间元素个数为零

(2)动图演示

快速排序

(3)代码实现

import sys
sys.setrecursionlimit(100000)  # 设置递归最大深度


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


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)

5、堆排序(重要)

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

(1)算法步骤

  1. 创建一个大根堆
  2. 使堆首与堆尾互换,之后堆的大小减一,即去掉堆尾使之不影响后面的步骤,调用创建大根堆的函数重新建堆
  3. 重复步骤 2,直到堆的尺寸为 1

(2)动图演示

堆排序

(3)代码实现

def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return: 
    """
    i = low  # i最开始指向根节点
    j = 2 * i + 1  # j开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    while j <= high:  # 只要j位置有数
        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):
        # 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

6、归并排序

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

(1)算法步骤

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

(2)动图演示

归并排序

(3)代码实现

def merge(li, low, mid, high):
    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执行完,肯定有一部分没数了
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp

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

7、希尔排序

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

(1)算法步骤

对序列进行分组、插入排序:

  1. 确定一个gap,定为序列元素个数的一半并取整
  2. 将相隔gap个的所有元素视为一组序列,对其进行插入排序
  3. gap对2整除,重复2,直到gap<1

(2)代码实现

def insert_sort_gap(li, gap):
    for i in range(gap, len(li)):
        tmp = li[i]
        j = i - gap
        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:
        insert_sort_gap(li, d)
        d //= 2

8、计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 [1] 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

(1)动图演示

计数排序

(2)代码实现

def count_sort(li, max_count=100):
    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)

9、桶排序

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

代码实现

def bucket_sort(li, n=100, max_num=10000):
    buckets = [[] for _ in range(n)]  # 创建桶
    for var in li:
        i = min(var // (max_num // n), n - 1)  # i 表示var放到几号桶里
        buckets[i].append(var)  # 把var加到桶里边
        # 保持桶内的顺序
        for j in range(len(buckets[i]) - 1, 0, -1):
            if buckets[i][j] < buckets[i][j - 1]:
                buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1], buckets[i][j]
            else:
                break
    sorted_li = []
    for buc in buckets:
        sorted_li.extend(buc)
    return sorted_li
li = bucket_sort(li)

10、基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

代码实现

def radix_sort(li):
    max_num = max(li)  # 最大值 9->1, 99->2, 888->3, 10000->5
    it = 0
    while 10 ** it <= max_num:
        buckets = [[] for _ in range(10)]
        for var in li:
            # 987 it=1  987//10->98 98%10->8;    it=2  987//100->9 9%10=9
            digit = (var // 10 ** it) % 10
            buckets[digit].append(var)
        # 分桶完成
        li.clear()
        for buc in buckets:
            li.extend(buc)
        # 把数重新写回li
        it += 1

在这里插入图片描述
ps:图片摘自:用 Python 手写十大经典排序算法

附:python内置sort()方法底层—Timsort

底层c代码

Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data. It was implemented by Tim Peters in 2002 for use in the Python programming language. The algorithm finds subsequences of the data that are already ordered (runs) and uses them to sort the remainder more efficiently. This is done by merging runs until certain criteria are fulfilled. Timsort has been Python’s standard sorting algorithm since version 2.3. It is also used to sort arrays of non-primitive type in Java SE 7,on the Android platform, in GNU Octave,on V8,Swift,and Rust.
It uses techniques from Peter McIlroy’s 1993 paper “Optimistic Sorting and Information Theoretic Complexity”. —来自维基百科

由于我的英语太过垃圾,这里直接粘贴译文: Timsort是一种混合稳定排序算法,是从归并排序和插入排序派生而来的,旨在很好地处理现实世界中的各种数据。 它由Tim Peters在2002年实现,用于Python编程语言。 该算法查找已排序(runs)的数据的子序列,并使用它们对其余部分进行更有效的排序。 这可以通过合并run块直到满足某些条件来完成。 自2.3版以来,Timsort一直是Python的标准排序算法。 在Android平台、Java SE 7、GNU Octave、V8、Swift和Rust中,它也同样用于对非基本类型的数组进行排序。

它使用了彼得·麦克罗伊(Peter McIlroy)在1993年发表的论文《乐观的分类和信息理论的复杂性》中的技术。

性能

在维基百科上也同样给出了Timsort的时间空间复杂度:

  • 最差时间复杂度:O(n log n)
  • 最佳时间复杂度:O(n)
  • 平均时间复杂度:O(n log n)
  • 最坏情况下空间复杂度:O(n)
    虽说时间复杂度上,十大排序算法有与之相同的性能,但考虑到其底层是用c实现的,即便是同样的复杂度,Timsort运行时间依旧是飞跃的。所以在进行排序的时候还是推荐使用内置函数sort,算法最重要的是思想,不必非要在进行排序时使用它们。

操作

  1. Timsort会先将数据中的所有的已排好序的部分分开分别放到一个个run块中,并将这些runs块存放在堆栈中
  2. 然后对相邻的2个run块进行合并,但此过程重新开辟一个临时存储空间,其空间大小是2个run中大小较小的run的大小。合并过程中会将较小的run块复制到临时存储空间,之后将较大的run块中的数据合并到临时空间中以作为合并2个run块后的新的run块
  3. 重复第二步操作直到只剩一个run块,也即完成排序

此为个人见解,如有不对欢迎指正。

事实上维基百科上的讲解十分详细,有兴趣且有能力(指英语比较好的。。)的可以看看,这里附上地址:维基百科–Timsort

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

老君忆清凝

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值