堆,可以保证max/min是O(1)的时间,通常用来查找最大/最小值。
本质上是一个完全二叉树,存储结构可以用数组表示。父节点位置为i,则左子节点位置为2i+1,右子节点位置为2i+2。
(java中的优先队列中用的堆为小顶堆,下面用大顶堆)
基本操作:
//返回一个索引所表示的元素的父亲节点索引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;}
向堆中添加元素
向堆中添加元素就是向数组尾中添加元素,然后使其满足堆的性质。
要满足堆的性质,我们就要定义一个siftUp(上浮)操作,将新加入堆的元素与其父节点做对比,如果新加入元素较大,则调用数组中的swap方法将两个元素交换位置,重复此过程,直到新加入元素小于其父节点元素或者已经上浮到了根节点
//k为新加入的元素索引,即数组的最后一个元素索引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 void add(E e){data.addLast(e);siftUp(data.getSize() - 1);}
查看堆中的中最大元素
就是元素0的值
public E findMax(){if(data.getSize()==0){throw new IllegalArgumentException("can not findMax when heap is empty");}return data.get(0);}
取出堆中的最大元素
首先用findMax函数取出并保存堆中最大的元素,然后将数组中的最后一个元素与第一个元素进行交换(swap),然后移除数组中的最后一个元素,然后对根节点的元素进行siftDown(下沉)操作,即不断与两个子节点中较大的节点比较,如果比较大的节点还要小,就与其交换。
private void siftDown(int k){//判断当前节点是否有左孩子while(leftChild(k) < data.getSize()){int j = leftChild(k);//判断当前节点是否有右孩子且右孩子比左孩子大if(j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j))>0){j = rightChild(k);}//判断当前节点与左右孩子节点中较大那个的大小比较if(data.get(k).compareTo(data.get(j)) >= 0){break;}data.swap(k,j);k = j;}}public E extractMax(){E ret = findMax();data.swap(0,data.getSize() - 1);data.removeLast();siftDown(0);return ret;}
heapify
heapify(数组建堆)是传入一个数组,将数组转化的一个堆的方法,我们首先要在数组中定义一个构造方法,接收一个指定的数组将其储存到自身数组中
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;}}
然后从堆中的最后一个不为叶子节点的节点开始,进行siftDown操作,直到根节点结束。这样一来就满足了堆的定义,那么如何找到最后一个不为叶子节点的节点呢?其实很容易,就是数组中最后一个元素的父亲节点索引所在的位置就是最后一个不为叶子节点的节点。即(size-2)/2所在的位置。
public MaxHeap(E[] arr){int i = parent(arr.length-1);data = new Array<>(arr);while(i>=0){siftDown(i);i--;}}
