5.排序(下)
归并排序
-
特点
- 非原地,空间复杂度O(n)
- 稳定
- O(nlogn)
-
归并排序的思想是如果要排序一个数组,我们先把数组从中间分为前后两部分,然后对前后部分分别排序,再将排好序的两部分合在一起,这样整个数组就都有序了
快速排序
-
特点
- 原地
- 不稳定
- O(nlongn)
-
选择一个分区点pivot,把它放到正确的地方,这样数组被分为两部分,然后前后两部分继续此过程
桶排序
-
核心思想:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序,最后把每个桶里的数据按顺序取出,组成的序列就是有序的了
-
时间复杂度:O(n)
-
如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素
-
每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk)
-
因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))
-
当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)
-
-
桶排序比较适合用在外部排序中,数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中
-
桶排序对数据的要求
-
数据容易被划分为m个桶,并且桶与桶直接有着天然的顺序
-
数据在各个桶之间分布比较均匀,在极端情况下,如果数据全都被划到一个桶里,那就退化为O(nlogn)的排序算法了
-
计数排序
-
桶排序的特殊情况,当要排序的数据所处范围并不大时,我们可以把数据分为max-min+1个桶(比如高考满分750分,设置751个桶放置考生成绩),桶内存放数据出现的次数
-
具体做法
-
遍历原数组A,数据每出现一次,对应桶的计数+1(C[n]++)
-
得出排序好的数据
-
将桶内数据顺序求和(每个桶内不再是数据出现次数,而是小于等于该数据的个数)
-
准备好一个结果数组R
-
从后到前遍历原数组A,取出C[n],C[n]代表原数组中小于等于n的数据一共有C[n]个,那么原数据A[n]就应该放到结果数组R的C[n]处,然后将C[n]--
-
快排、计数Java实现
/**
* 快速排序
*
* @param arr 数组
* @param p 要排序部分左下标
* @param r 右下标
*/
public static void quickSort(int arr[], int p, int r) {
if (p >= r) return;
//选择一个分区点(设为r)
int pivot = arr[r];
//将分区点放到它正确的位置
//[p,i)为已处理区间,[j,r)为未处理区间,处理完毕后i左边全小于等于pivot,右边全大于pivot
int i = p;
for(int j = p; j < r ; j++){
//将小于pivot的值放到已处理区间
if(arr[j] < pivot){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
}
}
//最后将pivot放在i处
int temp = arr[i];
arr[i] = arr[r];
arr[r] = temp;
//重复选择-放置过程
quickSort(arr,p,i-1);
quickSort(arr,i+1,r);
}
/**
* 计数排序
* @param arr
* @param max
*/
public static void countingSort(int arr[], int max){
//桶C用于存储累计值
//统计各数据出现次数
int c[] = new int[max + 1];
for(int i : arr)
c[i]++;
//得出排序好的数组
//C中数据累计相加
for(int i = 1; i < c.length; i++){
c[i] += c[i - 1];
}
//从后往前遍历原数组 将arr[n]放置在R[c[arr[n]]]处
int result[] = new int[arr.length];
for(int i = arr.length - 1; i >= 0; i--){
result[--c[arr[i]]] = arr[i];
}
for(int i = 0; i < arr.length; i++)
arr[i] = result[i];
}