Python3实现各种内部排序算法

一、前言

排序是计算机内经常进行的一种操作,目的是将一组无序的记录序列调整为有序的记录序列,通常分为内部排序外部排序。若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;反之,若排序的记录数量巨大,整个排序过程不可能在内存中完成,则称为外部排序。而在实际中,我们通常使用的是内部排序算法,故本文也主要讲解各种内部排序算法的原理及其实现。

二、插入排序算法

插入排序算法的基本思想是:每一步将下一条待排序的记录有序地插入到已经排好序的记录子集中,直到将所有待排序的记录全部插入为止。

1、直接插入排序

直接插入排序是一种最基本的插入排序算法,其具体的插入过程如下:将第 i 条记录的关键字 Ki 顺序与其前面记录的关键字 Ki-1, Ki-2, …, K1 进行比较,将所有关键字大于 Ki 的记录依次向右移动一个位置,直到遇见关键字小于或等于 Ki 的记录 Kj 。此时 Kj 后面必为空位置,将第 i 条记录插入该空位置即可。
完整的直接插入排序是从 i=1(第2条记录) 开始,即将第1条记录作为已排好序的单元素子集合,然后将第2条至第n条记录插入到该子集合中,具体的实现代码如下所示:


	def insert_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    for i in range(1, length):
	        # previous index
	        j = i - 1
	        # if current value is less than previous value, then move to previous
	        if data_list[i] < data_list[j]:
	            temp = data_list[i]
	            data_list[i] = data_list[j]
	            # continue to move previous
	            j -= 1
	            while j >= 0 and data_list[j] > temp:
	                data_list[j + 1] = data_list[j]
	                j -= 1
	            # until find the proper place for current value
	            data_list[j + 1] = temp
	    # return the sorted list from minimum to maximum
	    return data_list

2、希尔排序

希尔排序又称为缩小增量排序法,利用直接插入排序的最佳性质,其思想是首先将待排序的关键字序列分成若干个较小的子序列,然后对子序列进行直接插入排序操作
希尔排序在具体实现时,首先选定两条记录间的距离 d1 ,在整个待排序的记录序列中将所有间隔为 d1 的记录分成一组,然后在组内进行直接插入排序。接下来去两条记录间的距离 d2 < d1 ,在整个待排序的记录序列中将所有间隔为 d2 的记录分成一组,同样进行组内直接插入排序;直到最后选定的两条记录见的距离 dt=1 为止。希尔排序的具体实现代码如下所示:


	def shell_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    gap = length // 2
	    while gap >= 1:
	        # insert sort
	        for j in range(gap, length):
	            i = j
	            while i - gap >= 0:
	                if data_list[i] < data_list[i - gap]:
	                    data_list[i], data_list[i - gap] = data_list[i - gap], data_list[i]
	                    i -= gap
	                else:
	                    break
	        # decrease the gap
	        gap //= 2
	    # return the sorted list from minimum to maximum
	    return data_list

3、希尔排序和直接插入排序的速度比较

希尔排序其实是在直接插入排序的基础上进行排序处理的,减少了数据移动的次数,也就是对直接插入排序的一种优化。但是通过大量的程序实践表明,对于数据规模比较小的排序,其实插入排序的效率比希尔排序的效率高;而对于大规模的数据排序,希尔排序的效率更高。因此,建议大家简单程序使用插入排序,大型程序采用希尔排序

三、交换排序算法

1、冒泡排序

冒泡排序是一种简单的交换排序方法,通过对相邻的数据元素进行交换,从而逐步将待排序的序列变成有序序列。冒泡排序的基本思想是:从头扫描待排序的记录序列,在扫描的过程中顺次比较相邻的两个元素的大小。下面的代码则是对冒泡排序的具体实现:


	def bubble_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    for i in range(0, length - 1):
	        for j in range(0, length - 1 - i):
	            # compare near two elements and swap them
	            if data_list[j] > data_list[j + 1]:
	                data_list[j], data_list[j + 1] = data_list[j + 1], data_list[j]
	    # return the sorted list from minimum to maximum
	    return data_list

2、快速排序

