一颗完全二叉树——堆(Heap)

设计背景

二叉树有满二叉树(Full binary Tree)和完全二叉树(Complete Binary Tree)等特殊情况,满二叉树指“除了叶子节点外,所有节点都有左子树和右子树”,它的叶子节点只会出现在最后一层;而完全二叉树因为有的节点可能缺失子树,所以它的叶子节点可能出现在最后的两层中
在这里插入图片描述
而堆(Heap)可以看作一颗完全二叉树的数组,堆有最大堆(MaxHeap)和最小堆(MinHeap)之分,最大堆中某个节点的值一定不大于其父节点的值,而最小堆中某个节点的值一定不小于其父节点的值。


结构分析

【底层实现】动态数组(ArrayList)
【核心方法】
public void add(E e); //添加元素
private void siftUp(int k); //元素上浮
public E extractMax(); //删除堆中最大的元素
private void siftDown(int k); // 元素下浮
public E replace(E e); //替换堆中指定的元素
public E findMax(); // 找到堆中最大的元素
【特别注意】堆所依赖的数组需要增加两个方法:

/**
 * 带参构造器:对实例域进行初始化
 * @param arr 数组
 */
public ArrayList(E[] arr) {
	data = (E[])new Object[arr.length];
    for (int i = 0; i < arr.length; i++) {
    	data[i] = arr[i];
    }
    size = arr.length;
}

/**
 * 方法:交换两个元素的位置
 * @param i 第一个元素的索引
 * @param j 第二个元素的索引
 */
public void swap(int i, int j) {
	if (i < 0 || i >= size || j < 0 || j >= size) {
		throw new IllegalArgumentException("Index is illegal!");
    }
	E t = data[i];
    data[i] = data[j];
    data[j] = t;
}

代码实现

利用动态数组实现堆:

public class MaxHeap<E extends Comparable<E>> {

    /**
     * 实例域:动态数组
     */
    private ArrayList<E> data;

    /**
     * 带参构造器:对实例域进行初始化
     * @param capacity 堆的容量
     */
    public MaxHeap(int capacity) {
        data = new ArrayList<>(capacity);
    }

    /**
     * 带参构造器:将数组重新排序为MaxHeap的形式,并初始化实例域
     * @param arr
     */
    public MaxHeap(E[] arr) {
        data = new ArrayList<>(arr);
        for (int i = parent(arr.length - 1); i >= 0; i--) {
            siftDown(i);
        }
    }

    /**
     * 无参构造器:对实例域进行初始化
     */
    public MaxHeap() {
        data = new ArrayList<>();
    }

    /**
     * 方法:向堆中添加元素
     * @param e 元素
     */
    public void add(E e) {
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    /**
     * 方法:元素上浮
     * @param k 指定元素的索引
     */
    private void siftUp(int k) {
        // 如果k不为0,且k对应的元素比其父元素大,则执行循环
        while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
            // 将k对应的元素与其父元素交换位置
            data.swap(k, parent(k));
            // 更新k的信息
            k = parent(k);
        }
    }

    /**
     * 方法:查找堆中最大的元素
     * @return 最大的元素
     */
    public E findMax() {
        if (data.getSize() == 0) {
            throw new IllegalArgumentException("The heap is empty!");
        }
        return data.get(0);
    }

    /**
     * 方法:取出堆中最大元素
     * @return 最大元素
     */
    public E extractMax() {
        E ret = findMax();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return ret;
    }

    /**
     * 方法:元素下浮
     * @param k 元素的索引
     */
    private void siftDown(int k) {
        // 若k有左子树,则执行循环
        while (leftChild(k) < data.getSize()) {
            // 获取k左子树的索引
            int j = leftChild(k);
            // 如果k有右子树,而且k的右子树比左子树大
            if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
                // 获取k右子树的索引
                j = rightChild(k);
            }
            // 如果k与子树间的关系正常,则结束循环
            if (data.get(k).compareTo(data.get(j)) >= 0) {
                break;
            }
            // 如果k与子树间的关系异常,则发生交换(下浮)
            data.swap(k, j);
            // 更新k的索引
            k = j;
        }
    }

    /**
     * 方法:将堆中最大元素替换为e
     * @param e 新元素
     * @return 被替换的元素
     */
    public E replace(E e) {
        E ret = findMax();
        data.set(0, e);
        siftDown(0);
        return ret;
    }

    /**
     * 方法:找到指定索引元素的父节点的索引
     * @param index 指定索引
     * @return 父节点的索引
     */
    private int parent(int index) {
        if (index == 0) {
            throw new IllegalArgumentException("The root doesn't have parent!");
        }
        return (index - 1) / 2;
    }



    /**
     * 方法:找到指定索引元素的左子树的索引
     * @param index 指定索引
     * @return 左子树的索引
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 方法:找到指定索引元素的右子树的索引
     * @param index 指定索引
     * @return 右子树的索引
     */
    private int rightChild(int index) {
        return index * 2 + 2;
    }

    /**
     * 方法:获取堆的元素个数
     * @return
     */
    public int size() {
        return data.getSize();
    }

    /**
     * 方法:判断堆是否为空
     * @return
     */
    public boolean isEmpty() {
        return data.isEmpty();
    }

}
发布了48 篇原创文章 · 获赞 4 · 访问量 6157

猜你喜欢

转载自blog.csdn.net/Knightletter/article/details/103234708