排序算法
1. 简介
排序是将集合中的元素按照某种顺序排列的过程。许多查找算法的前提就是排序,排序也是计算机科学中一个重要的研究领域。排序过程通常需要将元素位置进行交换,交换在计算机中是比较耗时的操作,因此排序算法总的交换次数也作为衡量排序算法效率的重要指标。
本文主要介绍冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序。
2. 冒泡排序
冒泡排序需要多次遍历列表,每一次遍历将较大的元素沉底。
假设列表有 n n n个元素,那么第一次遍历需要比较 ( n − 1 ) (n-1) (n−1)对相邻值大小,第二次需要遍历 ( n − 2 ) (n-2) (n−2)对相邻值大小,最后一次需要遍历比较1对相邻值大小(其中第一次遍历流程如下图所示,之后的遍历流程类似),因此算法总的需要比较次数为 1 + 2 + . . . + ( n − 1 ) 1+2+...+(n-1) 1+2+...+(n−1) = 1 / 2 n 1/2n 1/2n2- 1 / 2 n 1/2n 1/2n,算法时间复杂度为 O ( n O(n O(n2 ) ) )。
#冒泡排序python实现
def bubbleSort(numlist): for j in range(len(numlist)-1):#遍历次数 i = 0 while i+1 < len(numlist)-j:#比较次数 if numlist[i] > numlist[i+1]: numlist[i],numlist[i+1] = numlist[i+1],numlist[i]#交换 i += 1 return numlist
3. 选择排序
选择排序是每次遍历选出未排序元素中的最大元素,然后将该最大元素沉底。与冒泡排序相比,选择排序遍历列表次数一样,但交换元素次数减少,每次遍历只进行一次元素交换或不交换。
假设列表有 n n n个元素,那么第一次遍历需要比较 ( n − 1 ) (n-1) (n−1)对元素的大小,定位最大值后,将最大元素交换至列表末尾(沉底完成),第二次遍历比较 ( n − 2 ) (n-2) (n−2)对元素的大小(沉底完成的元素无须再比较),定位未排序元素最大值后,将该最大元素交换至未排序列表末尾,以此类推,同样可得到算法时间复杂度为 O ( n O(n O(n2 ) ) )。选择排序第一次遍历如下图。
#选择排序python实现
def selectSort(numlist): for j in range(len(numlist)): i = 1 maxindex = 0 while i < len(numlist)-j:#定位 if numlist[maxindex] < numlist[i]: maxindex = i i += 1 numlist[maxindex],numlist[len(numlist)-j-1] = numlist[len(numlist)-j-1],numlist[maxindex]#交换 return numlist
4. 插入排序
插入排序是按顺序依次遍历列表,但每遍历一个元素,便将该元素按顺序大小插入到以遍历元素构成的子列表中。该算法只需遍历一次列表,但是将当前元素插入到子列表中需要对子列表元素进行比较对比。
假设列表有 n n n个元素,那么最好情况下空间复杂度为 O ( n ) O(n) O(n),最坏情况复杂度为 O ( n O(n O(n2 ) ) ),平均复杂度为 O ( n O(n O(n2 ) ) )。算法完整流程如下图。
#插入排序python实现
def insertSort(numlist): for i in range(1,len(numlist)):#从第二个元素开始遍历 num = numlist[i] j = i while (j>0) & (numlist[j-1]>num):#依次往后移动较大元素 numlist[j] = numlist[j-1] j -= 1 numlist[j] = num return numlist
5. 希尔排序
希尔排序,又称缩小增量排序,是在插入排序的基础上进行改进的排序算法。与插入排序相比,希尔排序通过将列表切分为 k k k个子列表,并对 k k k个子列表进行插入排序操作,过程中需要不断调整子列表数量,最终达到排序效果。如何切分列表是希尔排序的关键,切分的大小会影响算法的复杂度;列表通常不是连续切分,而是根据增量(步长)选择元素构成子列表。
假设列表有 n n n个元素,那么最好情况下空间复杂度为 O ( n ) O(n) O(n),最坏情况复杂度为 O ( n O(n O(n2 ) ) ),平均复杂度为 O ( n O(n O(n3/2 ) ) )。算法完整流程如下图。
希尔排序python实现
def shellSort(numlist): steplength = len(numlist) // 2 while steplength >= 1: for step in range(steplength): for i in range(step,len(numlist),steplength):#切分值列表,并插入排序 num = numlist[i] j = i while (j>=steplength) & (numlist[j-steplength]>num): numlist[j] = numlist[j-steplength] j -= steplength numlist[j] = num steplength = steplength//2 return numlist
6. 归并排序
归并排序是一种递归算法,首先将 n n n个元素的序列拆分为 n / 2 n/2 n/2个子序列,然后对子序列执行归并排序(递归调用),最后将两个排序完成的子序列合并成最终序列。该递归算法的基本情况是:当列表为空或者列表只有一个元素时,认为列表是有序的。
假设列表有 n n n个元素,算法首先进行拆分,再进行归并,拆分次数为 l o g log log2 n n n,每一次拆分需处理的次数为 n n n,因此归并次数平均复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。算法完整流程如下图。
归并排序python实现
def mergeSort(numlist): print(numlist) if len(numlist)>1: mid = len(numlist) // 2 left = numlist[:mid] right = numlist[mid:] mergeSort(left)#递归 mergeSort(right)#递归 i,j,k = 0,0,0#初始化,i指向左子列表,j指向右子列表,k指向左右两个子列表归并的列表 while (i < len(left)) & (j < len(right)): if left[i]<right[j]:#比较并左右列表元素大小 numlist[k] = left[i] i += 1 else: numlist[k] = right[j] j += 1 k += 1 if i < len(left): numlist[k:len(left)+len(right)] = left[i:len(left)].copy() if j < len(right): numlist[k:len(left)+len(right)] = right[j:len(right)].copy() return numlist
7. 快速排序
快速排序也是一种递归算法,算法首先选出一个基准值,然后根据基准值将有序数列拆分成左子序列(小于基准值)和右子序列(大于基准值),然后分别对子序列执行快速排序(递归调用)。每执行一次算法,选定的基准值便会确定其位置。
基准值的选择会影响算法切分的次数,当选择的基准值都是序列的中间值,那么只需要切分 l o g n logn logn次,此时与归并算法切割和操作此时一样,算法复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),而极端情况下,每次基准值左/右子序列均为空,那么此时算法复杂度为 O ( n O(n O(n2 ) ) )。为了使切分更趋近前者,选择基准值可以采用三数取中法,即选择开头、中间和末尾元素的中间值。
快速排序python实现
def quickSort(numlist): return subquickSort(numlist, 0, len(numlist)-1) def subquickSort(numlist, first, last): if (len(numlist)>1) & (first<last): basevalue = numlist[first]#默认基准值为第一个元素 leftpointer = first+1 rightpointer = last while True: while (leftpointer <= rightpointer) and (numlist[leftpointer]<=basevalue):#左指针移动 leftpointer += 1 while (leftpointer <= rightpointer) and (numlist[rightpointer]>=basevalue):#右指针移动 rightpointer -= 1 if leftpointer > rightpointer:#判断子序列是否遍历 break else: numlist[leftpointer],numlist[rightpointer] = numlist[rightpointer],numlist[leftpointer]#交换 numlist[first],numlist[rightpointer] = numlist[rightpointer],numlist[first]#确定基准值位置 subquickSort(numlist, first, rightpointer-1)#递归调用 subquickSort(numlist, rightpointer+1, last) return numlist