描述
如果我们想要在索引堆中修改索引为i的元素,我们就需要找到索引i(也可以理解为索引为i的元素)在堆中的位置,并对其进行维护以保持堆特性。就是我们需要对索引进行遍历直到indexes[j] == i表示索引i在堆中的位置为j,并对位置为j的索引进行维护,这样一来就需要耗费O(n)的时间复杂度。
为了提高这一操作的效率,我们可以引入一个reverse[i]表示索引i在堆中的位置,就可以非常方便地定位到一个元素的位置,算法的时间复杂度将降到O(1)。
思路
- 初始化时,令reverse[0…n]都等于0,表示堆中没有元素。
- 维护堆时,满足条件的索引会有所变更,这时候就要修改reverse[indexes[k]] = k,表示堆中第k个位置的索引(索引指向元素)在堆中的位置为k。
- 结束运行时,别忘了析构reverse。
实现代码
在索引堆的基础上增加了对reverse的维护工作,以最大索引堆为例。
#include<bits/stdc++.h>
using namespace std;
template<class Item>
class IndexMaxHeap {
private:
Item* data;
int* indexes;
int* reverse;
int count;
int capacity;
//只对堆中的索引进行操作
void siftUp(int k) {
//对索引指向的那个元素进行比较
while(data[indexes[k]/2] < data[indexes[k]] && k > 1) {
swap(indexes[k/2], indexes[k]);
//更新reverse,即元素所在的堆中位置信息需要变更
//索引index指向的元素的堆中位置改为k/2
reverse[indexes[k/2]] = k/2;
reverse[indexes[k]] = k;
k /= 2;
}
}
void shiftDown(int k) {
while(2*k <= count) {
int j = 2*k;
if(j + 1 <= count && data[indexes[j+1]] > data[indexes[j]])
j++;
if(data[indexes[k]] >= data[indexes[j]])
break;
swap(indexes[k], indexes[j]);
reverse[indexes[k]] = k;
reverse[indexes[j]] = j;
k = j;
}
}
public:
IndexMaxHeap(int capacity) {
data = new Item[capacity + 1];
indexes = new int[capacity + 1];
reverse = new int[capacity + 1];
//初始化reverse, 因为最开始堆为空
for(int i = 0; i <= capacity; i++) {
reverse[i] = 0;
}
count = 0;
this->capacity = capacity;
}
IndexMaxHeap(Item arr[], int n) {
data = new Item[n+1];
indexes = new int[n + 1];
reverse = new int[n + 1];
capacity = n;
for(int i = 0; i < n; i++) {
data[i+1] = arr[i];
indexes[i+1] = i+1;
reverse[i+1] = i+1;
}
count = n;
for(int i = n/2; i > 0; i--) {
shiftDown(i);
}
}
~IndexMaxHeap() {
delete[] data;
delete[] indexes;
delete[] reverse;
}
int size() {
return count;
}
bool isEmpty() {
return count == 0;
}
void reSize() {
Item* temp = new Item[count + 1 + capacity];
if(temp == NULL) {
printf("memory alloc error!\n");
system("pause");
exit(1);
}
for(int i = 0; i <= count; i++) {
temp[i] = data[i];
}
Item* del = data;
data = temp;
delete[] del;
int* t = new int[count + 1 + capacity];
if(t == NULL) {
printf("memory alloc error!\n");
system("pause");
exit(1);
}
for(int i = 0; i <= count; i++) {
t[i] = indexes[i];
}
int* del_2 = indexes;
indexes = t;
delete[] del_2;
int* now = new int[count + 1 + capacity];
if(now == NULL) {
printf("memory alloc error!\n");
system("pause");
exit(1);
}
for(int i = 0; i <= count; i++) {
now[i] = reverse[i];
}
int* del_3 = reverse;
reverse = now;
delete[] del_3;
}
void insert(int i, Item item) {
assert(i + 1 >=1);
if(count+1 > capacity || i + 1 > capacity)
reSize();
i += 1;
data[++count] = item;
indexes[count] = i;
reverse[i] = count;
shiftUp(count);
}
Item extractMax() {
assert(count > 0);
Item ret = data[indexes[1]];
indexes[1] = indexes[count];
reverse[indexes[1]] = 1;
//移出最后位置的索引指向的元素,把它在堆中的位置改为0,即不在堆中
reverse[indexes[count]] = 0;
count--;
shiftDown(1);
return ret;
}
int extractMaxIndex() {
assert(count > 0);
int ret = indexes[1] - 1;
indexes[1] = indexes[count];
reverse[indexes[1]] = 1;
reverse[indexes[count]] = 0;
count--;
shiftDown(1);
return ret;
}
Item getItem(int i) {
assert(i + 1 >= 1 && i + 1 <= capacity);
if(reverse[i+1] == 0) {
printf("Not found in heap\n");
system("pause");
exit(1);
}
return data[i+1];
}
//修改堆中元素值,并保证堆的性质
void change(int i, Item newItem) {
assert(i + 1 >= 1 && i + 1 <= capacity);
if(reverse[i+1] == 0) {
printf("Not found in heap\n");
system("pause");
return;
}
i += 1;
data[i] = newItem;
// for(int j = 1; j <= count; j++) {
// if(indexes[j] == i) {
// shiftUp(j);
// shiftDown(j);
// return;
// }
// }
//引入了reverse,方便了查找元素在堆中的位置,可以把上面注释的代码简写成这样
//降低了时间复杂度,从原来的O(n)到现在的O(1)
shiftUp(reverse[i]);
shiftDown(reverse[i]);
}
};
template<typename T>
void heapSortTest(T arr[], int n) {
IndexMaxHeap<T> indexMaxHeap = IndexMaxHeap<T>(arr, n);
int* indexes = new int[n];
//取出堆中元素,并取出索引
printf("heap data[x] :");
for(int i = 0; i < 10; i++) {
arr[i] = indexMaxHeap.getItem(i);
printf("%d ", arr[i]);
}
//大值的索引放到最后
for(int i = n-1; i >= 0; i--)
indexes[i] = indexMaxHeap.extractMaxIndex();
printf("\nheap data[indexes[x]]: ");
for(int i = 0; i < 10; i++) {
printf("%d ", arr[indexes[i]]);
}
//重新插入堆
for(int i = 0; i < n; i++)
indexMaxHeap.insert(i, arr[i]);
//修改堆中元素
indexMaxHeap.change(6, 99);
//此时堆中情况
printf("\nheap data[x] :");
for(int i = 0; i < 10; i++) {
arr[i] = indexMaxHeap.getItem(i);
printf("%d ", arr[i]);
}
//依然可以用堆的性质排序
for(int i = n-1; i >= 0; i--)
indexes[i] = indexMaxHeap.extractMaxIndex();
printf("\nheap data[indexes[x]] :");
for(int i = 0; i < 10; i++) {
printf("%d ", arr[indexes[i]]);
}
return;
}
int main(int argc, char const *argv[])
{
// 测试
int arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
heapSortTest(arr, 10);
return 0;
}
测试
运行结果说明:
- 第一行,表示的是堆中的数据没有改变位置,即原有的顺序未被打乱。
- 第二行,表示用排好的一套索引提取元素就可以得到满足顺序要求的结果。
- 第三行,修改了索引为6的元素的值后,堆中的数据情况。
- 第四行,修改后的堆依然可以用堆的特性进行排序。
heap data[x] :10 9 8 7 6 5 4 3 2 1
heap data[indexes[x]]: 1 2 3 4 5 6 7 8 9 10
heap data[x] :10 9 8 7 6 5 99 3 2 1
heap data[indexes[x]] :1 2 3 5 6 7 8 9 10 99
最后
- 由于博主水平有限,难免有疏漏之处,欢迎读者批评指正!

2065

被折叠的 条评论
为什么被折叠?



