小白数据结构学习_堆(Java)


前言

1.没啥的,一定要记博客上,不然像上次那样把东西乱删哭死你

2.以下所学心得来自于哔站黑马数据结构课程

一、堆介绍

了解堆之前,我们要知道什么是完全二叉树

  • 完全二叉树:
    • 从根节点往下,除最后一层外,其余层节点全满
    • 最后一层节点都向左靠拢
  • 比如这样(图解):

在计算机科学中,就有一类数据结构就近似完全二叉树性质,我们把它称为

而堆的实现方式,通常使用的是数组,也就是本节我们要用数组来实现一个完全二叉树,也就是堆

二、数组实现堆方式

在使用链表实现的树上,还可以说是节点,而下面开始,我都将称为结点

  • 具体实现方法
    • 将二叉树的结点以原本层级顺序放入到数组索引上
      • 比如根节点放在数组索引1的位置上,理所应当,它的左子节点就放在索引2的位置上,右子节点就放在索引3的位置上,当然这是有规律
      • 数组索引0的位置上放空,放空的原因是比较好理解好操作
    • 具体规律
      • 看下图,比如有一个在数组索引K位置的结点,那么它左子结点的位置一定是位于2K的位置,右子结点就是2K+1
      • 树不是有层级的划分吗?所以根据以上规律,也可得出一个K位置的结点,它的子结点(下一层)也一定是位于数组索引2K或者2K+1的位置,而上一层结点的在数组索引位置就是 k / 2
      • 当然还没完,继续往下看

(图解):

同时堆的实现也有很多种,我们本节将学习大顶堆的实现方式,以下简称堆

  • 大顶堆特性
    • 堆的每一个结点放置形式不再与二叉搜索树一样,堆的每个结点的key都大于等于自身的两个子结点
    • 所以堆的第一个结点,也就是数组的第一个有效数据位的Key值肯定是最大的
    • 堆是一颗完全二叉树,应用到数组中,那么元素的存放必然要是连续的,如果中间有元素删除了,必须补上去

了解完大顶堆如何实现,和结点存放特性,就可以来设计一个堆了

1、设计一个堆


一、堆的API设计如下

  • API介绍
    • 类设计,使用泛型,需要传入一个实现Comparable接口的类,因为结点元素的存放需要比较
    • 上浮、下沉算法作用:当我们添加了结点或者删除了结点,可能就会破坏堆的特性,所以要对某部分结点进行重排

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTt34loU-1615725205223)(C:\Users\66432\AppData\Roaming\Typora\typora-user-images\1615528094322.png)]

核心就是通过上浮下沉保持堆性质

2、构建堆并实现插入


  • 精髓

    • 实现堆中插入一个元素,与所处的位置息息相关,根据堆特性下一层的数据的Key值总是得比上一层的Key值小
    • 所以在进行插入元素方法,需要依次比较每一层进行交换数据
    • 那么就需要用到上浮算法
      • 上浮算法就是一种让当前结点和父结点进行交换的算法,交换罢了
  • 思考点

    • 来看看这张图

    • 1.当Key值S上浮到P的父节点后,那它还需要跟N比较吗?

      • 答案:理所应当是不用的,因为原本结点P就比N大,既然S又大于P,那么S也必然大于N
    • 2.想想还有啥 …ing

package datastructure.heap;

public class Heap<T extends Comparable<T>> {
    private T[] items;
    private int count; //指向着最后一位结点,同时也表示着结点个数
    public Heap(int capacity) {
        // 本次实现舍弃了索引0,也就是从索引1开始为有效数据
        // 那么:
        this.items = (T[])new Comparable[capacity + 1];
    }
    public void insert (T item) {
        // 根据堆存放性质直接在后面添加元素
        items[++count] = item;
        // 存放完毕,看看是否需要上浮
        swim(count);
    }
    // 上浮: 使当前索引位置K的元素上浮到正确位置
    private void swim (int k) {
        for (k = count; k > 1; k /= 2) {
            if (less(k / 2, k)) {
                swap(k / 2, k);
            }
        }
    }


    // 比较两个索引位置的元素
    private boolean less (int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }
    // 交换两个索引位置上的元素
    private void swap (int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

}

3、实现删除


  • 删除实现一共包括三个部分:执行删除入口获取待删除元素的索引位置执行下沉算法
  • 总体思路是:
    • 得到待删除节点的所在索引位置,然后与最后一个结点位置进行交换把最后一个索引位置置为空,那么就删除了该数据
    • 之后就对原本为最后一个结点的元素执行下沉
// 删除指定元素
public void remove (T item) {
    int k = indexOf(item);
    if (k != 0)
        sink(k);
}

二、获取待删除元素的所在索引位置

  • 思路是:用待删除元素的Key,然后与倒数第二层比较,如果是小的,那么只可能在倒数第一层
  • 注意只可能,因为完全二叉树的结点存放性质,比如下图
    • 倒数第二层谁一定有子结点啊,那就是count的父节点

    • 比如要查找结点3,我们首先就取倒数第二层的节点与之比较,也就是取count的父节点8,那么此时虽然比8小,但是结点3,并不在倒数第一层节点

/**
     * 传入一个元素,获取所在位置
     * @param k
     * @return 不存在返回0
     */
public int indexOf (T k) {
    for (int i = count; i >= 1; i /= 2) {
        // 且由于舍弃了索引0,所以: 
        if (k.compareTo(items[i / 2 == 0 ? 1 : i / 2]) <= 0) {
            // 遍历这一层: 从右往左找
            for (int j = i; j > i / 2; j--) {
                if (k == items[j]) return j;
            }
        }
    }
    return 0;
}

三、下沉算法

  • 根据总体思路:让待删除位置最后一个位置交换元素,接着就可把最后一个索引位,置为空,删掉该元素
  • 下沉算法思路
    • 交换后
    • 待删除结点变为待下沉结点,每下沉一次,就重新改变待下沉结点位置,一直判断到最后一层
    • so:层数的话,我们直接判断到最后一个节点的父节点即可
  • 要注意的是:当判断到倒数第二层时,那么最后一层不一定是满的,所以待下沉结点的右子结点可能会为空,左子结点不可能为空,因为我们是判断拿到最后一个节点的父节点结束
// 下浮
public T sink (int k) {
    // 与最后一个结点交换数据,然后进行下移这个结点
    T t = items[k];
    swap(k,count);
    items[count] = null;
    count--;
    // 此时这个K表示的位置为需要下浮,
    // 判断左右子结点,谁比较大谁浮上来,然后K位置沉下去
    // 判断到最后一个节点的父节点,Over
    for (int i = k; i <= count / 2;) {
        if (items[2*i+1] != null) {
            if (less(2 * i, 2 * i + 1)) {
                i = 2 * i + 1;
            } else {
                i = 2 * i;
            }
        } else i = 2 * i;
        if (less(k,i))
            swap(k,i);
        k = i;   // 重新改变待下沉位置
    }
    return t;
}

3.1、删除顶部元素


  • 这时就比较简单了,直接这样既可
// 删除最大元素
public T delMax () {
    return sink(1);
}

测试类

Heap<String> heap = new Heap<>(10);
heap.insert("A");
heap.insert("B");
heap.insert("C");
heap.insert("D");
heap.insert("E");
heap.insert("F");
heap.insert("G");
String result;
while ((result = heap.delMax()) != null) {
    System.out.print(result + " ");
}

输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9EcCAyK3-1615725205226)(C:\Users\66432\AppData\Roaming\Typora\typora-user-images\1615627298398.png)]

堆排序


学会了堆,重要的一点就是可以实现数据排序,那么接下来跳转到我的堆排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值