Heap简介

概念

堆是一种基于完全二叉树的数据结构,其中每个父节点都大于等于/小于等于其子节点。可以分为最大堆(Max Heap)和最小堆(Min Heap),其中最大堆要求父节点的值大于或等于所有子节点,而最小堆要求父节点的值小于或等于所有子节点。

特点

  • 使用数组实现:通过利用数组索引表示完全二叉树中元素之间关系来节省内存空间。
  • 快速查找:插入、删除和获取操作具有对数时间复杂度。
  • 动态扩展:能够自动调整大小以容纳更多元素。

优点

  1. 高效插入和删除操作:插入和删除具有对数时间复杂度。
  2. 高效获取最大/最小元素:可以快速获取具有极端值的顶部元素。

缺点

  1. 不支持随机访问:堆不同于数组或链表,无法直接通过索引访问特定位置上的元素。
  2. 中间位置元素难以访问和更新:要查找、更新或删除非顶部位置上的元素会变得复杂且低效。

适用场景

  • 需要快速获取最大/最小元素的场景。
  • 常用于优先队列、排序算法(如堆排序)等领域。

示例代码

下面是一个以最大堆为例的java代码简单实现:

import java.util.Arrays;
import java.util.NoSuchElementException;

class Heap {
    private int[] heapArray;
    private int maxSize;
    private int currentSize;

    public Heap(int size) {// 构造函数,创建一个具有指定大小的堆对象。
        this.maxSize = size;
        this.currentSize = 0;
        this.heapArray = new int[size];
    }

    public boolean isEmpty() {// 检查堆是否为空,并返回布尔值。
        return currentSize == 0;
    }

    public boolean isFull() {// 检查堆是否已满,并返回布尔值。
        return currentSize == maxSize;
    }

    public void insert(int value) {// 向堆中插入一个元素。如果堆已满,则抛出异常。该方法使用“上溯”操作来维护堆性质。
        if (isFull()) {
            throw new IllegalStateException("Heap is full");
        }

        heapArray[currentSize] = value;

        trickleUp(currentSize);

        currentSize++;
    }

    private void trickleUp(int index) {//从给定索引开始执行上溯操作以维护堆性质。它将当前节点与其父节点进行比较和交换,直到达到合适的位置或达到根节点为止。
        int parentIndex = (index - 1) / 2; // 计算父节点索引

        while (index > 0 && heapArray[parentIndex] < heapArray[index]) {
            // 交换父节点与子节点的值
            int temp = heapArray[parentIndex];
            heapArray[parentIndex] = heapArray[index];
            heapArray[index] = temp;

            index = parentIndex;
            parentIndex = (index - 1) / 2; // 更新父节点索引
        }
    }

    public int remove() {// 移除并返回顶部(最大)元素。如果堆为空,则抛出异常。该方法使用“下溯”操作来维护堆性质。
        if (isEmpty()) {
            throw new IllegalStateException("Heap is empty");
        }

        int root = heapArray[0]; // 根节点的值为最大/最小元素

        currentSize--;

        heapArray[0] = heapArray[currentSize];

        trickleDown(0);

        return root;
    }

    private void trickleDown(int index) {// 从给定索引开始执行下溯操作以维护堆性质。它将当前节点与其较大子节点进行比较和交换,直到没有子节点或找到合适的位置为止。
        int largerChildIndex;

        while (index < currentSize / 2) { // 判断是否有子节点
            int leftChildIndex = 2 * index + 1;
            int rightChildIndex= leftChildIndex + 1;

            if (rightChildIndex < currentSize &&
                    heapArray[leftChildIndex] =heapArray[largerChildIndex]){
                break;   // 当前位置已满足堆性质,不需要调整,退出循环
            }

            swap(index, largerChildIndex);
            index=largerChildIndex;//更新索引以继续下一轮比较
        }
    }


    private void swap(int index1,int index2){ //用于交换数组中两个索引位置上的元素。
        int temp=heapArray[index1];
        heapArray[index1]=heapArray[index2];
        heapArray[index2] = temp;
    }

    public int[] getHeapArray() {// 返回堆中所有元素
        return Arrays.copyOf(heapArray, currentSize);
    }

    public int peek() {// 查看但不删除顶部元素
        if (currentSize == 0) {
            throw new NoSuchElementException("Heap is empty.");
        }

        return heapArray[0];
    }
}

public class Main {
    public static void main(String[] args) {
        //创建一个最大堆
        Heap heap = new Heap(10);

        // 插入元素
        heap.insert(80);
        heap.insert(75);
        heap.insert(60);
        heap.insert(68);
        heap.insert(55);

        // 打印堆中所有元素
        System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));

        // 获取并移除顶部(最大)元素
        int maxElement=heap.remove();
        System.out.println("Maximum element removed from the heap: "+maxElement);


        //打印删除顶部元 素后的堆
        System.out.println("Current elements in the heap after removal:" + Arrays.toString(heap.getHeapArray()) );

        //插入新元素
        heap.insert(100);

        //查看但不删除顶部元素
        maxElement=heap.peek();
        System.out.println("Maximum element removed from the heap: "+maxElement);

        //打印堆中所有元素
        System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));
    }
}

常见问题

  1. 元素大小问题:在构建或插入时,需要确保父节点与子节点具有正确的大小关系。如果使用自定义对象作为堆中的元素,请实现正确的比较器来定义大小关系。
  2. 动态调整容量:如果底层数组满了,则需要进行动态扩展以容纳更多的元素。这可能涉及重新分配内存和复制数据,并带来一些性能开销。
  3. 并发问题:Java提供了线程安全的优先队列实现,如PriorityBlockingQueue。如果在多线程环境中使用,请确保选择适当的实现以避免并发问题。

总结

堆是一种基于完全二叉树的数据结构,通过父节点与子节点之间的比较来维护其特性。它支持快速插入、删除和获取最大/最小元素的操作,并用于优先队列和排序算法(如堆排序)等场景。处理大小关系、动态调整容量和并发问题时要注意正确性和性能。

你可能感兴趣的:(#,数据结构,算法)