什么是索引最小优先队列
一般的队列我们只能从头开始访问元素,如果我们要直接根据索引进行元素的访问呢?这个时候就需要使用索引优先队列了,本例中使用所用最小优先队列,注意,这里最小指的是最小堆,即根节点比子节点大的堆。
item数组用来装数据;
pq数组用来装数据对应的索引,并且这个数组是按照其对应的元数据的值堆排序的,使用的是最小堆;
qp数组是pq数组的逆序,也就是说两者的索引的和值是互换的,pq的索引是qp的值,pq的值是qp的索引;
pq数组和qp数组有什么用呢?首先我们的堆需要有序,这里采用最小堆的结构,之前的最小优先队列可以访问最小值,那么如果要修改或者访问某个值怎么办呢,只有使用索引的方式了,也就是将每个添加的元素关联一个索引,但是为了堆的有序性,我们就需要调整堆的元素顺序,如果这个时候直接在item上调整,则会使得原始索引和元素的对应关系打乱,怎么办呢,我们可以不改变item,转而使用一个pq数组用来装item元素对应的索引,然后我们对堆的有序操作直接在pq数组上操作,但是底层的比较大小还是根据item的大小进行的,只是相应的改变item元素索引在pq数组中的位置,解决了在满足堆有序的条件下,索引和元素的对应关系后,新的问题是,假如我们要在item中,根据索引改变某个值,那么我们怎么去找这个索引呢,我们可以循环遍历pq数组,找到索引,然后修改,之后在使得堆有序,但是如果数组很长,遍历很费时间,怎么办呢,我们可以创建一个数组qp,qp数组是pq数组的逆序,也就是说两者的索引的和值是互换的,pq的索引是qp的值,pq的值是qp的索引,其实item数组的索引和qp数组的索引是一致的,这样根据item的索引就可以在qp数组找到pq数组的索引,在根据这个索引在pq数组内找到item数组对应的索引。
以上有一个显而易见的环形思维,感觉走了弯路,修改某个值不能直接根据索引在item中修改吗,答案是可以,但是我们真正的队列结构其实是根据pq实现的,也就是说,即便是根据索引在item中修改,我们还是要在pq中解决堆有序的问题,这个时候还是需要根据那个索引,找到我们要在pq中要进行操作的索引,因此还是需要qp,然后再pq中进行操作。
java代码实现
/**
* 数组实现堆的数据结构
*/
package mypackage;
//堆类
//T extends Comparable<T>,后续的数据才可进行比较
class heap<T extends Comparable<T>> {
// 实现基础为数组,元素个数N
private T[] items;
private int[] pq;
private int[] qp;
private int N;
// 构造方法,注意capacity+1,因为我们第一个元素是从索引1开始的,0处的索引不装数据
// 因此虽然我们传入的容量为capacity,这个capacity表示的是堆的数量,capacity+1才是数组的大小
public heap(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.pq = new int[capacity + 1];
this.qp = new int[capacity + 1];
N = 0;
// 初始化为-1.表示没有元素
for (int i = 0; i < qp.length; i++) {
qp[i] = -1;
}
}
//堆元素个数
public int size() {
return N;
}
//是否为空
public boolean isEmpty() {
return N == 0;
}
// 判断数组元素的大小,i,j在pq中,但是实际上值的比较还是在item中
public boolean less(int i, int j) {
return items[pq[i]].compareTo(items[pq[j]]) < 0;
}
// 交换数组元素,i,j在pq中
public void exchange(int i, int j) {
int temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
// 还要更新qp中的值
qp[pq[i]] = i;
qp[pq[j]] = j;
}
// 判断k索引的元素是否存在,这里的k是在item中,等价于在qp中
public boolean isExist(int k) {
// 无元素为-1,不等于-1表示有元素
return qp[k] != -1;
// 其实也可以return items[k]!=null;
}
// 最小元素的索引
public int minIndex() {
// 因为pq是有序的item元素的索引,所以pq的第一个元素就是item最小元素的索引
return pq[1];
}
// 插入元素,并且关联索引i
// 如果已经存在就直接返回,不允许插入元素。
// 如果不存在,执行插入操作,在item指定索引处插入元素,pq内添加元素的索引,qp是pq的逆序,最后对pq上浮操作
public void insert(int i, T t) {
if (isExist(i)) {
return;
} else {
N++;
items[i] = t;
pq[N] = i;
qp[i] = N;
// 注意swim的是pq
swim(N);
}
}
// 删除最小的元素并返回该元素的索引,其实就是删除根节点的元素
// 先交换第一个元素和最后一个元素,然后删除最后一个元素,这个就是最小的元素
// 因为此时顺序是乱的,然后采用下沉算法,使得堆有序
public int delminIndex () {
int minIndex = pq[1];
exchange(1, N);
qp[pq[N]] = -1;
pq[N] = -1;
items[minIndex] = null;
N--;
sink(1);
return minIndex;
}
// 删除指定指定索引的元素并返回
public T del(int i) {
int k = qp[i];
exchange(k, N);
qp[pq[N]] = -1;
pq[N] = -1;
T itemsi=items[i];
items[i] = null;
N--;
// 注意因为不一定是首尾,因此要进行下沉和上浮
sink(k);
swim(k);
return itemsi;
}
// 把与索引i处的元素修改为t
public void changeItem(int i, T t) {
items[i] = t;
int k = qp[i];
// 注意因为不一定是首尾,因此要进行下沉和上浮
sink(k);
swim(k);
}
//浮算法swim让堆的元素有序,注意是对pq进行操作的
// k位置处的父节点位置为k/2,左子节点为2k,右子节点位置为2k+1
// 浮算法swim就是不断的循环比较,如果这个节点小于父节点,就交换
public void swim(int k) {
while (k > 1) {
if (less(k, k / 2)) {
exchange(k, k / 2);
} else {
break;
}
// 每次k=k/2
k = k / 2;
}
}
// 下沉算法,使得堆有序,这是比较麻烦的算法,注意是对pq进行操作的
// 思路就是,从某个节点开始,向下循环
// 如果有右节点,就比较左右节点的大小,选取较小的一个最为较小值,如果没有右节点,那么左节点作为较小值
// 如果当前节点比较小的节点大,就交换,且让当前索引设置为较小值得索引,继续下沉循环
public void sink(int k) {
while (2 * k <= N) {
int min;
if (2 * k + 1 <= N) {
if (less(2 * k, 2 * k + 1)) {
min = 2 * k;
} else {
min = 2 * k + 1;
}
} else {
min = 2 * k;
}
if (less(k, min)) {
break;
}
exchange(k, min);
k = min;
}
}
}
//测试
public class MyJava {
public static void main(String[] args) {
heap<String> heap = new heap<>(10);
heap.insert(1, "A");
heap.insert(5, "F");
heap.insert(2, "B");
heap.insert(3, "C");
heap.insert(4, "D");
// 添加元素后,堆元素个数
System.out.println("元素个数:" + heap.size());
// 循环弹出队列中最大的值
// 更改元素
heap.changeItem(5,"E");
//删除指定索引的元素,并返回
System.out.println("删除的元素为:"+heap.del(1));
System.out.print("依次弹出最小元素的索引:");
while (!heap.isEmpty()) {
System.out.print(heap.delminIndex () + ",");
}
System.out.println();
System.out.println("是否为空:" + heap.isEmpty());
}
}