索引堆
来源:互联网 发布:网络直播怎么兴起的 编辑:程序博客网 时间:2024/06/16 02:07
在之前的堆排序中是直接操作的堆节点存储的真实数据,如果每个节点存储的数据很大,这样操作就会很耗性能,这是索引堆就可以很好的解决此类问题。
如下图是一个Index Max Heap
其中,data[]存储的是每个节点的实际内容,而index[]存储的是每个节点的内容data[i]在整个堆中的编号。如根节点(这里根节点从1开始)的索引为index[1] = 10,所以指向的存储的实际内容为data[10] = 62。因此,我们在进行操作时(如向堆中插入元素或从堆中取出堆顶元素)实际操作的是索引堆数组index[],而不是实际堆元素数组data[],索引堆数组中依次存放了堆元素在data[]中的位置。
package com.h.heap;/** * Created by John on 2017/9/17. */import java.util.*;import java.lang.*;/** * 最大索引堆 */public class IndexMaxHeap<Item extends Comparable> { protected Item[] data; // 最大索引堆中的数据 protected int[] indexes; // 最大索引堆中的索引 protected int count; protected int capacity; // 构造函数, 构造一个空堆, 可容纳capacity个元素 public IndexMaxHeap(int capacity){ data = (Item[])new Comparable[capacity+1]; indexes = new int[capacity+1]; count = 0; this.capacity = capacity; } // 返回索引堆中的元素个数 public int size(){ return count; } // 返回一个布尔值, 表示索引堆中是否为空 public boolean isEmpty(){ return count == 0; } // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item // 传入的i对用户而言,是从0索引的 public void insert(int i, Item item){ assert count + 1 <= capacity; assert i + 1 >= 1 && i + 1 <= capacity; i += 1; data[i] = item; indexes[count+1] = i; count ++; shiftUp(count); } // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据 public Item extractMax(){ assert count > 0; Item ret = data[indexes[1]]; swapIndexes( 1 , count ); count --; shiftDown(1); return ret; } // 从最大索引堆中取出堆顶元素的索引 public int extractMaxIndex(){ assert count > 0; int ret = indexes[1] - 1; swapIndexes( 1 , count ); count --; shiftDown(1); return ret; } // 获取最大索引堆中的堆顶元素 public Item getMax(){ assert count > 0; return data[indexes[1]]; } // 获取最大索引堆中的堆顶元素的索引 public int getMaxIndex(){ assert count > 0; return indexes[1]-1; } // 获取最大索引堆中索引为i的元素 public Item getItem( int i ){ assert i + 1 >= 1 && i + 1 <= capacity; return data[i+1]; } // 将最大索引堆中索引为i的元素修改为newItem public void change( int i , Item newItem ){ i += 1; data[i] = newItem; // 找到indexes[j] = i, j表示data[i]在堆中的位置 // 之后shiftUp(j), 再shiftDown(j) for( int j = 1 ; j <= count ; j ++ ) if( indexes[j] == i ){ shiftUp(j); shiftDown(j); return; } } // 交换索引堆中的索引i和j private void swapIndexes(int i, int j){ int t = indexes[i]; indexes[i] = indexes[j]; indexes[j] = t; } //******************** //* 最大索引堆核心辅助函数 //******************** // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 private void shiftUp(int k){ while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){ swapIndexes(k, k/2); k /= 2; } } // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 private void shiftDown(int k){ while( 2*k <= count ){ int j = 2*k; if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 ) j ++; if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 ) break; swapIndexes(k, j); k = j; } } // 测试索引堆中的索引数组index // 注意:这个测试在向堆中插入元素以后, 不进行extract操作有效 public boolean testIndexes(){ int[] copyIndexes = new int[count+1]; for( int i = 0 ; i <= count ; i ++ ) copyIndexes[i] = indexes[i]; copyIndexes[0] = 0; Arrays.sort(copyIndexes); // 在对索引堆中的索引进行排序后, 应该正好是1...count这count个索引 boolean res = true; for( int i = 1 ; i <= count ; i ++ ) if( copyIndexes[i-1] + 1 != copyIndexes[i] ){ res = false; break; } if( !res ){ System.out.println("Error!"); return false; } return true; } // 测试 IndexMaxHeap public static void main(String[] args) { int N = 1000000; IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N); for( int i = 0 ; i < N ; i ++ ) indexMaxHeap.insert( i , (int)(Math.random()*N) ); assert indexMaxHeap.testIndexes(); }}
package com.h.heap;import java.util.*;// 使用最大索引堆进行堆排序, 来验证我们的最大索引堆的正确性// 最大索引堆的主要作用不是用于排序, 我们在这里使用排序只是为了验证我们的最大索引堆实现的正确性// 在后续的图论中, 无论是最小生成树算法, 还是最短路径算法, 我们都需要使用索引堆进行优化:)public class IndexHeapSort { // 我们的算法类不允许产生任何实例 private IndexHeapSort(){} public static void sort(Comparable[] arr){ int n = arr.length; IndexMaxHeap<Comparable> indexMaxHeap = new IndexMaxHeap<Comparable>(n); for( int i = 0 ; i < n ; i ++ ) indexMaxHeap.insert( i , arr[i] ); for( int i = n-1 ; i >= 0 ; i -- ) arr[i] = indexMaxHeap.extractMax(); } // 测试 Index Heap Sort public static void main(String[] args) { int N = 1000000; Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000); SortTestHelper.testSort("com.h.heap.IndexHeapSort", arr); return; }}
package com.h.heap;import java.lang.reflect.Method;import java.lang.Class;import java.util.*;public class SortTestHelper { // SortTestHelper不允许产生任何实例 private SortTestHelper(){} // 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR] public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) { assert rangeL <= rangeR; Integer[] arr = new Integer[n]; for (int i = 0; i < n; i++) arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL)); return arr; } // 生成一个近乎有序的数组 // 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据 // swapTimes定义了数组的无序程度: // swapTimes == 0 时, 数组完全有序 // swapTimes 越大, 数组越趋向于无序 public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){ Integer[] arr = new Integer[n]; for( int i = 0 ; i < n ; i ++ ) arr[i] = new Integer(i); for( int i = 0 ; i < swapTimes ; i ++ ){ int a = (int)(Math.random() * n); int b = (int)(Math.random() * n); int t = arr[a]; arr[a] = arr[b]; arr[b] = t; } return arr; } // 打印arr数组的所有内容 public static void printArray(Object[] arr) { for (int i = 0; i < arr.length; i++){ System.out.print( arr[i] ); System.out.print( ' ' ); } System.out.println(); return; } // 判断arr数组是否有序 public static boolean isSorted(Comparable[] arr){ for( int i = 0 ; i < arr.length - 1 ; i ++ ) if( arr[i].compareTo(arr[i+1]) > 0 ) return false; return true; } // 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间 // 将算法的运行时间打印在控制台上 public static void testSort(String sortClassName, Comparable[] arr){ // 通过Java的反射机制,通过排序的类名,运行排序函数 try{ // 通过sortClassName获得排序函数的Class对象 Class sortClass = Class.forName(sortClassName); // 通过排序函数的Class对象获得排序方法 Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class}); // 排序参数只有一个,是可比较数组arr Object[] params = new Object[]{arr}; long startTime = System.currentTimeMillis(); // 调用排序函数 sortMethod.invoke(null,params); long endTime = System.currentTimeMillis(); assert isSorted( arr ); System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" ); } catch(Exception e){ e.printStackTrace(); } } // 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间 // 将算法的运行时间以long类型返回, 单位为毫秒(ms) public static long testSort2(String sortClassName, Comparable[] arr){ // 通过Java的反射机制,通过排序的类名,运行排序函数 try{ // 通过sortClassName获得排序函数的Class对象 Class sortClass = Class.forName(sortClassName); // 通过排序函数的Class对象获得排序方法 Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class}); // 排序参数只有一个,是可比较数组arr Object[] params = new Object[]{arr}; long startTime = System.currentTimeMillis(); // 调用排序函数 sortMethod.invoke(null,params); long endTime = System.currentTimeMillis(); assert isSorted( arr ); return endTime - startTime; } catch(Exception e){ e.printStackTrace(); } return 0; }}
优化后的Index Max Heap:使用reverse数组反向查找
reverse[i] 表示索引i在indexes(堆)中的位置
indexes[i] = j
reverse[j] = i
indexes[reverse[i]] = i
reverse[indexes[i]] = i
package com.h.heap;import java.lang.reflect.Array;import java.util.*;import java.lang.*;// 最大索引堆public class IndexMaxHeap<Item extends Comparable> { protected Item[] data; // 最大索引堆中的数据 protected int[] indexes; // 最大索引堆中的索引, indexes[x] = i 表示索引i在x的位置 protected int[] reverse; // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置 protected int count; protected int capacity; // 构造函数, 构造一个空堆, 可容纳capacity个元素 public IndexMaxHeap(int capacity){ data = (Item[])new Comparable[capacity+1]; indexes = new int[capacity+1]; reverse = new int[capacity+1]; for( int i = 0 ; i <= capacity ; i ++ ) reverse[i] = 0; count = 0; this.capacity = capacity; } // 返回索引堆中的元素个数 public int size(){ return count; } // 返回一个布尔值, 表示索引堆中是否为空 public boolean isEmpty(){ return count == 0; } // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item // 传入的i对用户而言,是从0索引的 public void insert(int i, Item item){ assert count + 1 <= capacity; assert i + 1 >= 1 && i + 1 <= capacity; // 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。 assert !contain(i); i += 1; data[i] = item; indexes[count+1] = i; reverse[i] = count + 1; count ++; shiftUp(count); } // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据 public Item extractMax(){ assert count > 0; Item ret = data[indexes[1]]; swapIndexes( 1 , count ); reverse[indexes[count]] = 0; count --; shiftDown(1); return ret; } // 从最大索引堆中取出堆顶元素的索引 public int extractMaxIndex(){ assert count > 0; int ret = indexes[1] - 1; swapIndexes( 1 , count ); reverse[indexes[count]] = 0; count --; shiftDown(1); return ret; } // 获取最大索引堆中的堆顶元素 public Item getMax(){ assert count > 0; return data[indexes[1]]; } // 获取最大索引堆中的堆顶元素的索引 public int getMaxIndex(){ assert count > 0; return indexes[1]-1; } // 看索引i所在的位置是否存在元素 boolean contain( int i ){ assert i + 1 >= 1 && i + 1 <= capacity; return reverse[i+1] != 0; } // 获取最大索引堆中索引为i的元素 public Item getItem( int i ){ assert contain(i); return data[i+1]; } // 将最大索引堆中索引为i的元素修改为newItem public void change( int i , Item newItem ){ assert contain(i); i += 1; data[i] = newItem; // 找到indexes[j] = i, j表示data[i]在堆中的位置 // 之后shiftUp(j), 再shiftDown(j)// for( int j = 1 ; j <= count ; j ++ )// if( indexes[j] == i ){// shiftUp(j);// shiftDown(j);// return;// } // 有了 reverse 之后, // 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置 shiftUp( reverse[i] ); shiftDown( reverse[i] ); } // 交换索引堆中的索引i和j // 由于有了反向索引reverse数组, // indexes数组发生改变以后, 相应的就需要维护reverse数组 private void swapIndexes(int i, int j){ int t = indexes[i]; indexes[i] = indexes[j]; indexes[j] = t; reverse[indexes[i]] = i; reverse[indexes[j]] = j; } //******************** //* 最大索引堆核心辅助函数 //******************** // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 private void shiftUp(int k){ while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){ swapIndexes(k, k/2); k /= 2; } } // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 private void shiftDown(int k){ while( 2*k <= count ){ int j = 2*k; if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 ) j ++; if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 ) break; swapIndexes(k, j); k = j; } } // 测试索引堆中的索引数组index和反向数组reverse // 注意:这个测试在向堆中插入元素以后, 不进行extract操作有效 public boolean testIndexes(){ int[] copyIndexes = new int[count+1]; int[] copyReverseIndexes = new int[count+1]; for( int i = 0 ; i <= count ; i ++ ) { copyIndexes[i] = indexes[i]; copyReverseIndexes[i] = reverse[i]; } copyIndexes[0] = 0; copyReverseIndexes[0] = 0; Arrays.sort(copyIndexes); Arrays.sort(copyReverseIndexes); // 在对索引堆中的索引和反向索引进行排序后, // 两个数组都应该正好是1...count这count个索引 boolean res = true; for( int i = 1 ; i <= count ; i ++ ) if( copyIndexes[i-1] + 1 != copyIndexes[i] || copyReverseIndexes[i-1] + 1 != copyReverseIndexes[i] ){ res = false; break; } if( !res ){ System.out.println("Error!"); return false; } return true; } // 测试 IndexMaxHeap public static void main(String[] args) { int N = 1000000; IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N); for( int i = 0 ; i < N ; i ++ ) indexMaxHeap.insert( i , (int)(Math.random()*N) ); assert indexMaxHeap.testIndexes(); }}
补充:堆的实现细节的优化
- ShiftUp 和 ShiftDown 中使用赋值操作替换swap操作
- 表示堆的数组从0开始索引
- 没有capacity的限制,动态的调整堆中数组的大小
相关概念:最小堆,最小索引堆,多路归并排序,最大最小队列,二叉堆(Binary Heap),d叉堆(d-ary Heap),二项堆,斐波那契堆
阅读全文
0 0
- 索引堆
- 索引堆
- 索引堆
- 索引堆排序
- 4-8 索引堆(最大索引堆)
- Index Heap(索引堆)
- java实现最大索引堆(最大堆的优化版)
- 最大堆、索引堆、二叉搜索树的JavaScript实现
- 堆表和索引组织表区别
- 堆表和索引组织表区别
- 堆表和索引组织表区别
- 堆表和索引组织表区别
- 堆组织表,索引组织表和索引聚簇表
- 堆组织表、索引组织表、索引聚簇表
- 4-9 索引堆的优化(索引最大堆的表示)
- Mysql聚集索引和非聚集索引(堆组织表和索引组织表)
- SQL Server索引 (原理、存储)聚集索引、非聚集索引、堆
- (表-上)堆表 索引组织表 聚簇表
- git常用操作
- 数组去重
- The Dominator of Strings
- 抽象类 接口 多态
- MYSQL总结
- 索引堆
- 前段成长之路——CSS3基础(二)选择器
- C++基础
- mysql之路第二篇
- Java SE面试题
- noip倒计时
- Servlet实现文件上传
- java的代码技巧
- Fedora 安装electronic-wechat 解决[续]