Python各种排序算法整理

1,453 阅读10分钟

最近重新温习数据结构与算法,于是使用Python将各个排序算法实现以下,以便做个总结

排序算法分类

  • 1:简单排序
  • 简单插入排序
  • 冒泡排序
  • 2:复杂排序
  • 快速排序
  • 归并排序
  • 堆排序

简单插入排序

  • 原理:不断地把一个个元素插入一个序列中,最终得到排序序列
  • 注意:为减少辅助空间最合适的办法就是把正在构造的排序序列嵌入到原来的表中
  • 时间复杂度:最坏:O(n ** 2),平均:O(n ** 2),最好:O(n)
  • 空间复杂度:O(1)
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(1,len(lst)):
    x = lst[i]
    j = i
    while j > 0 and lst[j - 1] > x:
        lst[j] = lst[j - 1]
        j -= 1
    lst[j] = x
print('排序后:',lst)
排序前: [12, 43, 2, 5, 7, 8]
排序后: [2, 5, 7, 8, 12, 43]

选择排序

  • 原理:找出序列中最小的元素放在第0个位置,然后从其余元素中找出最小元素放在第1个位置,依次类推,直到只有一个元素为止
  • 注意:只需要遍历len(lst) - 1次就可以
  • 时间复杂度:任何情况下都是O(n ** 2),没有适应性
  • 空间复杂度:O(1)
#直接选择排序
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(len(lst) - 1):
    j = i      #记录遍历元素中最小元素的位置
    for k in range(i,len(lst)):
        if lst[k] < lst[j] : #发现后面比要替换位置元素小的数据
            j = k
    if j != i :#遍历完成后如果j和初始位置i不相等,则交换j和i位置的元素
        lst[j],lst[i] = lst[i],lst[j]
print('排序后:',lst)
排序前: [12, 43, 2, 5, 7, 8]
排序后: [2, 5, 7, 8, 12, 43]

冒泡排序

  • 原理:冒泡排序是通过交换元素消除逆序实现排序方法,比较相邻的元素,如果是逆序就交换
  • 注意:如果某次排序没有发生任何数据交换情况,说明排序已完成,就终止循环
  • 时间复杂度:最好O(n)最坏O(n ** 2)平均O(n ** 2)
  • 空间复杂度:O(1)
  • 说明:冒泡排序效率较低,实际效果劣于复杂度相同的简单插入排序算法,主要原因1、反复交换中做赋值操作比较多,累计起来代价比较大2、一些距离最终位置很远的记录可能拖累整个算法
#直接选择排序
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(len(lst)):
    found = False #记录此次是否找到交换的元素,如果没有则表示排序已完成
    for j in range(1,len(lst) - i):
        #如果发现前一个元素比后一个元素,就将两个元素交换位置
        if lst[j - 1] > lst[j] :
            lst[j - 1],lst[j] = lst[j],lst[j - 1]
            found = True
    if not found :
        break
print('排序前:',lst)
排序前: [12, 43, 2, 5, 7, 8]
排序前: [2, 5, 7, 8, 12, 43]

快速排序

  • 原理:选择一个标准,把排序序列中的记录按照这个标准分为大小两组,较小的一组排在前面,较大的一组排在后面,然后使用递归对大组小组重复以上操作,直到组里只有一个元素为止
  • 时间复杂度:最坏时间复杂度O(n**2),平均时间复杂度O(n log n),最好时间复杂度O(n log n)
  • 空间复杂度:O(log n)
  • 说明:在各种基于关键码比较的内排序算法中,快速排序是实践中平均速度最快的算法之一
#快速排序
lst = [12,43,2,5,23,51,7,8]
print(lst,':排序前')
def ks_sort(lst,begin,end):
    if begin >= end :
        return None
    piovt = lst[begin]
    i = begin
    for j in range(begin + 1,end + 1):
        if lst[j] < piovt:#发现比begin为止小的元素就用i+1为止的元素和它交换
            i += 1
            lst[i],lst[j] = lst[j],lst[i]
    lst[begin],lst[i] = lst[i],lst[begin]#遍历一次后将begin移动到大小组中间的位置
    print(lst)
    #使用递归方式处理大小组,现第i个位置的元素是原来的begin位置的元素,不需要再考虑
    ks_sort(lst,begin,i - 1)
    ks_sort(lst,i + 1,end)
    
ks_sort(lst,0,len(lst) - 1)
[12, 43, 2, 5, 23, 51, 7, 8] :排序前
[8, 2, 5, 7, 12, 51, 43, 23]
[7, 2, 5, 8, 12, 51, 43, 23]
[5, 2, 7, 8, 12, 51, 43, 23]
[2, 5, 7, 8, 12, 51, 43, 23]
[2, 5, 7, 8, 12, 23, 43, 51]
[2, 5, 7, 8, 12, 23, 43, 51]

