二叉堆是一颗完全二叉树,其满足以下性质:
对于一个二叉堆的实现,我们底层采用一个动态数组Array的结构。
然后我们先定义一下堆的三个查找索引的操作
(本次试验用的二叉堆为最大堆,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方法将两个元素交换位置,重复此过程,直到新加入元素小于其父节点元素或者已经上浮到了根节点
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;
}
public E replace(E e){
E ret = findMax();
data.set(0,e);
siftDown(0);
return ret;
}
public Array(E[] arr){
data = (E[])new Object[arr.length];
for(int i = 0 ; i
然后在堆中也添加一个入参为数组的构造器,首先调用Array的构造方法,为堆中的Array进行初始化,然后从堆中的最后一个不为叶子节点的节点开始,进行siftDown操作,直到根节点结束。这样一来就满足了堆的定义,那么如何找到最后一个不为叶子节点的节点呢?其实很容易,就是数组中最后一个元素的父亲节点索引所在的位置就是最后一个不为叶子节点的节点。即(size-2)/2所在的位置
public MaxHeap(E[] arr){
int i = parent(arr.length-1);
data = new Array<>(arr);
while(i>=0){
siftDown(i);
i--;
}
}
这样一来我们就将堆中的基本方法都写完了
一般的队列中,我们采用先进先出(First In First Out)的方式,但是这种排队方式有时并不适用,比如在医院中,病人等待治疗需要使用手术室,肯定是有一个优先级的,如果突然有优先级更高的患者(例如严重车祸)出现,那么他使用手术室的位置肯定是排在一些小手术患者的前面。
对于优先队列,首先,优先队列也满足队列的接口
public interface Queue {
void enqueue(E e);
E dequeue();
E getFront();
int getSize();
boolean isEmpty();
}
由于底层我们使用一个最大堆实现,那么在入队的时候我们将元素放入堆中即可,出队就将元素从堆的根节点(优先级最大)的地方取出,getFront取出头元素就将堆中根节点元素返回,其他的基本操作都可以调用数组的操作实现。
public class PriorityQueue> implements Queue {
private MaxHeap maxHeap;
public PriorityQueue(){
maxHeap = new MaxHeap<>();
}
@Override
public int getSize(){
return maxHeap.size();
}
@Override
public boolean isEmpty(){
return maxHeap.isEmpty();
}
@Override
public E getFront(){
return maxHeap.findMax();
}
@Override
public void enqueue(E e){
maxHeap.add(e);
}
@Override
public E dequeue(){
return maxHeap.extractMax();
}
}