在前面的冒泡排序中,在扫描过程中只是比较了相邻的两个元素,进行交换时只能消除一个逆序。因此,产生了快速排序,对多个不相邻的元素进行交换,这样就能消除待排序记录中的多个逆序,从而加快排序的速度。快速排序的基本思想是:
(1)从待排序记录序列中选取一条记录,通常选取第一条记录作为基数,将其关键字设为K1
(2)将关键字小于K1的记录移到前面,将关键字大于K1的记录移动后面,从而将待排序记录序列分为两个子序列。
(3)将关键字为K1的记录插入到其分界线的位置。
通常上述的3个步骤称为一趟快速排序,后续需要递归对左右子序列各自进行快速排序,直到子序列的长度不超过1停止,最后待排序记录序列就变成一个有序序列。下面是对快速排序的实现代码:


	def quick_sort(data_list):
	    if len(data_list) <= 1:
	        return data_list
	    # left is smaller than base, while right is bigger than base
	    left, right = [], []
	    # base element
	    base = data_list.pop()
	    # partition of original list
	    for data in data_list:
	        if data < base:
	            left.append(data)
	        else:
	            right.append(data)
	    # return the sorted list from minimum to maximum
	    return quick_sort(left) + [base] + quick_sort(right)

3、快速排序的时间耗费

快速排序的时间耗费和使用递归调用的深度的趟数有关,因此快速排序的时间耗费可分为最好情况、最坏情况和一般情况三种。一般情况介于最好情况和最坏情况之间,没有讨论的必要,下面着重讲下最好和最坏这两种情况。
(1)在最好的情况下,每趟将序列分为长度相等的两个子序列,这就相当于是二分查找(也就是折半查找),此时时间耗费为 T(n)=O(nlog2n)
(2)在最坏的情况下,即原带排序记录已经排好序时,算法执行的时间最长。第一趟需要经过 n-1 次比较,第二趟需要经过 n-2 次比较,以此类推,最后总的比较次数为 1 + 2 + 3 + … + n-1 = n(n-1)/2。此时的快速排序其实就相当于是冒泡排序,故时间消耗为 T(n)=O(n2)

四、选择排序算法

选择排序算法其实就是有选择性的进行排序,但是并不是随意的选择,选择排序算法中每一趟会从待排序的记录中选择出关键字最小的记录,然后顺序放在已经排好序的子序列最后,直到排序完全部记录为止。常用的选择排序算法有直接选择排序和堆排序。

1、直接选择排序

直接选择排序又称之为简单选择排序,其过程是:第 i 趟简单选择排序是指通过 n-i 次关键字的比较,从 n-i+1 条记录中选择出关键字最小的记录,并与第 i 条记录进行交换。这样共需要进行 i-1 趟比较,直到排序完全部记录为止。下面是直接选择排序的具体实现代码:


	def select_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    for i in range(0, length):
	        for j in range(i + 1, length):
	            if data_list[i] > data_list[j]:
	                data_list[i], data_list[j] = data_list[j], data_list[i]
	    # return the sorted list from minimum to maximum
	    return data_list

2、堆排序

堆排序是指在排序过程中,将向量中存储的数据看成一棵完全二叉树,利用完全二叉树中双亲节点和孩子节点之间的内在关系,以选择关键字最小的记录的过程。待排序记录仍采用向量数组方式存储,并非采用树这种存储结构,而仅仅根据完全二叉树的顺序结构特征进行分析而已。
堆排序的具体做法是:将待排序的记录的关键字存放在数组 r[1…n] 中,将 r 用一棵完全二叉树的顺序来表示。每个节点表示 一条记录,第一条记录 r[1] 作为二叉树的根,后面的各条记录 r[2…n] 依次逐层从左往右顺序排序,任意节点 r[i] 的做孩子是 r[2i]、右孩子是 r[2i+1]、双亲是 r[r/2]。调整这棵完全二叉树,使各节点的关键字值满足下列条件:

r[i].key >= r[2i].key && r[i].key >= r[2i+1].key (i=1, 2, 3, …, n/2)

