数据结构之堆

本文介绍了堆这种特殊二叉树的数据结构,包括最大堆和最小堆的特性。堆可以在O(logN)的时间复杂度内插入和删除元素,以及O(1)的时间复杂度获取最大或最小值。在Java中,可以使用PriorityQueue类实现堆。同时,文章还提供了自定义最大堆和最小堆的Java代码实现,详细展示了插入、删除、获取堆顶元素等操作。
摘要由CSDN通过智能技术生成

堆是一种特别的二叉树,满足以下条件的二叉树,可以称之为堆:

  • 完全二叉树;
  • 每一个节点的值都必须 大于等于或者小于等于 其孩子节点的值。

堆具有以下的特点:

  • 可以在 O(logN) 的时间复杂度内向堆中插入元素;
  • 可以在 O(logN) 的时间复杂度内向堆中删除元素;
  • 可以在 O(1)的时间复杂度内获取堆中的最大值或最小值。

堆的分类

堆有两种类型:最大堆和最小堆。

  • 最大堆:堆中每一个节点的值都大于等于其孩子节点的值。所以最大堆的特性是 堆顶元素(根节点)是堆中的最大值。
  • 最小堆:堆中每一个节点的值都小于等于其孩子节点的值。所以最小堆的特性是 堆顶元素(根节点)是堆中的最小值。

在这里插入图片描述

PriorityQueue

在Java中的堆可以使用 java.util 包下的优先队列 PriorityQueue 类来表示。

//创建一个空的最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
//创建一个空的最大堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// 创建带初始值的「堆」, 或者称为「堆化」操作
PriorityQueue<Integer> heapWithValues= new PriorityQueue<>(Arrays.asList(3,1,2));

//插入元素
minHeap.add(1);
maxHeap.add(1);
//获取堆顶元素
minHeap.peek();//最小值
maxHeap.peek();//最大值
//删除堆顶元素
minHeap.poll();
maxHeap.poll();
//获取堆的长度
minHeap.size();
maxHeap.size();
//判断堆是否存在元素,若存在返回false,不存在返回true
minHeap.isEmpty();
maxHeap.isEmpty();

基本用法

public class Demo_01_PriorityQueue {
    public static void main(String[] args) {
        //堆化[5,7,8]
        PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> b - a);
        minHeap.add(8);
        minHeap.add(7);
        minHeap.add(1);
        minHeap.add(3);
        minHeap.add(9);
        // 添加元素,add方法是通过调用offer方法添加元素的
        System.out.println(minHeap.offer(2));
        System.out.println("size:" + minHeap.size());
        System.out.println("toString:" + minHeap.toString());
        // 判断堆是否为空
        System.out.println("isEmpty:" + minHeap.size());
        // 获取堆顶元素
        System.out.println("peek:" + minHeap.peek());
        // 删除元素
        System.out.println("poll:" + minHeap.poll());
        // 获取堆的比较器对象
        System.out.println(minHeap.comparator());
        // 查找堆中是否存在元素 6
        System.out.println("contains:" + minHeap.contains(6));
        // 将堆转换成一个对象数组
        Object[] array = minHeap.toArray();
        System.out.println(Arrays.toString(array));
    }
}

时间复杂度和空间复杂度分析

堆的用法时间复杂度空间复杂度
创建堆O(N)O(N)
插入元素O(logN)O(1)
获取堆顶元素O(1)O(1)
删除堆顶元素O(logN)O(1)
获取堆的长度O(1)O(1)

自定义堆

初始化堆

// 数组创建完全二叉树的结构
private int[] maxHeap;
// 数组总大小
private int size;
// 记录堆的元素个数
private int count;
// 默认堆大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;

// 默认初始化堆大小
public MaxHeap() {
    this(DEFAULT_INITIAL_CAPACITY);
}

// 初始化
public MaxHeap(int size) {
    this.size = size;
    this.maxHeap = new int[size + 1];
    // 第1个元素不使用,随便赋值
    maxHeap[0] = -1;
}
/**
  * 交换值
  *
  * @param arr 堆
  * @param a   a元素的索引
  * @param b   b元素的索引
  */
