当前位置: 首页 > news >正文

【算法笔记】堆和堆排序

1、比较器

比较器

  • 1)比较器的实质就是重载比较运算符
  • 2)比较器可以很好的应用在特殊标准的排序上
  • 3)比较器可以很好的应用在根据特殊标准排序的结构上
  • 4)写代码变得异常容易,还用于范型编程


    任何比较器的compare方法里,遵循一个统一的规范:
  • 返回负数的情况,就是o1比o2优先的情况,o1排前面
  • 返回正数的情况,就是o2比o1优先的情况,o2排前面
  • 返回0的情况,就是o1与o2同样优先的情况,谁排前面都无所谓
    java中的比较器接口如下:
    /*** java.util.Comparator接口如下:* 主要是要实现compare方法** @param <T>*/@FunctionalInterfacepublic interface Comparator<T> {int compare(T o1, T o2);}

示例一:整数的升序比较器写法

     /*** 整数升序比较器*/public static class IntegerASCComparator implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o1 - o2;}}

示例二:整数的降序比较器写法

    /*** 整数降序比较器*/public static class IntegerDESCComparator implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}}

2、堆

堆结构:

  • 1)堆结构就是用数组实现的完全二叉树结构,数组中从0出发的一段连续的数组,可以生成一个完全二叉树
  • 2)任意一个i位置,左孩子:2i+1,右孩子:2i+2;父节点位置:(i-1)/2
  • 3)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
  • 4)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
  • 5)堆结构的增大和减少要进行heapInsert(向上调整)与heapify(向下调整)操作
  • 6)优先级队列结构,就是堆结构

