[Python教程] 各大排序算法总结

排序算法

1.冒泡排序

依次比较两个数大小,游标从前向后走,一次冒泡将最大元素移至末尾,一共做n - 1次冒泡。

def bubble_sort(alist):
    n = len(alist)
    for i in range(n -1):
        count = 0
        for j in range(n - 1 - i):
            if alist[j] > alist[j + 1]:
                alist[j], alist[j + 1] = alist[j + 1], alist[j]
                count += 1
        if count == 0:
            return

上面的冒泡排序已经是优化后的冒泡排序了,最坏时间复杂度为O(n2),最优时间复杂度为O(n),排序方式稳定。

2.选择排序

从所有序列中找到最小值,提到前面,变成有序序列。

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

选择排序的最坏时间复杂度与最优时间复杂度都为O(n2),排序方式不稳定(升序排列选择最大数的情况)。

3.插入排序

将前面序列看作是顺序序列,每次将后面一个数插入到前面的顺序序列中正确位置。

def insert_sort(alist):
    n = len(alist)
    for i in range(1, n):
        j = i
        while j > 0:
            if alist[j] < alist[j - 1]:
                alist[j], alist[j - 1] = alist[j - 1], alist[j]
                j -= 1
            else:
                break

插入排序的最坏时间复杂度是O(n2),最好时间复杂度是O(n),排序方式稳定。

4.希尔排序

希尔排序其实是进阶版的插入排序,设计不同的gap,将原序列按照gap分组插入排序,依次减少gap,得到最后结果。

def shell_sort(alist):
    n = len(alist)
    gap = n // 2
    while gap >= 1:
        for i in range(gap, n):
            j = i
            while j > 0:
                if alist[j] < alist[j - gap]:
                    alist[j], alist[j - gap] = alist[j - gap], alist[j]
                    j -= gap
                else:
                    break
        gap //= 2

希尔排序的最坏时间复杂度是O(n2),最好时间复杂度是O(n),排序方式不稳定。

5.快速排序

快速排序使用双游标方法,取序列首位,最左边游标为low,最右边游标为high,两游标向中间行进,当high游标取到比首位小时,将low游标数取为high游标位置的数;当low游标取到比首位大的数时,将high游标数取为low游标位置的数,这样使得low与high游标相遇位置左边比首位小,右边比首位大,这样依次对左右剩余序列进行递归,得到最后序列。

def quick_sort(alist, first, last):
    if first >= last:
        return
    mid_value = alist[first]
    low = first
    high = last
    while low < high:
        while low < high and alist[high] >= mid_value:
            high -= 1
        alist[low] = alist[high]
        while low < high and alist[low] < mid_value:
            low += 1
        alist[high] = alist[low]
    alist[low] = mid_value
    #递归
    quick_sort(alist, first, low - 1)
    quick_sort(alist, low + 1, last)

快速排序最坏时间复杂度是O(n2),最优时间复杂度是O(nlogn),排序方式不稳定。

6.归并排序

将原序列向下依次拆分,拆成单个元素的集合,然后向上边排序边合并。

def merge_sort(alist):
    n = len(alist)
    if n <= 1:
        return alist
    mid = n // 2
    left_li = merge_sort(alist[:mid])
    right_li = merge_sort(alist[mid:])
    left_pointer, right_pointer = 0, 0
    result = []
    while left_pointer < len(left_li) and right_pointer < len(right_li):
        if left_li[left_pointer] <= right_li[right_pointer]:
            result.append(left_li[left_pointer])
            left_pointer += 1
        else:
            result.append(right_li[right_pointer])
            right_pointer += 1
    result += left_li[left_pointer:]
    result += right_li[right_pointer:]
    return result

归并排序最优与最坏时间复杂度都是O(nlogn),但是有额外的空间复杂度,排序方式稳定。

7.堆排序

在这里首先要先解释一下什么是堆,堆栈是计算机的两种最基本的数据结构。堆的特点就是FIFO(first in first out)先进先出,这里的话我觉得可以理解成树的结构。堆在接收数据的时候先接收的数据会被先弹出。
栈的特性正好与堆相反,是属于FILO(first in/last out)先进后出的类型。栈处于一级缓存而堆处于二级缓存中。这个不是本文重点所以不做过多展开。

堆排序节点访问和操作定义

堆节点的访问

