为了账号安全,请及时绑定邮箱和手机立即绑定

数据结构--索引堆

标签:
Java 算法
何为索引堆?

索引堆是对堆进行了优化。关于堆的介绍可以查看数据结构--堆

优化了什么?

在堆中,构建堆、插入、删除过程都需要大量的交换操作。在之前的实现中,进行交换操作是直接交换datas数组中两个元素。而索引堆交换的是这两个元素的索引,而不是直接交换元素

有什么好处?

主要有两个好处:

  1. 减小交换操作的消耗,尤其是对于元素交换需要很多资源的对象来说,比如大字符串。
  2. 可以根据原位置找到元素,即便这个元素已经换了位置。
如何做到的?

索引堆使用了一个新的int类型的数组,用于存放索引信息。部分代码如下:

// 属性
T[] datas; // 存放数据的数组 datas[1..n]
int[] indexes; // 索引数组

这里这个indexes数组,存放的是什么信息呢?它是如何工作的呢?假如我们有这样一个最小堆:

堆.PNG

那么用数组表示就是:

datas: [-, 1, 15, 20, 34, 7]

现在要维护最小堆的有序性,就需要交换157这两个元素。交换之后的元素数组是:

datas: [-, 1, 7, 20, 34, 15]

而此时,我们再想找到原来在datas[2]位置的元素,已经找不到了。因为此时data[2]已经换成了7,而系统并没有记录15被换到了什么地方。

这个时候,想要得到i位置的元素,直接datas[i]就可以了。

使用索引堆

使用索引堆后,初始化两个数组应该是这样的:

  • datas: [-, 1, 15, 20, 34, 7]
  • indexes: [-, 1, 2, 3, 4, 5]

这个时候,我们就交换indexes数组里面的索引25,而不操作datas数组。交换后两个数组是这个样子:

  • datas: [-, 1, 15, 20, 34, 7]
  • indexes: [-, 1, 5, 3, 4, 2]

这个时候,想要得到i位置的元素,就需要datas[indexes[i]]来获取。

实现代码

索引堆的Java实现代码:

public class MyIndexHeap<T> implements IHeap<T> {
    T[] datas; // 存放数据的数组 datas[1..n]
    int[] indexes; // 索引数组
    private int count = 0; // 计数
    private Comparator<T> comparator; // 比较器

    // 使用空数组构造,容量为capacity
    public MyIndexHeap(int capacity, Comparator<T> comparator) {
        this.datas = (T[]) new Object[capacity + 1];
        // 为索引开辟空间
        this.indexes = new int[capacity + 1];
        this.comparator = comparator;
    }

    // 使用已给的数组构造,容量为数组长度
    public MyIndexHeap(T[] datas, Comparator<T> comparator) {
        this.count = datas.length;
        this.datas = (T[]) new Object[count + 1];
        this.indexes = new int[count + 1];
        for (int i = 0; i < count + 1; i++) {
            indexes[i] = i;
        }
        this.comparator = comparator;
        System.arraycopy(datas, 0, this.datas, 1, count);
        // 对前半部分逆序下沉,构造堆
        for (int k = count / 2; k >= 1; k--)
            sink(k);
        // 对最后一层,也就是后半部分,不断交换到堆顶,再下沉
        int k = count;
    }

    @Override
    public int size() {
        return count;
    }

    @Override
    public boolean isEmpty() {
        return count == 0;
    }

    @Override
    public void add(T data) {
        if (count == datas.length - 1)
            throw new RuntimeException("堆已满");
        datas[indexes[++count]] = data;
        swim(count);
    }

    @Override
    public T pop() {
        return remove(1);
    }

    @Override
    public T remove(int i) {
        if (isEmpty())
            throw new RuntimeException("堆为空");
        if (i < 1 || i > count)
            throw new RuntimeException("下标非法");
        T res = datas[indexes[i]];
        datas[indexes[i]] = datas[indexes[count]];
        datas[indexes[count--]] = null; // 防止对象游离
        sink(i);
        return res;
    }

    @Override
    public T peek() {
        if (isEmpty())
            throw new RuntimeException("堆为空");
        return datas[indexes[1]];
    }

    /**
     * 比较两个数,看谁上浮
     * @param i 第一个数
     * @param j 第二个数
     * @return true 为i上浮, false 为j上浮
     */
    private boolean needSwim(int i, int j) {
        return comparator.compare(datas[indexes[i]], datas[indexes[j]]) < 0;
    }

    private void exchange(int i, int j) {
        int temp = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = temp;
    }

    /**
     * 上浮
     * @param k 开始上浮的位置
     */
    private void swim(int k) {
        while (k > 1 && needSwim(k, k / 2)) {
            exchange(k, k / 2);
            k /= 2;
        }
    }

    /**
     * 下沉
     * @param k 开始下沉的位置
     */
    private void sink(int k) {
        while (2 * k <= count) {
            int j = 2 * k;
            // 判断是否超出范围,再判断j 和 j + 1 那个更需要上浮
            if (j < count && needSwim(j + 1, j))
                j++; // 跟需要上浮的那个换
            if (needSwim(j, k))
                exchange(k, j);
            else break;
            k = j;
        }
    }
}

索引堆测试代码

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消