索引堆的实现

   之前讲的基础堆的实现,是将数组元素进行交换最后建成最大堆,这种交换数组元素来建堆的方式有2个局限性:

(1)若数组元素是非常复杂的结构(比如巨大的字符串),那么每次交换这些字符串消耗性能都是巨大的;

(2)若数组成为最大堆以后,我们需要对某个下标下原来的元素进行操作,那么操作将会是很复杂的(比如假设每个下标代表一个进程,每个进程为一个系统任务,下标处对应的元素为该任务的优先级,那么在建成最大堆后,数组元素位置发生改变,这个时候索引和该位置上的元素没有关联,如果我们需要将某个索引处的任务优先级提高,这个时候是很难找到该索引对应的任务优先级的);

  为此,引入索引堆的概念来解决这些问题。

  索引堆:数组由两部分构成(索引和数据),建堆的时候是对索引进行操作,最后形成的堆中每个节点处的元素就是索引值,而数据的位置没有发生改变。这样,在建堆时交换索引值是很快的,并且在建成堆以后,可以根据索引值找到对应的数组下标处存放的数据。

  接下来看索引堆的C++实现:

#include<iostream>
#include<cassert>

using namespace std;

//最大索引堆
template<typename Item>
class IndexMaxHeap
{
private:
    Item *data;  //数据
    int *indexs;  //索引
    int count;
    int capacity;
    void shiftUp(int k)
    {
        int index = indexs[k];
        while(k > 1 && data[indexs[k/2]] < data[index])
        {
            indexs[k] = indexs[k/2];
            k /= 2;
        }
        indexs[k] = index;
    }
    void shiftDown(int k)
    {
        int index = indexs[1];
        while( 2*k <= count )
        {
            int j = 2*k;
            if(j + 1 <= count && data[indexs[j]] < data[indexs[j + 1]])
            {
                j += 1;
            }
            if(data[indexs[k]] >= data[indexs[j]])
            {
                break;
            }
            indexs[k] = indexs[j];
            k = j;
        }
        indexs[k] = index;
    }

public:
    IndexMaxHeap(int capacity)
    {
        data = new Item[capacity + 1];  //堆从1开始存放
        indexs = new int[capacity + 1];
        this->capacity = capacity;
        count = 0;
    }
    ~IndexMaxHeap()
    {
        delete[] data;
        delete[] indexs;
    }
    int size() const
    {
        return count;
    }
    bool isEmpty() const
    {
        return count == 0;
    }
    void insert(int i, Item item)  //data[i] = item
    {
        assert(i >= 0 && i < capacity);
        assert(count + 1 <= capacity);

        //由于用户给定的数组下标是从0开始,所以对于i我们需要将其加1
        i += 1;  //对于堆从1开始存放
        data[i] = item;
        indexs[count + 1] = i;
        count++;
        shiftUp(count);
    }
    Item extractMax()  //返回最大值
    {
        assert(count > 0);

        Item ret = data[indexs[1]];
        indexs[1] = indexs[count];
        count--;
        shiftDown(1);
        return ret;
    }
    int extractMaxIndex()  //返回最大值索引
    {
        assert(count > 0);

        //对于外界来说,希望给出的索引是从0开始的,所以返回值减1
        int index = indexs[1] - 1;
        indexs[1] = indexs[count];
        count--;
        shiftDown(1);
        return index;
    }
    Item getItem(int i)  //返回给定索引值下的数据
    {
        assert(i >= 0 && i < capacity);

        return data[i + 1];
    }
    void change(int i, Item newItem)  //将索引i下的数据修改为新的数据
    {
        i += 1;
        data[i] = newItem;

        for(int j = 1; j <= count; j++)
        {
            //寻找i在indexs数组中的位置下标
            if(indexs[j] == i)
            {
                //从j这个位置shiftUp和shiftDown操作
                shiftUp(j);
                shiftDown(j);
                return;
            }
        }
    }
    void print() const
    {
        for(int i = 1; i <= count; i++)
        {
            cout << indexs[i] << "  ";
        }
        cout << endl;
        for(int i = 1; i <= count; i++)
        {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

  注意在change()方法中,寻找索引 i 在indexs数组中的位置 j 时,使用的是for循环遍历indexs数组(时间复杂度为O(n)),找到 j 之后进行shiftUp、shiftDown的操作(时间复杂度为O(logn)),所以这个过程的时间复杂度为O(n),若对 n 个索引堆进行change操作,那么时间复杂度有可能变成O(n^2),这是很不理想的。

  于是对方法change进行优化:索引堆由3部分构成(索引 indexs、数据 data、反向索引 rev),其中 rev[ i ] = j 代表索引 i 在 indexs(堆)中的位置下标为 j ;所以我们只需要维护好 rev 数组,那么在修改了索引 i 下的元素后,可以直接通过 rev[ i ] 查找到索引 i 在 indexs中的位置,然后进行相应的shiftUp、shiftDown操作。

  现对最大索引堆的类声明修改如下:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值