作者|Hustcc排序算法是《数据结构与算法》中最基础的算法之一。排序算法可分为内部排序和外部排序。内排序是指数据记录在内存中排序,而外排序是指排序后的数据太大,一次无法容纳所有的排序记录,排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图总结一下:关于时间复杂度:方阶(O(n2))排序各种类型的简单排序:直接插入、直接选择和冒泡排序。线性对数顺序(O(nlog2n))对Quicksort、heapsort和mergesort进行排序;O(n1+§))排序,其中§是0到1之间的常数。希尔排序线性顺序(O(n))排序基数排序,此外还有桶、盒排序。关于稳定性:2个相等键值在排序后的顺序和它们在排序前的顺序是一样的稳定的排序算法:冒泡排序、插入排序、归并排序、基数排序。不稳定排序算法:选择排序、快速排序、希尔排序、堆排序。名词解释:n:数据大小k:“桶”个数In-place:占用常量内存,不占用额外内存Out-place:占用额外内存直观的排序算法。它迭代遍历要排序的数组,一次比较两个元素,如果顺序错误则交换它们。重复访问序列的工作,直到不需要交换,即序列已经排序。这个算法的名字来源于这样一个事实,即较小的元素会通过交换慢慢“浮”到序列的顶部。冒泡排序作为最简单的排序算法之一,给我的感觉就像单词书上出现了Abandon。每次总是排在第一页的第一位,所以是最熟悉的。冒泡排序还有一个优化算法,就是设置一个flag。当序列遍历过程中没有交换元素时,证明序列已经有序。但是这种改进对提高性能没有多大作用。(1)算法步骤比较相邻元素。如果第一个大于第二个,则交换它们。对每对相邻元素执行相同的操作,从开头的第一对到结尾的最后一对。完成这一步后,最后一个元素就是最大的数。对除最后一个元素之外的所有元素重复上述步骤。每次对越来越少的元素继续重复上述步骤,直到没有要比较的数字对。(2)动画演示(3)Python代码defbubbleSort(arr):foriinrange(1,len(arr)):forjinrange(0,len(arr)-i):ifarr[j]>arr[j+1]:arr[j],arr[j+1]=arr[j+1],arr[j]返回arr2,选择排序选择排序是一种简单直观的排序算法,不管什么数据去in是O(n2)时间复杂度。所以在使用的时候,数据量越小越好。唯一的好处可能就是不占用额外的内存空间。(1)算法步骤首先在未排序的序列中找到最小(最大)的元素,将其存放在已排序序列的开头,然后继续从剩余的未排序的元素中寻找最小(最大)的元素,然后放在排序序列的末尾。重复第二步,直到所有元素都排序完毕。(2)动画演示(3)Python代码defselectionSort(arr):foriinrange(len(arr)-1):#记录最小数的索引minIndex=iforjinrange(i+1,len(arr)):ifarr[j]=0andarr[preIndex]>current:arr[preIndex+1]=arr[preIndex]Inde1predearrx[+1]=currentreturnarr4、Hillsorting希尔排序又称递减增量排序算法,是一种效率更高的插入排序改进版。但是希尔排序是一种不稳定的排序算法。希尔排序基于插入排序的以下两个性质提出了一种改进方法:插入排序在对几乎已排序的数据进行操作时效率很高,即可以达到线性排序的效率;但是插入排序通常是低效的,因为插入排序一次只能移动数据一位;希尔排序的基本思想是:首先将整个待排序记录序列分成若干个子序列进行直接插入排序,等待整个序列中的记录“基本有序”时,再对其进行直接插入排序所有记录顺序。(1)算法步骤选择一个增量序列t1,t2,...,tk,其中ti>tj,tk=1;根据递增序列的个数k对序列排序k次;每次排序根据对应的增量ti,将待排序的列分成若干个长度为m的子序列,对每个子列表直接插入排序。只有当增量因子为1时,整个序列才被当作一个表,表的长度就是整个序列的长度。(2)Python代码defshellSort(arr):importmathgap=1while(gap0:foriinrange(gap,len(arr):TEMP=ARR[i]j=I-GAP而J>=0且ARR[J]>Temp:Arr[J+Gap]=ARR[J]J-=GapARR[J+Gap]=TEMPgap=math.floor(gap/3)returnarr}5.归并排序归并排序是一种基于归并操作的有效排序算法,该算法是分治法的一个非常典型的应用,作为分治法的典型算法应用思考,归并排序的实现包括两种方法:自上而下的递归(所有递归的方法都可以用迭代重写,所以有第二种方法);和上面的迭代;和选择排序一样,归并排序的性能不受输入数据的影响,但是性能比选择排序要好很多,因为它总是O(nlogn)的时间复杂度。代价是需要额外的内存空间。(1)算法步骤申请空间使得它的大小是两个排序后的序列之和,这个空间用来存放合并后的序列;设置两个指针,初始位置分别为两个排序序列的起始位置;比较两个指针指向的元素,选择比较小的元素放入合并空间,将指针移动到下一个位置;重复步骤3,直到指针到达序列的末尾;直接将其他序列的所有剩余元素复制到合并序列的末尾。(2)动画演示(3)Python代码defmergeSort(arr):importmathif(len(arr)<2):returnarrmiddle=math.floor(len(arr)/2)left,right=arr[0:middle],arr[middle:]returnmerge(mergeSort(left),mergeSort(right))defmerge(left,right):result=[]而leftandright:ifleft[0]<=right[0]:result.append(left.pop(0));否则:result.append(right.pop(0));离开时:result.append(left.pop(0));whileright:end(result.appright.pop(0));返回结果6.快速排序快速排序是由托尼·霍尔开发的一种排序算法。平均而言,对n项进行排序需要Ω(nlogn)次比较。在最坏的情况下,需要O(n2)次比较,但这并不常见。事实上,快速排序通常比其他OO(nlogn)算法快得多,因为它的内部循环可以在大多数体系结构上有效地实现。快速排序使用分而治之的策略将一个列表分成两个子列表。快速排序是分治思想在排序算法中的另一种典型应用。从本质上讲,快速排序应该看作是一种基于冒泡排序的递归分治法。QuickSort的名字简单粗暴,因为一听名字就知道它存在的意义,快速高效!它是大数据最快的排序算法之一。WorstCase的时间复杂度虽然达到了O(n2),但是已经很优秀了。在大多数情况下,它们的性能优于平均时间复杂度为O(nlogn)的排序算法,但为什么呢?我也不知道。幸运的是,我又患上了强迫症。查了N多资料,终于在《算法艺术与信息学竞赛》上找到了一个满意的答案:快速排序的最坏情况是O(n2),比如对序号进行快速排序。但是它的摊销期望时间是O(nlogn),O(nlogn)表示法中隐含的常数因子很小,远小于复杂度稳定在O(nlogn)的归并排序。因此,对于绝大多数顺序较弱的随机数序列,快速排序总是优于归并排序。(1)算法步骤从序列中挑选一个元素,称为“枢轴”(pivot);重新排序序列,所有小于pivot值的元素都放在pivot前面,所有大于pivot值的元素放在pivot后面(相同的数可以去两边)。此分区退出后,基准测试位于序列的中间。这称为分区操作;对小于参考值的元素子数组和大于参考值的元素子数组进行递归(recursively)排序;递归的底例是数组的大小为零或一,即永远都已经排序。虽然一直在递归进行,但是这个算法总会退出,因为在每次迭代(iteration)中,它都会将至少一个元素放到它最后的位置。(2)动画演示(3)Python代码defquickSort(arr,left=None,right=None):left=0ifnotisinstance(left,(int,float))elseleftright=len(arr)-1ifnotisinstance(right,(int,float))elserightifleftarr[largest]:largest=leftifrightarr[largest]:largest=rightiflargest!=i:swap(arr,i,largest)heapify(arr,largest)defswap(arr,i,j)最大=左边:arr[i],arr[j]=arr[j],arr[i]defheapSort(arr):globalarrLenarrLen=len(arr)buildMaxHeap(arr)foriinrange(len(arr)-1,0,-1):swap(arr,0,i)arrLen-=1heapify(arr,0)returnarr8,计数排序计数排序的核心是将输入的数据值转换成key存入额外的数组空间。作为一种线性时间复杂度的排序,计数排序要求输入数据必须是一定范围内的整数。(1)动画演示(2)Python代码defcountingSort(arr,maxValue):bucketLen=maxValue+1bucket=[0]*bucketLensortedIndex=0arrLen=len(arr)foriinrange(arrLen):notifbucket[arr[i]]:bucket[arr[i]]=0bucket[arr[i]]+=1forjinrange(bucketLen):whilebucket[j]>0:Insortarr[sortedIndex]de+jeds=1bucket[j]-=1返回arr9。桶排序桶排序是计数排序的升级版。它利用了函数的映射关系,而高效的关键就在于这个映射函数的确定。为了让桶排序更高效,我们需要做两件事:在额外空间足够的情况下,用于最大化桶数的映射函数可以将输入的N个数据均匀分布到K个桶中。同时,对于桶中元素的排序,比较排序算法的选择对性能的影响非常重要。什么时候输入数据可以均匀分布到每个桶中是最快的。当输入数据分配在同一个桶中时,什么时候最慢。Python代码defbucket_sort(s):"""桶排序"""min_num=min(s)max_num=max(s)#桶大小bucket_range=(max_num-min_num)/len(s)#桶数组count_list=[[]foriinrange(len(s)+1)]#用数字填充桶数组foriins:count_list[int((i-min_num)//bucket_range)].append(i)s.clear()#回填,这里bucket内部排序直接调用sortedforiincount_list:forjinsorted(i):s.append(j)if__name__==__main__:a=[3.2,6,8,4,2,6,7,3]bucket_sort(a)print(a)#[2,3,3.2,4,6,6,7,8]10.基数排序基数排序是一种非比较整数排序算法,其原理is将整数个数字切割成不同的数字,并单独比较每个数字。由于整数也可以表示某些格式的字符串(例如名称或日期)和浮点数,因此基数排序不限于整数。基数排序vs计数排序vs桶排序基数排序有两种方式:三种排序算法都使用了桶的概念,但是在桶的使用上有明显区别:基数排序:根据key值的每一位来分配桶;计数排序:每个桶只存储单个键值;桶排序:每个桶存储一定范围内的值;动画展示Python代码defRadixSort(list):i=0最小位数设置为1(包括0)max_num=max(list)#获取排序数组中的最大数whilemax_num>10**n:#获取最大位数n+=1whilei