完全二叉堆及典型应用

一、关于最大值数据结构的比较

获取最大值的平均时间复杂度 删除最大值的平均时间复杂度 尾部添加元素的平均时间复杂度
动态数组 O(N) O(N) O(1)
双向链表 O(N) O(N) O(1)
平衡的二叉搜索树 O(log N) O(log N) O(log N)
最大堆 O(1) O(log N) O(log N)



二、堆

1、堆的一些性质

       任意结点的值都大于等于其子结点的值,叫做最大堆。
       任意结点的值都小于等于其子结点的值,叫做最小堆。

       最大堆和最小堆如下图所示:
Alt
       堆中元素必须具有可比较性。而且所有元素并不是全排列的



2、堆(最大堆)中元素的添加

       上滤:比较自己的值和父结点上值的大小关系。如果自己比父结点大,就交换两者的位置;如果比父节点小则不做处理。

       直接加在数组的末尾即可,然后从最后一个结点开始往上挨个进行上滤。



3、堆(最大堆)中元素的删除

       下滤:比较自己的值和左右子结点上最大值的大小关系。如果自己比子结点上最大值小,就交换两者的位置;如果比子结点上最大值大则不做处理。

       将数组的最后个元素替换堆顶元素,将数组最后个元素删除,然后从第一个结点开始挨个往下进行下滤。



4、批量建堆

       自上而下的上滤法本质:相当于是往堆中添加元素的情况。添加之前这个堆已经是最大堆了,所以每添加一个元素之后都要用上滤法维护一下。

       自下而上的下滤法本质:相当于是往堆中删除元素的情况。“删除”即添加之前这个结点的两边已经是最大堆了,所以每“删除”即添加一个元素都要用下滤法维护一下。

       自下而上的下滤法效率比自上而下的上滤法高




三、堆的实现

1、设计结构图

Alt


2、代码

       接口代码:

	public interface Heap<E> {
	    int size();                 // 元素的数量
	
	    boolean isEmpty();          // 是否为空
	
	    void clear();               // 清空
	
	    void add(E element);        // 添加元素
	
	    E get();                    // 获得堆顶元素
	
	    E remove();                 // 删除堆顶元素
	
	    E replace(E element);       // 删除堆顶元素的同时插入一个新元素
	}

       抽象类代码:

	public abstract class AbstractHeap<E> implements Heap<E> {
	    protected int size = 0;
	    protected Comparator<E> comparator;
	
	    public AbstractHeap(Comparator<E> comparator) {
	        this.comparator = comparator;
	    }
	
	    public AbstractHeap() {
	        this((null));
	    }
	
	    @Override
	    public int size() {
	        return size;
	    }
	
	    @Override
	    public boolean isEmpty() {
	        return size == 0;
	    }
	
	    protected int compare(E e1, E e2) {
	        return comparator != null ? comparator.compare(e1, e2) : ((Comparable) e1).compareTo(e2);
	    }
	}

       二叉堆代码:

	public class BinaryHeap<E> extends AbstractHeap<E> {
	    private static final int DEFAULT_CAPACITY = 10;
	    private E[] elements;
	
	    public BinaryHeap(Comparator<E> comparator) {
	        super(comparator);
	    }
	
	    public BinaryHeap() {
	        super(null);
	    }
	
	    public BinaryHeap(E[] elements, Comparator<E> comparator) {
	        this.comparator = comparator;
	        if (elements == null || elements.length == 0) {
	            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
	        } else {
	            this.size = elements.length;
	            int max = Math.max(elements.length, DEFAULT_CAPACITY);
	            this.elements = (E[]) new Object[max];
	
	//            this.elements = elements;     //直接用地址指向外部数组不可行。一旦外部数组改变了, 堆也会受到影响
	            for (int i = 0; i < elements.length; i++) {
	                this.elements[i] = elements[i];
	            }
	            heapBatch();
	        }
	    }
	
	    public BinaryHeap(E[] elements) {
	        this(elements, null);
	    }
	
	    @Override
	    public void clear() {
	        size = 0;
	        for (int i = 0; i < size; i++)
	            elements[i] = null;
	    }
	
	    @Override
	    public void add(E element) {
	        if (element == null)
	            throw new IllegalArgumentException("传入的元素为空!");
	
	        ensureCapacity(size + 1);
	        elements[size++] = element;
	        //上滤
	        slipUp(size - 1);
	    }
	
	    @Override
	    public E get() {
	        if (size == 0)
	            throw new IndexOutOfBoundsException("堆为空!");
	        return elements[0];
	    }
	
	    @Override
	    public E remove() {
	        if (size == 0)
	            throw new IndexOutOfBoundsException("堆为空!");
	        E old = elements[0];
	        elements[0] = elements[--size];
	        slipDown(0);
	        elements[size] = null;
	        return old;
	    }
	
	    @Override
	    public E replace(E element) {
	        E old = null;
	        if (size != 0)
	            old = elements[0];
	        elements[0] = element;
	        slipDown(0);
	
	        return old;
	    }
	
	
	    /**
	     * 批量建堆
	     */
	    private void heapBatch() {
	        //自上而下的上滤法
	//        for(int i=1;i<size;i++)
	//            slipUp(i);
	
	        //自下而上的下滤法
	        for (int i = (size >> 1) - 1; i >= 0; i--)
	            slipDown(i);
	    }
	
	    /**
	     * 动态扩容
	     *
	     * @param capacity
	     */
	    private void ensureCapacity(int capacity) {
	        int oldCapacity = elements.length;
	        int newCapacity = oldCapacity + (oldCapacity >> 1);  //扩容为1.5倍
	        if (capacity > oldCapacity) {
	            //位运算比浮点高效
	            E[] newArray = (E[]) new Object[newCapacity];
	            //拷贝数据
	            for (int i = 0; i < size; i++) {
	                newArray[i] = elements[i];
	            }
	            elements = newArray;
	        }
	    }
	
	    /**
	     * 上滤法
	     *
	     * @param index
	     */
	    private void slipUp(int index) {
	        E element = elements[index];
	        while (index > 0) {
	            int parentIndex = (index - 1) >> 1;
	            E parent = elements[parentIndex];
	            if (compare(element, parent) <= 0)
	                break;
	            elements[index] = parent;
	            index = parentIndex;
	        }
	        elements[index] = element;
	    }
	
	    /**
	     * 下滤法
	     *
	     * @param index
	     */
	    private void slipDown(int index) {
	        E element = elements[index];
	
	        while (index < (size >> 1)) {        //遍历到的都是非叶子结点
	            int leftChildIndex = (index << 1) + 1;
	
	            //左子树一定存在, 但是右子树不一定存在
	            int maxChildIndex = leftChildIndex + 1 < size
	                    && compare(elements[leftChildIndex], elements[leftChildIndex + 1]) < 0
	                    ? leftChildIndex + 1
	                    : leftChildIndex;
	
	            E maxChild = elements[maxChildIndex];
	
	            if (compare(element, maxChild) >= 0)
	                break;
	            elements[index] = maxChild;
	            index = maxChildIndex;
	        }
	        elements[index] = element;
	    }
    }




