TimSort(简易版)和堆排序的Python实现

一、timsort排序

简述:

timsort排序是Python、Java等编程语言默认的排序函数。timsort排序是一种高效、稳定的排序算法,其平均时间复杂度为 O(nlogn)。timsort是将插入排序和归并排序结合起来并从提高排序速度角度提出了很多有效的提速方法。

在现实的待排序序列中有很多序列存在许多已经排好序的“小块”,timsort就是很好的利用这一特性。在timsort中我们将这种有序的小块称为“run”,同时还提出了“minrun”的概念。minrun在实际的timsort中非常重要的一个组成部分,对于降低时间复杂度有着很重要的意义。我们称一个run为一个分区,当然minrun就是对这个分区的一种限制条件,就是要求run中的元素个数必须尽可能的接近或者等于minrun规定的数值。如果原始的run小于minrun的长度,用插入排序扩充run,直到达到条件,之后使用归并排序来合并多个run。

在将多个run合并的时候一般采用的是插入排序;在形成了多个内部有序的run之后,再采用归并排序来实现整体序列的有序。

算法思想:

①根据数列大小产生minrun

  • minrun是划分run的一个条件值,run的大小小于这个minrun,就要进行扩充,将后面元素填充到run中,直到符合要求等于minrun。因此说明一下minrun 的选取方式,如果待排序序列长度为 n,则我们总共会生成[n/minrun]个初始 run 。

       [n/minrun]刚好是2的整数次幂,则归并过程将会非常“完美”,可以表述为一个满二叉树。
       [n/minrun]比2的某个整数次幂稍大一点点,则到算法的最后阶段会出现一个超长 run 与一个超短 run 的合并,这是一种非常不好的的情况。
因此,我们会选取minrun,使得[n/minrun]刚好是2的整数次幂或比某个2的整数次幂稍小一点的数。

  • 当数组元素个数小于64时,minrun就是数组的长度,此时就采用二分查找插入排序来进行数组排序。
  • 当数组元素个数大于64时,Timsort 选择范围为 [32,64]的 minrun,使得原始数组的长度除以 minrun 时[n/minrun]等于或者略小于2的整数幂次方。
    具体而言,选择数组长度的六个最高标志位,如果其余的标志位被设置,则加1:
    • 189:10111101,取前六个最高标志位为101111(47),同时最后两位为01,所以 minrun 为47+1=48,[n/minrun]=4符合要求。
    • 976:11 1101 0000,取前六个最高标志位为111101(61),同时最后几位为0000,所以 minrun 为61,[n/minrun]=16符合要求。

②依次寻找待排序序列中的run,并判断run的大小是否小于minrun,如果小于,则向后进行扩充,采用插入排序的方式将后面的元素加入到当前的run中,直到等于minrun。如果run区间的元素为逆序时,就地线性时间调整为顺序。

③采用归并排序将run合并。

Python代码实现:

#timSort
''''
timSort是python、Java等语言的内置排序函数。timSort将插入排序和归并排序结合,首先将
原始无须列表分割成一个个run,run是将原始无需序列按照对应关系分割而成,一般这里的run
制的是原始序列里面存在的有序序列。任何无序序列可以被分割成有序序列的集合。
说明:这里仅仅实现了timsort的雏形,没有考虑minrun。
'''

import time

def binary_search(the_array, item, start, end):#二分法插入排序
    if start == end:
        if the_array[start] > item:
            return start
        else:
            return start + 1
    if start > end:
        return start

    mid = round((start + end)/ 2)

    if the_array[mid] < item:
        return binary_search(the_array, item, mid + 1, end)

    elif the_array[mid] > item:
        return binary_search(the_array, item, start, mid - 1)

    else:
        return mid

def insertion_sort(the_array):
    l = len(the_array)
    for index in range(1, l):
        value = the_array[index]
        pos = binary_search(the_array, value, 0, index - 1)
        the_array = the_array[:pos] + [value] + the_array[pos:index] + the_array[index+1:]
    return the_array

def merge(left, right):#归并排序
    if not left:
        return right
    if not right:
        return left
    if left[0] < right[0]:
        return [left[0]] + merge(left[1:], right)
    return [right[0]] + merge(left, right[1:])

def timSort(the_array):
    runs, sorted_runs = [], []
    length = len(the_array)
    new_run = []

    for i in range(1, length):#将序列分割成多个有序的run
        if i == length - 1:
            new_run.append(the_array[i])
            runs.append(new_run)
            break
        if the_array[i] < the_array[i-1]:
            if not new_run:
                runs.append([the_array[i-1]])
                new_run.append(the_array[i])
            else:
                runs.append(new_run)
                new_run = []
        else:
            new_run.append(the_array[i])

    for item in runs:
        sorted_runs.append(insertion_sort(item))

    sorted_array = []
    for run in sorted_runs:
        sorted_array = merge(sorted_array, run)

    print(sorted_array)

arr = [45,2.1,3,67,21,90,20,13,45,23,12,34,56,78,90,0,1,2,3,1,2,9,7,8,4,6]
t0 = time.perf_counter()
timSort(arr)
t1 = time.perf_counter()
print('共%.5f秒' %(t1-t0))

 最后附上Python的sort()函数的源码,源码是C语言,一个小小的排序算法就3000多行。膜~

https://github.com/python/cpython/blob/master/Objects/listobject.c

 

 

二、堆排序

堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

TimSort(简易版)和堆排序的Python实现_第1张图片

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

算法步骤:

①将原始无序序列演变成大堆顶的数据结构形式
②调整堆结构,使非子叶节点大于不小于其子节点。
③将大堆顶的父节点与最后一个子节点交换位置,从而使最大值排到序列的最后,形成新的未排序
④重复步骤①②③直到不存在任何节点

Python代码实现:

#堆排序
'''
①将原始无序序列演变成大堆顶的数据结构形式
②调整堆结构,使非子叶节点大于不小于其子节点。
③将大堆顶的父节点与最后一个子节点交换位置,从而使最大值排到序列的最后,形成新的未排序
④重复步骤①②③直到不存在任何节点
'''
import time

def bigHeap(arr,i):
    n = (len(arr)-i)//2 - 1
    while n >= 0:
        if arr[2*n+1]>arr[n]:
            arr[2*n+1],arr[n] = arr[n],arr[2*n+1]
        if 2*n+2 <= len(arr)-i-1:
            if arr[2*n+2]>arr[n]:
                arr[2*n+2],arr[n] = arr[n],arr[2*n+2]
        n-=1
    arr[0],arr[-1-i] = arr[-1-i],arr[0]
    return arr

def heapSort(arr):
    for i in range(0,len(arr)-1):
        bigHeap(arr,i)
    print(arr)
    
arr = [45,2.1,3,67,21,90,20,13,45,23,12,34,56,78,90,0,1,2,3,1,2,9,7,8,4,6]
t0 = time.perf_counter()
heapSort(arr)
t1 = time.perf_counter()
print('共%.5f秒' %(t1-t0))

 

三、总结

排序算法的学习告一段落了,感觉自己在算法方面的欠缺真的很多。需要找机会好好补一补算法与数据结构了。

你可能感兴趣的:(Algorithm,python)