桶排序
堆
- 堆结构就是用数组实现的完全二叉树结构
什么是完全二叉树结构?
从左到右依次遍满的就是完全二叉树结构。
满足此结构,那么i结点的左孩子为2×i+1,右孩子为2×i+2。父节点为 (i-1)/2.
堆是特殊的完全二叉树
- 完全二叉树中如果每棵子树的最大值都在顶部就是大树根
- 完全二叉树中如果每棵子树的最小值都在顶部就是小树根
- 堆结构的heapInsert与heapify操作
- 堆结构的增大和减小
- 优先级队列结构,就是堆结构
堆排序
大根堆,父结点不能比子节点小。
将最大值的顶结点去除,并且使得剩下的结构还是大根堆。
可以将最大值跟最后一位根节点交换,然后断开连接。交换上去的值,通过比较调换位置,使得堆恢复成大根堆。
public class Code06_HeapSort {
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;
}
}
满二叉树N个结点
叶结点N/2个,进行 1 的动作
上一层N/4个,要进行 2次 动作
总体的复杂度 T(N)= N/2 + N/4*2 + N/8 *3 + ……
利用错位相减法,得到T(N) = N + N/2 + N/4 + N/8 + ……
属于O(N)级别的复杂度
题目1
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
假设k = 6
准备一个小根堆结构
遍历数组,先遍历前七个数字。弹出这七个数的最小值,就是当前数组的最小值,因为这个数组是几乎有序的,最小值调整到第一位是移动不超过6的。因此这样就找到了一个最小值,然后将这7个数字向后移动,每移动一次,就可以从小根堆中弹出一个当前最小值,最后遍历完之后,就完成了这个数组的排序。
这个方法的复杂度是O(N*logk), 那么几乎可以认为是一个O(N)的算法
数组需要扩容,但是扩容的次数是有限的。扩容的水平是O(logN)不会影响最终的表现
黑盒
<- add
-> poll
黑盒的情况,不需要手写堆。
比较器的使用
- 比较器的实质就是重载比较运算符
- 比较器可以很好的应用在特殊标准的排序上
- 比较器可以很好的应用在根据特殊标准排序的结构上 比如堆
桶排序
(不基于比较的排序,要根据数据状况定制的。)
桶排序思想下的排序
计数排序
要将[0,200]年龄的人进行排序,可以准备一个[0,200]的一个数组。
每遇到一个k岁的人,那么arr[k]++,就可以统计出年龄的次频表出来。
然后按照年龄这个数组的顺序,将人倒出就进行了排序。
但是如果这个数据不是年龄,而是其他很大的数,那么就需要很大的数组来装载,这很浪费空间,也很没必要。
因此可以采用基数排序。
基数排序
先根据各位数字,放进0-9十个桶中,然后按顺序倒出来。然后再按照十位,放进对应的桶里,然后倒出来。 最后一直放到最高位,就成功排序了。(十进制的数)
仍然是要根据数据状况排序的,如果没有进制的数,还是没法排序
桶应该怎么表示
用一个词频数组来表示,这个词频数组处理十进制数的时候就是10位的一个数组。
然后将词频数组count,变成词频的累加数组count。那么count每一位表示的就是这一位,小于等于k的数有多少个。
再建立一个辅助的help数组,长度跟需要排序的数组一样长。
然后从右往左,看原来的数组。最后一位的个位数是k,count[k]–,然后并将这个数放在help的count[k]位置,cont[k]为当前个位数小于等于k的数的数量。
然后十位,百位同样这样操作。
代码
import java.util.Arrays;
public class Code08_RadixSort {
// only for no-negative value
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
public static int maxbits(int[] arr) { //求最大值有几个十进制位
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
public static void radixSort(int[] arr, int begin, int end, int digit) {
final int radix = 10; //digit 表示最大值有几个十进制位
int i = 0, j = 0;
int[] bucket = new int[end - begin + 1]; //和原数组一样长的桶
for (int d = 1; d <= digit; d++) { //有多少位,出桶入桶多少次
//count[i] 当前位是(0~i)的数有几个。处理成累计数组之后
int[] count = new int[radix];
for (i = begin; i <= end; i++) {
j = getDigit(arr[i], d); //拿出第d位数
count[j]++; //形成词频表
}
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1]; //变成累加数组
}
for (i = end; i >= begin; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i]; //根据count-1,填入辅助数组
count[j]--;
}
for (i = begin, j = 0; i <= end; i++, j++) {
arr[i] = bucket[j]; //把这个结构放回arr
}
}
}
public static int getDigit(int x, int d) {
return ((x / ((int) Math.pow(10, d - 1))) % 10);
}
}