2.1、 java中堆的使用

  • java中的堆是用优先级队列java.util.PriorityQueue实现的
  • 1)默认是小根堆
  • 2)可以传入比较器,自定义堆的排序规则,比如传入逆序比较器,就成了大根堆
    使用示例:
    /*** java中的堆是用优先级队列java.util.PriorityQueue实现的* 1)默认是小根堆* 2)可以传入比较器,自定义堆的排序规则,比如传入逆序比较器,就成了大根堆*/public static void javaHeapTest() {//小跟堆PriorityQueue<Integer> minHeap = new PriorityQueue<>();minHeap.add(1);minHeap.add(2);minHeap.add(1);minHeap.peek();while (!minHeap.isEmpty()) {int temp = minHeap.poll();System.out.print(temp + " ");}System.out.println();// 大根堆PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntegerDESCComparator());maxHeap.add(1);maxHeap.add(2);maxHeap.add(1);maxHeap.peek();while (!maxHeap.isEmpty()) {int temp = maxHeap.poll();System.out.print(temp + " ");}}

2.2、大根堆实现

自定义大根堆:

  • 1)数组从0开始,0位置是堆顶
  • 2)任意一个i位置,左孩子:2i+1,右孩子:2i+2;父节点位置:(i-1)/2
  • 3)堆的增大和减少要进行heapInsert(向上调整)与heapify(向下调整)操作
    代码如下:
    /*** 自定义大根堆:* 1)数组从0开始,0位置是堆顶* 2)任意一个i位置,左孩子:2*i+1,右孩子:2*i+2;父节点位置:(i-1)/2* 3)堆的增大和减少要进行heapInsert(向上调整)与heapify(向下调整)操作*/public static class MaxHeap {// 保存数据的数组private int[] arr;// 数组最大长度private final int limit;// 数组当前元素个数,同时也是新加入的数字的下标,size-1是当前堆中最后一个元素的存储下标private int size;public MaxHeap(int limit) {this.limit = limit;this.arr = new int[limit];this.size = 0;}public boolean isEmpty() {return size == 0;}public boolean isFull() {return size == limit;}public void add(int val) {if (isFull()) {throw new RuntimeException("堆已满");}// 先把最后一个元素放到最后arr[size] = val;// 向上调整heapInsert(arr, size);// size加一,也可以在调整时用heapInsert(arr,size++)完成size++;}public int peer() {if (isEmpty()) {throw new RuntimeException("堆为空");}return arr[0];}/*** 弹出堆顶元素* 1) 先保存需要弹出的堆顶元素* 2)将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1* 3)从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部*/public int poll() {if (isEmpty()) {throw new RuntimeException("堆为空");}// 先保存需要弹出的堆顶元素int ans = arr[0];// 将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1swap(arr, 0, --size);// 从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部heapify(arr, 0, size);return ans;}/*** 向下调整:* 将指定下标index的数字向下调整,直到最终的下标为maxIndex的节点,不包含maxIndex* 过程:* 1)判断当前节点是否有左孩子,如果没有左孩子,直接返回,因为堆中必然有左孩子* 2)将左右孩子中比较大的那个和当前节点交换* 4)交换后,index变成交换的孩子的下标,继续向下调整*/private void heapify(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {// 定义一个largetIndex的变量,将左右孩子中值最大的下标赋值给largestIndex// left + 1 < maxIndex 是判断有没有右孩子int largestIndex = (left + 1 < maxIndex) && (arr[left + 1] > arr[left]) ? left + 1 : left;// 比较孩子中的最大的值是不是比当前值大,大的话需要调整,不大的话就可以结束调整if (arr[largestIndex] <= arr[index]) {break;}// 交换swap(arr, index, largestIndex);// index变成交换的孩子的下标index = largestIndex;// 重新计算左孩子的下标left = index * 2 + 1;}}/*** 向上调整:* 将指定下标index的数字向上调整,直到最终的下标为0的节点* index的父节点为(index-1)/2*/private void heapInsert(int[] arr, int index) {// 这里不需要判断index是否为0,因为当index为0的时候,(index -1)/2也为0,arr[0] 和arr[0]是同一个数,循环会退出while (arr[index] > arr[(index - 1) / 2]) {// 交换swap(arr, index, (index - 1) / 2);// 下标变成父节点的下标index = (index - 1) / 2;}}}

2.3、小根堆实现

  • 小根堆的写法和步骤都是和大根堆一样的,只是在调整判断的时候,做相反的判断即可
    代码如下:
    /*** 小根堆:* 小根堆的写法和步骤都是和大根堆一样的,只是在调整判断的时候,做相反的判断即可*/public static class MinHeap {// 保存数据的数组private int[] arr;// 数组最大长度private final int limit;// 数组当前元素个数,同时也是新加入的数字的下标,size-1是当前堆中最后一个元素的存储下标private int size;public MinHeap(int limit) {this.limit = limit;this.arr = new int[limit];this.size = 0;}public boolean isEmpty() {return size == 0;}public boolean isFull() {return size == limit;}public void add(int val) {if (isFull()) {throw new RuntimeException("堆已满");}// 先把最后一个元素放到最后arr[size] = val;// 向上调整heapInsert(arr, size);// size加一,也可以在调整时用heapInsert(arr,size++)完成size++;}public int peer() {if (isEmpty()) {throw new RuntimeException("堆为空");}return arr[0];}/*** 弹出堆顶元素* 1) 先保存需要弹出的堆顶元素* 2)将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1* 3)从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部*/public int poll() {if (isEmpty()) {throw new RuntimeException("堆为空");}// 先保存需要弹出的堆顶元素int ans = arr[0];// 将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1swap(arr, 0, --size);// 从堆顶往下调整,将孩子中比较小的调整到父节点位置,直到到达底部heapify(arr, 0, size);return ans;}/*** 向下调整:* 将指定下标index的数字向下调整,直到最终的下标为maxIndex的节点,不包含maxIndex* 过程:* 1)判断当前节点是否有左孩子,如果没有左孩子,直接返回,因为堆中必然有左孩子* 2)将左右孩子中比较小的那个和当前节点交换* 4)交换后,index变成交换的孩子的下标,继续向下调整*/private void heapify(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {// 定义一个mixIndex的变量,将左右孩子中值最小的下标赋值给mixIndex// left + 1 < maxIndex 是判断有没有右孩子int mixIndex = (left + 1 < maxIndex) && (arr[left + 1] < arr[left]) ? left + 1 : left;// 比较孩子中的最大的值是不是比当前值大,大的话需要调整,不大的话就可以结束调整if (arr[mixIndex] > arr[index]) {break;}// 交换swap(arr, index, mixIndex);// index变成交换的孩子的下标index = mixIndex;// 重新计算左孩子的下标left = index * 2 + 1;}}/*** 向上调整:* 将指定下标index的数字向上调整,直到最终的下标为0的节点* index的父节点为(index-1)/2*/private void heapInsert(int[] arr, int index) {// 这里不需要判断index是否为0,因为当index为0的时候,(index -1)/2也为0,arr[0] 和arr[0]是同一个数,循环会退出while (arr[index] < arr[(index - 1) / 2]) {// 交换swap(arr, index, (index - 1) / 2);// 下标变成父节点的下标index = (index - 1) / 2;}}}

自定义的大根堆和小根堆的测试完整代码如下:

import java.util.Comparator;
import java.util.PriorityQueue;/*** 堆结构:* 1)堆结构就是用数组实现的完全二叉树结构,数组中从0出发的一段连续的数组,可以生成一个完全二叉树* 2)任意一个i位置,左孩子:2*i+1,右孩子:2*i+2;父节点位置:(i-1)/2* 3)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆* 4)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆* 5)堆结构的增大和减少要进行heapInsert(向上调整)与heapify(向下调整)操作* 6)优先级队列结构,就是堆结构*/
public class Heap {/*** java中的堆是用优先级队列java.util.PriorityQueue实现的* 1)默认是小根堆* 2)可以传入比较器,自定义堆的排序规则,比如传入逆序比较器,就成了大根堆*/public static void javaHeapTest() {//小跟堆PriorityQueue<Integer> minHeap = new PriorityQueue<>();minHeap.add(1);minHeap.add(2);minHeap.add(1);minHeap.peek();while (!minHeap.isEmpty()) {int temp = minHeap.poll();System.out.print(temp + " ");}System.out.println();// 大根堆PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntegerDESCComparator());maxHeap.add(1);maxHeap.add(2);maxHeap.add(1);maxHeap.peek();while (!maxHeap.isEmpty()) {int temp = maxHeap.poll();System.out.print(temp + " ");}}/*** 自定义大根堆:* 1)数组从0开始,0位置是堆顶* 2)任意一个i位置,左孩子:2*i+1,右孩子:2*i+2;父节点位置:(i-1)/2* 3)堆的增大和减少要进行heapInsert(向上调整)与heapify(向下调整)操作*/public static class MaxHeap {// 保存数据的数组private int[] arr;// 数组最大长度private final int limit;// 数组当前元素个数,同时也是新加入的数字的下标,size-1是当前堆中最后一个元素的存储下标private int size;public MaxHeap(int limit) {this.limit = limit;this.arr = new int[limit];this.size = 0;}public boolean isEmpty() {return size == 0;}public boolean isFull() {return size == limit;}public void add(int val) {if (isFull()) {throw new RuntimeException("堆已满");}// 先把最后一个元素放到最后arr[size] = val;// 向上调整heapInsert(arr, size);// size加一,也可以在调整时用heapInsert(arr,size++)完成size++;}public int peer() {if (isEmpty()) {throw new RuntimeException("堆为空");}return arr[0];}/*** 弹出堆顶元素* 1) 先保存需要弹出的堆顶元素* 2)将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1* 3)从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部*/public int poll() {if (isEmpty()) {throw new RuntimeException("堆为空");}// 先保存需要弹出的堆顶元素int ans = arr[0];// 将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1swap(arr, 0, --size);// 从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部heapify(arr, 0, size);return ans;}/*** 向下调整:* 将指定下标index的数字向下调整,直到最终的下标为maxIndex的节点,不包含maxIndex* 过程:* 1)判断当前节点是否有左孩子,如果没有左孩子,直接返回,因为堆中必然有左孩子* 2)将左右孩子中比较大的那个和当前节点交换* 4)交换后,index变成交换的孩子的下标,继续向下调整*/private void heapify(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {// 定义一个largetIndex的变量,将左右孩子中值最大的下标赋值给largestIndex// left + 1 < maxIndex 是判断有没有右孩子int largestIndex = (left + 1 < maxIndex) && (arr[left + 1] > arr[left]) ? left + 1 : left;// 比较孩子中的最大的值是不是比当前值大,大的话需要调整,不大的话就可以结束调整if (arr[largestIndex] <= arr[index]) {break;}// 交换swap(arr, index, largestIndex);// index变成交换的孩子的下标index = largestIndex;// 重新计算左孩子的下标left = index * 2 + 1;}}/*** 向上调整:* 将指定下标index的数字向上调整,直到最终的下标为0的节点* index的父节点为(index-1)/2*/private void heapInsert(int[] arr, int index) {// 这里不需要判断index是否为0,因为当index为0的时候,(index -1)/2也为0,arr[0] 和arr[0]是同一个数,循环会退出while (arr[index] > arr[(index - 1) / 2]) {// 交换swap(arr, index, (index - 1) / 2);// 下标变成父节点的下标index = (index - 1) / 2;}}}/*** 小根堆:* 小根堆的写法和步骤都是和大根堆一样的,只是在调整判断的时候,做相反的判断即可*/public static class MinHeap {// 保存数据的数组private int[] arr;// 数组最大长度private final int limit;// 数组当前元素个数,同时也是新加入的数字的下标,size-1是当前堆中最后一个元素的存储下标private int size;public MinHeap(int limit) {this.limit = limit;this.arr = new int[limit];this.size = 0;}public boolean isEmpty() {return size == 0;}public boolean isFull() {return size == limit;}public void add(int val) {if (isFull()) {throw new RuntimeException("堆已满");}// 先把最后一个元素放到最后arr[size] = val;// 向上调整heapInsert(arr, size);// size加一,也可以在调整时用heapInsert(arr,size++)完成size++;}public int peer() {if (isEmpty()) {throw new RuntimeException("堆为空");}return arr[0];}/*** 弹出堆顶元素* 1) 先保存需要弹出的堆顶元素* 2)将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1* 3)从堆顶往下调整,将孩子中比较大的调整到父节点位置,直到到达底部*/public int poll() {if (isEmpty()) {throw new RuntimeException("堆为空");}// 先保存需要弹出的堆顶元素int ans = arr[0];// 将堆顶元素与最后一个元素交换,表示逻辑删除最后一个元素,同时size-1swap(arr, 0, --size);// 从堆顶往下调整,将孩子中比较小的调整到父节点位置,直到到达底部heapify(arr, 0, size);return ans;}/*** 向下调整:* 将指定下标index的数字向下调整,直到最终的下标为maxIndex的节点,不包含maxIndex* 过程:* 1)判断当前节点是否有左孩子,如果没有左孩子,直接返回,因为堆中必然有左孩子* 2)将左右孩子中比较小的那个和当前节点交换* 4)交换后,index变成交换的孩子的下标,继续向下调整*/private void heapify(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {// 定义一个mixIndex的变量,将左右孩子中值最小的下标赋值给mixIndex// left + 1 < maxIndex 是判断有没有右孩子int mixIndex = (left + 1 < maxIndex) && (arr[left + 1] < arr[left]) ? left + 1 : left;// 比较孩子中的最大的值是不是比当前值大,大的话需要调整,不大的话就可以结束调整if (arr[mixIndex] > arr[index]) {break;}// 交换swap(arr, index, mixIndex);// index变成交换的孩子的下标index = mixIndex;// 重新计算左孩子的下标left = index * 2 + 1;}}/*** 向上调整:* 将指定下标index的数字向上调整,直到最终的下标为0的节点* index的父节点为(index-1)/2*/private void heapInsert(int[] arr, int index) {// 这里不需要判断index是否为0,因为当index为0的时候,(index -1)/2也为0,arr[0] 和arr[0]是同一个数,循环会退出while (arr[index] < arr[(index - 1) / 2]) {// 交换swap(arr, index, (index - 1) / 2);// 下标变成父节点的下标index = (index - 1) / 2;}}}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[] arr = generateRandomArr(maxSize, maxValue);if (arr.length == 0) {continue;}// 测试大根堆// 系统的大根堆大根堆PriorityQueue<Integer> maxHeapComparator = new PriorityQueue<>(new IntegerDESCComparator());MaxHeap maxHeap = new MaxHeap(arr.length);// 将数组加入到两个堆中for (int j = 0; j < arr.length; j++) {maxHeapComparator.add(arr[j]);maxHeap.add(arr[j]);}while (!maxHeapComparator.isEmpty()) {if (maxHeap.isEmpty() || maxHeapComparator.poll() != maxHeap.poll()) {succeed = false;System.out.println("测试大根堆失败!原数组:");printArray(arr);break;}}if (!succeed) {break;}// 测试小根堆PriorityQueue<Integer> minHeapComparator = new PriorityQueue<>();MinHeap minHeap = new MinHeap(arr.length);// 将数组加入到两个堆中for (int j = 0; j < arr.length; j++) {minHeapComparator.add(arr[j]);minHeap.add(arr[j]);}while (!minHeapComparator.isEmpty()) {if (minHeap.isEmpty() || minHeapComparator.poll() != minHeap.poll()) {succeed = false;System.out.println("测试小根堆失败!原数组:");printArray(arr);break;}}if (!succeed) {break;}}System.out.println(succeed ? "successful!" : "error!");}public static int[] generateRandomArr(int maxSize, int maxValue) {// Math.random() -> [0,1) 所有的小数,等概率返回一个// Math.random() * N -> [0,N) 所有小数,等概率返回一个// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个// (int)(Math.random() * (maxSize + 1)) -> [0,maxSize] 所有的整数,等概率返回一个int size = (int) (Math.random() * (maxSize + 1));int[] arr = new int[size];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);}return arr;}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 class IntegerDESCComparator implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}}public static void swap(int[] arr, int i, int j) {if (i == j) return;arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}
}

3、整个数组建堆方式

整个数组的建堆方式不是一个一个数据加入,而是依次把一个数组都给你,将其调整成大根堆或者小根堆,其有两种方式:自上而下和自下而上,常用的是自下而上,因为其时间复杂度更好。

3.1、 从上往下建堆

        // 整个数组调整成堆的方式一(从上往下降建堆):// 从0开始加入,数据先加入到堆尾,用heapInsert向上,时间复杂度O(N*logN)for (int i = 0; i < arr.length; i++) { // O(N)heapInsert(arr, i); // O(logN)}

3.2、从下往上建堆

        //  整个数组调整成的方式二(从下往上建堆)://  从最后一个数arr.length - 1开始,数据相当于加入到堆顶,用heapify向下,时间复杂度O(N)for (int i = arr.length - 1; i >= 0; i--) {heapify(arr, i, arr.length);}

4、堆排序

堆排序:利用堆结构实现排序

4.1、大根堆实现

大根堆实现排序(常用的堆排序方式)

  • 1,先让整个数组都变成大根堆结构,建立堆的过程:
  •    1) 从上到下的方法,时间复杂度为O(N*logN)
  •    2) 从下到上的方法,时间复杂度为O(N)
  • 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)
  • 3,堆的大小减小成0之后,排序完成
  • 时间复杂度O(N*logN),额外空间复杂度O(1)
    代码如下:
    /*** 大根堆实现排序(常用的堆排序方式)* 1,先让整个数组都变成大根堆结构,建立堆的过程:* 1)从上到下的方法,时间复杂度为O(N*logN)* 2)从下到上的方法,时间复杂度为O(N)* 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)* 3,堆的大小减小成0之后,排序完成* <p>* 时间复杂度O(N*logN),额外空间复杂度O(1)*/public static void heapSortWithMaxHep(int[] arr) {if (arr == null || arr.length < 2) {return;}// 1,先把数组调整成堆,用从下往上建堆的方式for (int i = arr.length - 1; i >= 0; i--) {heapifyMaxHeap(arr, i, arr.length);}// 2、将堆顶的最大值与最后一个交换,然后重新调整堆int heapSize = arr.length; // 定义一个堆大小的变量,同时heapSize-1的下标就是需要交换的最右侧的位置swap(arr, 0, --heapSize);while (heapSize > 0) {heapifyMaxHeap(arr, 0, heapSize);swap(arr, 0, --heapSize);}}/*** 大根堆的向下调整*/private static void heapifyMaxHeap(int[] arr, int index, int maxSize) {int left = index * 2 + 1;while (left < maxSize) {int largest = (left + 1 < maxSize && arr[left + 1] > arr[left]) ? left + 1 : left;if (arr[largest] <= arr[index]) {break;}swap(arr, index, largest);index = largest;left = index * 2 + 1;}}

4.2、小根堆实现

小根堆实现排序

  • 1、先从下往上建小顶堆,这是后0位置就是最小的数
  • 2、将最后一个元素与0位置的元素交换,重新调整小根堆
  • 3、这样得到的逆序,然后再逆序一次,就是顺序的了


    小顶堆排序一定要注意:
  • 1、整体建完小顶堆以后,不能直接从1位置开始重新调整小根堆,因为这就破坏了父子下标的关系,这样出来是错的
  • 2、小根堆如果将最后一个元素与0位置的元素交换,重新调整小根堆,这样得到的是逆序
  • 3、常见的堆排序是大根堆的排序(因为少一步逆序的过程),这里用小根堆只是自己练习一下
  • 4、小根堆排序的时间复杂度是O(N*logN),额外空间复杂度是O(1)
    代码如下:
    /*** 小根堆实现排序* 1、先从下往上建小顶堆,这是后0位置就是最小的数* 2、将最后一个元素与0位置的元素交换,重新调整小根堆* 3、这样得到的逆序,然后再逆序一次,就是顺序的了* 小顶堆排序一定要注意:* 1、整体建完小顶堆以后,不能直接从1位置开始重新调整小根堆,因为这就破坏了父子下标的关系,这样出来是错的* 2、小根堆如果将最后一个元素与0位置的元素交换,重新调整小根堆,这样得到的是逆序* 3、常见的堆排序是大根堆的排序(因为少一步逆序的过程),这里用小根堆只是自己练习一下* 4、小根堆排序的时间复杂度是O(N*logN),额外空间复杂度是O(1)*/public static void heapSortWithMinHep(int[] arr) {if (arr == null || arr.length < 2) {return;}// 1、先从下往上建小顶堆for (int i = arr.length - 1; i >= 0; i--) {heapifyMinHeap(arr, i, arr.length);}// 2、将最后一个元素与0位置的元素交换,重新调整小根堆int heapSize = arr.length;swap(arr, 0, --heapSize);while (heapSize > 0) {heapifyMinHeap(arr, 0, heapSize);swap(arr, 0, --heapSize);}// 3、逆序一次int start = 0;int end = arr.length - 1;while (start < end) {swap(arr, start++, end--);}}public static void heapifyMinHeap(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {int smallest = (left + 1 < maxIndex && arr[left + 1] < arr[left]) ? left + 1 : left;if (arr[smallest] > arr[index]) {break;}swap(arr, index, smallest);index = smallest;left = index * 2 + 1;}}

整个堆排序和测试代码如下:

import java.util.Arrays;/*** 堆排序* 利用堆结构实现排序*/
public class HeapSort {/*** 数组转换为堆的两种方式*/public static void arrToHeap(int[] arr) {// 整个数组调整成堆的方式一(从上往下降建堆):// 从0开始加入,数据先加入到堆尾,用heapInsert向上,时间复杂度O(N*logN)
//        for (int i = 0; i < arr.length; i++) { // O(N)
//            heapInsert(arr, i); // O(logN)
//        }//  整个数组调整成的方式二(从下往上建堆)://  从最后一个数arr.length - 1开始,数据相当于加入到堆顶,用heapify向下,时间复杂度O(N)
//        for (int i = arr.length - 1; i >= 0; i--) {
//            heapify(arr, i, arr.length);
//        }// 整个数组从下往上建堆的方式时间复杂度更低,常用在堆排序中}/*** 大根堆实现排序(常用的堆排序方式)* 1,先让整个数组都变成大根堆结构,建立堆的过程:* 1)从上到下的方法,时间复杂度为O(N*logN)* 2)从下到上的方法,时间复杂度为O(N)* 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)* 3,堆的大小减小成0之后,排序完成* <p>* 时间复杂度O(N*logN),额外空间复杂度O(1)*/public static void heapSortWithMaxHep(int[] arr) {if (arr == null || arr.length < 2) {return;}// 1,先把数组调整成堆,用从下往上建堆的方式for (int i = arr.length - 1; i >= 0; i--) {heapifyMaxHeap(arr, i, arr.length);}// 2、将堆顶的最大值与最后一个交换,然后重新调整堆int heapSize = arr.length; // 定义一个堆大小的变量,同时heapSize-1的下标就是需要交换的最右侧的位置swap(arr, 0, --heapSize);while (heapSize > 0) {heapifyMaxHeap(arr, 0, heapSize);swap(arr, 0, --heapSize);}}/*** 大根堆的向下调整*/private static void heapifyMaxHeap(int[] arr, int index, int maxSize) {int left = index * 2 + 1;while (left < maxSize) {int largest = (left + 1 < maxSize && arr[left + 1] > arr[left]) ? left + 1 : left;if (arr[largest] <= arr[index]) {break;}swap(arr, index, largest);index = largest;left = index * 2 + 1;}}/*** 小根堆实现排序* 1、先从下往上建小顶堆,这是后0位置就是最小的数* 2、将最后一个元素与0位置的元素交换,重新调整小根堆* 3、这样得到的逆序,然后再逆序一次,就是顺序的了* 小顶堆排序一定要注意:* 1、整体建完小顶堆以后,不能直接从1位置开始重新调整小根堆,因为这就破坏了父子下标的关系,这样出来是错的* 2、小根堆如果将最后一个元素与0位置的元素交换,重新调整小根堆,这样得到的是逆序* 3、常见的堆排序是大根堆的排序(因为少一步逆序的过程),这里用小根堆只是自己练习一下* 4、小根堆排序的时间复杂度是O(N*logN),额外空间复杂度是O(1)*/public static void heapSortWithMinHep(int[] arr) {if (arr == null || arr.length < 2) {return;}// 1、先从下往上建小顶堆for (int i = arr.length - 1; i >= 0; i--) {heapifyMinHeap(arr, i, arr.length);}// 2、将最后一个元素与0位置的元素交换,重新调整小根堆int heapSize = arr.length;swap(arr, 0, --heapSize);while (heapSize > 0) {heapifyMinHeap(arr, 0, heapSize);swap(arr, 0, --heapSize);}// 3、逆序一次int start = 0;int end = arr.length - 1;while (start < end) {swap(arr, start++, end--);}}public static void heapifyMinHeap(int[] arr, int index, int maxIndex) {int left = index * 2 + 1;while (left < maxIndex) {int smallest = (left + 1 < maxIndex && arr[left + 1] < arr[left]) ? left + 1 : left;if (arr[smallest] > arr[index]) {break;}swap(arr, index, smallest);index = smallest;left = index * 2 + 1;}}/*** 用对数器的方法测试*/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[] arr = generateRandomArr(maxSize, maxValue);// 复制一个数组,用来做对照int[] arr1 = copyArr(arr);int[] arr2 = copyArr(arr);int[] arr3 = copyArr(arr);// 大根堆的方式de堆排序heapSortWithMaxHep(arr1);// 系统排序,比较器sortComparator(arr3);if (!isEqual(arr1, arr3)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("大根堆排序后:");printArray(arr1);System.out.println("系统排序后:");printArray(arr3);break;}// 小根堆的方式排序heapSortWithMinHep(arr2);if (!isEqual(arr2, arr3)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("小根堆排序后:");printArray(arr2);System.out.println("系统排序后:");printArray(arr3);break;}}System.out.println(succeed ? "successful!" : "error!");}/*** 生成随机数组* 长度是[0,maxSize]* 每个值是[-maxValue,maxValue]** @param maxSize  数组最大长度* @param maxValue 数组最大值* @return 随机数组*/public static int[] generateRandomArr(int maxSize, int maxValue) {// Math.random() -> [0,1) 所有的小数,等概率返回一个// Math.random() * N -> [0,N) 所有小数,等概率返回一个// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个// (int)(Math.random() * (maxSize + 1)) -> [0,maxSize] 所有的整数,等概率返回一个int size = (int) (Math.random() * (maxSize + 1));int[] arr = new int[size];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);}return arr;}/*** 复制数组** @param arr 原数组* @return 复制数组*/public static int[] copyArr(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;}/*** 比较数组是否相等** @param arr1 数组1* @param arr2 数组2* @return 是否相等*/public static boolean isEqual(int[] arr1, int[] arr2) {if (arr1 == null && arr2 == null) {return true;}if (arr1 == null || arr2 == null) {return false;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}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();}/*** 获取的系统排序的对数器,用来验证自己的排序是不是正确的参照** @param arr 数组*/public static void sortComparator(int[] arr) {Arrays.sort(arr);}public static void swap(int[] arr, int i, int j) {if (i == j) return;arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}}

5、堆排序题目:

5.1、移动距离不超过k的排序

移动距离不超过k的排序

  • 已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。
  • 请选择一个合适的排序策略,对这个数组进行排序。


    思路:
    1. 先把0…K-1这K个数放到小根堆中
    1. 小根堆堆顶元素弹出,放到0位置,然后把K位置的数放到小根堆中
    1. 重复2步骤,直到小根堆为空
    /*** 这里直接用了java自带的堆,也可以用自己写的*/public static void sortedArrDistanceLessK(int[] arr, int k) {if (arr == null || arr.length < 2) {return;}// 1. 先把0...K-1这K个数放到小根堆中// 默认小根堆PriorityQueue<Integer> heap = new PriorityQueue<>();int index = 0;for (; index <= Math.min(arr.length - 1, k); index++) {heap.add(arr[index]);}// 2. 小根堆堆顶元素弹出,放到0位置,然后把K位置的数放到小根堆中int i = 0;for (; index < arr.length; i++, index++) {arr[i] = heap.poll();heap.add(arr[index]);}// 3. 重复2步骤弹出最小值,直到小根堆为空while (!heap.isEmpty()) {arr[i++] = heap.poll();}}

完整的测试代码如下:

import java.util.Arrays;
import java.util.PriorityQueue;/*** 移动距离不超过k的排序* 已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。* 请选择一个合适的排序策略,对这个数组进行排序。* <p>* 思路:* 1. 先把0...K-1这K个数放到小根堆中* 2. 小根堆堆顶元素弹出,放到0位置,然后把K位置的数放到小根堆中* 3. 重复2步骤,直到小根堆为空*/
public class DistanceLessKArrSort {/*** 这里直接用了java自带的堆,也可以用自己写的*/public static void sortedArrDistanceLessK(int[] arr, int k) {if (arr == null || arr.length < 2) {return;}// 1. 先把0...K-1这K个数放到小根堆中// 默认小根堆PriorityQueue<Integer> heap = new PriorityQueue<>();int index = 0;for (; index <= Math.min(arr.length - 1, k); index++) {heap.add(arr[index]);}// 2. 小根堆堆顶元素弹出,放到0位置,然后把K位置的数放到小根堆中int i = 0;for (; index < arr.length; i++, index++) {arr[i] = heap.poll();heap.add(arr[index]);}// 3. 重复2步骤弹出最小值,直到小根堆为空while (!heap.isEmpty()) {arr[i++] = heap.poll();}}/*** 用对数器的方法测试*/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 k = (int) (Math.random() * maxSize) + 1;// 先生成一个随机的数组int[] arr = randomArrayNoMoveMoreK(maxSize, maxValue, k);// 复制一个数组,用来做对照int[] arr1 = copyArr(arr);int[] arr2 = copyArr(arr);// 自定义的排序sortedArrDistanceLessK(arr1, k);// 系统排序,比较器sortComparator(arr2);if (!isEqual(arr1, arr2)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("根堆排序后:");printArray(arr1);System.out.println("系统排序后:");printArray(arr2);break;}}System.out.println(succeed ? "successful!" : "error!");}/*** 生成随机数组* 长度是[0,maxSize]* 每个值是[-maxValue,maxValue]*/public static int[] randomArrayNoMoveMoreK(int maxSize, int maxValue, int K) {// 先随机生成一个数组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());}// 先排个序Arrays.sort(arr);// 然后开始随意交换,但是保证每个数距离不超过K// swap[i] == true, 表示i位置已经参与过交换// swap[i] == false, 表示i位置没有参与过交换boolean[] isSwap = new boolean[arr.length];for (int i = 0; i < arr.length; i++) {int j = Math.min(i + (int) (Math.random() * (K + 1)), arr.length - 1);if (!isSwap[i] && !isSwap[j]) {isSwap[i] = true;isSwap[j] = true;int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}}return arr;}/*** 复制数组** @param arr 原数组* @return 复制数组*/public static int[] copyArr(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;}/*** 比较数组是否相等** @param arr1 数组1* @param arr2 数组2* @return 是否相等*/public static boolean isEqual(int[] arr1, int[] arr2) {if (arr1 == null && arr2 == null) {return true;}if (arr1 == null || arr2 == null) {return false;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {System.out.printf("index:%d,arr[1]:%d,arr[2]:%d\n", i, arr1[i], arr2[i]);return false;}}return true;}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();}/*** 获取的系统排序的对数器,用来验证自己的排序是不是正确的参照** @param arr 数组*/public static void sortComparator(int[] arr) {Arrays.sort(arr);}public static void swap(int[] arr, int i, int j) {if (i == j) return;arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}
}

后记
个人学习总结笔记,不能保证非常详细,轻喷


文章转载自:

http://WlVtScuA.fpqsd.cn
http://peUPu3Ga.fpqsd.cn
http://0y67XtFB.fpqsd.cn
http://jlSDO0wc.fpqsd.cn
http://d94dp665.fpqsd.cn
http://XbfINf59.fpqsd.cn
http://KoDo1Wzm.fpqsd.cn
http://IzqDKHP7.fpqsd.cn
http://xOIHgHLB.fpqsd.cn
http://wgTATbAD.fpqsd.cn
http://SPHMUjZB.fpqsd.cn
http://X4a8KEFV.fpqsd.cn
http://uThl2VNM.fpqsd.cn
http://8ijAMJAO.fpqsd.cn
http://ibgU2eJB.fpqsd.cn
http://qrc7qGxk.fpqsd.cn
http://w8FiMEHd.fpqsd.cn
http://aciLfisb.fpqsd.cn
http://4sFtVxTl.fpqsd.cn
http://vsq4ZHmN.fpqsd.cn
http://2lDjDxXm.fpqsd.cn
http://n1xUWuSw.fpqsd.cn
http://jMPLvsH8.fpqsd.cn
http://ADoMuF9I.fpqsd.cn
http://Zk7yTcE4.fpqsd.cn
http://oxq0XVRd.fpqsd.cn
http://UnC4IYWh.fpqsd.cn
http://9dghA2tD.fpqsd.cn
http://MW7VyW1Q.fpqsd.cn
http://8agwaGnb.fpqsd.cn
http://www.dtcms.com/a/382765.html

相关文章:

  • 电商导购系统的微服务监控体系:基于Prometheus与Grafana的可视化方案
  • fMoE论文阅读笔记
  • 721SJBH笔记本电脑销售网站
  • k3s集群部署(使用外部etcd集群)
  • 京东返利app的分布式ID生成策略:雪花算法在订单系统中的实践
  • 大数据分析岗位发展前景与行业需求分析
  • 【Linux手册】共享内存:零拷贝实现共享的优势与实操指南
  • ARM的TrustZone
  • 返利app排行榜的缓存更新策略:基于过期时间与主动更新的混合方案
  • springboot+zookeeper+(2025最新)Dubbo-admin实现分布式
  • 缓存与数据库一致性实战手册:从故障修复到架构演进
  • 基于 Linux 内核模块的字符设备 FIFO 驱动设计与实现解析(C/C++代码实现)
  • 【C++】类和对象(下):初始化列表、类型转换、Static、友元、内部类、匿名对象/有名对象、优化
  • JSON、Ajax
  • 第2课:Agent系统架构与设计模式
  • Python上下文管理器进阶指南:不仅仅是with语句
  • Entities - Entity 的创建模式
  • 用html5写王者荣耀之王者坟墓的游戏2deepseek版
  • 【Wit】pure-admin后台管理系统前端与FastAPI后端联调通信实例
  • godot+c#使用godot-sqlite连接数据库
  • 【pure-admin】pureadmin的登录对接后端
  • tcpump | 深入探索网络抓包工具
  • scikit-learn 分层聚类算法详解
  • Kafka面试精讲 Day 18:磁盘IO与网络优化
  • javaweb CSS
  • css`min()` 、`max()`、 `clamp()`
  • 超越平面交互:SLAM技术如何驱动MR迈向空间计算时代?诠视科技以算法引领变革
  • Win11桌面的word文件以及PPT文件变为白色,但是可以正常打开,如何修复
  • 【系统架构设计(31)】操作系统下:存储、设备与文件管理
  • Flask学习笔记(三)--URL构建与模板的使用