11、堆

内容来自刘宇波老师算法与数据结构体系课

1、堆的结构

image

2、建堆方式

1、将 n 个元素逐个插⼊到⼀个空堆中,算法复杂度是 O(N * logN)
2、heapify:从最后一个非叶子节点开始,从后向前,倒序进行 siftDown 操作,算法复杂度为 O(n)

image

3、实现最大堆

/**
 * 二叉堆是是一棵完全二叉树
 * 最大堆: 所有节点都大于等于孩子节点
 */
public class MaxHeap<E extends Comparable<E>> {

    private Array<E> data;

    public MaxHeap() {
        data = new Array<>();
    }

    public MaxHeap(int capacity) {
        data = new Array<>(capacity);
    }

    public MaxHeap(E[] arr) {
        heapify(arr);
    }

    public int size() {
        return data.getSize();
    }

    public boolean isEmpty() {
        return data.isEmpty();
    }

    /**
     * 返回完全二叉树的数组表示中, 一个索引所表示的元素的父亲节点的索引
     */
    private int parent(int index) {
        if (index == 0) throw new IllegalArgumentException("index-0 doesn't have parent.");
        return (index - 1) / 2;
    }

    /**
     * 返回完全二叉树的数组表示中, 一个索引所表示的元素的左孩子节点的索引
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 返回完全二叉树的数组表示中, 一个索引所表示的元素的右孩子节点的索引
     */
    private int rightChild(int index) {
        return index * 2 + 2;
    }

