本篇博客中的有序都是指的升序,降序情况稍微改改就出来了。

排序算法 时间复杂度(最好) 时间复杂度(最差) 时间复杂度(平均) 空间复杂度 稳定性
插入排序 O(n)O(n)O(n) O(n2)O(n^2)O(n2) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 稳定
冒泡排序 O(n2)O(n^2)O(n2) / 及时终止O(n)O(n)O(n) O(n2)O(n^2)O(n2) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 稳定
选择排序 O(n2)O(n^2)O(n2) / 及时终止O(n)O(n)O(n) O(n2)O(n^2)O(n2) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 不稳定
快速排序 O(nlog⁡n)O(n\log{n})O(nlogn) O(n2)O(n^2)O(n2) O(nlog⁡n)O(n\log{n})O(nlogn) O(log⁡n)O(\log{n})O(logn) 不稳定
谢尔排序 O(nlog⁡n)O(n\log{n})O(nlogn) O(n2)O(n^2)O(n2) O(nlog⁡n)−O(n2)O(n\log{n})-O(n^2)O(nlogn)−O(n2) O(1)O(1)O(1) 不稳定

目录

  • 1. 插入排序
  • 2. 冒泡排序
  • 3. 选择排序
  • 4. 快速排序
  • 5. 谢尔排序
  • 参考资料

1. 插入排序

从左往右依次建立有序数组,a[0:1]就一个元素,就是一个有序数组,考虑a[0:i]已经排好序是一个有序数组,下一步把a[i]插入这个有序数组得到a[0:i+1]的有序数组,这种插入是从最右边开始,连续与各个元素比较,把比a[i]大的元素右移一个位置,直到不满足条件即视为找到插入位置,再进行下一个a[i+1]的插入,直至a[n-1]。

def insertionSort(a):n = len(a)for i in range(1, n):# 把a[i]插入a[0:i]t = a[i]j = i-1while (j >= 0 and a[j] > t):# 比较i之前的元素a[j]a[j+1] = a[j]j -= 1a[j+1] = treturn a

性能分析

  • 最好情况:(数组升序排列时)比较次数为n−1n-1n−1,所以时间复杂度为O(n)O(n)O(n)

  • 最坏情况:(数组降序排列时)比较次数为1+2+⋯+n−1=n(n−1)/21+2+\cdots+n-1=n(n-1)/21+2+⋯+n−1=n(n−1)/2,所以时间复杂度为O(n2)O(n^2)O(n2)

  • 平均情况:a[i]可能插入的位置索引有0,1,…,i0,1,\dots,i0,1,…,i,且位于每个位置的概率相等,均为1/i1/ i1/i,若a[i]插入iii,则比较1次,插入i−1i-1i−1,则比较2次,……插入000,则比较i−1i-1i−1次,所以平均a[i]的平均比较次数为1i[1+2+⋯+(i−1)]=i−12{1\over i}[1+2+\cdots+(i-1)]={i-1\over 2}i1​[1+2+⋯+(i−1)]=2i−1​。所以n个元素一共比较了∑i=1ni−12=n2−n4\sum_{i=1}^n{i-1\over 2}={n^2-n\over 4}∑i=1n​2i−1​=4n2−n​次,所以平均时间复杂度为O(n2)O(n^2)O(n2)

  • 稳定性:由于插入排序只是将比较大的元素顺序移动,并不会改变原本大小相等的元素的相对位置,所以是稳定的。

  • 空间复杂度:只需一个额外空间给t存放当前需要插入的元素,因此空间复杂度为O(1)O(1)O(1)

2. 冒泡排序

通过一次或者多次冒泡过程依次将左边未排序的子数组中最大值通过冒泡操作放到该子数组最右边,即比较相邻两个元素,若左边较大则互换位置,反之不变,最终得到一个有序数组。

def bubbleSort(a):n = len(a)for i in range(n-1):# 下面遍历未排序的子数组for j in range(n-1-i):if a[j] > a[j+1]:a[j], a[j+1] = a[j+1], a[j]return a

若未排序子数组已经有序,那就不需要再进行之后的遍历,可以直接输出有序数组,这样可以及时终止程序,下面是及时终止的冒泡排序:

def bubbleSort(a):n = len(a)for i in range(n-1):swapped = False# 未排序的子数组for j in range(n-1-i):if a[j] > a[j+1]:a[j], a[j+1] = a[j+1], a[j]swapped = True# 如果本次遍历子数组都没有发生交换,说明该子数字已经有序,不用再继续if swapped==False:breakreturn a

