堆的实现
什么是堆
堆就是用数组实现的二叉树,堆有两种形式:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
这是一个最大堆,,因为每一个父节点的值都比其子节点要大。根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。而对于最小堆,根节点中的元素总是树中的最小值。堆属性非常有用,因为堆常常被当做优先队列使用,因为可以快速地访问到“最重要”的元素。
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。--唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。
堆的Java实现
堆的接口
import java.util.NoSuchElementException;
/**
* Priority queue where objects have a priority that is provided extrinsically,
* i.e., priorities are supplied as an argument during insertion and can be changed
* using the changePriority method. Cannot contain duplicate or null items.
*/
public interface ExtrinsicMinPQ<T> {
/**
* Adds an item with the given priority value.
* @throws IllegalArgumentException if item is null or is already present in the PQ
*/
void add(T item, double priority);
/** Returns true if the PQ contains the given item; false otherwise. */
boolean contains(T item);
/**
* Returns the item with the least-valued priority.
* @throws NoSuchElementException if the PQ is empty
*/
T peekMin();
/**
* Removes and returns the item with the least-valued priority.
* @throws NoSuchElementException if the PQ is empty
*/
T removeMin();
/**
* Changes the priority of the given item.
* @throws NoSuchElementException if the item is not present in the PQ
*/
void changePriority(T item, double priority);
/** Returns the number of items in the PQ. */
int size();
/** Returns true if the PQ is empty; false otherwise. */
default boolean isEmpty() {
return size() == 0;
}
}
堆的节点类
class PriorityNode<T> {
private final T item;
private double priority;
PriorityNode(T e, double p) {
this.item = e;
this.priority = p;
}
T getItem() {
return this.item;
}
double getPriority() {
return this.priority;
}
void setPriority(double priority) {
this.priority = priority;
}
@Override
public String toString() {
return "PriorityNode{" +
"item=" + item +
", priority=" + priority +
'}';
}
}
最小值堆的实现
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
/**
* @see ExtrinsicMinPQ
*/
public class ArrayHeapMinPQ<T> implements ExtrinsicMinPQ<T> {
static final int START_INDEX = 1;//实现的堆下标从1开始
List<PriorityNode<T>> items;//存储元素的数组
HashMap<T,Integer> map = new HashMap<>();//使用一个map记录key在数组中的小标序号,增加查找速度
// TODO: add fields as necessary
public ArrayHeapMinPQ() {
items = new ArrayList<>();
T a = null;
this.items.add(new PriorityNode<T>(a,-10000));//因为下标从一开始,所以给第0个位置加入一个元素
// TODO: add code as necessary
}
// Here's a method stub that may be useful. Feel free to change or remove it, if you wish.
// You'll probably want to add more helper methods like this one to make your code easier to read.
/**
* A helper method for swapping the items at two indices of the array heap.
* 交换堆里的两个元素位置,同时需要修改map里对应的值
*/
private void swap(int a, int b) {
// TODO: replace this with your code
PriorityNode<T> t = this.items.get(a);
this.items.set(a,this.items.get(b));
map.put(this.items.get(b).getItem(),a);
this.items.set(b,t);
map.put(t.getItem(),b);
}
//当前节点的父节点下标
private int getparent(int a)
{
return a/2;
}
//当前节点的左子节点下标
private int getLeftChid(int a)
{
return 2*a;
}
//当前节点的右子节点下标
private int getRightChild(int a)
{
return 2*a+1;
}
/*
修改了第i个节点的权值后
从第i个节点开始向上重新调整这个堆
*/
private void buildHeap(int i)
{
if(i>0)
{
if(this.items.get(i).getPriority()<this.items.get(getparent(i)).getPriority())//如果这个节点的权值比父节点的权值小的话,与父节点交换位置
{
swap(i,getparent(i));
}
buildHeap(getparent(i));//重新调整父节点以上的堆结构
}
}
/**
* 添加一个元素
*/
@Override
public void add(T item, double priority) {
// TODO: replace this with your code
if(this.contains(item))
{
throw new IllegalArgumentException();//如果堆里存在这个元素则抛出异常
}
else
{
//将该节点先放入堆的最后一个位置,并且加入map中
this.items.add(new PriorityNode<>(item,priority));
map.put(item,this.items.size()-1);
//向上调整堆结构
buildHeap(this.items.size() - 1);
}
}
/*
使用map检查堆中是否包含指定元素
*/
@Override
public boolean contains(T item) {
// TODO: replace this with your code
if(item==null)
{
if(map.containsKey(null))
return true;
else
return false;
}
else
{
if(map.containsKey(item))
return true;
else
return false;
}
}
/*
查看最小值(堆的顶部)
*/
@Override
public T peekMin() {
// TODO: replace this with your code
if(this.items.size()<=1)
throw new NoSuchElementException();
return this.items.get(1).getItem();
}
/*
调整了第i个节点的权值后,从当前节点向下调整堆结构
*/
private void fixDown(int i)//从节点i开始向下调整
{
PriorityNode<T> t = this.items.get(i);
int child = i * 2;//左子节点下标,child用来记录两个子节点中权值较小的一个
while(child <this.items.size())
{
//如果右子节点(如果存在的话)的权值比左子节点权值小,那么child++
if(child+1<this.items.size() && this.items.get(child+1).getPriority()<this.items.get(child).getPriority())
{
child++;
}
//如果当前节点的权值比两个子节点的权值都小,那么不需要调整下面的节点了
if(this.items.get(i).getPriority()<this.items.get(child).getPriority())
{
break;
}
//和子节点交换,并进行下一层的调整
swap(i,child);
i = child;
child = i*2;
}
this.items.set(i,t);
}
/*
删除最小的元素,即将顶部元素删除
*/
@Override
public T removeMin() {
// TODO: replace this with your code
if(this.items.size()<=1)
throw new NoSuchElementException();
//将堆的最后元素调整到顶部位置,之后从顶部开始向下调整堆
swap(1,this.items.size()-1);
T res = this.items.remove(this.items.size()-1).getItem();
if(this.items.size()>1)
{
fixDown(1);
}
map.remove(res);
return res;
}
/*
调整某一个节点的权值
*/
@Override
public void changePriority(T item, double priority) {
// TODO: replace this with your code
for (int i=1;i<this.items.size();i++)
{
if(this.items.get(i).getItem().equals(item))
{
double thispri = this.items.get(i).getPriority();
this.items.get(i).setPriority(priority);
if(thispri> priority)
{
//如果调整的权值比之前小了,那么只需要从该节点开始向上调整堆
buildHeap(i);
}
else
{
//如果调整的权值比之前大了,那么只需要从该节点开始向下调整堆
fixDown(i);
}
return ;
}
}
throw new NoSuchElementException();
}
@Override
public int size() {
// TODO: replace this with your code
return this.items.size() - 1;
}
}