四、Top K问题

1、问题描述

       从arr[1, n]这n个数中,找出最大的k个数,k远小于n。


2、思路

       遍历数组,维护一个容量为k的最小堆,每次比较堆顶元素和数组中的元素大小关系。如果遍历到的元素比堆顶元素大,就用该元素替换堆顶元素;如果遍历到的元素比堆顶元素要小,不做任何处理。

       随机生成了一百万个在10000000 - 99999999的随机数进行测试。

	public static void main(String[] args) {
	        final int k = 3;
	
	        BinaryHeap<Integer> heap = new BinaryHeap<>(new Comparator<Integer>() {
	            @Override
	            public int compare(Integer o1, Integer o2) {
	                return o2 - o1;
	            }
	        });
	
	        long begin = System.currentTimeMillis();
	        Integer[] datas = new Integer[1000000];
	        for (int i = 0; i < 1000000; i++) {
	            datas[i] = 10000000 + (int) (Math.random() * 9000000);
	            if (heap.size() < k)
	                heap.add(datas[i]);
	            else if (datas[i] > heap.get())
	                heap.replace(datas[i]);
	        }
	        long end = System.currentTimeMillis();
	        double delta = (end - begin) / 1000.0;
	        BinaryTrees.println(heap);
	        System.out.println("最小堆耗时:" + delta + "秒");
	
	        System.out.println("--------------------");
	
	        begin = System.currentTimeMillis();
	        Arrays.sort(datas);
	        for (int i = datas.length - 1; i >= datas.length - k; i--)
	            System.out.println(datas[i]);
	        end = System.currentTimeMillis();
	        delta = (end - begin) / 1000.0;
	        System.out.println("快排耗时:" + delta + "秒");
	    }

       用堆不知道比快排牛逼多少勒!

Alt

发布了54 篇原创文章 · 获赞 5 · 访问量 4606

猜你喜欢

转载自blog.csdn.net/cj1561435010/article/details/104658275