常见排序算法
0、概述
0.1、排序的稳定性
当待排序列关键字各不相同时,排序结果唯一,否则结果不唯一。在待排序列文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,则该方法是稳定的。
0.2、排序的分类
内部排序和外部排序,此处只讨论内部排序
内部排序分为:
- 插入类:
- 交换类:
- 选择类:
- 归并类:
- 分配类:
0.3、排序的性能评价
执行时间与所需辅助空间和算法本身的复杂程度
注意:下面的算法中,待排序数组的零地址不存放数组元素,用来做“监视哨”或者空闲不用
0.4、总体比较
1、插入排序
1.1、直接插入排序
public static void insertSort(int[] arr){
for(int i = 2;i<arr.length;i++){
if(arr[i]<arr[i-1]){
arr[0] = arr[i];
int j = i-1;
for (; arr[0]<arr[j] ;j--){
arr[j+1] = arr[j];
}
arr[j+1] = arr[0];
}
}
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[1, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:正序时O(n) 逆序时O(n[^2])
当待排元素已经为正序时,或者接近正序时所比较的次数和移动次数比较少。对于小规模来说,插入排序是一个快速的排序法。
空间复杂度:O(1) 只需要一个监视哨arr[0]
稳定性:稳定
1.2、希尔排序
private static void shelltSort(int[] arr,int dk) {
int i = dk+1;
for(;i<arr.length;i++){
if(arr[i]<arr[i-dk]){
arr[0] = arr[i];
int j = i-dk;
for(;j>=0&&(arr[0]<arr[j]);j=j-dk){
arr[j+dk] = arr[j];
}
arr[j+dk] = arr[0];
}
}
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[8, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:dk的取值为{2[k]-1,2[k-1]-1,…,7,3,1}时,时间复杂度 O(n[^3/2])
如果dk每次除以2递减那么时间复杂度依旧是O(n[^2])
空间复杂度:O(1) 只需一个辅助空间
稳定性:不稳定
2、交换排序
2.1、冒泡排序
private static void bubbleSort(int[] arr) {
for(int i = 1;i<=arr.length-1;i++){
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for(int j = 1;j<arr.length-i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag =false;
}
}
if(flag) break;
}
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:正序时为O(n) 逆序时O(n[^2])
空间复杂度:O(1) 只需要一个辅助空间交换元素
稳定性:稳定
2.2、快速排序
private static void quickSort(int[] arr,int low,int high) {
if(low<high){
int pos = quickPass(arr,low,high);
quickSort(arr,low,pos-1);
quickSort(arr,pos+1,high);
}
}
//一趟快速排序
private static int quickPass(int[] arr,int low,int high){
arr[0] = arr[low];
while (low<high){
while (low<high&&arr[high]>arr[0]) high--;
arr[low] = arr[high];
while (low<high&&arr[low]<arr[0]) low++;
arr[high] = arr[low];
}
arr[low] = arr[0];
return low;
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[4, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:快速排序的最坏运行情况(正序)是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
空间复杂度:总共需要分割logn次所以空间复杂度为logn
稳定性:不稳定
3、选择排序
3.1、简单选择排序
private static void selectSort(int[] arr) {
int i = 1;
for(;i<=arr.length-1;i++){
int k = i;
for (int j = i+1;j<=arr.length-1;j++ ){
if(arr[j]<arr[k]){
k = j;
}
}
if(k != i){
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
}
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:O(n[^2])
空间复杂度:O(1)
稳定性:不稳定
3.2、堆排序
注意:堆排序的数组第一个位置我没有空闲,而是放了关键字
所以左孩子是i * 2+1,右孩子是i * 2+2,最后一个非叶子结点是n/2-1
//堆排序
private static void heapSort(int[] arr) {
creatHeap(arr);
for(int i = arr.length-1;i>=1;i--){
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapAdjust(arr,0,i-1);
}
}
//创建堆
private static void creatHeap(int[] arr) {
int len = arr.length;
//最后一个非叶子节点
for(int i = len/2-1;i>=0;i--){
heapAdjust(arr,i,arr.length-1);
}
}
/**
* 调整堆
* @param arr
* @param i 堆得根节点的下标
* @param length 堆的最后节点的下标
*/
private static void heapAdjust(int[] arr, int i, int length) {
int temp = arr[i];
for (int j = 2*i+1;j<=length;j=j*2+1){
if(j<length && arr[j] < arr[j+1]){
j++;
}
if(temp>=arr[j]) break;
arr[i] = arr[j];
i = j;
}
arr[i] = temp;
}
样例:[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
排序结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
时间复杂度:O(logn)
空间复杂度:O(1)
稳定性:不稳定