1 索引堆基本概念
堆的基本概念不用多种介绍了,但是对于堆来说,有时堆结点存储的数据结构交换时间及空间比较大,或者结点存储的是比较复杂的数据结构。这个时候,我们可以采用索引堆这种数据结构来解决
索引堆:在堆的基础上用一个索引数组来存储数据元素的位置,即索引堆里面包含两个数组,如下:
建堆之前:
建堆之后
在这个图中,有两个数组,data[]存储真实的数据元素,index[]存储data的索引,并且index构成一个特殊的堆。
例如:index[1]是堆顶的第一个元素,index[1] = 10,表示取data数组索引10的元素62。可以看到62确实是最大的一个元素,这是一个最大的索引堆
索引堆的两大重要特点
1 比较的是真实元素的大小,交换的是对应的索引index的位置,真实的data数组并没有任何改变
2 访问数据元素,必须先找到其索引,即先找到index[]的值
注意:data[]数组我们是从1开始存储的,但是真实的索引是从0开始的
索引堆在图论算法中求最短路径以及最小生成树中都有应用。
2 最大索引堆
如上定义,最大的索引堆就是堆顶元素是数组最大元素的索引,即index[1] 的值是最大数组元素的索引。一般一个完整的最大索引堆定义如下:
package com.qiyei.heap;
import com.qiyei.util.LogUtil;
/**
* @author Created by qiyei2015 on 2018/3/31.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 索引最大堆indexArray[]构成堆 最大特点,比较是比较pq[]的值,交换是交换indexArray[]的值
*/
public class IndexMaxHeap<T extends Comparable<T>> extends BaseHeap{
/**
* 索引 indexArray的值是pq[]的下标
*/
private int[] indexArray;
public IndexMaxHeap() {
super();
indexArray = new int[0];
}
public IndexMaxHeap(int max) {
super(max);
indexArray = new int[max + 1];
}
// public IndexMaxHeap(Comparable[] array) {
// super(array);
//// System.arraycopy(array,0,in,1,array.length);
// }
/**
* 插入元素到最大索引堆
* @param i 从0 开始计算
* @param t
*/
public void insert(int i,T t){
if (i < 0 || i >= N){
return;
}
i += 1;
//记录数据 pq[] 下标i为t
pq[i] = t;
//记录索引位置 从1开始记录索引
count = count + 1;
indexArray[count] = i;
swimIndex(count);
}
/**
* 删除最大的元素 从最大堆取出最大元素
* @return
*/
public T delMax(){
//先找到索引,根据索引找到数
int index = indexArray[1];
T t = (T) pq[index];
exchIndex(1,count);
count--;
sinkIndex(1);
return t;
}
/**
* 获取最大的堆顶的元素
* @return
*/
public T getMax(){
return (T) pq[indexArray[1]];
}
/**
* 从堆中取出最大元素的索引,不删除元素
* @return
*/
public int getIndexMax(){
return indexArray[1] - 1;
}
/**
* 取出最大堆堆顶元素,并删除该元素
* @return
*/
public int extractIndexMax(){
int index = indexArray[1] - 1;
//交换索引
exchIndex(1,count);
count--;
sinkIndex(1);
return index;
}
/**
* 获取最大索引堆索引为i的元素的值
* @param i
* @return
*/
public T getItemIndex(int i){
return (T) pq[indexArray[i+1]];
}
/**
* 将堆中索引为i的元素值替换为t
* @param i
* @param t
*/
public void replace(int i , T t){
i++;
pq[i] = t;
for (int j = 0 ; j < count ; j++){
if (indexArray[j] == i){
sinkIndex(j);
swimIndex(j);
return;
}
}
}
/**
* 堆的上浮,解决子节点比父结点大的问题,少交换,优化堆的上浮过程
* 比较是比较pq[]的值,交换是交换indexArray[]的值
* @param k 节点k上浮
*/
private void swimIndex(int k){
//取索引数组的值,然后得到pq的索引
int temp = indexArray[k];
//子节点比父结点大
while (k > 1 && less(indexArray[k/2],indexArray[k])){
//父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
indexArray[k] = indexArray[k/2];
indexArray[k/2] = temp;
k = k/2;
}
}
/**
* 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
* 比较是比较pq[]的值,交换是交换indexArray[]的值
* @param k
*/
private void sinkIndex(int k) {
int temp = indexArray[k];
//判断有左孩子,有孩子就行
while (2 * k <= count) {
int j = 2 * k; //此轮循环中 k 与j交换
if ((j + 1) <= count && less(indexArray[j], indexArray[j + 1])) {
j++; //更新为右孩子
}
//父结点大于子节点
if (!less(indexArray[k], indexArray[j])) {
break;
}
//将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
indexArray[k] = indexArray[j];
indexArray[j] = temp;
k = j; //更新k的位置
}
}
/**
* 交换索引位置
* @param i
* @param j
*/
private void exchIndex(int i, int j) {
int temp = indexArray[i];
indexArray[i] = indexArray[j];
indexArray[j] = temp;
}
/**
* 打印数组
*/
public void printData(){
for (int i = 1 ; i < pq.length ;i++){
LogUtil.println("data:[ " + (i - 1) + " " + pq[i] + " ]");
}
}
}
注意点:可以看到在swimIndex(int k)和sinkIndex(int k)我们比较的是真实的数据的大小(通过indexarray[k]得到pq数组的索引)。但是交换的是indexArray的数组
测试代码如下:
/**
* 测试最大索引堆
*/
private static void testIndexMaxHeap(){
//索引堆测试
IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<>(100);
for (int i = 0 ; i < 100;i++){
int value = random.nextInt(100);
if ( i == 5){
LogUtil.println("indexMaxHeap index:" + i + " "+ value);
}
indexMaxHeap.insert(i,value);
}
LogUtil.println("indexMaxHeap size:" + indexMaxHeap.size());
LogUtil.println("indexMaxHeap getIndexMax():" + indexMaxHeap.getIndexMax());
LogUtil.println("indexMaxHeap getMax:" + indexMaxHeap.getMax());
LogUtil.println("indexMaxHeap getItemIndex(5):" + indexMaxHeap.getItemIndex(5));
LogUtil.println("indexMaxHeap data:");
indexMaxHeap.printData();
for (int i = 0 ; i < 100;i++){
LogUtil.println("[ " + indexMaxHeap.getIndexMax() + " " + indexMaxHeap.delMax() + " ]");
}
}
3 最小索引堆
如上定义,最小的索引堆就是堆顶元素是数组最小的元素索引,即index[1] 的值是最小数组元素的索引。一般一个完整的最小索引堆定义如下:
package com.qiyei.heap;
import com.qiyei.util.LogUtil;
/**
* @author Created by qiyei2015 on 2018/4/19.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 索引最小堆indexArray[]构成堆 最大特点,比较是比较pq[]的值,交换是交换indexArray[]的值
*/
public class IndexMinHeap<T extends Comparable<T>> extends BaseHeap {
/**
* 索引 indexArray的值是pq[]的下标
*/
private int[] indexArray;
public IndexMinHeap() {
super();
indexArray = new int[0];
}
public IndexMinHeap(int max) {
super(max);
indexArray = new int[max + 1];
}
// public IndexMinHeap(Comparable[] array) {
// super(array);
// }
/**
* 插入元素到最小索引堆
* @param i 从0 开始计算
* @param t
*/
public void insert(int i,T t){
if (i < 0 || i >= N){
return;
}
i += 1;
//记录数据 pq[] 下标i为t
pq[i] = t;
//记录索引位置 从1开始记录索引
count = count + 1;
indexArray[count] = i;
//上浮count
swimIndex(count);
}
/**
* 删除最小的元素 从最小堆取出堆顶元素
* @return
*/
public T delMin(){
//先找到索引,根据索引找到数
int index = indexArray[1];
T t = (T) pq[index];
exchIndex(1,count);
count--;
//下沉堆顶元素
sinkIndex(1);
return t;
}
/**
* 获取最小的堆顶的元素
* @return
*/
public T getMin(){
return (T) pq[indexArray[1]];
}
/**
* 从堆中取出最小元素的索引,不删除元素
* @return
*/
public int getIndexMin(){
return indexArray[1] - 1;
}
/**
* 取出最小堆堆顶元素,并删除该元素
* @return
*/
public int extractIndexMin(){
int index = indexArray[1] - 1;
//交换索引
exchIndex(1,count);
count--;
//下沉
sinkIndex(1);
return index;
}
/**
* 获取最大索引堆索引为i的元素的值
* @param i
* @return
*/
public T getItemIndex(int i){
return (T) pq[indexArray[i+1]];
}
/**
* 将堆中索引为i的元素值替换为t
* @param i
* @param t
*/
public void replace(int i , T t){
i++;
pq[i] = t;
for (int j = 0 ; j < count ; j++){
if (indexArray[j] == i){
sinkIndex(j);
swimIndex(j);
return;
}
}
}
/**
* 是否包含w结点
* @param w
* @return
*/
public boolean contains(int w){
w++;
if (w >= 1 && w <= count){
return true;
}
return false;
}
/**
* 堆的上浮,解决子节点比父结点小的问题,少交换,优化堆的上浮过程
* 比较是比较pq[]的值,交换是交换indexArray[]的值
* @param k 节点k上浮
*/
private void swimIndex(int k){
//取索引数组的值,然后得到pq的索引
int temp = indexArray[k];
//如果子节点比父节点小,就交换二者的索引数组
while (k > 1 && less(indexArray[k],indexArray[k/2])){
//父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
indexArray[k] = indexArray[k/2];
indexArray[k/2] = temp;
k = k/2;
}
}
/**
* 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换
* 比较是比较pq[]的值,交换是交换indexArray[]的值
* @param k
*/
private void sinkIndex(int k) {
int temp = indexArray[k];
//判断有左孩子,有孩子就行
while (2 * k <= count) {
int j = 2 * k; //此轮循环中 k 与j交换 交换较小的孩子
if ((j + 1) <= count && less(indexArray[j+1], indexArray[j])) {
j++; //更新为右孩子
}
//父结点小于子节点
if (less(indexArray[k], indexArray[j])) {
break;
}
//将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
indexArray[k] = indexArray[j];
indexArray[j] = temp;
k = j; //更新k的位置
}
}
/**
* 交换索引位置
* @param i
* @param j
*/
private void exchIndex(int i, int j) {
int temp = indexArray[i];
indexArray[i] = indexArray[j];
indexArray[j] = temp;
}
/**
* 打印数组
*/
public void printData(){
for (int i = 1 ; i < pq.length ;i++){
LogUtil.println("data:[ " + (i - 1) + " " + pq[i] + " ]");
}
}
}
如最大索引堆一样,比较的是真实的数据元素,交换的是索引数组元素
测试代码如下:
/**
* 测试最小索引堆
*/
private static void testIndexMinHeap(){
//索引堆测试
IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<>(100);
for (int i = 0 ; i < 100;i++){
int value = random.nextInt(100);
if ( i == 5){
LogUtil.println("indexMinHeap index:" + i + " "+ value);
}
indexMinHeap.insert(i,value);
}
LogUtil.println("indexMinHeap size:" + indexMinHeap.size());
LogUtil.println("indexMinHeap getIndexMin():" + indexMinHeap.getIndexMin());
LogUtil.println("indexMinHeap getMin:" + indexMinHeap.getMin());
LogUtil.println("indexMinHeap getItemIndex(5):" + indexMinHeap.getItemIndex(5));
LogUtil.println("indexMinHeap data:");
indexMinHeap.printData();
for (int i = 0 ; i < 100;i++){
LogUtil.println("[ " + indexMinHeap.getIndexMin() + " " + indexMinHeap.delMin() + " ]");
}
}
最后:
源代码github https://github.com/qiyei2015/Algorithms heap部分