性能分析

  • 最好情况:(数组升序排列时)对于及时终止的冒泡排序只用比较n−1n-1n−1次,没有发生交换所以直接退出循环,所以时间复杂度为O(n)O(n)O(n);而不是及时终止的始终会执行外层循环,因此这时时间复杂度和最坏情况一样都是O(n2)O(n^2)O(n2)

  • 最坏情况:(数组降序排列时)比较次数为(n−1)+(n−2)+⋯+1=n(n−1)/2(n-1)+(n-2)+\cdots+1=n(n-1)/2(n−1)+(n−2)+⋯+1=n(n−1)/2,所以时间复杂度为O(n2)O(n^2)O(n2)

  • 平均情况:对于不及时终止的最好最坏情况都一样,所以平均来说也是一样的O(n2)O(n^2)O(n2);及时终止的分析比较麻烦,但平均时间复杂度也是O(n2)O(n^2)O(n2)

  • 稳定性:只比较相邻元素,前一个比后一个大才会交换,因此若原本相等的元素并不会改变他们的相对顺序,所以是稳定的。

  • 空间复杂度:其实是需要一个临时变量来存放交换时的元素,会占一个内存空间,因此空间复杂度为O(1)O(1)O(1)

3. 选择排序

在未排序的数组中找到最大元素然后与该数组的最末位交换,然后在余下的未排序数组中又找最大的元素再与该数组的最末位交换,重复操作,直到数组有序。

def selectionSort(a):n = len(a)for i in range(n-1):# 记录最大元素位置t = 0for j in range(1, n-i):# 遍历未排序数组if a[j] >= a[t]:t = j# 与未排序数组的最末位交换a[t], a[n-i-1] = a[n-i-1], a[t]return a

假如数组在第二次排序之后就已经是有序数组,但是上面这个程序依旧会把外层n−1n-1n−1次循环都执行完。为了在数组有序时及时终止程序,可以进行下面的优化:

def selectionSort(a):n = len(a)for i in range(n-1):# 默认数组有序flag = Truet = 0for j in range(1, n-i):if a[j] >= a[t]:t = jelse:# 如果没有赋值,说明左大右小,数组无序flag = Falseif flag==True:breaka[t], a[n-i-1] = a[n-i-1], a[t]return a

性能分析

  • 最好情况:(数组升序排列时)对于及时终止的选择排序只用比较n−1n-1n−1次,外层循环只执行一次就直接退出循环,所以时间复杂度为O(n)O(n)O(n);而不是及时终止的始终会执行外层循环,因此这时时间复杂度和最坏情况一样都是O(n2)O(n^2)O(n2)

  • 最坏情况:(数组降序排列时)比较次数为(n−1)+(n−2)+⋯+1=n(n−1)/2(n-1)+(n-2)+\cdots+1=n(n-1)/2(n−1)+(n−2)+⋯+1=n(n−1)/2,所以时间复杂度为O(n2)O(n^2)O(n2)

  • 平均情况:对于不及时终止的最好最坏情况都一样,所以平均来说也是一样的O(n2)O(n^2)O(n2);及时终止的分析比较麻烦,但平均时间复杂度也是O(n2)O(n^2)O(n2)

  • 稳定性:不稳定,在选择出当前最大的进行交换位置后可能会改变原本相同的元素的相对位置。例如数组[2 7 3 5 5],第一次遍历会选择最大值7与最后的5交换,那这样原本在后面的5就跑到前面去了,所以原本相等的元素相对位置变化了,因此选择排序是不稳定的。

  • 空间复杂度:需要一个临时变量来存放每次最大元素的位置,因此空间复杂度为O(1)O(1)O(1)

4. 快速排序

快速排序就是选择一个基准,逐个元素与这个基准元素进行比较,小于基准的放在左边,大于基准的放在右边,由此找到基准的位置,在对两部分递归进行这样的排序,直到最后只剩下一个元素即为有序。

按照这个思路算法实现如下:

def quickSort(a):# 递归结束条件if len(a) <= 1:return a# 选取基准,并分别储存小于基准和大于基准的元素base = a.pop()left = []right = []for i in a:if i < base:left.append(i)else:right.append(i)# 递归调用return quickSort(left) + [base] + quickSort(right)

上述代码需要额外的left和right空间来储存,而实际上只需要一个额外的存储空间即可完成排序(除开递归所需要的栈空间外)。

