Java手写堆实现

堆的相关概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。

根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。

举例说明
![小根堆](https://i-blog.csdnimg.cn/direct/8aebcf7ffee54706960c0e50a0edcb6e.pn
小根堆可以看到堆顶元素是最小的,每颗子树的根节点都是该树所有元素中最小的。
大根堆
大根堆和小根堆相反,堆顶是最大的。

堆只是保证了堆顶的原始是整个堆中最大的或最小的,并不能保证层序遍历得到的序列是有序的。

Java中的堆

java中提供了一个优先队列PriorityQueue,他的底层实现就是堆,常见的操作有peek()offer()poll()
示例代码:

PriorityQueue<Integer> que1 = new PriorityQueue<>(); // 默认是小根堆
PriorityQueue<Integer> que2 = new PriorityQueue<>((a, b)->(b - a)); // 大根堆
que.offer(3); // 添加元素
que.offer(1);
que.peek(); // 获取堆顶 1
Integer num = que.poll(); // 返回堆顶 1
offer方法

java的优先队列底层也是数组,每次调用offer()方法插入一个元素之后,要对堆进行重新调整,关键函数是siftUp()

private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

如果没有指定comparator,会继续调用siftUpComparable(k, x)

private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

可以看到添加一个元素实际上是向上调整堆的一个过程。

poll方法

poll方法是从堆顶移除一个元素,所以移除之后也要调整,堆是基于数组的,和末尾元素交换就行了,之后再向下调整。
向下调整:

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

手写堆需要实现哪些方法

已知堆支持操作:

  • 获取堆顶元素
  • 向堆中添加元素
  • 删除堆顶元素

所以只实现down和up的逻辑就能支持上述操作。且可以支持任意位置插入和删除。
下面是实例代码:

int[] nums = new int[]{4,2,1,5,3}; // 长度为5的序列

int[] arr = new int[10]; // 堆数组,预留的大一点,方便演示add
int size = 0;
// 初始化
for (int i = 1; i <= 5; i++) {
    arr[i] = nums[i - 1];
    size++;
}

// 建堆 从第一个非叶子节点开始down
for (int i = 5 / 2; i >= 1; i--) {
    down(arr, i, 5);
}

建堆的时候只用到了down,并且从第一个非叶子节点开始down。
以下是down和up函数:

public static void down(int[] arr, int u, int size) {
  int t = u; // 标记最小的
    if (u * 2 <= size && arr[u * 2] < arr[t]) t = u * 2;
    if (u * 2 + 1 <= size && arr[u * 2 + 1] < arr[t]) t = u * 2 + 1;
    if (t != u) {
        // 交换
        int temp = arr[t];
        arr[t] = arr[u];
        arr[u] = temp;
        down(arr, t, size);
    }
}
public static void up(int[] arr, int u) {
    while (u / 2 != 0 && arr[u / 2] > arr[u]) {
        int parent = u / 2;
        int temp = arr[parent];
        arr[parent] = arr[u];
        arr[u] = temp;
        u /= 2;
    }
}

堆中插入元素之后,在对应的位置需要up。
堆中删除元素之后需要down。
任意位置修改后需要down和up一遍。

堆排序O(nlogn):

while (size > 0) {
	int top = arr[1];
  	arr[1] = arr[size--];
  	down(arr, 1, size);
  	System.out.print(top + " ");
}

手写堆不支持动态扩容,所以适合知道元素最大长度的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值