private void swap(int[] arr, int a, int b) {
    int t = arr[a];
    arr[a] = arr[b];
    arr[b] = t;
}

插入元素

/**
  * 插入元素
  *
  * @param e 元素
  * @return 成功返回true,失败返回false
  */
public boolean add(int e) {
    return offer(e);
}
public boolean offer(int e) {
    // 堆元素加1
    count++;
    // 堆中元素的个数大于初始化数组的个数
    if (count > size) {
        count--;
        return false;
    }
    // 添加元素
    maxHeap[count] = e;
    // 新增元素的索引位置
    int index = count;
    /**
      * 注意,若用数组表示完全二叉树,并且根结点存储在数组的【索引1】的位置的时
      * 任何一个节点的父节点索引位置为【该节点的索引位置/2】,任何一个节点的左孩
      * 子节点的索引位置为【该节点的索引位置*2】,任何一个节点的右孩子节点的索引
      * 位置为【该节点的索引位置*2+1】;
      */
    // 新增元素的父节点的索引位置
    int parent = index >>> 1;
    // 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
    while (index > 1 && maxHeap[index] > maxHeap[parent]) {
        // 交换
        swap(maxHeap, index, parent);
        // 元素上浮
        index = parent;
        parent = index >>> 1;
    }
    return true;
}

获取堆顶元素

/**
  * 获取堆顶元素
  *
  * @return 返回堆顶元素
  */
public int peek() {
    return maxHeap[1];
}

删除堆顶元素

/**
  * 删除堆顶元素
  *
  * @return 返回被删除的元素
  */
public int poll() {
    // 返回结果
    int result = 0x80000000;
    // 堆个数为0
    if (count <= 0) {
        return result;
    } else { // 堆个数至少为1
        result = maxHeap[1];
        // 将堆中最后一个元素替换到堆顶
        maxHeap[1] = maxHeap[count];
        // 堆中元素减1
        count--;
        // 被删除元素索引
        int index = 1;
        // 若删除的节点是根节点
        while (index < count && index <= (count >>> 1)) {
            // 被删除节点的左节点和右节点
            int left = 2 * index, right = 2 * index + 1;
            // 当删除节点的元素小于,左节点或右节点,表示该元素值小,此时需要将该元素与左、右孩子节点中最大的进行交换
            if (maxHeap[index] < maxHeap[left] || maxHeap[index] < maxHeap[right]) {
                // 与左、右节点中最大的进行交换
                if (maxHeap[left] > maxHeap[right]) {
                    swap(maxHeap, index, left);
                    // 左元素下沉,记录下沉后索引的位置
                    index = left;
                } else {
                    swap(maxHeap, index, right);
                    // 右元素下沉,同上
                    index = right;
                }
            } else {
                break;
            }
        }
    }
    return result;
}

堆的大小

/**
  * 获取堆的大小
  *
  * @return 返回堆中元素个数
  */
public int size() {
    return count;
}

/**
  * 判断堆是否为空
  *
  * @return 空返回true,非空返回false
  */
public boolean isEmpty() {
    return count == 0;
}

最大堆完整代码

/**
 * Author : AiTao
 * Create : 2021/4/1 22:20
 * Description : 最大堆类
 */
public class MaxHeap {
    // 数组创建完全二叉树的结构
    private int[] maxHeap;
    // 数组总大小
    private int size;
    // 记录堆的元素个数
    private int count;
    // 默认堆大小
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 默认初始化堆大小
    public MaxHeap() {
        this(DEFAULT_INITIAL_CAPACITY);
    }

    // 初始化
    public MaxHeap(int size) {
        this.size = size;
        this.maxHeap = new int[size + 1];
        // 第1个元素不使用,随便赋值
        maxHeap[0] = 0x80000000;
    }

