1 归并排序
分而治之(divide - conquer);每个递归过程涉及三个步骤
第一, 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素.
第二, 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作
第三, 合并: 合并两个排好序的子序列,生成排序结果.
时间复杂度:O(nlogn)
public static void mergeSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
mergeSort(arr,0,arr.length-1);
}
public static void mergeSort(int[] arr,int L,int R){
if(L == R){
return;
}
int mid = L + (R-L)/2;
mergeSort(arr,L,mid);
mergeSort(arr,mid+1,R)
merge(arr,L,mid,R);
}
public static void merge(int[] arr,int L,int mid,int R){
int[] help = new int[R - L + 1];//辅助数组
int p1 = L;
int p2 = mid + 1;
int i = 0;
// 把较小的数先移到新数组中
while(p1 <= mid && p2 <= R){
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 把左边剩余的数移入数组
while(p1 <= mid){
help[i++] = arr[p1++];
}
// 把右边剩余的数移入数组
while(p2 <= R){
help[i++] = arr[p2++];
}
// 把新数组中的数覆盖nums数组
for(int j=0; j < help.length; j++){
arr[L + j] = help[j];
}
}
2快速排序
本文为经典快排的改进版–随机快排
- 是一种比较快的排序,适合基本无序的数据
- 从数列中挑取最后一个元素(最后一个元素随机选出)做基准,重新排列,所有比基准小的放前面,比基准大的放后面
- 分成两拨后,继续递归的使用上述方法,最终有序
- 时间复杂度:O(nlogn)
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr,int L,int R){
if(L < R){
swap(arr, L + (int)(Math.random()*(R - L + 1)), R);//在数组中随机选取一个作为基准
int[] p = partition(arr, L, R);
quickSort(arr, L, p[0] - 1);
quickSort(arr, p[1] + 1);
}
}
/*
less指针为小于基准的区域,初始指向-1,
more指针为大于基准的区域,初始指向基准位置,
开始时让数组第一位即L与基准相比,小于则交换L与less前一位,L+1;
大于则交换L与more的后一位,L继续与基准比较;相等则L+1
由于基准没参与交换,所以每次遍历完之后要交换more位置与基准位置,确保有序
*/
public static int[] partition(int[] arr,int L,int R){
int less = L - 1;
int more = R;
while(L < more){//
if(arr[L] < arr[R]){
swap(arr,++less,L++);
}else if(arr[L] > arr[R]){
swap(arr,--more,L);
}else{
i++;
}
}
swap(arr,more,R);
return new int[]{less + 1, more};//返回基准值相等的左右边界
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
3堆排序
1 将序列构建成大顶堆。
2 将根节点与最后一个节点交换,然后断开最后一个节点
3 重复第一、二步,直到所有节点断开。
- 时间复杂度:O(nlogn)
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i=0; i<arr.length; i++){
heapInsert(arr,i);
}
int size = arr.length;
swap(arr, 0, --size);
while(size > 0 ){
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
public static void heapInsert(int[] arr,int index){
while(arr[index] > arr[index - 1] / 2){
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void heapify(int[] arr,int index,int size){
int left = index * 2 + 1;
while(left < size){
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index){
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 |
---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(1) | 是 |
选择排序 | O(n2) | O(n2) | O(1) | 不是 |
直接插入排序 | O(n2) | O(n2) | O(1) | 是 |
归并排序 | O(nlogn) | O(nlogn) | O(n) | 是 |
快速排序 | O(nlogn) | O(n2) | O(logn) | 不是 |
堆排序 | O(nlogn) | O(nlogn) | O(1) | 不是 |
希尔排序 | O(nlogn)O | (ns) | O(1) | 不是 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | 是 |
基数排序 | O(N∗M) | O(N∗M) | O(M) | 是 |