稳定性
排序完不改变相对顺序为具有稳定性的排序
内部排序
数据元素全部放在内存中的排序
外部排序
数据元素太多不能放在内存中,根据排序过程·1的要求不能再内外存之间移动数据类型的排序
插入排序
- 数据越有序,时间复杂度越低。
- 具体稳定性
最慢时间复杂度: O(n^2)
最快时间复杂度: O(n)
空间复杂度:O(1)
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j;
for (j = i-1; j >= 0; j--) {
if (array[j] > tmp) array[j + 1] = array[j];
else break;
}
array[j+1] = tmp;
}
}
希尔排序
- 先分组在进行插入排序,充分利用插入排序越有序时间复杂度越低的特点
- 不具有稳定性
时间复杂度:O(n^1.3 ~ n^1.5)
空间复杂度:O(1)
private static void shell(int[] array,int gap){
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j;
for (j = i-gap; j >= 0; j-=gap) {
if (array[j] > tmp) array[j + gap] = array[j];
else break;
}
array[j+gap] = tmp;
}
}
public static void shellSort(int[] array){
for(int i=array.length-1;i>=1;i--){
shell(array,i);
}
}
选择排序
- 除了新手易懂,没有其它优势
- 不具有稳定性
时间复杂度:O(n^2)
空间复杂度:O(1)
public static void selectSort(int[] array){
for (int i = 0 ; i < array.length; i++) {
int min_index = i;
for (int j = i; j < array.length; j++) {
if(array[min_index] > array[j]){
min_index = j;
}
}
swap(min_index,i,array);
}
}
堆排序
- 较快排序中唯一不需要额外空间的排序
- 不具有稳定性
时间复杂度:O(n^logn)
空间复杂度:O(1)
private static void shiftDown(int parent,int[] elem,int limit){
int child = 2*parent + 1;
while(child < limit){
if(child+1 < limit && elem[child] < elem[child + 1]){
child++;
}
if(elem[parent] < elem[child]){
swap(parent,child,elem);
parent = child;
child = 2*parent + 1;
}
else break;
}
}
public static void heapSort(int[] array){
for(int parent = (array.length-1-1)/2; parent >= 0; parent--){
shiftDown(parent,array,array.length);
}
for (int i = array.length-1; i >= 0; i--) {
swap(0,i,array);
shiftDown(0,array,i);
}
}
冒泡排序
- 具有稳定性
时间复杂度:O(n^2)
如果在每次循环判断是否有交换可以降低时间复杂度,使其最快时间复杂度达到0(n)
空间复杂度:O(1)
public static void bubbleSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
for (int j = 0; j < array.length-i-1; j++) {
if(array[j] > array[j+1])
swap(j,j+1,array);
}
}
}
快速排序
- 平均最快的排序
- 不具有稳定性
时间复杂度:O(nlogn)
最慢时间复杂度:O(n^2)
空间复杂度:O(logn)
最坏空间复杂度:O(n)
public static void partition(int start,int end,int[] array){
if(start >= end)return;
int key = array[start];
int l = start,r = end;
while(l < r){
while(l < r && array[r] >= key)r--;
array[l] = array[r];
while(l < r && array[l] <= key)l++;
array[r] = array[l];
}
array[l] = key;
partition(start,l-1,array);
partition(l+1,end,array);
}
public static void quickSort(int[] array){
partition(0,array.length-1,array);
}
这样的快速排序缺点很多,例如如果该数组本来就是有序的,这就需要开辟n个函数栈帧,一方面大大增加了时间复杂度,退化为O(n^2)级别,另一方面几十万数据量的有序数组进行排序就会栈溢出。因此我们可以对快排进行优化。
-
有序数组之所以会出现这种情况,归根结底是因为每次我们取的分界点是每个递归区间的第一个数,最简单的思路就是我们可以随机选择分界点,这样就较好避免了大量开辟函数栈帧的问题。但是随机还是不够稳定,比较常用的另一种方式是比较每个区间的三个数,我们取区间的最左边,最右边和中间作为这三个数,取它们中大小为中间的数作为分界点,这样就几乎完美避免了大量开辟函数栈帧的问题。(找到分界点直接把数字与区间第一个数字交换即可)
-
其次我们可以在最后几次递归直接使用插入排序,因为插入排序是区间越有序时间复杂度越低的,在最后两次递归时区间已经基本有序,所以使用插入排序和快速排序时间上几乎相差无几。同时,利用插入排序优化还能减少函数栈帧的开辟。
优化的快速排序
public static void insertSort(int start,int end,int[] array){
for (int i = start+1; i <= end; i++) {
int tmp = array[i];
int j;
for (j = i-1; j >= 0; j--) {
if (array[j] > tmp) array[j + 1] = array[j];
else break;
}
array[j+1] = tmp;
}
}
private static int getPrivot(int a,int b,int c,int[] array){
if( array[a] <= array[b]){
if(array[b]<=array[c])return b;
else{
if (array[c]<= array[a])return a;
else return c;
}
}
if( array[a] >= array[b]){
if( array[c] >= array[a])return a;
else{
if( array[c] >= array[b])return c;
else return b;
}
}
return -1;
}
public static void partition(int start,int end,int[] array){
if(end - start < 7){//插入排序优化
insertSort(start,end,array);
return;
}
if(start >= end)return;
int l = start,r = end;
int privot = getPrivot(start,end,start+end>>1,array);
swap(start,privot,array);
int key = array[start];
while(l < r){
while(l < r && array[r] >= key)r--;
array[l] = array[r];
while(l < r && array[l] <= key)l++;
array[r] = array[l];
}
array[l] = key;
partition(start,l-1,array);
partition(l+1,end,array);
}
public static void quickSort(int[] array){
partition(0,array.length-1,array);
}
public static void main(String[] args) {
int[] array = {4,9,12,12,4,5,9,2,8,6,0,9,5};
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
归并排序
时间复杂度:O(nlogn)
空间复杂度:O(n)
具有稳定性
import java.util.Arrays;
public class MergeSort {
private static void merge(int start, int end, int[] array){
if(start >= end)return;
int mid = start+end>>1;
merge(start,mid,array);
merge(mid+1,end,array);
int s1 = start;
int e1 = mid;
int s2 = mid+1;
int e2 = end;
int[] tmp = new int[end - start + 1];
int index = 0;
while(s1 <= e1 && s2 <= e2){
if(array[s1] <= array[s2])tmp[index++] = array[s1++];
else tmp[index++] = array[s2++];
}
while(s1 <= e1){
tmp[index++] = array[s1++];
}
while( s2 <= e2){
tmp[index++] = array[s2++];
}
index = start;
for (int i = 0; i < tmp.length; i++) {
array[index++] = tmp[i];
}
}
public static void mergeSort(int[] array){
merge(0,array.length-1,array);
}
public static void main(String[] args) {
int[] array = {4,6,2,8,3,6,4,7,9,3,6,5,4,6,8,9,12,14,13};
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}
排序总结
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(1) | 不稳定 |
快速排序 | O(n * log(n)) | O(n * log(n)) | O(n^2) | O(log(n)) ~ O(n) | 不稳定 |
归并排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(n) | 稳定 |