算法与数据结构-索引堆优化(C++)

描述

如果我们想要在索引堆中修改索引为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

最后

  • 由于博主水平有限,难免有疏漏之处,欢迎读者批评指正!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值