目录
10、对数器
11、桶排序
11.1 不完全二叉树
11.2 完全二叉树
11.3 大根堆
11.4 大根堆的排序:
11.6 优先队列
11.5 堆排序扩展题目
11.6 计数排序:
11.7 基数排序
对数器的概念和使用
1,有一个你想要测的方法a
2,实现复杂度不好但是容易实现的方法b
3,实现一个随机样本产生器
4,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者 方法b
6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
使用对数器检查排序算法的准确
算法c是自己写的,算法b是系统提供的排序算法,使用随机数组,2种算法对比,看c是否有错。不依赖线上测试平台,自己就能测出来。
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
左儿子:2 * i + 1
右儿子:2 * i + 1
父:(i - 1)/ 2
父节点的数比子节点的数要大,示例:
利用新进堆的数与父节点比较,形成大根堆,把新的数插入到堆中,就是上移:
//取出最大元素之后执行此操作变成大根堆
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 ;
}
}
某数a在index位置,将其往下移动,至堆结构符合大根堆要求,就是下移:
//大根堆什么也没有,此时不断进行插入排序,执行此操作
//某数a在index位置,将其往下移动
public static void heapify(int[] arr, int index, int size) {//size为数组长度
int left = index * 2 + 1;//左孩子位置
while (left < size) {//判断孩子是否存在
//只有当右孩子存在且大于左孩子时,才取右孩子作为最大值;
//其余情况选左孩子,包括
// 1.右孩子不存在
// 2.右孩子存在但没左孩子大
//largest记录最大值的位置
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);
//记录某数a的新位置
index = largest;
//记录处于新位置的某数a的左孩子
left = index * 2 + 1;
}
}
新增一个数,或删除最大值,调整的复杂度都是 O(logN)。
做法2 O(NlogN):
所有数字先入大根堆,然后将最大数字于heapsize最后一个元素交换,heapsize减一,然后第一个数做heapify的下移操作,如此反复,就能将全部数字排序,调整的复杂度都是 O(NlogN)。
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
//将所有数字搞成大根堆
//做法1:
// for (int i = 0; i < arr.length; i++) {// O(N)
// heapInsert(arr, i);// O(logN)
// }
//做法2:
for (int i = arr.length-1; i >= 0 ; i--) {
heapify(arr, i, arr.length);
}
int size = arr.length;
//0位置上的数与heapsize最后一个数交换
swap(arr, 0, --size);
while (size > 0) {// O(N)
//0位置上的数重新调整位置
heapify(arr, 0, size);// O(logN)
//0位置上的数与heapsize最后一个数交换,heapsize减小
swap(arr, 0, --size);// O(1)
}
}
做法1 O(N):
如果只是进行大根堆排序有O(N)的算法,即算法一:
第一步,全部数字变成大根堆,有优化做法,最小的树做heapify,然后次小…
时间复杂度分析:
假设一共N个数 最底层最差情况是heapInsert一次,共N/2个数需要进行heapInsert, 倒数第二层最差情况是heapInsert二次,共N/4个数需要进行heapInsert,如此类推:
复杂度使用错位相加法:
最终复杂度为 O(N)
黑盒封装的优先队列实际上就是小根堆
public static void main(String[] args) {
PriorityQueue heap = new PriorityQueue<>();
heap.add(8);
heap.add(3);
heap.add(6);
heap.add(2);
heap.add(4);
while (!heap.isEmpty()){
System.out.println(heap.poll());
}
}
//输出:2 3 4 6 8
小根堆会遇到不够空间时扩容,扩容就会复制一次(2,4,8,16,32),长度为多少,复杂度就为多少,一共扩容 logN 次,总扩容复杂度为 O(N*logN),均摊下来每个元素,复杂度为O(logN)。
如果给其传入比较器可以变成大根堆;
比较器的使用
1)比较器的实质就是重载比较运算符
2)比较器可以很好的应用在特殊标准的排序上
3)比较器可以很好的应用在根据特殊标准排序的结构上
public static class AComp implements Comparator{
//如果返回负数,认为第一个参数应该排在前面
//如果返回正数,认为第二个参数应该排在前面
//如果返回0,认为谁放前面都行
@Override
public int compare(Integer argo, Integer arg1) {
return arg1 - arg0;
}
}
public static void main(String[] args){
PriorityQueue heap = new PriorityQueue<>(new AComp());
heap.add(6);
heap.add(9);
heap.add(3);
heap.add(2);
heap.add(10);
while(!heap.isEmpty()) {
System.out.println(heap.pol1());
}
}
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
因为0位置上的正确数一定在0-6这七个数中,所以将这7个数在小根堆中排好序,最小值就可以弹出放到0位置上,然后再加入下一个数,进行重复操作。复杂度为O(Nlogk)。
public void sortedArrDistanceLessK(int[] arr, int k) {
PriorityQueue heap = new PriorityQueue<>();
int index = 0;
//k个数形成小根堆
for (; index < Math.min(arr.length, k); index++) {
//index < Math.min(arr.length, k)进行的操作是避免传的参数不合适造成程序的无法正常运行
heap.add(arr[index]);
}
int i = 0;
for (; index < arr.length; i++, index++) {
heap.add(arr[index]);//加一个数
arr[i] = heap.poll();//弹出一个最小值
}
while (!heap.isEmpty()) {//依次弹出k个最小值
arr[i++] = heap.poll();
}
}
系统提供的堆,只能给一个数,弹出一个数,不能做到上述的高效操作,要实现有高效操作的,必须自己写。
桶排序思想下的排序
1)计数排序
2)基数排序
分析:
1)桶排序思想下的排序都是不基于比较的排序
2)时间复杂度为0(N),额外空间负载度O(M)
3)应用范围有限,需要样本的数据状况满足桶的划分
先按个位数放进桶,然后从左往右,先进先出导出,再按十位数排序,重复,再按百位
代码的实现count不是记录桶 i 里面有多少个数,而是记录 ≤ i 里面有多少个数。
// 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;//自动整除,因为max是int
}
return res;
}
public static void radixSort(int[] arr, int begin, int end, int digit) {
final int radix = 10;
int i = 0, j = 0;
int[] bucket = new int[end - begin + 1];
//digit多少哥十进制位,也代表入桶出桶的次数
for (int d = 1; d <= digit; d++) {
int[] count = new int[radix];
//用于记录当前位上等于0,...,等于9的各有多少个数
for (i = begin; i <= end; i++) {
j = getDigit(arr[i], d);//确认当位上的数是多少
count[j]++;//等于该位上的数,统计加1
}
//用于记录当前位上小于等于0,...,小于等于9的各有多少个数
//同时也记录了当前位上等于0,...,等于9的数组最后一个数出桶后的位置
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[j]--;//该桶上的数减一
}
for (i = begin, j = 0; i <= end; i++, j++) {
//把bucket的数组导入arr中,相当于保留了这次桶排序
arr[i] = bucket[j];
}
}
}