最近在找工做面试的时候老是会被问到排序算法的种种,如今对排序算法进行一个系统的总结。也算是再复习一遍的,有好多本身也忘得差很少了。python
排序有内部排序和外部排序两大类。内部排序值得是全部的排序是在内存中进行;外部排序则是由于数据量太大,一次不能将所有数据放在内存中,在排序过程当中,须要访问外存。面试
关于时间复杂度和空间复杂度算法
因为时间关系,我没有本身画,如下的表格是在别处转的,详见经常使用排序算法总结shell
1.插入排序--直接插入排序(Straight Insertion Sort)数组
基本思想:数据结构
将一个记录插入到已经有序的表中,从而获得一个新的,记录数+1的有序表。即:先将序列的第一个记录当作是一个有序的子序列,而后从第二个记录逐个进行插入,直至整个序列有序为止。app
若是遇到元素相等时,那么将插入的元素插入到与其相等的元素以后。相等元素的先后顺序并无改变,因此插入排序是稳定的。less
算法的实现:ide
def insertion_sort(arr): arrlen = len(arr) for i in range(1, arrlen): insert(arr, i) def insert(arr, i): tmp = arr[i] j = i # 查找第i的元素应该的位置, 而且 # 顺便把比它大的元素日后挪 # 实际上是用了心思在里面的 while j > 0 and tmp < arr[j - 1]: arr[j] = arr[j - 1] j -= 1 arr[j] = tmp a = [65,6,3,5,54,65] insertion_sort(a) print a
2.希尔排序学习
基本思想:
希尔排序又叫作缩小增量排序。先将整个序列分割成若干个子序列,并对每一个子序列进行直接插入排序,再对所有序列进行直接插入排序。
算法实现:
def shell_sort(seq): incr = len(seq)/2 while(incr>=1): for i in range(incr,len(seq)): tmp=seq[i] pos=i; for j in range(i-incr,-1,-incr): if seq[j]>tmp: seq[j+incr]=seq[j] pos=j seq[pos]=tmp incr = incr/2 return seq if __name__ == '__main__': A = [10, -3, 5, 7, 1, 3, 7] print 'Before sort:',A shell_sort(A) print 'After sort:',A
3.选择排序--简单选择排序
基本思想:
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;而后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
算法实现:
def selectionSort(seq): length=len(seq) for i in range(length): mini=min(seq[i:]) if seq[i]>mini: j=seq.index(mini,i) seq[i],seq[j]=seq[j],seq[i] if __name__=='__main__': seq=[3,4,5,9,3,1,5,7,90,-2,] selectionSort(seq) print(seq)
效率:
最坏状况下,即待排序记录初始状态是按第一条记录最大,以后的记录从小到大顺序排列,则须要移动记录的次数最多为3(n-1)。简单选择排序过程当中须要进行的比较次数与初始状态下待排序的记录序列的排列状况无关。当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共须要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操做的时间复杂度为O(n^2),进行移动操做的时间复杂度为O(n)。(百度百科)
简单选择排序是不稳定排序。
简单选择排序的改进——二元选择排序
简单选择排序,每趟循环只能肯定一个元素排序后的定位。咱们能够考虑改进为每趟循环肯定两个元素(当前趟最大和最小记录)的位置,从而减小排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环便可。
4.选择排序--堆排序
基本思想:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。能够利用数组的特色快速定位指定索引的元素。堆分为大根堆和小根堆,是彻底二叉树。大根堆的要求是每一个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,须要使用的就是大根堆,由于根据大根堆的要求可知,最大的值必定在堆顶。
堆排序的过程相对来讲比较复杂,现只给出算法实现,详细过程请参照另一篇博文:堆排序学习笔记
算法实现:
def fixDown(a,k,n): #自顶向下堆化,从k开始堆化 N=n-1 while 2*k<=N: j=2*k if j<N and a[j]<a[j+1]: #选出左右孩子节点中更大的那个 j+=1 if a[k]<a[j]: a[k],a[j]=a[j],a[k] k=j else: break def heapSort(l): n=len(l)-1 for i in range(n//2,0,-1): fixDown(l,i,len(l)) while n>1: l[1],l[n]=l[n],l[1] fixDown(l,1,n) n-=1 return l[1:] l=[-1,26,5,77,1,61,11,59,15,48,19] #第一个元素不用,占位 res=heapSort(l) print(res)
5.交换排序--冒泡排序
基本思想:
在要排序的一组数中,对当前还未排好序的范围内的所有数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的数往上冒。即:每当相邻的数比较后,发现排序不符合要求时,将他们互换。
示例:
#!/usr/bin/env python #coding:utf-8 def bubbleSort(seq): length=len(seq) for i in range(length): for j in range(length-1,i,-1): if seq[j-1]>seq[j]: seq[j-1],seq[j]=seq[j],seq[j-1] if __name__=='__main__': seq=[2,9,7,7,4,3,2,-4,54,-7,0] bubbleSort(seq) print(seq)
冒泡排序算法的改进(两种):
设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。因为pos位置以后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置便可。
传统冒泡排序中每一趟排序操做只能找到一个最大值或最小值,咱们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次能够获得两个最终值(最大者和最小者) , 从而使排序趟数几乎减小了一半。
基本思想:
1)选择一个基准元素,一般选择第一个元素或者最后一个元素,
2)经过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另外一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)而后分别对这两部分记录用一样的方法继续进行排序,直到整个序列有序。
示例:
python代码实现:
#!/usr/bin/env python #coding:utf-8 def qsort(seq): if seq==[]: return [] else: pivot=seq[0] lesser=qsort([x for x in seq[1:] if x<pivot]) greater=qsort([x for x in seq[1:] if x>=pivot]) return lesser+[pivot]+greater if __name__=='__main__': seq=[5,6,78,9,0,-1,2,3,-65,12] print(qsort(seq))
基本思想:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每一个子序列是有序的。而后再把有序子序列合并为总体有序序列。
def mergesort(seq): if len(seq)<=1: return seq mid=int(len(seq)/2) left=mergesort(seq[:mid]) right=mergesort(seq[mid:]) return merge(left,right) def merge(left,right): result=[] i,j=0,0 while i<len(left) and j<len(right): if left[i]<=right[j]: result.append(left[i]) i+=1 else: result.append(right[j]) j+=1 result+=left[i:] result+=right[j:] return result if __name__=='__main__': seq=[4,5,7,9,7,5,1,0,7,-2,3,-99,6] print(mergesort(seq))
效率:
时间复杂度无论在什么状况下都为n(nlogn)