一、桶排序
桶排序作为非通用的排序算法,桶的数量要根据原数组中元素大小分布来确定。为方便说明和测试,此处以LeetCode75——Sort Colors的题目为代码环境。
题目大意:原数组包含若干个0、1、2,对原数组进行排序。
1. 创建大小为3的数组(3个桶),各元素初始值为0。数组第1、2、3位的值分别代表原数组中0、1、2的数目;
2. 扫描原数组,更新各桶的值;
3. 扫描各桶,更新排序原数组。
以下为代码实现:
public void bucketSort(int[] nums) {
// 桶排序
int buckets[] = new int[3];
for(int item : nums) {
buckets[item]++;
}
int cursor = 0;
for(int bucket = 0; bucket < buckets.length; bucket++) {
for(int i = 0; i < buckets[bucket]; i++) {
nums[cursor] = bucket;
cursor++;
}
}
}
时间复杂度:O(n+r),n为原数组元素个数,r为桶的个数;空间复杂度:O(r),即桶的个数,也就是桶数组的大小。
二、堆排序
堆排序在经典排序算法里算是比较复杂的了。算法分为两个部分:
1. 对原始数组建立大顶堆;
2. 删除堆顶元素,在数组中从右往左放置,再更新堆的结构和大小。(可以避免引入新的缓存数组)
在这种策略下,堆排序的空间复杂度仅为O(1),而时间复杂度在最好、最坏、平均情况下均为O(nlogn)。此时看来优于快速排序、归并排序(快排空间复杂度为O(logn)且最坏时间复杂度为O(n2),归并排序空间复杂度为O(n))。但堆排序和快排一样是不稳定的(比如原始数组为[6, 6, 6],天然大顶堆,删除堆顶也就是最左边的6时,该6会被放到最右边去),因此Java自带的Sort是用归并排序为主体算法。
以下为算法实现代码:
public void heapSort(int[] nums) {
buildMaxHeap(nums); // 建立大顶堆
for(int i = nums.length - 1; i > 0; i--) {
swap(nums, 0, i); // 大顶堆最大数换到数组最右,次大换到次右,以此类推
downPerco(nums, 0, i); // 每换一次,堆的大小减一并重新下滤树根
}
}
public void buildMaxHeap(int[] nums) {
for(int i = nums.length / 2 - 1; i >= 0; i--) {
downPerco(nums, i, nums.length);
}
}
public void downPerco(int[] nums, int item, int n) {
int tmp = nums[item];
while(item <= n / 2 - 1) { // 有儿子
int child = 2 * item + 1; //左儿子
if(child < n - 1 && nums[child] < nums[child + 1]) { // 如果有右儿子且右儿子更大,则右儿子是大儿子
child++;
}
if(nums[child] > tmp) { // 如果大儿子大过祖宗,就继续往下走;否则终止
nums[item] = nums[child];
item = child;
} else {
break;
}
}
nums[item] = tmp;
}