Python 实现十大排序算法

排序(有小到大排)

1.1 冒泡排序

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

  1. 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(a_list):
    length = len(a_list)
    for i in range(length - 1):
        count = 0
        for j in range(length - i - 1):
            if a_list[j] > a_list[j + 1]:
                a_list[j], a_list[j + 1] = a_list[j + 1], a_list[j]
                count += 1
        if count == 0:
            break

1.2 选择排序

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

选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对 n 个元素的列表表进行排序总共进行至多 n-1 次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

def selection_sort(a_list):
    length = len(a_list)
    for i in range(length - 1):
        min_index = i
        for j in range(i + 1, length):
            if a_list[j] < a_list[min_index]:
                min_index = j
        if i != min_index:
            a_list[min_index], a_list[i] = a_list[i], a_list[min_index]

1.3 插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

def insert_sort(a_list):
    length = len(a_list)
    for i in range(1, length):
        for j in range(i, 0, -1):
            if a_list[j] < a_list[j - 1]:
                a_list[j], a_list[j - 1] = a_list[j - 1], a_list[j]
            else:
                break

1.4 希尔排序

希尔排序 (Shell Sort) 是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减至 1 时,整个列表恰被分成一组,算法便终止。

def shell_sort(a_list):
    length = len(a_list)
    gap = length // 2
    while gap >= 1:
        for i in range(gap, length):
            j = i
            while j >= gap and a_list[j - gap] > a_list[j]:
                a_list[j], a_list[j - gap] = a_list[j - gap], a_list[j]
                j -= gap
        gap //= 2

1.5 归并排序

归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解列表,再合并列表。

将列表分解最小之后,然后合并两个有序列表,基本思路是比较两个列表的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个列表为空,最后把另一个列表的剩余部分复制过来即可。

def merge(list1, list2):
    m = len(list1)
    n = len(list2)
    i = j = 0
    result = []
    while i < m and j < n:
        if list1[i] <= list2[j]:
            result.append(list1[i])
            i += 1
        else:
            result.append(list2[j])
            j += 1
    if i == m:
        result += list2[j:]
    else:
        result += list1[i:]
    return result


def merge_sort(a_list):
    length = len(a_list)
    if length <= 1:
        return a_list
    mid = length // 2
    left = merge_sort(a_list[:mid])
    right = merge_sort(a_list[mid:])
    return merge(left, right)

1.6 快速排序

