常见排序算法总结:插入排序,希尔排序,冒泡排序,快速排序,简单选择排序以及java实现
今天来总结一下常用的内部排序算法。内部排序算法们需要掌握的知识点大概有:算法的原理,算法的编码实现,算法的时空复杂度的计算和记忆,何时出现最差时间复杂度,以及是否稳定,何时不稳定。
首先来总结下常用内部排序算法:
| 类别 | 名称 | 时间复杂度(默认最坏情况) | 空间复杂度 | 稳定性 | 备注 |
| 插入排序 | 直接插入排序 | O(n^2) | O(1) | 稳定 | |
| 插入排序 | 希尔排序 | 最坏O(n^2),平均O(n^1.3) | O(1) | 不稳定 | |
| 交换排序 | 冒泡排序 | O(n^2) | O(1) | 稳定 | |
| 交换排序 | 快速排序 | 最坏O(n^2),平均O(nlogn) | 最坏O(n),平均O(logn) | 不稳定 | |
| 选择排序 | 简单选择排序 | O(n^2) | O(1) | 不稳定 | 简单方法中唯一一个不稳定 |
一、基础知识温习:
在进行具体算法的总结前,我们先来温习一下一些基本的概念和方法:
算法的稳定性:如果排序后,两个拥有相等关键字的元素a和b的相对位置没有发生变换,则稳定,否则不稳定。
内部排序是指在排序期间元素全部存放在内存中的排序;外部排序是指在排序期间元素无法全部同时存放在内存中,必须在排序过程中根据要求不断地在内、外存之间移动的操作。
然后再来温习一下时间复杂度的计算:
时间复杂度的计算其实就是计算出算法某条执行语句的具体执行频度,之后取频度的数量级即可。下面用例子温习:
例1:
void fun(int n){
;
;
}
设语句 i*=2运行t次,则,$t=max_t t, s.t. 2^t<n$,即t是使2^t<n成立最大的t。故$t=log_2 n(向下取整)$。故时间复杂度O(log2 n)。
例2:
count=; ;k<=n;k*=) ;j<=n;j++) count++;
外循环t次,2^t=n,t=log2 n。内循环n次。故O(nlog2n)。
例3:
y=; )(y+)<=n) y+=;
$t=max_t t , s.t. t^2<=n$t=n^(1/2)。O(n^0.5)。
例4:
;i<=n;i++)
;j<=i;j++)
;k<=j;k++)
x++;
内层循环受外循环控制变量大小控制,故不能简单相乘。研究发现,i=某值c时,最内层循环次数为:1+2+…+c。故$O(\sum_i^n i*(n-i-1))$但不会算,不过有个结论可以用:$\sum_{i=1}^n\sum_{j=1}^i\sum_{k=1}^j l=O(\frac{n^3}{6})=O(n^3)$
另外总结一些常用数学公式:
等差数列求和公式:$Sn = \frac{a_1+a_n}{2}n=a_1n+\frac{n(n-1)}{2}d$
等比数列求和公式:$Sn = a_1\frac{1-q^n}{1-q}$
和公式:$1+2+...+n=\frac{n(n+1)}{2}$
平方和公式:$1^2+2^2+...+n^2=\frac{n(n+1)(2n+1)}{6}$
立方和公式:$1^3+2^3+...+n^3=\frac{n^2(n+1)^2}{4}=(\frac{n(n+1)}{2})^2$
二、排序算法们:
2.1直接插入排序:
直接插入排序的思想是,在序列前一部分有序时,将后一部分最前面的元素插入到有序部分,使插入后的序列依然有序。(找位置,找位置过程中移动元素腾出位置,然后插入)具体的做法(文字描述就省略啦,自己看代码吧):
public static void directInsertSort(int[] a) {
int len = a.length;
int temp;
for(int i=1;i<len;i++) { //从第二个元素开始,因为一个元素时必然是有序的
if(a[i]<a[i-1]) {
temp = a[i];
int j=i-1;
for(;j>=0;j--) {
if(a[j]>temp) a[j+1]=a[j]; //不用>=是为了保证算法的稳定性,不然后出现的元素就会排在前出现的元素之前了,而且那样也会增加无谓的开销
}
a[j+1]=temp;
}
}
}
最差时间复杂度:应该出现在每次内循环中,都要找到最前面的位置插入,即,原数组是倒序的。这时运行频次t=0+1+2+...+(n-1)=n(n-1)/2。故O(n^2)。空间开销为常量们,O(1)。
2.2冒泡排序(基本交换排序):
冒泡排序的思想是,每次将最小的元素交换到队首。(对比左右元素,只要右元素小,就交换位置)。具体算法:
public static void bubbleSort(int[] a) {
int len = a.length;
int temp;
for(int i=0;i<len;i++) {
boolean flag=false; //表示本躺冒泡是否发生交换
for(int j=len-1;j>i;j--) {
if(a[j]<a[j-1]) { //稳定
temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
flag=true;
}
}
if(flag==false) return; //已经有序了,避免后续无用功
}
}
最差时间复杂度:倒序。同上O(n^2),O(1)。
2.3简单选择排序:
简单选择排序的思想是,每轮找到一个最小的数,和无序部分的第一个元素交换位置。(遍历后交换位置,不存在略过的元素的移动)。具体代码:
public static void selectionSort(int[] a) {
int len=a.length;
int temp;
for(int i=0;i<len;i++) {
int min = i;
for(int j=i+1;j<len;j++) {
if(a[j]<a[min]) min=j;
}
if(min!=i) {
temp=a[i];
a[i]=a[min];
a[min]=temp;
}
}
}
首先由于交换位置的原因,那么第一个最小元素(出现在靠后的位置)就会跑到前面,故不稳定。
时间复杂度方面相同,O(n^2),O(1)。
2.4希尔排序:
希尔排序是升级版的直接插入排序,它引入了步长的概念,先对等步长的每个子序列进行直接插入排序,然后缩短步长,最后步长=1,进行最后一次排序。
public static void shellSort(int[] a) {
int len = a.length;
int temp;
for(int dk=len/2;dk>=1;dk/=2) { //步长变化
for(int i=dk;i<=len;i++) {
if(a[i]<a[i-dk]) {//如果需要插入
temp=a[i];
int j=i-dk;
for(;j>=0;j-=dk) {
if(a[j]>temp) {a[j+dk]=a[j];}
}
a[j+dk]=temp;
}
}
}
}
希尔排序的时间复杂度不好计算,直接记结论,最坏O(n^2),平均O(n^1.3)。当相同关键字的元素被分到不同的子表时,顺序可能会发生变化,故不稳定。
2.5快速排序:
著名的快排,是交换排序的一种,思想是每轮把一个数放到它该在的位置(左边的元素都比它小,右边都比它大),即左右各一个指针,先从右边往左找,找到第一个比pivot(基准)小的值,停下,左指针往右找第一个比pivot大的元素,停下,交换两个元素,之后继续,直到两个指针相遇,右指针的位置的元素应小于pivot的值(右指针先走的原因),即为pivot应在的位置,右指针元素与pivot交换即可。还不明白可以参考http://wiki.jikexueyuan.com/project/easy-learn-algorithm/fast-sort.html的介绍。具体:
public static void quickSort(int[] a) {
forQuickSort(a,0,a.length-1);
}
public static void forQuickSort(int[] a,int l,int r) {
if(l==r) return;
int temp;
int pr=r,pl=l; //工作指针,先从右边开始
while(pr>pl) {
while(pr>pl&&a[pr]>=a[l]) pr--;
while(pr>pl&&a[pl]<=a[l]) pl++;
if(pr==pl) {temp=a[l];a[l]=a[pr];a[pr]=temp;}
else {temp=a[pr];a[pr]=a[pl];a[pl]=temp;}
}
forQuickSort(a,l,pr-1);
forQuickSort(a,pr+1,pr);
}
还有一种写法:
private int Partition(int [] input,int start,int end){
int p = input[start];
int left = start+1,right=end;
while(left<=right){
while(left<=right&&input[right]>=p) right--;
while(left<=right&&input[left]<=p) left++;
if(left<right){int temp=input[right];input[right]=input[left];input[left]=temp;}
else{input[start]=input[right];input[right]=p;}
}
return right;
}
当元素基本有序或者逆序时,快排会得到最坏时间复杂度,O(n^2)。如果比较顺利,快排两边比较均衡,则运行速度将大大提升,达到O(nlogn),而快排的平均时间复杂度和最佳表现接近,也是O(nlogn)。快排是内部排序算法中平均性能最优的排序算法。
对于空间复杂度,快排是递归的,故需要一个递归工作栈,最坏O(n),平均O(logn)。
因为存在左右指针相互交换的情况,所以快排是不稳定的。
关于对多个有序数组合并排序,看mergesort,时间复杂度O(m+n)
https://www.cnblogs.com/kkun/archive/2011/11/23/merge_sort.html
https://www.cnblogs.com/chengxiao/p/6194356.html
关于快速排序时间复杂度证明:
https://www.cnblogs.com/fengty90/p/3768827.html
常见排序算法总结:插入排序,希尔排序,冒泡排序,快速排序,简单选择排序以及java实现的更多相关文章
- 八大排序算法的python实现(八)简单选择排序
代码: #coding:utf-8 #author:徐卜灵 # L = [6, 3, 2, 32, 5, 4] def Select_sort(L): for i in range(0,len(L)) ...
- Python实现八大排序(基数排序、归并排序、堆排序、简单选择排序、直接插入排序、希尔排序、快速排序、冒泡排序)
目录 八大排序 基数排序 归并排序 堆排序 简单选择排序 直接插入排序 希尔排序 快速排序 冒泡排序 时间测试 八大排序 大概了解了一下八大排序,发现排序方法的难易程度相差很多,相应的,他们计算同一列 ...
- 冒泡排序与简单选择排序——Java实现
1.冒泡排序 1)原理说明:反复遍历要排序的数列,一次比較两个元素,假设他们的顺序错误就把他们交换过来.走訪数列的工作是反复地进行直到没有再须要交换,也就是说该数列已经排序完毕. 2)代码实现: pa ...
- Java学习笔记day07_琐碎知识_水仙花数_ASCII码_冒泡排序_简单选择排序_折半查找
琐碎知识: 水仙花数, ASCII码, 冒泡排序, 简单选择排序, 折半查找 1.水仙花数 每位数的平方的和等于本身. 如100到999之间的水仙花数满足: 个位的平方+十位的平方+百位的平方 = 本 ...
- 冒泡排序Vs直接选择排序
什么是排序?为什么要使用排序?事实上我们生活中处处都用到了排序.拿字典来说,如今,我们要在字典中查找某个字(已经知道这个字的读音),首先.我们须要依据这个字的读音,找到它所所在文件夹中的位置,然后依据 ...
- python实现排序算法 时间复杂度、稳定性分析 冒泡排序、选择排序、插入排序、希尔排序
说到排序算法,就不得不提时间复杂度和稳定性! 其实一直对稳定性不是很理解,今天研究python实现排序算法的时候突然有了新的体会,一定要记录下来 稳定性: 稳定性指的是 当排序碰到两个相等数的时候,他 ...
- C数据结构排序算法——直接插入排序法用法总结(转http://blog.csdn.net/lg1259156776/)
声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 排序相关的的基本概念 排序:将一组杂乱无章的数据按一定的规律顺次排列起来. 数据表( data list): ...
- java排序算法(四):冒泡排序
java排序算法(四):冒泡排序 冒泡排序是计算机的一种排序方法,它的时间复杂度是o(n^2),虽然不及堆排序.快速排序o(nlogn,底数为2).但是有两个优点 1.编程复杂度很低.很容易写出代码 ...
- 冒泡排序算法和简单选择排序算法的js实现
之前已经介绍过冒泡排序算法和简单选择排序算法和原理,现在有Js实现. 冒泡排序算法 let dat=[5, 8, 10, 3, 2, 18, 17, 9]; function bubbleSort(d ...
随机推荐
- Spring Boot 2.x(十一):AOP实战--打印接口日志
接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息.比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提 ...
- SqlSugar ORM 入门篇2 【查询】 让我们实现零SQL
SqlSugar在查询的功能是非常强大的,多表查询.分页查询 . 一对一查询.二级缓存.一对多查.WhenCase等复杂函数.Mapper功能.和拉姆达自定义扩展等,用好了是可以做到真正零SQL的一款 ...
- oracle数据库导出表结构步骤
导出完成后在状态栏中显示Find
- 折腾Java设计模式之中介者模式
博文原址:折腾Java设计模式之中介者模式 中介者模式 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性.这种模式提供了一个中介类,该类通常处理不同类之间的通信,并 ...
- 弹性布局--flex方向
flex方向 flex方向由flex-direction特性决定,用于定义弹性布局模式.flex-direction共有4种模式:从左向右.从右向左.从上往下.从下往上. 主轴 主轴的起点与终点定义了 ...
- 虹软免费人脸识别SDK注册指南
成为开发者三步完成账号的基本注册与认证:STEP1:点击注册虹软AI开放平台右上角注册选项,完成注册流程.STEP2:首次使用,登录后进入开发者中心,点击账号管理完成企业或者个人认证,若未进行实名认证 ...
- 深入理解group by 语句的执行顺序 from→where→group by→select(含聚合函数)
由于之前没有对group by 语句的执行顺序(执行原理)做深入的了解,所以导致在实际应用过程中出现了一些问题.举个简单的粟子,比如一个表testA中的所有数据如下图: 我现在想从testA中查询us ...
- C++设计模式视频讲解
设计模式(C++) 视频网址: http://www.qghkt.com/ 设计模式(C++)视频地址: https://ke.qq.com/course/318637?tuin=a508ea62 目 ...
- Ajax异步交互基础
1. ajax是什么? * asynchronous javascript and xml:异步的js和xml * 它能使用js访问服务器,而且是异步访问! * 服务器给客户端的响应一般是整个页面,一 ...
- 卸载或安装Git出现Invalid drive错误的解决方案【简记】
前言:工作中由于公司的电脑(SSD+HDD)硬盘(HDD)突然坏了,只剩下一个系统盘(SSD).然后就是有个比较紧急的需求正在做,申请换的新硬盘不能立刻换上,因为工作的机器不在公司,操作远程机器工作, ...