归并排序

  • 归并排序原理:

    • 1、初始时,将待排序序列中的n个记录看做是n个有序子序列

    • 2、把当时序列组里的有序子序列两两归并,完成一遍序列组里的排序序列个数减半,每个序列的长度加倍

    • 3、对加长的有序序列重复上面的操作,最终得到一个长度为n的有序序列

    • 这种归并方法为二路归并排序,还有三路归并排序和多路归并排序

    • 归并排序操作过程中对数据的访问具有局部性,适合外存数据交换的特点,特别适合处理一组记录形成的数据块

    • 由于这些情况,归并排序适合于处理存储在外存的大量数据

  • 归并排序分为三层,每层负责不同的工作

    • 1、最下层:实现表中相邻的一对有序序列的归并工作,将归并的结果存入另一个顺序表里相同的位置
    • 2、中间层:基于操作一(一对序列的归并操作),实现对整个表里顺序各对有序序列的归并,完成一遍归并,
    • 对序列的归并结果顺序存入另一个顺序表里的同位置分段
    • 3、最高层:在两个顺序表之间往复执行操作2,完成一遍归并后交换两个表的地位,然后重复2的工作,
    • 直到整个表中只有一个有序序列
  • 时间复杂度:最坏情况O(n log n) 平均O(n log n) 最好 O(n log n)

  • 空间复杂度:O(n)

def merge(lfrom,lto,low,mid,high):
    '''
    功能:完成表中连续排放的两个有序序列的归并工作
    :param lfrom:将lfrom中的数据归并到lto中,
    :param lto:将归并的结果放到lto中
    :param low:需要归并的序列分别是lfrom[low:mid],lfrom[mid:high]
    :param mid:
    :param high:
    :return:None
    '''
    i,j,k = low,mid,low
    while i < mid and j < high: #反复复制分段首记录中较小的
        if lfrom[i] <= lfrom[j]:
            lto[k] = lfrom[i]
            i += 1
        else :
            lto[k] = lfrom[j]
            j += 1
        k += 1
    while i < mid:              #复制第一段剩余记录
        lto[k] = lfrom[i]
        i += 1
        k += 1
    while j < high:             #复制第二段剩余记录
        lto[k] = lfrom[j]
        j += 1
        k += 1


def merge_pass(lfrom,lto,llen,slen):
    '''
    功能:归并长为slen的两段序列
    :param lfrom: 要归并的序列
    :param lto: 归并后存放的序列
    :param llen: 开始归并时分段长度,
    :param slen: 整个表的长度
    :return:None
    '''
    i = 0
    while i + 2 * slen < llen:  #归并长slen的两段
        merge(lfrom , lto , i , i + slen , i + 2 * slen)
        i += 2 * slen
    if i + slen < llen:         #剩下两段,后段长度小于slen
        merge(lfrom,lto,i,i + slen,llen)
    else :                      #只剩下一段,复制到lto
        for j in range(i,llen):
            lto[j] = lfrom[j]


def merge_sort(lst):
    '''
    功能:创建另一个长度相同的表,然后在两个表中往复做一遍遍的归并
    :param lst: 要排序的列表
    :return:None
    '''
    slen,llen = 1,len(lst)
    #创建另一个长度相同的列表
    templst = [None]*llen
    while slen < llen :
        merge_pass(lst,templst,llen,slen)
        slen *= 2
        merge_pass(templst,lst,llen,slen)#结果存回原位
        slen *= 2


lst = [21,43,6,3,7,31,43,23]
print('开始:',lst)
merge_sort(lst)
print('结束:',lst)
开始: [21, 43, 6, 3, 7, 31, 43, 23]
结束: [3, 6, 7, 21, 23, 31, 43, 43]

堆排序

  • 什么是堆

    • 在这里首先要先解释一下什么是堆,堆栈是计算机的两种最基本的数据结构。
    • 堆的特点就是FIFO(first in first out)先进先出,
    • 堆在接收数据的时候先接收的数据会被先弹出。
    • 栈的特性正好与堆相反,是属于FILO(first in/last out)先进后出的类型。
    • 栈处于一级缓存而堆处于二级缓存中。
  • 堆节点的访问

    • 通常堆是通过一维数组来实现的。在阵列起始位置为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次操作。
  • 时间复杂度:O(nlgn)

  • 空间复杂度:O(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 HeapSort(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



lst = [21,43,6,3,7,31,43,23]
print('排序前:',lst)
HeapSort(lst)
print('排序后:',lst)
排序前: [21, 43, 6, 3, 7, 31, 43, 23]
排序后: [3, 6, 7, 21, 23, 31, 43, 43]