    /**
     * 插入元素
     *
     * @param e 元素
     * @return 成功返回true,失败返回false
     */
    public boolean add(int e) {
        return offer(e);
    }

    public boolean offer(int e) {
        // 堆元素加1
        count++;
        // 堆中元素的个数大于初始化数组的个数
        if (count > size) {
            count--;
            return false;
        }
        // 添加元素
        maxHeap[count] = e;
        // 新增元素的索引位置
        int index = count;
        /**
         * 注意,如果用数组表示完全二叉树,并且根结点存储在数组的[索引1]的位置的时候,
         * 任何一个节点的父节点索引位置为「该节点的索引位置/2」,任何一个节点的左孩
         * 子节点的索引位置为「该节点的索引位置*2」,任何一个节点的右孩子节点的索引
         * 位置为「该节点的索引位置*2+1」
         */
        // 新增元素的父节点的索引位置
        int parent = index >>> 1;
        // 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
        while (index > 1 && maxHeap[index] > maxHeap[parent]) {
            // 交换
            swap(maxHeap, index, parent);
            // 元素上浮
            index = parent;
            parent = index >>> 1;
        }
        return true;
    }

    /**
     * 获取堆顶元素
     *
     * @return 返回堆顶元素
     */
    public int peek() {
        return maxHeap[1];
    }

    /**
     * 删除堆顶元素
     *
     * @return 返回被删除的元素
     */
    public int poll() {
        // 返回结果
        int result = 0x80000000;
        // 堆个数为0
        if (count <= 0) {
            return result;
        } else { // 堆个数至少为1
            result = maxHeap[1];
            // 将堆中最后一个元素替换到堆顶
            maxHeap[1] = maxHeap[count];
            // 堆中元素减1
            count--;
            // 被删除元素索引
            int index = 1;
            // 若删除的节点是根节点
            while (index < count && index <= (count >>> 1)) {
                // 被删除节点的左节点和右节点
                int left = 2 * index, right = 2 * index + 1;
                // 当删除节点的元素小于,左节点或右节点,表示该元素值小,此时需要将该元素与左、右孩子节点中最大的进行交换
                if (maxHeap[index] < maxHeap[left] || maxHeap[index] < maxHeap[right]) {
                    // 与左、右节点中最大的进行交换
                    if (maxHeap[left] > maxHeap[right]) {
                        swap(maxHeap, index, left);
                        // 左元素下沉,记录下沉后索引的位置
                        index = left;
                    } else {
                        swap(maxHeap, index, right);
                        // 右元素下沉,同上
                        index = right;
                    }
                } else {
                    break;
                }
            }
        }
        return result;
    }

    /**
     * 获取堆的大小
     *
     * @return 返回堆中元素个数
     */
    public int size() {
        return count;
    }

    /**
     * 判断堆是否为空
     *
     * @return 空返回true,非空返回false
     */
    public boolean isEmpty() {
        return count == 0;
    }

    /**
     * 重写toString
     */
    public String toString() {
        if (count == 0) {
            return "堆为空!";
        } else {
            // 打印 [5,4,3,2,1]
            StringBuilder sb = new StringBuilder();
            sb.append('[');
            for (int i = 1; i <= count; i++) {
                sb.append(maxHeap[i]);
                if (i != count) {
                    sb.append(',');
                }
            }
            sb.append(']');
            return sb.toString();
        }
    }

    /**
     * 交换值
     *
     * @param arr 堆
     * @param a   a元素的索引
     * @param b   b元素的索引
     */
    private void swap(int[] arr, int a, int b) {
        int t = arr[a];
        arr[a] = arr[b];
        arr[b] = t;
    }
}

最小堆完整代码

/**
 * Author : AiTao
 * Create : 2021/4/1 22:21
 * Description : 最小堆类
 */
public class MinHeap {
    // 数组创建完全二叉树的结构
    private int[] minHeap;
    // 数组总大小
    private int size;
    // 记录堆的元素个数
    private int count;
    // 默认堆大小
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 默认初始化堆大小
    public MinHeap() {
        this(DEFAULT_INITIAL_CAPACITY);
    }