    /**
     * 上浮: O(logN)
     */
    private void siftUp(int index) {
        while (index > 0 && data.get(index).compareTo(data.get(parent(index))) > 0) {
            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    /**
     * 下沉: O(logN)
     */
    private void siftDown(int index) {
        while (leftChild(index) < data.getSize()) {
            int bigger = leftChild(index);
            if (bigger + 1 < data.getSize() && data.get(bigger + 1).compareTo(data.get(bigger)) > 0) bigger++;

            if (data.get(index).compareTo(data.get(bigger)) >= 0) break;
            data.swap(index, bigger);
            index = bigger;
        }
    }

    /**
     * 将任意数组整理成堆的形状: O(n)
     * 最后一个节点的父节点就是最后一个非叶子节点
     */
    private void heapify(E[] arr) {
        data = new Array<>(arr);
        for (int i = parent(data.getSize() - 1); i >= 0; i--) siftDown(i);
    }

    /**
     * O(logN)
     */
    public void add(E e) {
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    /**
     * 查看堆中的最大元素
     */
    public E findMax() {
        if (isEmpty()) throw new RuntimeException("Heap is empty!");
        return data.get(0);
    }

    /**
     * 取出堆中的最大元素: O(logN)
     */
    public E extractMax() {
        E max = findMax();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return max;
    }

    /**
     * 取出堆中的最大元素, 并替换为一个新元素
     */
    public E replace(E e) {
        E max = findMax();
        data.set(0, e);
        siftDown(0);
        return max;
    }
}

4、实现最小堆

/**
 * 二叉堆是是一棵完全二叉树
 * 最大堆: 所有节点都小于等于孩子节点
 */
public class MinHeap<E extends Comparable<E>> {

    private Array<E> data;

    public MinHeap() {
        data = new Array<>();
    }

    public MinHeap(int capacity) {
        data = new Array<>(capacity);
    }

    public MinHeap(E[] arr) {
        heapify(arr);
    }

    public int size() {
        return data.getSize();
    }

    public boolean isEmpty() {
        return data.isEmpty();
    }

    private int parent(int index) {
        if (index == 0) throw new IllegalArgumentException("index-0 doesn't have parent.");
        return (index - 1) / 2;
    }

    private int leftChild(int index) {
        return index * 2 + 1;
    }

    private int rightChild(int index) {
        return index * 2 + 2;
    }

    private void siftUp(int index) {
        while (index > 0 && data.get(index).compareTo(data.get(parent(index))) < 0) {
            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    private void siftDown(int index) {
        while (leftChild(index) < data.getSize()) {
            int smaller = leftChild(index);
            if (smaller + 1 < data.getSize() && data.get(smaller + 1).compareTo(data.get(smaller)) < 0) smaller++;

            if (data.get(index).compareTo(data.get(smaller)) <= 0) break;
            data.swap(index, smaller);
            index = smaller;
        }
    }

    private void heapify(E[] arr) {
        data = new Array<>(arr);
        for (int i = parent(data.getSize() - 1); i >= 0; i--) siftDown(i);
    }

    public void add(E e) {
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    public E findMin() {
        if (isEmpty()) throw new RuntimeException("Heap is empty!");
        return data.get(0);
    }

    public E extractMin() {
        E min = findMin();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return min;
    }

    public E replace(E e) {
        E min = findMin();
        data.set(0, e);
        siftDown(0);
        return min;
    }
}

5、优先队列

public interface Queue<E> {

    void enqueue(E e);

    E dequeue();

    E getFront();

    int getSize();

    boolean isEmpty();

}
/**
 * 基于最大堆实现的优先队列
 */
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private final MaxHeap<E> maxHeap;

    public PriorityQueue() {
        maxHeap = new MaxHeap<>();
    }

    @Override
    public void enqueue(E e) {
        maxHeap.add(e);
    }

    @Override
    public E dequeue() {
        return maxHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxHeap.findMax();
    }

    @Override
    public int getSize() {
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty() {
        return maxHeap.isEmpty();
    }
}

6、堆排序

/**
 * 堆排序: O(N * logN)
 * 基于最大堆来实现
 */
public class HeapSort {

    private HeapSort() {
    }

    /**
     * 自顶向下建堆: O(N * logN)
     */
    public static <E extends Comparable<E>> void sort1(E[] arr) {
        MaxHeap<E> maxHeap = new MaxHeap<>();
        for (E e : arr) maxHeap.add(e); // 自顶向下建堆: O(N * logN)
        for (int i = arr.length - 1; i >= 0; i--) arr[i] = maxHeap.extractMax();
    }

    /**
     * 堆排序优化, 自底向上建堆: O(n)
     */
    public static <E extends Comparable<E>> void sort2(E[] arr) {
        if (arr == null || arr.length <= 1) return;

        // 把 arr[] 变成最大堆, 自底向上建堆: O(n)
        int last = arr.length - 1;
        for (int i = (last - 1) / 2; i >= 0; i--) siftDown(arr, i, last);

        while (last >= 1) {
            swap(arr, 0, last);
            last--;
            siftDown(arr, 0, last);
        }
    }

    /**
     * 对 arr[0, last] 所形成的最大堆中, 索引 index 的元素, 执行 siftDown
     */
    private static <E extends Comparable<E>> void siftDown(E[] arr, int index, int last) {
        while (index * 2 + 1 <= last) {
            int bigger = index * 2 + 1;
            if (bigger + 1 <= last && arr[bigger + 1].compareTo(arr[bigger]) > 0) bigger++;

            if (arr[index].compareTo(arr[bigger]) >= 0) break;
            swap(arr, index, bigger);
            index = bigger;
        }
    }

    private static <E> void swap(E[] arr, int a, int b) {
        E k = arr[a];
        arr[a] = arr[b];
        arr[b] = k;
    }
}
/**
 * 堆排序: O(N * logN)
 * 基于最小堆来实现
 */
public class HeapSort {

    private HeapSort() {
    }

    public static <E extends Comparable<E>> void sort1(E[] arr) {
        MinHeap<E> minHeap = new MinHeap<>();
        for (E e : arr) minHeap.add(e);
        for (int i = arr.length - 1; i >= 0; i--) arr[i] = minHeap.extractMin();
    }

    public static <E extends Comparable<E>> void sort2(E[] arr) {
        if (arr == null || arr.length <= 1) return;

        int last = arr.length - 1;
        for (int i = (last - 1) / 2; i >= 0; i--) siftDown(arr, i, last);

        while (last >= 1) {
            swap(arr, 0, last);
            last--;
            siftDown(arr, 0, last);
        }
    }

    /**
     * 对 arr[0, last] 所形成的最小堆中, 索引 index 的元素, 执行 siftDown
     */
    private static <E extends Comparable<E>> void siftDown(E[] arr, int index, int last) {
        while (index * 2 + 1 <= last) {
            int smaller = index * 2 + 1;
            if (smaller + 1 <= last && arr[smaller + 1].compareTo(arr[smaller]) < 0) smaller++;

            if (arr[index].compareTo(arr[smaller]) <= 0) break;
            swap(arr, index, smaller);
            index = smaller;
        }
    }

    private static <E> void swap(E[] arr, int a, int b) {
        E k = arr[a];
        arr[a] = arr[b];
        arr[b] = k;
    }
}

7、Select K 与 Top K 问题

另一种思路,利用快排思想解决 Select K 与 Top K 问题

image

7.1、Select K 问题

215 - 数组中的第 K 个最大元素

/**
 * 求数组升序排序好后, 从右往左数第 K 个元素, 它的索引是 length - K
 * 直接使用最小堆
 * 复杂度: O(N * logK)
 */
public class FindKthLargest {

    public static int findKthLargest(int[] arr, int k) {
        int[] minHeap = Arrays.copyOf(arr, k);
        int last = k - 1;
        for (int i = (last - 1) / 2; i >= 0; i--) siftDown(minHeap, i);

        for (int i = k; i < arr.length; i++) {
            if (arr[i] > minHeap[0]) {
                minHeap[0] = arr[i];
                siftDown(minHeap, 0);
            }
        }

        return minHeap[0];
    }

    private static void siftDown(int[] arr, int index) {
        int last = arr.length - 1;
        while (index * 2 + 1 <= last) {
            int smaller = index * 2 + 1;
            if (smaller + 1 <= last && arr[smaller + 1] < arr[smaller]) smaller++;

            if (arr[index] <= arr[smaller]) break;
            swap(arr, index, smaller);
            index = smaller;
        }
    }

    private static void swap(int[] arr, int a, int b) {
        int k = arr[a];
        arr[a] = arr[b];
        arr[b] = k;
    }
}

7.2、Top K 问题

剑指 Offer 40 - 最小的 K 个数

/**
 * 求数组升序排序好后, 从左往右数共 K 个元素
 * 直接使用最大堆
 * 复杂度: O(N * logK)
 */
public class GetLeastNumbers {

    public static int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0) return new int[0];

        int[] maxHeap = Arrays.copyOf(arr, k);
        int last = k - 1;
        for (int i = (last - 1) / 2; i >= 0; i--) siftDown(maxHeap, i);

        for (int i = k; i < arr.length; i++) {
            if (arr[i] < maxHeap[0]) {
                maxHeap[0] = arr[i];
                siftDown(maxHeap, 0);
            }
        }

        return maxHeap;
    }

    private static void siftDown(int[] arr, int index) {
        int last = arr.length - 1;
        while (index * 2 + 1 <= last) {
            int bigger = index * 2 + 1;
            if (bigger + 1 <= last && arr[bigger + 1] > arr[bigger]) bigger++;

            if (arr[index] >= arr[bigger]) break;
            swap(arr, index, bigger);
            index = bigger;
        }
    }

    private static void swap(int[] arr, int a, int b) {
        int k = arr[a];
        arr[a] = arr[b];
        arr[b] = k;
    }
}

8、快排思想和优先队列比较

image

9、更多话题

参考链接:索引堆二项堆 1二项堆 2斐波那契堆

  • d 叉堆
  • 索引堆:之前根据内容排序,现在根据索引排序
  • 二项堆、斐波那契堆

广义队列

  • 普通队列
  • 优先队列
  • 随机队列

image

posted @ 2023-04-11 00:23  lidongdongdong~  阅读(15)  评论(0编辑  收藏  举报