1.比较器
1)比较器的实质就是重载比较运算符
2)比较器可以很好的应用在特殊标准的排序上
3)比较器可以很好的应用在根据特殊标准排序的结构上
4)写代码变得异常容易,还用于范型编程
2.堆结构
(1)完全二叉树:一个树满节点或者在变满的路上(从左到右依次递加),不满的一定在最后一层
相当于数组与二叉树的对应--->>>连续并对应
(2)当需要找出对应的父节点或者子节点时,使用下面的公式:
- i :索引
- 左节点:2*i + 1
- 右孩子:2*i + 2
- 父节点:(i-1)/2
(3)堆
- 大顶堆:最大数在节点最上面,这里增加一个heapsize用于记录堆中存储的元素个数,将其用二叉树的遍历方式画出二叉树的图形
// 新加进来的数,现在停在了index位置,请依次往上移动,
// 移动到0位置,或者干不掉自己的父亲了,停!
private void heapInsert(int[] arr, int index) {
// [index] [index-1]/2
// index == 0
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
- 数字下沉:将较大的孩子节点与父节点进行比较,从index位置往下看,不断的下沉。如果较大的孩子都不比index位置的数大,已经没有孩子了。
private void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;//左孩子
while (left < heapSize) {//未越界时
// 如果有左孩子,有没有右孩子,可能有可能没有!//将较大的孩子的下标 给largest
//右孩子:left + 1,如果有右孩子且大于左孩子就返回右孩子值,否则返回左孩子
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ?left + 1:left;
//将较大孩子与父PK,
largest = arr[largest] > arr[index] ?largest:index;
if (largest == index) {
break;
}
// index和较大孩子,要互换
swap (arr,largest,index);
index = largest;
left = index * 2 + 1;
}
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
(4)堆时间复杂度:heapify与heapInsert都为O(logN)
题目: ---无序数组调整为大根堆与小根堆---
思路:
- 先将整个数组调整为大根堆 (记录大根堆的大小heapSize)--将数组调整为大根堆
- 将大根堆最大值6与2交换,同时将heapSize调整为5,重新 将2进行下沉,重新得到一个大根堆。再将heapSize 设为4,将4与0交换,擦去4
代码实现
//堆排序额外空间复杂度O(1)
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2){
return;
}
// O(N*logN)
// for (int i = 0; i < arr.length; i++) { // O(N)
// heapInsert(arr, i); // O(logN)
// }
// O(N)
for (int i = arr.length - 1; i >= 0; i--) {
heapify(arr, i, arr, arr.length);
}
int heapSize = arr.length;
swap(arr, 0, --heapSize);
// O(N*logN)
while (heapSize > 0) {//O(N)
heapify(arr, 0, heapSize); // O(logN)
swap(arr, 0, --heapSize); // O(1)
}
}
// arr[index]刚来的数,往上
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;
}
}
// arr[index]位置的数,能否往下移动
public static void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1; // 左孩子的下标
while (left < heapSize) { // 下方还有孩子的时候
// 两个孩子中,谁的值大,把下标给largest
// 1)只有左孩子,left -> largest
// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
// 父和较大的孩子之间,谁的值大,把下标给largest
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(N*logN)O(N)
// O(N)
for (int i = arr.length - 1; i >= 0; i--) {
heapify(arr, i, arr.length);
}
题:已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序。
思路:在有序的位置下对数组进行排序--》》》移动位置不超过k
这里使用小根堆排序的时候,如何确认是有序数组???
//使用小跟堆:每次都对前一个数大小进行heapify()
//每次弹出的结果即为当前数组的最小数
public static void sortedArrDistanceLessK(int[] arr, int k) {
if (k k== 0) {
return;
}
//默认小根堆
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
// 0...K-1
for (; index <= Math.min(arr.length - 1, k - 1); index++) {
heap.add(arr[index]);
}
int i = 0;
for (; index < arr.length; i++, index++) {
heap.add(arr[index]);
arr[i] = heap.poll();
}
while (!heap.isEmpty()) {
arr[i++] = heap.poll();
}
}