基于动态数组(构建完全二叉树)实现最大堆并实现添加,替换,删除,取堆顶元素等操作

完全二叉树,通俗的理解就是,把元素一层一层的往下放,直到放不下位置,所以右下角有可能是空的,还缺少一些元素

 二叉堆首先是一颗完全二叉树,除此之外它还有一个非常重要的性质,在堆中某个节点的值总是不大于其父节点的值,就是所有的父节点的值都大于等于它的孩子节点的值,这就是最大堆,反之就是最小堆

完全二叉树的特点其实就是一个个节点按顺序一层一层的放下来,所以可以用数组的方式来表示一颗完全二叉树,那么问题就来了,在数组中的每个节点,如何找到它的左右孩子是谁呢,仔细观察是存在如下规律的

parent (i) = i/2

left child (i) = 2 * i

right child (i) = 2 * i + 1

注意是整数除法,如果是奇数除以2,那么小数要被抹去

扫描二维码关注公众号,回复: 5531079 查看本文章

如果以下标0为起点,那么公式会发生以下改变:如图所示:

有了这个思路,就可以进行最大堆的实现:

在实现之前,附上所使用的动态数组实现:  

/**
 * @author BestQiang
 */
public class Array<E> {
    private E[] data;
    private int size;

    /**
     * 构造函数
     *
     * @param capacity 传入数组的容量
     */
    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;
    }

    /**
     * 无参数的构造函数,默认数组的容量capacity=10
     */
    public Array() {
        this(10);
    }

    /**
     * 构造参数传入一个数组,将其转换为动态数组(用于堆操作)
     * @param arr
     */
    public Array(E[] arr) {
        data = (E[]) new Object[arr.length];
        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        size = arr.length;
    }

    /**
     * 获取数组中的元素个数
     *
     * @return
     */
    public int getSize() {
        return this.size;
    }

    /**
     * 获取数组的容量
     *
     * @return
     */
    public int getCapacity() {
        return data.length;
    }

    /**
     * 判断数组是否为空
     *
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 向所有元素后添加一个元素
     *
     * @param e
     */
    public void addLast(E e) {
        /*if(size == data.length) {
            throw new IllegalArgumentException("AddLast failed. Array is full.");
        }
        data[size] = e;
        size ++;*/
        add(size, e);
    }

    /**
     * 在所有数组前添加元素
     *
     * @param e
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 在第index个位置插入一个新元素e
     *
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        // 数组现在的长度等于声明长度,则说明数组已满,就对数组进行扩容
        if (size == data.length) {
//            throw new IllegalArgumentException("AddLast failed. Array is full.");
            resize(2 * data.length);
        }

        // 如果要插入位置的索引小于0,或者大于现有数据(size代表插入数组末尾,如果插入位置大于size则数组会空)
        // 注意因为是插入,所以允许插入size位置(size - 1的位置为末尾位置),而删除就不允许越过(size - 1)的位置
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("AddLast failed. Require index >= 0 and index < size");
        }

        //从最后向前依次进行遍历,插入元素索引之后的元素进行后移,为插入元素腾出位置
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }

        //给对应索引的位置进行赋值
        data[index] = e;
        size++;
    }

    /**
     * 获得index索引位置的元素
     *
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Get faild. Index is illegal");
        }
        return data[index];
    }

    public E getFirst() {
        return get(0);
    }

    public E getLast() {
        return get(size - 1);
    }

    // 修改index索引位置的元素为e
    public void set(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        }
        data[index] = e;
    }

    // 查找数组中是否有元素e
    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i] == e)
                return true;
        }
        return false;
    }

    /**
     * 查找指定索引的元素
     *
     * @param e
     * @return
     */
    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i] == e) {
                return i;
            }
        }
        return -1;
    }


    /**
     * 从数组中删除index位置的元素, 返回删除的元素
     *
     * @param index
     * @return
     */
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        }
        E ret = data[index];
        // 将删除的元素的后面的元素向前移动
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        data[size] = null; // loitering objects != memory leak(内存泄漏)
        //如果data的剩余长度低于总长度的四分之一且其长度不为零,就进行缩容
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }
        return ret;
    }

    /**
     * 从数组中删除第一个元素, 返回删除的元素
     *
     * @return
     */
    public E removeFirst() {
        return remove(0);
    }

    /**
     * 从数组中删除最后一个元素, 返回删除的元素
     *
     * @return
     */
    public E removeLast() {
        return remove(size - 1);
    }

    /**
     * 从数组汇总删除元素e
     *
     * @param e
     */
    public void removeElement(E e) {
        int index = find(e);
        if (index != -1) {
            remove(index);
        }
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Array: size = " + size + " , " + "capacity = " + data.length + "\n");
        res.append("[");
        for (int i = 0; i < size; i++) {
            res.append(data[i]);
            if (i != size - 1) {
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }

    // 对数组进行扩容
    private void resize(int newCapacity) {
        // new一个newCpacity大小的新数组进行交接
        E[] newData = (E[]) new Object[newCapacity];
        // 利用for循环将原数组的数据转移至新数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    // 交换指定索引位置的两个元素的值
    public void swap(int i, int j) {
        if (i < 0 || i >= size || j < 0 || j >= size) {
            throw new IllegalArgumentException("Index is illegal.");
        }
        E temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
}

 接下来基于动态数组实现最大堆:

/**
 * @author BestQiang
 */
public class MaxHeap<E extends Comparable<E>> { // 元素必须要具备可比较性
    private Array<E> data;

    public MaxHeap(int capacity) {
        data = new Array<>(capacity);
    }

    public MaxHeap() {
        data = new Array<>();
    }

    // 传入一个数组,将数组转换为最大堆
    public MaxHeap(E[] arr) {
        data = new Array<>(arr);
        // 就是找到最后一个节点的父节点,这个父节点也就是最后一个父节点,然后依次向前遍历,下沉较小的元素即可
        for(int i = parent(arr.length - 1); i >= 0; i--)
            siftDown(i);
    }

    // 返回堆中的元素个数
    public int size() {
        return data.getSize();
    }

    // 返回一个布尔值,表示堆中是否为空
    public boolean isEmpty() {
        return data.isEmpty();
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
    private int parent(int index) {
        if (index == 0) {
            throw new IllegalArgumentException("index-0 doesn't have parent.");
        }
        return (index - 1) / 2;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点索引
    private int rightChild(int index) {
        return index * 2 + 2;
    }

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

    private void siftUp(int k) {
        while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
            data.swap(k, parent(k));
            k = parent(k);
        }
    }

    // 看堆中的最大元素
    public E findMax() {
        if (data.getSize() == 0) {
            throw new IllegalArgumentException("Can not findMax when heap is empty");
        }
        return data.get(0);
    }

    // 取出堆中的最大元素
    public E extractMax() {
        //找到最大元素用于返回
        E ret = findMax();
        // 将堆顶元素与堆末元素进行位置交换
        data.swap(0, data.getSize() - 1);
        // 移除堆末元素
        data.removeLast();
        // 将堆顶元素沉到合适的位置
        siftDown(0);
        return ret;
    }

    // 思路:查找到堆顶元素的左右孩子,然后将堆顶元素与左右孩子的大小相比,如果比最大的还大,就符合要求,结束,
    // 否则,将其与左右孩子中较大的进行位置互换,然后接下来一次类推
    public void siftDown(int k) {
        while (leftChild(k) < data.getSize()) {
            // 1.先找到右孩子索引
            int j = leftChild(k);
            // 2.将有孩子与做孩子比较,找出较大的然后与父亲比较
            if (j + 1 < data.getSize() && data.get(j).compareTo(data.get(j + 1)) < 0) {
                j = j + 1;
            }
            if(data.get(k).compareTo(data.get(j)) > 0) {
                break;
            } else {
                //3. 如果父亲比孩子小就沉下去
                data.swap(k, j);
                //3. 将此时的孩子的位置赋值给k,进行下一次比较
                k = j;
            }

        }
    }

    // 取出堆中最大的元素,并且替换成元素e
    // 思路就是现在将堆顶元素替换为e,然后进行下沉
    public E replace(E e) {
        E res = findMax();
        data.set(0, e);
        siftDown(0);
        return res;
    }

}

对最大堆进行测试

import java.util.Random;

/**
 * @author BestQiang
 */
public class Main {
    public static void main(String[] args) {
        int n = 1000000;

        MaxHeap<Integer> maxHeap = new MaxHeap<>();
        Random random = new Random();
        // 向最大堆中添加随机元素
        for (int i = 0; i < n; i++) {
            maxHeap.add(random.nextInt(Integer.MAX_VALUE));
        }
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = maxHeap.extractMax();
        }
        // 如果数组中的元素不是从大到小排列的,则抛出异常
        for (int i = 1; i < n; i++) {
            if(arr[i-1] < arr[i]) {
                throw new RuntimeException("Error");
            }
        }
        System.out.println("Test MaxHeap completed.");
    }
}

 测试结果:

Test MaxHeap completed.

至此,最大堆实现完成,接下来可以使用最大堆实现优先队列

猜你喜欢

转载自blog.csdn.net/qq_29951485/article/details/88542415
今日推荐