堆结构
逻辑概念上就是完全二叉树结构
完全二叉树,通俗的说就是,要么这一层是满的,在不满的这一层也是从左到右依次变满的。
如果下标从0开始算,那么
- 左孩子(下标):2*i+1
- 右孩子:2*i+2
- 父节点:(i-1)/2
但是有时候0弃而不用,从1开始算起
- 左:2*i(i<<1)
- 右:2*i+1((i<<1)|1)
- 父:i/2(i>>1)
为什么?因为频繁操作i的时候,位运算效率更高!
大根堆:每一棵子树,最大值都是自己头节点的值
小根堆:每一棵子树,最小值都是自己头节点的值
package com.harrison.four;
import java.util.Comparator;
//用数组实现一个大根堆结构
//之后找出大根堆中的最大值并删除
//且剩下的数仍要求是一个大根堆结构
public class Code02_Heap {
public static class MyMaxHeap {
private int[] heap;// 用数组表示堆
private int limit;
private int heapSize;
public MyMaxHeap(int limit) {
heap = new int[limit];
this.limit = limit;
heapSize = 0;
}
public boolean isEmpty() {
return heapSize == 0;
}
public boolean isFull() {
return heapSize == limit;
}
public void push(int value) {
if (heapSize == limit) {
throw new RuntimeException("heap is full");
}
heap[heapSize] = value;
heapInsert(heap, heapSize++);
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 要求返回大根堆中的最大值,并且在大根堆中把最大值删除
// 且剩下的数,依然要求保持为大根堆结构
public int pop() {
int ans = heap[0];
// 将大根堆中最后的数与第一个数交换,且大根堆缩小一个数
swap(heap, 0, --heapSize);
// 从零位置开始寻找比自己大的孩子
heapify(heap, 0, heapSize);
return ans;
}
// 堆结构的两个关键操作:从某个位置开始往上看
// 动态地建立大根堆
// 如果收了N个数,时间复杂度为logN
private void heapInsert(int[] arr, int index) {
// arr[index]新进来的数,依次往上移动
// 移动到0位置或者比父节点小就不要往上移动
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
// 堆结构的两个关键操作:从某个位置开始往下调,时间复杂度logN
// 停:两个孩子都不比我大、没有孩子了
public void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;// 左孩子下标
// 如果有左孩子,没有有右孩子?有可能有有可能没有
// 如果没有左孩子,一定没有右孩子
while (left < heapSize) {
// 判断左孩子有没有越界,只在我们自己想象的范围里,而不是整个数组
// 把左右孩子中值大的孩子的下标给largest
// 右-> 1)有右孩子 && 右孩子比左孩子大
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
// index位置和较大孩子互换
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
}
// 不使用堆结构的暴力办法
public static class RightMaxHeap {
private int[] arr;// 用数组表示堆
private int limit;
private int size;
public RightMaxHeap(int limit) {
arr = new int[limit];
this.limit = limit;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == limit;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("heap is full");
}
arr[size++] = value;
}
public int pop() {
int maxIndex = 0;
for (int i = 0; i < size; i++) {
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
int ans = arr[maxIndex];
arr[maxIndex] = arr[--size];
return ans;
}
}
public static class Mycomparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public static void main(String[] args) {
int testTimes = 1000000;
int value = 1000;
int limit = 100;
for (int i = 0; i < testTimes; i++) {
int curLimit = (int) (Math.random() * limit) + 1;
MyMaxHeap my = new MyMaxHeap(curLimit);
RightMaxHeap test = new RightMaxHeap(curLimit);
int curOpTimes = (int) (Math.random() * limit);
for (int j = 0; j < curOpTimes; j++) {
if (my.isEmpty() != test.isEmpty()) {
System.out.println("Oops!");
}
if (my.isFull() != test.isFull()) {
System.out.println("Oops!");
}
if (my.isEmpty()) {
int curValue = (int) (Math.random() * value);
my.push(curValue);
test.push(curValue);
} else if (my.isFull()) {
if (my.pop() != test.pop()) {
System.out.println("Oops!");
}
} else {
if (Math.random() < 0.5) {
int curValue = (int) (Math.random() * value);
my.push(curValue);
test.push(curValue);
} else {
if (my.pop() != test.pop()) {
System.out.println("Oops!");
}
}
}
}
}
System.out.println("finish!");
}
}
堆排序
- 将数组变成大根堆
- 将[0]与[N-1]交换,然后堆缩小一个元素,以此循环
如果用户一次性给出了数组里的所有元素,且只要求数组变为大根堆结构,不用有序,则时间复杂度可以优化为:O(N)
package com.harrison.four;
import java.util.Arrays;
//堆排序
/**
*
* @author Harrison
* 1.先让整个数组都变成大根堆结构,建立堆的过程:
* 1)从上到小的方法heapify(),时间复杂度为O(N*logN)
* 2)从下到上的方法heapInsert(),时间复杂度为O(N)
* 2.把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,
* 时间复杂度为O(N*logN)
* 3.堆的大小减少成0之后,排序完成
*/
public class Code04_HeapSort {
// 堆排序额外空间复杂度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.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)
}
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 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 comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
heapSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
heapSort(arr);
printArray(arr);
}
}
与堆有关的题目
已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过K,并且K相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序
假设K=5,生成一个小根堆,先把K+1个数放在小根堆里,所以只有原始数组里0~5位置的数来到0位置上。然后从小根堆里弹出最小值放在0位置上,接下来1位置上的数只有可能是1 ~ 6位置上的数。依次类推
package com.harrison.four;
import java.util.PriorityQueue;
//与堆有关的题目
public class Code05_SortArrayDistanceLessK {
public void sortedArrayDistanceLessK(int [] arr, int k) {
//默认小根堆
PriorityQueue<Integer> heap=new PriorityQueue<>();
int index=0;
//0...k
//如果k比数组长度还大,选两个中较小的一个
for(; index<=Math.min(arr.length-1, k); index++) {
heap.add(arr[index]);
}
int i=0;
for(; index<arr.length; i++,index++) {
//添加和弹出谁先谁后都一样,因为题目 “每个元素移动的距离一定不超过K”
heap.add(arr[index]);
arr[i]=heap.poll();
}
while(!heap.isEmpty()) {
arr[i++]=heap.poll();//到后面数组没有数可以加了的话,那就只弹出并依次放好
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
语言提供的堆结构 VS 手写的堆结构
-
取决于,你有没有动态该信息的需求!
-
语言提供的堆结构,如果你动态改数据,不保证依然有序
-
手写堆结构,因为增加了对象的位置表,所以能够满足动态改信息的需求
package com.harrison.four;
import java.util.Comparator;
//用数组实现一个大根堆结构
//之后找出大根堆中的最大值并删除
//且剩下的数仍要求是一个大根堆结构
public class Code02_Heap {
public static class MyMaxHeap {
private int[] heap;// 用数组表示堆
private int limit;
private int heapSize;
public MyMaxHeap(int limit) {
heap = new int[limit];
this.limit = limit;
heapSize = 0;
}
public boolean isEmpty() {
return heapSize == 0;
}
public boolean isFull() {
return heapSize == limit;
}
public void push(int value) {
if (heapSize == limit) {
throw new RuntimeException("heap is full");
}
heap[heapSize] = value;
heapInsert(heap, heapSize++);
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 要求返回大根堆中的最大值,并且在大根堆中把最大值删除
// 且剩下的数,依然要求保持为大根堆结构
public int pop() {
int ans = heap[0];
// 将大根堆中最后的数与第一个数交换,且大根堆缩小一个数
swap(heap, 0, --heapSize);
// 从零位置开始寻找比自己大的孩子
heapify(heap, 0, heapSize);
return ans;
}
// 堆结构的两个关键操作:从某个位置开始往上看
// 动态地建立大根堆
// 如果收了N个数,时间复杂度为logN
private void heapInsert(int[] arr, int index) {
// arr[index]新进来的数,依次往上移动
// 移动到0位置或者比父节点小就不要往上移动
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
// 堆结构的两个关键操作:从某个位置开始往下调,时间复杂度logN
// 停:两个孩子都不比我大、没有孩子了
public void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;// 左孩子下标
// 如果有左孩子,没有有右孩子?有可能有有可能没有
// 如果没有左孩子,一定没有右孩子
while (left < heapSize) {
// 判断左孩子有没有越界,只在我们自己想象的范围里,而不是整个数组
// 把左右孩子中值大的孩子的下标给largest
// 右-> 1)有右孩子 && 右孩子比左孩子大
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
// index位置和较大孩子互换
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
}
// 不使用堆结构的暴力办法
public static class RightMaxHeap {
private int[] arr;// 用数组表示堆
private int limit;
private int size;
public RightMaxHeap(int limit) {
arr = new int[limit];
this.limit = limit;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == limit;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("heap is full");
}
arr[size++] = value;
}
public int pop() {
int maxIndex = 0;
for (int i = 0; i < size; i++) {
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
int ans = arr[maxIndex];
arr[maxIndex] = arr[--size];
return ans;
}
}
public static class Mycomparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public static void main(String[] args) {
int testTimes = 1000000;
int value = 1000;
int limit = 100;
for (int i = 0; i < testTimes; i++) {
int curLimit = (int) (Math.random() * limit) + 1;
MyMaxHeap my = new MyMaxHeap(curLimit);
RightMaxHeap test = new RightMaxHeap(curLimit);
int curOpTimes = (int) (Math.random() * limit);
for (int j = 0; j < curOpTimes; j++) {
if (my.isEmpty() != test.isEmpty()) {
System.out.println("Oops!");
}
if (my.isFull() != test.isFull()) {
System.out.println("Oops!");
}
if (my.isEmpty()) {
int curValue = (int) (Math.random() * value);
my.push(curValue);
test.push(curValue);
} else if (my.isFull()) {
if (my.pop() != test.pop()) {
System.out.println("Oops!");
}
} else {
if (Math.random() < 0.5) {
int curValue = (int) (Math.random() * value);
my.push(curValue);
test.push(curValue);
} else {
if (my.pop() != test.pop()) {
System.out.println("Oops!");
}
}
}
}
}
System.out.println("finish!");
}
}
比较器
- 比较器的实质就是重载比较运算符
- 比较器可以很好地应用在特殊标准的排序上
- 比较器可以很好地应用在特殊标准排序的结构上
- 写代码变得异常容易,还用于范型编程
package com.harrison.four;
import java.util.Arrays;
import java.util.Comparator;
//比较器
//任何比较器:
// compare方法里,遵循一个统一的规范:
// 返回负数的时候,认为第一个参数应该排在前面
// 返回正数的时候,认为第二个参数应该排在前面
// 返回0的时候,认为无所谓谁放前面
public class Code01_Comparator {
public static class Student {
public String name;
public int id;
public int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
public static class IdAscdingComparator implements Comparator<Student> {
// 返回负数的时候,第一个参数排在前面
// 返回正数的时候,第二个参数排在前面
// 返回0的时候,谁在前面无所谓
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
}
}
public static void printStudents(Student[] students) {
for (Student student : students) {
System.out.println("name:" + student.name + " id:" + student.id + " age:" + student.age);
}
// for (int i = 0; i < students.length; i++) {
// Student s = students[i];
// System.out.println(s.name + "," + s.id + "," + s.age);
// }
}
public static class IdShengAgeJiangOrder implements Comparator<Student> {
// 根据id从小到大,但是如果id一样,按照年龄从大到小
@Override
public int compare(Student o1, Student o2) {
return o1.id != o2.id ? (o1.id - o2.id) : (o2.age - o1.age);
}
}
public static class IdDescendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.id - o1.id;
}
}
// 先按照id排序,id小的,放前面;
// id一样,age大的,前面;
public static class IdInAgeDe implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id != o2.id ? o1.id - o2.id : (o2.age - o1.age);
}
}
public static void printArray(Integer[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Student student1 = new Student("A", 2, 20);
Student student2 = new Student("B", 3, 21);
Student student3 = new Student("C", 1, 22);
Student[] students = new Student[] {
student1, student2, student3 };
System.out.println("第一条打印");
Arrays.sort(students, new IdAscdingComparator());
printStudents(students);
}
}