排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:
关于稳定性
名词解释
1、冒泡排序
冒泡排序(BubbleSort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
作为最简单的排序算法之一,冒泡排序给我的感觉就像Abandon在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。
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]returnarr2、选择排序
defselectionSort(arr):foriinrange(len(arr)-1):#记录最小数的索引minIndex=iforjinrange(i+1,len(arr)):ifarr[j] 插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。 definsertionSort(arr):foriinrange(len(arr)):preIndex=i-1current=arr[i]whilepreIndex>=0andarr[preIndex]>current:arr[preIndex+1]=arr[preIndex]preIndex-=1arr[preIndex+1]=currentreturnarr4、希尔排序 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。 defshellSort(arr):importmathgap=1while(gap 归并排序(Mergesort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(DivideandConquer)的一个非常典型的应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法: 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=[]whileleftandright:ifleft[0]<=right[0]:result.append(left.pop(0));else:result.append(right.pop(0));whileleft:result.append(left.pop(0));whileright:result.append(right.pop(0));returnresult6、快速排序 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个项目要Ο(nlogn)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(nlogn)算法更快,因为它的内部循环(innerloop)可以在大部分的架构上很有效率地被实现出来。 快速排序使用分治法(Divideandconquer)策略来把一个串行(list)分为两个子串行(sub-lists)。 快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。 ①从数列中挑出一个元素,称为“基准”(pivot); ②重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; ③递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 defquickSort(arr,left=None,right=None):left=0ifnotisinstance(left,(int,float))elseleftright=len(arr)-1ifnotisinstance(right,(int,float))elserightifleft 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法: defbuildMaxHeap(arr):importmathforiinrange(math.floor(len(arr)/2),-1,-1):heapify(arr,i)defheapify(arr,i):left=2*i+1right=2*i+2largest=iifleft defcountingSort(arr,maxValue):bucketLen=maxValue+1bucket=[0]*bucketLensortedIndex=0arrLen=len(arr)foriinrange(arrLen):ifnotbucket[arr[i]]:bucket[arr[i]]=0bucket[arr[i]]+=1forjinrange(bucketLen):whilebucket[j]>0:arr[sortedIndex]=jsortedIndex+=1bucket[j]-=1returnarr9、桶排序 桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点: 同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。 当输入的数据可以均匀的分配到每一个桶中。 当输入的数据被分配到了同一个桶中。 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()#回填,这里桶内部排序直接调用了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、基数排序 基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。 基数排序有两种方法: 这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异: