堆其实可以看作一个二叉树。大顶堆,也就是树中的每一个父节点都比子节点大,但是左右节点并没有严格的大小关系。小顶堆就是树中的每一个父节点都比子节点小。
对堆中的元素进行排序,按层进行排序,第一层即根节点为0,第二层的两个结点分别为1,2,使用数组进行存储,这样在存储时,这些排序就是各个结点在数组中的索引。
向堆中插入一个元素的过程
向一个大顶堆中插入一个元素,首先把这个元素放在堆的最后一个位置,也即放入树中的最后一个结点,因为这里实现堆是使用的数组,所以在数组中访问最后一个元素的时间复杂度为O(1),然后用此结点的索引值计算出它的父亲结点,接着继续让这个结点和它的父节点进行比较,如果大于它的父节点,就进行值交换。比如原来树中一共有7个结点,再插入一个结点后,此结点的索引值为7,父节点的索引值为3,让数组中索引为3和索引为7的结点进行交换。交换之后新插入的结点就在索引为3的这个位置,继续与父节点进行比较,它的父节点索引为1,比较结果同上。如果它比父节点小的话就不用进行比较了,因为既然比父节点都小,那必然比它的爷爷结点更小。
其实堆排序天然适合实现优先队列。为什么这么说呢,每次进入队列时带有优先级,就像这里的大小值一样。每次进入堆中进行简单的排序。然后每次出队时,直接出根节点,因为根节点属于优先级最高的,那出了根节点之后,树不就成了两半了吗,所以这里引出了一个出队的方法。
出队的过程
出队即删除根节点,因为根节点在数组中在索引为0的位置,首先让索引为0的元素和数组中最后一个元素交换值,然后删除最后一个元素。经过这样的操作后,这个堆就不符合规则了,因为可能交换了之后根节点的值是最小的。所以需要有一个沉底的过程,即让根节点和左右子树中较大的进行比较,如果小于左右子树中较大的子树就就进行交换,交换之后接着进行与左右子树比较。
如果采用数组实现优先队列,则必须入队时就进行排序,后者出队时进行排序,时间复杂度为O(n),而使用堆排序实现优先队列每次只需要排列每条树的路径上的元素,设树的高度为h,时间复杂度为O(h),即O(logn),当队列中的元素越多,性能差异越明显。当n=1024,h=10;n=1000000时,h=20…
大顶堆的实现:
/**
* @Author: Cui
* @Date: 2020/12/22
* @Description: 大顶堆
*/
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 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);
}
//向堆中添加元素时进行比较,当索引=0或者自身值<父亲结点值时停止循环
private void siftUp(int k) {
while (k > 0 && data.get(parent(k)).compareTo(data.get(k))<0){
data.swap(k,parent(k)); //交换数组中索引为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;
}
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++;//进入这里的条件是存在右孩子,且左孩子的值小于右孩子的值
}
//这时 j 存储的为左右孩子中值较大的
//当未违反堆的性质时,即自身结点大于孩子结点的值的较大值时,直接结束循环
if(data.get(k).compareTo(data.get(j)) >= 0){
break;
}
//当违反了堆的性质时,进行交换
data.swap(k,j);
k = j;
}
}
}
队列接口
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
实现的优先队列
/**
* @Author: Cui
* @Date: 2020/12/23
* @Description:
*/
public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{
private MaxHeap<E> maxHeap;
public PriorityQueue(){
maxHeap = new MaxHeap<>();
}
@Override
public int getSize() {
return maxHeap.size();
}
@Override
public boolean isEmpty() {
return maxHeap.isEmpty();
}
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
@Override
public E dequeue() {
return maxHeap.extractMax();
}
@Override
public E getFront() {
return maxHeap.findMax();
}
}