索引堆
[为什么要引入索引堆?]
给出一个数组,构建如下树形结构
通过heappify操作,构建一个堆
当堆构建完成后,我们无法通过索引获取到原来元素所在位置,所以后续很难通过索引改变节点的值。所以我们引入索引堆
[什么是索引堆?]
将数组中的元素和索引分开存储,而真正表现成堆的是索引数组
通过Index-heapify操作,构建索引堆,构建完成后,索引堆如下。
通过构建堆前后进行对比,我们能够发现data数组没有发生改变,真正改变的只是index数组中的内容(索引)发生交换。
例如:data[index[1]]=62,最大元素。
[代码实现]
template<typename Item>
class IndexMaxHeap{
private:
Item* data;
Item* indexes;
int capacity;
int count;
void shiftUp(int k){
while(k>1 && data[indexes[k/2]]<=data[indexes[k]]){
swap(indexes[k/2],indexes[k]);
k /= 2;
}
}
void shiftDown(int k){
while(2*k<=count){
int j = 2 * k;
if(j+1<=count && data[indexes[j]]<data[indexes[j+1]])
j += 1;
if(data[indexes[k]]>=data[indexes[j]])
break;
swap(indexes[k],indexes[j]);
k = j;
}
}
public:
IndexMaxHeap(int capacity){
this->capacity = capacity ;
count = 0;
data = new Item[capacity+1];
indexes = new Item[capacity+1];
}
~IndexMaxHeap(){
delete[] data;
delete[] indexes;
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(int i, Item item){
//由于构建的为索引堆,所以插入元素时,同时要指定元素的索引。
//对于外部用户而言,索引是从0开始,构建的索引堆索引是从1开始的
assert(count+1<=capacity);
assert(i+1<=capacity && i+1>=1);
i += 1;//索引从1开始
data[i] = item;
indexes[count+1] = i;
count++;
shiftUp(count);
}
Item extractMaxItem(){//获取最大元素
assert(count>0);
Item res = data[indexes[1]];
swap(indexes[1],indexes[count]);
count--;
shiftDown(1);
return res;
}
int extractMaxIndex()[//获取最大元素的索引
assert(count>0);
int res = indexes[1]-1;//外部索引从0开始
swap(indexes[1],indexes[count]);
count--;
shiftDown(1);
return res;
}
Item getItem(int i){//通过索引获取相应的元素,外部用户索引从0开始
return data[i+1];
}
void change(int i, Item newItem){//外部用户通过调用这个函数将索引i位置的元
//素改为newItem
i += 1;//内部改为从1开始
data[i] = newItem;
//找到indexes[j]=i,j表示data[i]在堆中的位置
//之后尝试进行shiftDown和shiftUp操作
for(int j=1;j<=count;j++){
if(indexes[j] == i){
shiftUp(j);
shiftDown(j);
return;
}
}
}
};
change函数时间复杂度分析:
for循环时间复杂度为O(n),shiftUp和shiftDown时间复杂度为O(logn)
整体的时间复杂度为O(n+logn),所以change函数的时间复杂度为O(n)
当用户需要执行n次change操作时,最坏的情况为O(n*n),相比较于O(nlogn),change操作时间复杂度变差。
[索引堆的优化]
通过上面的change函数的时间复杂度分析,若用户想改变一个位置的值时,时间复杂度过高,针对此进行优化。
最小索引堆如下:
若用户想改变位置4的元素,即data[4] = 15(13变为15),我们需要维护index数组的堆定义,之前的change操作使用过遍历一遍index数组,找到4所在位置,即index[9],之后再进行shiftUp(9),shiftDown(9)操作。这样我们的算法复杂度就变为O(n)级别。
改进方法:
我们通过另外一个rev数组,rev[i]表示索引i在index(堆)中的位置
若堆中i位置索引变为j
则索引j在堆中位置变为i
例如,堆中4位置索引变为8,即index[4] = 8;
则索引8在堆中位置变为i,即rev[8] = 4;
index[ i ] = j;
rec[ j ] = i;
[代码实现]
//伪代码
//在IndexMaxHeap中添加
int* reverse;
//IndexMaxHeap的构造函数
IndexMaxHeap(int capacity){
this->capacity = capacity ;
count = 0;
data = new Item[capacity+1];
indexes = new Item[capacity+1];
reverse = new int[capacity+1];
for(int i=0;i<=capacity;i++)
reverse[i] = 0;//堆中索引从1开始,所以reverse[i]=0没有意义
}
//insert操作
void insert(int i, Item item){
//由于构建的为索引堆,所以插入元素时,同时要指定元素的索引。
//对于外部用户而言,索引是从0开始,构建的索引堆索引是从1开始的
assert(count+1<=capacity);
assert(i+1<=capacity && i+1>=1);
i += 1;//索引从1开始
data[i] = item;
indexes[count+1] = i;
reverse[i] = count + 1;//新添加的元素的索引i在堆中的最后位置
count++;
shiftUp(count);
}
//shifitUp操作
void shiftUp(int k){
while(k>1 && data[indexes[k/2]]<=data[indexes[k]]){
swap(indexes[k/2],indexes[k]);//k/2和k两个位置上的元素已经发生改变
reverse[indexes[k/2]] = k/2;//索引indexes[k/2]在index(堆)中的位置
reverse[indexes[k]] = k;//索引indexes[k]在index(堆)中的位置
k /= 2;
}
}
//extractMaxItem操作,extractMaxIndex同理
Item extractMaxItem(){//获取最大元素
assert(count>0);
Item res = data[indexes[1]];
swap(indexes[1],indexes[count]);
reverse[indexes[1]] = 1;
reverse[indexes[count]] = 0;//相当于删除
count--;
shiftDown(1);
return res;
}
//shiftDown操作
void shiftDown(int k){
while(2*k<=count){
int j = 2 * k;
if(j+1<=count && data[indexes[j]]<data[indexes[j+1]])
j += 1;
if(data[indexes[k]]>=data[indexes[j]])
break;
swap(indexes[k],indexes[j]);
reverse[indexes[k]] = k;
reverse[indexes[j]] = j;
k = j;
}
}
//change操作,时间复杂度O(logn)
void change(int i, Item newItem){//外部用户通过调用这个函数将索引i位置的元
//素改为newItem
assert(contain(i));//检查索引堆中是否包含i
i += 1;//内部改为从1开始
data[i] = newItem;
//找到indexes[j]=i,j表示data[i]在堆中的位置
//之后尝试进行shiftDown和shiftUp操作
int j = reverse[i];
shiftUp(j);
shiftDown(j);
return;
}
bool contain(int i){//对于外部用户而言数组从0开始
assert(i+1>0 && i+1<=capacity);
return reverse[i+1] != 0;//堆中索引从1开始
想说的话
以上堆和堆排序的全部内容就整理完了,索引堆是一种高级的数据结构,在后续的图论中还会用到,下期更新算法中另外一个很重要的问题,查找问题,以及二分搜索树,大家加油~