将满足上述条件的完全二叉树称为堆,将堆中根节点的最大关键字称为最大根堆。反之,若完全二叉树的任意节点的关键字小于或等于其左孩子和右孩子的关键字,则对应的堆称为最小根堆
堆排序的基本思想是:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,使之成为堆,这时堆的根节点的数值最大。然后将根节点与堆的最后一个节点交换,接着将前面的 n-1 个数重新调整为堆。以此类推,直到只有两个节点的堆,并对它们作交换,最后得到有 n 个节点的有序序列。下面是堆排序的具体实现代码,主要包括两个过程:一是建立堆,二是堆顶与堆的最后一个元素交换位置。


	def adjust_heap(data_list, index, length):
	    lchild = 2 * index + 1
	    rchild = 2 * index + 2
	    maxi = index
	    if index < length // 2:
	        if lchild < length and data_list[lchild] > data_list[maxi]:
	            maxi = lchild
	        if rchild < length and data_list[rchild] > data_list[maxi]:
	            maxi = rchild
	        if maxi != index:
	            data_list[maxi], data_list[index] = data_list[index], data_list[maxi]
	            adjust_heap(data_list, maxi, length)
	
	
	def build_heap(data_list):
	    # get the length of data list
	    length = len(data_list)
	    for i in range(0, length // 2)[::-1]:
	        adjust_heap(data_list, i, length)
	
	
	def heap_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    build_heap(data_list)
	    for i in range(0, length)[::-1]:
	        data_list[0], data_list[i] = data_list[i], data_list[0]
	        adjust_heap(data_list, 0, i)
	    return data_list

五、归并排序算法

归并排序的基本思想是:利用归并过程,开始时将 k 个数据看成 k 个长度为1的已排好序的表,将相邻的表成对合并,得到长度为2的 (k/2) 个有序表,每个表含有2个数据;进一步再将相邻的表成对合并,得到长度为4的 (k/4) 个有序表,…。如此重复下去,直到将所有数据全都合并到一个长度为k的有序表为止,从而完成排序。
归并排序的实现方法通常有两种,分别是自底向上自顶向下方法。
1、自底向上的方法
其基本思想是,当第1趟归并排序时,将带排序的记录列表 R[1…n] 看作n个长度为1的有序子列表,然后将这些子列表进行两两归并。
① 如果 n 为偶数,则得到 n/2 个长度为2的有序子列表。
② 如果 n 为奇数,则最后一个子列表轮空(不参与归并)。
所以当完成本趟归并后,前 [log2n] 个有序子列表的长度为2,最后一个子列表的长度仍为1。
第2趟归并的功能是,对第1趟归并得到的 [log2n] 个有序子列表实现两两归并。如此反复操作,直到得到一个长度为n的有序列列表为止。
上述每次归并操作,都是将两个有序的子列表合并为一个有序的子列表,所以也称为“二路归并排序”。

2、自顶向下的方法
使用分治法进行自顶向下的算法设计,主要分为3个步骤。假设归并排序的当前区间是 R[low,…,high],分治法的3个步骤如下:
(1)分解:将当前区间一分为二,即求分裂点。
(2)求解:递归地对两个子区间 R[low,…,mid] 和 R[mid+1,…,high] 进行归并排序。
(3)组合:将已经排序的两个子区间 R[low,…,mid] 和 R[mid+1,…,high] 归并为一个有序的区间 R[low,…,high]。
递归的终止条件是:子区间的长度为1。
下面则是对归并排序的自顶向下方法的实现代码:


	def merge(left_list, right_list):
	    result = []
	    i, j = 0, 0
	    while i < len(left_list) and j < len(right_list):
	        if left_list[i] <= right_list[j]:
	            result.append(left_list[i])
	            i += 1
	        else:
	            result.append(right_list[j])
	            j += 1
	    result += left_list[i:]
	    result += right_list[j:]
	    return result
	
	
	def merge_sort(data_list):
	    # get the length of data list
	    length = len(data_list)
	    if length <= 1:
	        return data_list
	    mid = length // 2
	    left_list = merge_sort(data_list[:mid])
	    right_list = merge_sort(data_list[mid:])
	    return merge(left_list, right_list)

归并排序与快速排序、堆排序相比,其最大的特点是,它是一种稳定的排序方法。归并排序中一趟归并要多次用到二路归并算法,一趟归并的操作是调用 (n/2h) 次合并算法,对 r[1…n] 中前后相邻且长度为h的有序段进行两两归并,得到前后相邻、长度为2h的有序段,并存放在 r[1…n] 中,时间复杂度为 O(n)。整个归并排序需要进行 m(m=log2n) 趟二路归并,所以归并排序总的时间复杂度为 O(nlog2n)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值