索引堆

来源:互联网 发布:网络直播怎么兴起的 编辑:程序博客网 时间: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();    }}

补充:堆的实现细节的优化

  1. ShiftUp 和 ShiftDown 中使用赋值操作替换swap操作
  2. 表示堆的数组从0开始索引
  3. 没有capacity的限制,动态的调整堆中数组的大小

相关概念:最小堆,最小索引堆,多路归并排序,最大最小队列,二叉堆(Binary Heap),d叉堆(d-ary Heap),二项堆,斐波那契堆

原创粉丝点击