在这里我们借用wiki的定义来说明:
通常堆是通过一维数组来实现的。在阵列起始位置为0的情况中

  1. 父节点i的左子节点在位置(2*i+1);
  2. 父节点i的右子节点在位置(2*i+2);
  3. 子节点i的父节点在位置floor((i-1)/2);
堆操作

堆可以分为大根堆和小根堆,这里用最大堆的情况来定义操作:

  1. 最大堆调整(MAX_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点。这是核心步骤,在建堆和堆排序都会用到。比较i的根节点和与其所对应i的孩子节点的值。当i根节点的值比左孩子节点的值要小的时候,就把i根节点和左孩子节点所对应的值交换,当i根节点的值比右孩子的节点所对应的值要小的时候,就把i根节点和右孩子节点所对应的值交换。然后再调用堆调整这个过程,可见这是一个递归的过程。
  2. 建立最大堆(Build_Max_Heap):将堆所有数据重新排序。建堆的过程其实就是不断做最大堆调整的过程,从len/2出开始调整,一直比到第一个节点。
  3. 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算。堆排序是利用建堆和堆调整来进行的。首先先建堆,然后将堆的根节点选出与最后一个节点进行交换,然后将前面len-1个节点继续做堆调整的过程。直到将所有的节点取出,对于n个数我们只需要做n-1次操作。

def MAX_Heapify(heap,HeapSize,root):#在堆中做结构调整使得父节点的值大于子节点

    left = 2*root + 1
    right = left + 1
    larger = root
    if left < HeapSize and heap[larger] < heap[left]:
        larger = left
    if right < HeapSize and heap[larger] < heap[right]:
        larger = right
    if larger != root:#如果做了堆调整则larger的值等于左节点或者右节点的,这个时候做对调值操作
        heap[larger],heap[root] = heap[root],heap[larger]
        MAX_Heapify(heap, HeapSize, larger)

def Build_MAX_Heap(heap):#构造一个堆,将堆中所有数据重新排序
    HeapSize = len(heap)#将堆的长度当独拿出来方便
    for i in range((HeapSize -2)//2,-1,-1):#从后往前出数
        MAX_Heapify(heap,HeapSize,i)

def heap_sort(heap):#将根节点取出与最后一位做对调,对前面len-1个节点继续进行对调整过程。
    Build_MAX_Heap(heap)
    for i in range(len(heap)-1,-1,-1):
        heap[0],heap[i] = heap[i],heap[0]
        MAX_Heapify(heap, i, 0)
    return heap

堆排序最坏与最优时间复杂度都是O(nlogn),排序方式不稳定。

8.计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。计数排序的基本思想为一组数在排序之前先统计这组数中其他数小于这个数的个数,则可以确定这个数的位置。

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
def count_sort(alist,k):
    n=len(alist)
    b=[0 for i in xrange(n)]
    c=[0 for i in xrange(k+1)]
    for i in alist:
        c[i]+=1
    for i in xrange(1,len(c)):
        c[i]=c[i-1]+c[i]
    for i in alist:
        b[c[i]-1]=i
        c[i]-=1
    return b

计数排序时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法,排序方式稳定。

9.桶排序

如果有一个数组A,包含N个整数,值从1到M,我们可以得到一种非常快速的排序,桶排序(bucket sort)。留置一个数组S,里面含有M个桶,初始化为0。然后遍历数组A,读入Ai时,S[Ai]增一。所有输入被读进后,扫描数组S得出排好序的表。该算法时间花费O(M+N),空间上不能原地排序。

def bucket_sort(alist):
    min, max = alist[0], alist[0]
    for i in alist:
        if i > max:
            max = i
        if i < min:
            min = i
    list = [0 for _ in range(min, max + 1)]
    for i in alist:
        list[i - min] += 1
    pos = 0
    for i in range(len(list)):
        if list[i] == 0:
            continue
        else:
            alist[pos] = i + min
            list[i] -= 1
            pos += 1

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

10.基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

  1. 取得数组中的最大数,并取得位数
  2. arr为原始数组,从最低位开始取每个位组成radix数组
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点)
def radix_sort(alist):
    for k in xrange(4):  #4轮排序      
        s=[[] for i in xrange(10)]
        for i in alist:
            s[i/(10**k)%10].append(i)
        alist=[a for b in s for a in b]
    return alist

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。

基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。

总结

参考内容:

  1. 十大经典排序算法(动图演示)
  2. 利用Python实现堆排序
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值