索引堆(c++代码实现)

索引堆

[为什么要引入索引堆?]

给出一个数组,构建如下树形结构
在这里插入图片描述
通过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开始

想说的话

以上堆和堆排序的全部内容就整理完了,索引堆是一种高级的数据结构,在后续的图论中还会用到,下期更新算法中另外一个很重要的问题,查找问题,以及二分搜索树,大家加油~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值