实现的思路是利用双指针i、j,排序开始的时候:i=0,j=n-1。把数组的第一个元素当做基准(也可以随机取一个)并储存起来,这时a[i]相当于是一个空位;从j开始从右往左搜索(j–),找到第一个小于基准的值a[j],将a[j]放到刚刚的空位a[i]去,这时a[j]相当于是一个空位;然后i开始从左往右搜索(i++),找到第一个大于基准的值a[i],将a[i]放到刚刚的空位a[j]去,a[i]就有空出来了;重复这种移动和交换过程直到i=j,然后把基准值填入a[i],这样就完成了一趟快排。接下来对基准两边的分别进行快排即可。

def quickSort(a):return quickSortHelper(a, 0, len(a)-1)def quickSortHelper(a, left, right):# 递归结束条件,只有一个元素或者数组为空时if left >= right:return aelse:i = leftj = rightbase = a[i]# 一趟排序while i < j:# 从右往左找第一个小于基准的值while a[j] >= base and i < j:j -= 1# 找到之后放空位去if i < j:a[i] = a[j]# 从左往右找第一个大于基准的值while a[i] <= base and i < j:i += 1# 找到之后放空位去if i < j:a[j] = a[i]# 填入基准值a[i] = base# 递归quickSortHelper(a, left, i-1)quickSortHelper(a, i+1, right)return a

性能分析

  • 最好情况:每次选择的基准恰好可以均匀二分数组,这样总共需要O(log⁡n)O(\log{n})O(logn)次划分,而每次放空位移动都需要与基准进行比较,复杂度为O(n)O(n)O(n),因此这种情况下时间复杂度为O(nlog⁡n)O(n\log{n})O(nlogn)

  • 最坏情况:每次选择的基准都是最大或者最小的情况,那划分就没有意义,这时就类似选择排序,时间复杂度为O(n2)O(n^2)O(n2)

  • 平均情况:评价情况下时间复杂度为O(nlog⁡n)O(n\log{n})O(nlogn)

  • 稳定性:不稳定

  • 空间复杂度:只需要一个额外空间来储存基准,所以主要空间还是递归造成的栈空间,最好情况即每次都能均匀二分,栈的最大深度为log⁡(n+1)\log{(n+1)}log(n+1),这样空间复杂度为O(log⁡n)O(\log{n})O(logn),最差情况栈的最大深度为nnn,平均情况下空间复杂度为O(log⁡n)O(\log{n})O(logn)。

5. 谢尔排序

谢尔排序(希尔排序)就是对无序序列等间隔的划分为好几个子序列,对子序列分别进行插入排序。例如序列[4,1,5,8,2,3,7,9][4,1,5,8,2,3,7,9][4,1,5,8,2,3,7,9]先划分为间隔为8//2=48//2=48//2=4的子序列[4,2][4,2][4,2]、[1,3][1,3][1,3]、[5,7][5,7][5,7]和[8,9][8,9][8,9]分别进行插入排序,第一次排序后序列就变成[2,1,5,8,4,3,7,9][2,1,5,8,4,3,7,9][2,1,5,8,4,3,7,9];然后间隔递减为4//2=24//2=24//2=2,再分别对子序列[2,5,4,7][2,5,4,7][2,5,4,7]和[1,8,3,9][1,8,3,9][1,8,3,9]分别进行插入排序,第二次排序后序列变成[2,1,4,3,5,8,7,9][2,1,4,3,5,8,7,9][2,1,4,3,5,8,7,9];最后间隔递减为2//2=12//2=12//2=1,即随着间隔的缩小,子序列越来越长,最后直到间隔为1时子序列就是原序列,再进行一次插入排序即可获得有序序列。

因为列表越接近有序,插入排序的比对次数就越少,在最后一次的插入排序前先尽量把序列弄得有序些,减少了很多无效的比较。

def shellSort(a):# 设定间隔gap = len(a) // 2while gap > 0:# 对间隔子序列做插入排序for start in range(gap):# 带间隔的插入排序(当最后gap=1时子序列就是原序列,即正常的插入排序)for i in range(start+gap, len(a), gap):t = a[i]j = i - gapwhile j >= 0 and a[j] > t:a[j+gap] = a[j]j -= gapa[j+gap] = t# 缩小间隔,使得子序列越来越长gap = gap // 2return a

性能分析

  • 时间复杂度:谢尔排序的详尽分析比较复杂,还未找到比较官方的材料和证明,百度百科上说下界是O(nlog⁡n)O(n\log{n})O(nlogn),总之大致是介于O(nlog⁡n)−O(n2)O(n\log{n})-O(n^2)O(nlogn)−O(n2)

  • 稳定性:间隔的插入排序是跳跃式的,因此不稳定

  • 空间复杂度:空间复杂度为O(1)O(1)O(1)。

