数据结构与算法(第一季):二叉堆(Heap)

一、二叉堆(Heap)

1、思考

  • 设计一种数据结构,用来存放整数,要求提3个接口。
    • 添加元素
    • 获取最大值
    • 删除最大值
image
  • 更优秀的数据结构:,获取最大值复杂度O(1),删除最大值O(logn),添加元素O(logn)

2、概念

  • 堆(Heap)也是一种树状的数据结构
  • 堆的一个重要性质:任意节点的值总是大于等于小于等于子节点的值。
    • 如果任意节点的值总是大于等于子节点的值,称为最大堆大根堆大顶堆

      image

    • 反之称为最小堆小根堆小顶堆

image
  • 最大堆和最小堆的最大值/最小值都在顶部。且堆中的元素必须具备可比较性。

3、堆的接口设计

public interface Heap<E> {
    int size(); // 元素的数量
    boolean isEmpty();  // 是否为空
    void clear();   // 清空
    void add(E element);     // 添加元素
    E get();    // 获得堆顶元素
    E remove(); // 删除堆顶元素
    E replace(E element); // 删除堆顶元素的同时插入一个新元素
}
复制代码

二、二叉堆(Binary Heap)

  • 二叉堆的逻辑结构就是一颗完全二叉树,所以也叫完全二叉堆
image
  • 鉴于完全二叉树的一些性质,二叉堆的底层(物理结构)一般用数组实现即可。
  • 索引i的规律(n是元素数量)
    • 如果i = 0,它是节点。
    • 如果i > 0,它的父节点的索引为floor((i-1) / 2)
    • 如果2i + 1 <= n - 1,它的左子节点的索引为2i + 1
    • 如果2i + 1 > n - 1,它左子节点。
    • 如果2i + 1 <= n - 1,它的右子节点的索引为2i + 2`。
    • 如果2i + 1 > n - 1,它右子节点。

三、二叉堆(Binary Heap)接口实现

1、构造函数
public BinaryHeap(E[] elements, Comparator<E> comparator)  {
    super(comparator);

    if (elements == null || elements.length == 0) {
        this.elements = (E[]) new Object[DEFAULT_CAPACITY];
    } else {
        size = elements.length;
        int capacity = Math.max(elements.length, DEFAULT_CAPACITY);
        this.elements = (E[]) new Object[capacity];
        for (int i = 0; i < elements.length; i++) {
            this.elements[i] = elements[I];
        }
    heapify();
    }   
}
复制代码
2、添加
image
  • 添加操作需要循环执行以下步骤:
    • 如果node > 父节点,则与父节点交换位置。
    • 如果node <= 父节点,或者node没有父节点,则退出循环。
  • 这个过程叫做上滤(Sift Up),时间复杂度为O(logn)
image
@Override
public void add(E element) {
    elementNotNullCheck(element); //非空判断
    ensureCapacity(size + 1); //扩容代码
    elements[size++] = element;
    siftUp(size - 1);
}

//非空判断
private void elementNotNullCheck(E element) {
    if (element == null) {
        throw new IllegalArgumentException("element must not be null");
    }
}

// 扩容
private void ensureCapacity(int capacity) {
    int oldCapacity = elements.length;
    if (oldCapacity >= capacity) return;

    // 新容量为旧容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[I];
    }
    elements = newElements;
}

//上滤
/**
 * 让index位置的元素上滤
 * @param index
 */
private void siftUp(int index) {
    E element = elements[index]; //先备份一份节点的值
    while (index > 0) {
        int parentIndex = (index - 1) >> 1; //获取父节点索引
        E parent = elements[parentIndex]; //获取父节点的内容
        if (compare(element, parent) <= 0) break;

        // 将父元素存储在index位置
        elements[index] = parent;

        // 重新赋值index
        index = parentIndex;
    }
    elements[index] = element; //当最终确认交换位置后,再将备份的值赋给新的位置。
}
复制代码
3、删除
  • 用最后一个节点覆盖根节点
  • 删除最后一个节点
  • 循环执行以下步骤(43简称为node
    • 如果node < 最大的子节点,与最大的子节点交换位置。
    • 如果node >= 最大的子节点,或者node没有子节点,则退出循环。
  • 这个过程叫做下滤(Sift Down),时间复杂度:O(logn)
image
  • 同样的,交换位置的操作可以像添加那样进行优化。
@Override
public E remove() {
    emptyCheck();

    int lastIndex = --size; //最后一个元素的索引
    E root = elements[0]; //拿出0位置的元素
    elements[0] = elements[lastIndex];
    elements[lastIndex] = null;

    siftDown(0); //下滤
    return root;
}

/**
 * 让index位置的元素下滤
 * @param index
 */
private void siftDown(int index) {
    E element = elements[index];
    int half = size >> 1;
    // 第一个叶子节点的索引 == 非叶子节点的数量
    // index < 第一个叶子节点的索引
    // 必须保证index位置是非叶子节点
    while (index < half) { 
        // index的节点有2种情况
        // 1.只有左子节点
        // 2.同时有左右子节点

        // 默认为左子节点跟它进行比较
        int childIndex = (index << 1) + 1;
        E child = elements[childIndex];

        // 右子节点
        int rightIndex = childIndex + 1;

        // 选出左右子节点最大的那个
        if (rightIndex < size && compare(elements[rightIndex], child) > 0) {
            child = elements[childIndex = rightIndex];
        }

        if (compare(element, child) >= 0) break;

        // 将子节点存放到index位置
        elements[index] = child;
        // 重新设置index
        index = childIndex;
    }
    elements[index] = element;
}
复制代码
4、replace操作
  • 删除堆顶元素,并用新值替换。
  • 用新值替换堆顶,然后做下滤操作即可。
@Override
public E replace(E element) {
    elementNotNullCheck(element);

    E root = null;
    if (size == 0) {
        elements[0] = element;
        size++;
    } else {
        root = elements[0]; //保存要删除的元素
        elements[0] = element; //替换堆顶元素
        siftDown(0); // 下滤
    }
    return root;
}
复制代码
5、批量建堆
  • 批量建堆有两种做法
    • 自上而下的上滤
    • 自下而上的下滤
  • 自上而下的上滤:从上而下拿到每个元素,然后上滤。
image
  • 自下而上的下滤:从下而上拿到每个元素,然后下滤。
    • 叶子节点无须下滤,所以从第一个非叶子节点开始操作。
image
  • 效率比较:
image
  • 下滤执行最长操作的元素最少,而上滤需要执行最长操作的元素非常多。所以下滤效率更高。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容