快速排序(Quick Sort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

步骤为:

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

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

def partition(l, left, right):
    pivot = l[right]
    i = left - 1
    for j in range(left, right):
        if l[j] < pivot:
            i += 1
            l[i], l[j] = l[j], l[i]
    i += 1
    l[i], l[right] = l[right], l[i]
    return i


def quick_sort(a_list, left, right):
    if left >= right:
        return
    p = partition(a_list, left, right)
    quick_sort(a_list, left, p-1)
    quick_sort(a_list, p+1, right)

1.7 堆排序

升序使用最大堆,最大堆是一个特殊的完全二叉树。
完全二叉树:假设二叉树的层数为 h,出了最后一层,每一层的节点数都达到了最大个数,第 h 层中节点都连续集中在最左边。
最大堆:父节点中的值比左右子树的值都大。即树中值最大的节点就是根节点。
堆排序的意思就是:构建一个最大堆,然后将根节点与最下面最右边节点交换。这个时候可能破坏最大堆的性质,因此调整使其再次成为(不在考虑先前交换的最大节点)最大堆。以此交替进行,最后得到升序结果。

def build_heap(a_list, i, length):
    left = i * 2 + 1
    right = i * 2 + 2
    max_index = i
    if left < length and a_list[left] > a_list[max_index]:
        max_index = left
    if right < length and a_list[right] > a_list[max_index]:
        max_index = right
    if max_index != i:
        a_list[max_index], a_list[i] = a_list[i], a_list[max_index]
        build_heap(a_list, max_index, length)


def heap_sort(a_list):
    length = len(a_list)
    for i in range(length // 2 - 1, -1, -1):
        build_heap(a_list, i, length)
    for j in range(length-1, 0, -1):
        a_list[0], a_list[j] = a_list[j], a_list[0]
        build_heap(a_list, 0, j)

1.8 计数排序

计数排序是一个非基于比较的排序算法。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n + k)(其中 k 是整数的范围)。
排序过程:

  1. 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
  2. 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
  3. 根据额外空间还原回已排序的数组;
 def count_sort(a_list):
    max_value, min_value = max(a_list), min(a_list)
    count = [0] * (max_value - min_value + 1)
    for value in a_list:
        count[value - min_value] += 1
    res = []
    for i in range(max_value - min_value + 1):
        if count[i] > 0:
            res += [min_value + i] * count[i]
    return res

1.9 桶排序

桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的。

快速排序是将集合拆分为两个值域,这里称为两个桶,再分别对两个桶进行排序,最终完成排序。桶排序则是将集合拆分为多个桶,对每个桶进行排序,则完成排序过程。两者不同之处在于,快排是在集合本身上进行排序,属于原地排序方式,且对每个桶的排序方式也是快排。桶排序则是提供了额外的操作空间,在额外空间上对桶进行排序,避免了构成桶过程的元素比较和交换操作,同时可以自主选择恰当的排序算法对桶进行排序。
当然桶排序更是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。桶排序则是弱化了这种浪费情况,将最小值到最大值之间的每一个位置申请空间,更新为最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况。

参考桶排序

def bucket_sort(a_list):
    max_value, min_value = max(a_list), min(a_list)
    length = len(a_list)
    bucket_each = (max_value - min_value) / length
    count = [[] for _ in range(length + 1)]
    for value in a_list:
        count[int((value - min_value) // bucket_each)].append(value)
    res = []
    for i in count:
        for j in sorted(i):
            res.append(j)
    return res

1.10 基数排序

基数排序也可以称为多关键字排序,同计数排序类似,也是一种非比较性质的排序算法。将待排序集合中的每个元素拆分为多个总容量空间较小的对象,对每个对象执行桶排序后,则完成排序过程。
基数排序过程中也使用了桶排序操作,不过对于桶排序面向的对象进行了优化。例如,若元素是整数类型,则选择元素的每位数字作为排序对象,因为每个数字的容量空间大小只是 10;所以在基数排序过程中,给其中的桶排序操作选择了容量空间有限的排序对象。

基数排序中的桶排序操作具有一点特殊性,即每个桶的宽度,或者称为值域跨度为一,所以将待排序集合中所有元素移动到各个桶上之后,不需要再对每个桶进行排序。

算法过程

  1. 根据待排序元素的类型申请桶空间,并从待排序集合中计算出元素的最大位数;
  2. 从右向左,根据元素当前位数的值,将所有元素移动到对应的桶中;
  3. 将所有桶中元素移动回原始集合中;
  4. 重复步骤 2, 3,直到遍历完所有位数。

参考基数排序

def radix_sort(a_list):
    i = 0
    max_length = len(str(max(a_list)))
    while i < max_length:
        bucket = {}
        for j in range(10):
            bucket.setdefault(j, [])
        for value in a_list:
            radix = int(value / (10**i) % 10)
            bucket[radix].append(value)
        j = 0
        for k in range(10):
            if len(bucket[k]) != 0:
                for y in bucket[k]:
                    a_list[j] = y
                    j += 1
        i += 1

2. 时间复杂度分析

方法平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性是否原地
冒泡排序O( n 2 n^2 n2)O( n 2 n^2 n2)O( n n n)O( 1 1 1)稳定原地
选择排序O( n 2 n^2 n2)O( n 2 n^2 n2)O( n 2 n^2 n2)O( 1 1 1)不稳定原地
插入排序O( n 2 n^2 n2)O( n 2 n^2 n2)O( n n n)O( 1 1 1)稳定原地
希尔排序O( n 1.3 n^{1.3} n1.3)O( n 2 n^2 n2)O( n n n)O( 1 1 1)不稳定原地
归并排序O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( n n n)稳定不原地
快速排序O( n l o g n nlogn nlogn)O( n 2 n^2 n2)O( n l o g n nlogn nlogn)O( l o g n logn logn)不稳定原地
堆排序O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( 1 1 1)不稳定原地
计数排序O( n + k n+k n+k)O( n + k n+k n+k)O( n + k n+k n+k)O( n + k n+k n+k)稳定不原地
桶排序O( n + k n+k n+k)O( n 2 n^2 n2)O( n n n)O( n + k n+k n+k)稳定不原地
基数排序O( n k nk nk)O( n k nk nk)O( n k nk nk)O( n + r n+r n+r)稳定不原地

计数排序中 k 为辅助计数数组 count 的长度;
桶排序中 k 为痛的个数;
基数排序中 k 为元素的最大位数,r 为元素的基数;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值