参考资料

1、数据结构、算法与应用 C++语言描述 原书第2版
2、MOOC:数据结构与算法Python版(北大 陈斌老师)
3、参考博客1:十大经典排序算法(动图演示)
4、参考博客2:排序算法总结(Python版)

经典排序算法总结与Python实现(上)相关推荐

  1. 经典排序算法总结与Python实现(下)

    在之前的博客经典排序算法总结与Python实现(上)中已经讨论过插入.冒泡.选择.快排.谢尔排序.这篇博客主要完成剩下的几个排序算法. 排序算法 时间复杂度(最好) 时间复杂度(最差) 时间复杂度(平 ...

  2. 十大经典排序算法6(Python版本)

    文章目录 九.桶排序 十.基数排序 九.桶排序 1.桶排序介绍 桶排序是计数排序的升级版.它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定.为了使桶排序更加高效,我们需要做到这两点: 在 ...

  3. 十大经典排序算法4(Python版本)

    文章目录 六.快速排序 六.快速排序 1.快速介绍 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较.在最坏状况下则需要 Ο(n2) 次比较,但这 ...

  4. 十大经典排序算法3(Python版本)

    文章目录 四.希尔排序 五.归并排序 四.希尔排序 1.希尔介绍 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本.但希尔排序是非稳定排序算法. 希尔排序是基于插入排序的以下两点性质而 ...

  5. 十大经典排序算法1(Python版本)

    文章目录 一.排序算法与时间复杂度 二.冒泡排序 一.排序算法与时间复杂度 1.十大排序算法 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次 ...

  6. 十大经典排序算法5(Python版本)

    文章目录 七.堆排序 八.计数排序 七.堆排序 1.堆排序介绍 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法.堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键 ...

  7. 十大经典排序算法2(Python版本)

    文章目录 二.选择排序 三.插入排序 二.选择排序 1.选择介绍 选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度.所以用到它的时候,数据规模越小越好.唯一的好处可能就 ...

  8. 3min利用Python实现9种经典排序算法可视化!(附源代码)

    来源:恋习Python 本文附视频,建议收藏. 本文为你分享实现9种经典排序算法可视化的方法,3分钟即可实现. [导 读]近在某网站上看到一个视频,是关于排序算法的可视化的,看着挺有意思的,也特别喜感 ...

  9. python遍历数组冒泡排序_经典排序算法(冒泡排序,选择排序,插入排序,快速排序,堆排序)python实现...

    最近在复习经典排序算法,自己用python也实现了一下,这里不会涉及到原理(因为网上方法已经很详细啦),就把函数贴上来,可以让大家自己试着运行下,再结合别处的原理也可以更好地理解它们的实现. 如果有错 ...

最新文章

  1. vue创建二:引入本地图片
  2. XCode修改工程名注意
  3. java 混合排序_字母、数字混合方式,按照自然语言排序,java版
  4. 在Sqlserver下巧用行列转换日期的数据统计
  5. SAP RFC-RFC概述
  6. 【3分钟掌握】什么是DNS解析
  7. Android 解决mac无法识别手机设备
  8. java版本lstm_LSTM java 实现
  9. linux中service的问题
  10. Scala模式匹配和类型系统
  11. 车联网发展对汽车经销商的影响
  12. 编写python程序、输出*图形_Python用程序输出字母“C”的图案
  13. 地铁系统_北斗授时助力北京地铁地下定位系统
  14. python基础读后感_Python基础教程【读书笔记】 - 2016/7/4
  15. mysql rank_MySQL实现rank排名(一)自定义变量@rank
  16. MPI和OpenMP混合编程计算pi π值
  17. 蔡英珠:向前一步,跨越ICT“戈壁”
  18. 【天华学术】古代文学论文:酒文化传播中唐代文学的作用分析(节选)
  19. C++ 超详细8Bit图像直方图统计并绘制显示,不使用calcHist()函数,自己手写统计
  20. iOS 后台运行 Background Task与Background Mode

热门文章

  1. 二、python_base
  2. zookeeper配置集群
  3. 关于HTML Button点击自动刷新页面的问题解决
  4. 验证控件jQuery Validation Engine调用外部函数验证
  5. Java Web ServletResponse
  6. 汇编:汇编语言实现冒泡排序(loop指令实现)
  7. 为了搞懂什么是区块链,我都快抑郁了(转)
  8. pwershell custom objects
  9. mac find桌面显示desktop问题
  10. 关注健康,从现在开始(视力篇)