    // 初始化
    public MinHeap(int size) {
        this.size = size;
        this.minHeap = new int[size + 1];
        // 第1个元素不使用,随便赋值
        minHeap[0] = 0x7fffffff;
    }

    /**
     * 插入元素
     *
     * @param e 元素
     * @return 成功返回true,失败返回false
     */
    public boolean add(int e) {
        return offer(e);
    }

    public boolean offer(int e) {
        // 堆元素加1
        count++;
        // 堆中元素的个数大于初始化数组的个数
        if (count > size) {
            count--;
            return false;
        }
        // 添加元素
        minHeap[count] = e;
        // 新增元素的索引位置
        int index = count;
        /**
         * 注意,如果用数组表示完全二叉树,并且根结点存储在数组的[索引1]的位置的时候,
         * 任何一个节点的父节点索引位置为「该节点的索引位置/2」,任何一个节点的左孩
         * 子节点的索引位置为「该节点的索引位置*2」,任何一个节点的右孩子节点的索引
         * 位置为「该节点的索引位置*2+1」
         */
        // 新增元素的父节点的索引位置
        int parent = index >>> 1;
        // 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
        while (index > 1 && minHeap[index] < minHeap[parent]) {
            // 交换
            swap(minHeap, index, parent);
            // 元素上浮
            index = parent;
            parent = index >>> 1;
        }
        return true;
    }

    /**
     * 获取堆顶元素
     *
     * @return 返回堆顶元素
     */
    public int peek() {
        return minHeap[1];
    }

    /**
     * 删除堆顶元素
     *
     * @return 返回被删除的元素
     */
    public int poll() {
        // 返回结果
        int result = 0x7fffffff;
        // 堆个数为0
        if (count <= 0) {
            return result;
        } else { // 堆个数至少为1
            result = minHeap[1];
            // 将堆中最后一个元素替换到堆顶
            minHeap[1] = minHeap[count];
            // 堆中元素减1
            count--;
            // 被删除元素索引
            int index = 1;
            // 若删除的节点是根节点
            while (index < count && index <= (count >>> 1)) {
                // 被删除节点的左节点和右节点
                int left = 2 * index, right = 2 * index + 1;
                // 当删除节点的元素小于,左节点或右节点,表示该元素值大,此时需要将该元素与左、右孩子节点中最小的进行交换
                if (minHeap[index] > minHeap[left] || minHeap[index] > minHeap[right]) {
                    // 与左、右节点中最大的进行交换
                    if (minHeap[left] > minHeap[right]) {
                        swap(minHeap, index, left);
                        // 左元素下沉,记录下沉后索引的位置
                        index = left;
                    } else {
                        swap(minHeap, index, right);
                        // 右元素下沉,同上
                        index = right;
                    }
                } else {
                    break;
                }
            }
        }
        return result;
    }

    /**
     * 获取堆的大小
     *
     * @return 返回堆中元素个数
     */
    public int size() {
        return count;
    }

    /**
     * 判断堆是否为空
     *
     * @return 空返回true,非空返回false
     */
    public boolean isEmpty() {
        return count == 0;
    }

    /**
     * 重写toString
     */
    public String toString() {
        if (count == 0) {
            return "堆为空!";
        } else {
            // 打印 [5,4,3,2,1]
            StringBuilder sb = new StringBuilder();
            sb.append('[');
            for (int i = 1; i <= count; i++) {
                sb.append(minHeap[i]);
                if (i != count) {
                    sb.append(',');
                }
            }
            sb.append(']');
            return sb.toString();
        }
    }
    
    /**
     * 交换值
     *
     * @param arr 堆
     * @param a   a元素的索引
     * @param b   b元素的索引
     */
    private void swap(int[] arr, int a, int b) {
        int t = arr[a];
        arr[a] = arr[b];
        arr[b] = t;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

所幸你是例外

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值