概念
我们平常说的排序,通常都是按照从小到大的顺序,而且指的是原地排序(in place sort)。
排序又分内部排序和外部排序:
内部排序——将数据拉到内存中进行排序
外部排序——将数据放到磁盘排序,数据量太大,导致在内存当中不可存放
以下介绍的排序都是基于比较的内部排序:
标准
判断排序的标准:时间复杂度、空间复杂度、稳定性
这里重点说稳定性:如果一个序列中有若干相等的数据,排序之后其相对位置没有发生改变排序就是稳定的。
比较快速判断是否稳定的方法:如果在排序过程中没有发生跳跃式交换(隔几个数地去交换),就是稳定的。
1. 直接插入排序:
思想:
- 假定第一个元素是有序的,从第二个元素开始排序
- i 遍历无序序列,将 i 的值赋给tmp
- 从tmp前一个元素开始从后往前遍历有序序列,寻找合适位置插入
- 若tmp比当前元素大或相等:说明前面的序列已经有序,tmp之间插到当前位置即可; 若tmp比当前元素小:当前元素向后移,前面元素重复比较,直到找到对应位置
代码实现:
//直接插入排序:每次和前面的数据从后往前比较,找合适位置插入
//时间复杂度:最坏情况下为O(n^2),最好情况下为O(n)(有序)
//空间复杂度:O(1)
//稳定性:稳定的
//特点:数据越有序,用直接插入排序越快
public static void insertSort(int[] arr) {
for(int i = 1; i < arr.length; i++) {
int tmp = arr[i];
int j;
for(j = i-1; j >= 0; j--) {
//if里的判断条件有等号的不稳定,去掉等号是稳定的-》看作是稳定的
if(arr[j] > tmp) {
arr[j+1] = arr[j];
}else {
//前面已经有序
break;
}
}
arr[j+1] = tmp;
}
}
优化:折半插入排序——前面的区间已经有序,可以使用二分查找来找。当然了,这里所说的二分查找要稍作改变。
//未排序元素在排序序列中寻找合适位置时,若在此过程中能应用二分查找,速度会大大提高
public static void binarySearchInsertSort(int[] arr) {
for(int i = 1; i < arr.length; i++) {
int tmp = arr[i];
//[left,right)
int left = 0;
int right = i;
while(left < right) {
int mid = (left+right)/2;
if(arr[mid] > tmp) {
right = mid;
}else {
left = mid+1;
}
}
for(int j = i; j > left; j--) {
arr[j] = arr[j-1];
}
arr[left] = tmp;
}
}
2.希尔排序
思想:
将序列分为很多组,各自组内排序之后,接着分组,分很多次组,但是不管分几次,最后一次那个组一定是整体看成一组 (此时序列已接近有序)。
分组原则:
- 分组的增量序列都是质数,且最后一个分组应是1。
- 不是等分的组,因为直接插入排序是越有序越快
- 分多少次组,每组怎么分才能最快这些都没有明确规定,因为数据大小不确定,无法得出结论。
代码实现:
//希尔排序(分组思想)
//时间复杂度:最坏情况下为O(n^2),最好情况下为O(n)
//空间复杂度:O(1)
//稳定性:不稳定的(有跳跃式交换)
public void shell(int[] arr,int gap) {
for(int i = gap; i<arr.length; i++) {
int tmp = arr[i];
int j;
for( j = i-gap; j >= 0; j -= gap) {
if(arr[j] > tmp) {
arr[j+gap] = arr[j];
}else {
break;
}
}
arr[j+gap] = tmp;
}
}
public void shellSort(int[] arr) {
int[] drr = {
5,3,1};
for(int i = 0; i < drr.length;i++) {
shell(arr,drr[i]);
}
}
3. 选择排序
思想:
每次从待排序数字的后面选取一个比当前数字小的数据进行交换,直到当前序列遍历完成。
用 i 来遍历序列,每一趟排序用 i 位置的元素和后面的无序序列元素依此比较大小,将较小的换到 i 位置,一趟排序过程后,无序序列中最小的元素换到 i 位置处。
相较于其他排序算法,选择排序思想比较简单,实现容易。
//选择排序
//时间复杂度:O(n^2) 不分好坏
//空间复杂度:O(1)
//稳定性:不稳定的(有跳跃式交换)
public void selectSort(int[] arr) {
for(int i = 0; i < arr.length; i++) {
for(int j = i+1; j < arr.length; i++) {
if(arr[j] < arr[i]) {
int tmp =arr[i];
arr[i] =arr[j];
arr[j] = tmp;
}
}
}
}
4. 堆排序
思想:
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大/小的数。
实质是利用了堆底层是完全二叉树,大根堆堆顶元素是二叉树的最大值,小根堆堆顶元素是二叉树的最小值的性质。
注意: 排升序要建大堆;排降序要建小堆
下面以排升序为例:
- 先将堆排序序列,建立为大根堆
- 每次拿堆顶元素和最后一个待排序序列的数据进行交换,然后调整0号下标这棵树