C++加强堆


为什么需要加强堆

08 stack、queue和priority_queue的使用和模拟实现这一章已经实现过堆。
但是堆有一些不足,那就是堆不能做到修改堆中元素的值,并且只能删掉头部的值,不能够删除堆中其他的值,因为这样会破坏堆的结构。

如果想要不破坏堆的结构也很好办,只需要重新向上或者向下堆的结构即可,但是这样又会有新的问题,那就是修改或删除元素在堆中的位置是未知的,如果想要获取该位置,则需要重新遍历整个堆,时间复杂度为O(N),很显然时间复杂度过高,并不符合堆高效的特点。为了解决这一问题,则需要加强堆。


加强堆的具体实现

加强堆的基本结构

template<class T, class Compare>
class HeapGreater
{
public:
	HeapGreater()
	{}
	//迭代器初始化
	template<class InputIterator>
	HeapGreater(InputIterator first, InputIterator last);
	~HeapGreater()
	{}

	void push(const T& x);
	void pop();

	//将obj改为target
	void set(const T& obj,const T& target);
	//删除指定的值
	void erase(const T& obj) ;
	T top();
	size_t size();
	bool empty();
private:
	//需要封装交换函数,因为heap和indexMap的值都需要交换
	void swap(int i, int j);
	//向上调整算法,每push一个数都要调用向上调整算法,保证插入后是一个大堆
	void AdjustUp(int child);
	//向下调整算法,每次调用pop都要进行向下调整算法重新构成大堆
	void AdjustDwon(int parent);
	//调整obj所在的位置
	void resign(const T& obj);
	
	vector<T> heap;
	//反向索引表
	unordered_map<T, int>indexMap;
	int heapSize = 0;
};

加强堆和普通堆的区别在于多了一个哈希表unordered_map<T, int>indexMap,其作用主要是用来记录每个元素在vector<T> heap的具体下标位置,只要有了具体的下标位置,对元素进行修改或者删除就非常容易且高效了。
值得注意的是在向上或向下调整的时候,不光vector中的元素要交换,indexMap中对应的映射关系也要交换。

但是这样还有一个问题
vector<T> heap中如果出现相同元素的话,就会导致unordered_map<T, int>indexMap中它们的下标无法映射,因为哈希表的key不能重复,即使换成能够重复的unordered_multimap也不行,因为要获取其中一个key的下标时,会与其他相同的key产生冲突。

要解决这个问题也很简单,那就是传入对象的指针,而不是传入具体的对象,因为对象的地址是不会冲突的,另外传指针也会比传对象减少很多空间

插入push和删除pop操作

void push(const T& x)
{
	heap.push_back(x);
	indexMap[x] = heapSize;
	AdjustUp(heapSize++);
}
void pop()
{
	assert(!empty());
	T& node = heap[0];
	//交换首尾元素,然后删除
	swap(0, heapSize - 1);
	heapSize--;
	indexMap.erase(node);
	heap.pop_back();
	AdjustDwon(0);
}

插入和删除的函数与普通堆类似,主要是需要将indexMap中对应的映射进行改变。

修改和删除任意值操作

//将obj改为target
void set(const T& obj,const T& target)
{
	//获取obj在vector的位置
	//将obj改为target
	int index = indexMap[obj];
	heap[index] = target;
	//修改obj的位置,value不变,但是key要改变
	//采用的方式是删除后重新插入
	indexMap.erase(obj);
	indexMap[target] = index;
	//向上或向下调整
	AdjustUp(index);
	AdjustDwon(index);
}
//删除指定的值
void erase(const T& obj) 
{
	//获取尾部的值
	T& replace = heap[heapSize - 1];
	//获取obj在vector的位置
	int index = indexMap[obj];
	indexMap.erase(obj);

	--heapSize;
	//用尾部的值替换掉obj,然后调整
	if (obj != replace) 
	{
		heap[index] = replace;
		indexMap[replace] = index;
		resign(replace);
	}
	heap.pop_back();
}

//调整obj所在的位置
void resign(const T& obj)
{
	//obj位置的值发生改变,向上和向下调整
	//向上调整和向下调整只会发生一个
	AdjustUp(indexMap[obj]);
	AdjustDwon(indexMap[obj]);
}

由于可以获取任意值在vector的位置,因此实现起来也很简单。删除值的操作和pop类似,只需要尾部的值替换掉然后调整即可。

而修改值只需要在修改后调整位置以及改变indexMap的映射即可。


附录

  • 实现代码:
#include<iostream>
#include<algorithm>
#include<iostream>
#include<vector>
#include<unordered_map>
#include<assert.h>
using namespace std;


template<class T, class Compare>
class HeapGreater
{
public:
	HeapGreater()
	{}
	//迭代器初始化
	template<class InputIterator>
	HeapGreater(InputIterator first, InputIterator last)
	{
		while (first != last)
		{
			push(*first);
			first++;
		}
	}
	~HeapGreater()
	{}

	void push(const T& x)
	{
		heap.push_back(x);
		indexMap[x] = heapSize;
		AdjustUp(heapSize++);
	}
	void pop()
	{
		assert(!empty());
		T& node = heap[0];
		//交换首尾元素,然后删除
		swap(0, heapSize - 1);
		heapSize--;
		indexMap.erase(node);
		heap.pop_back();
		AdjustDwon(0);
	}

	//将obj改为target
	void set(const T& obj,const T& target)
	{
		//获取obj在vector的位置
		//将obj改为target
		int index = indexMap[obj];
		heap[index] = target;
		//修改obj的位置,value不变,但是key要改变
		//采用的方式是删除后重新插入
		indexMap.erase(obj);
		indexMap[target] = index;
		//向上或向下调整
		AdjustUp(index);
		AdjustDwon(index);
	}
	//删除指定的值
	void erase(const T& obj) 
	{
		//获取尾部的值
		T& replace = heap[heapSize - 1];
		//获取obj在vector的位置
		int index = indexMap[obj];
		indexMap.erase(obj);

		--heapSize;
		//用尾部的值替换掉obj,然后调整
		if (obj != replace) 
		{
			heap[index] = replace;
			indexMap[replace] = index;
			resign(replace);
		}
		heap.pop_back();
	}
	T top()
	{
		assert(!empty());
		return heap[0];
	}
	size_t size()
	{
		return heapSize;
	}
	bool empty()
	{
		return heap.empty();
	}
private:
	//需要封装交换函数,因为heap和indexMap的值都需要交换
	void swap(int i, int j)
	{
		T o1 = heap[i];
		T o2 = heap[j];
		heap[i] = o2;
		heap[j] = o1;
		indexMap[o2] = i;
		indexMap[o1] = j;

	}
	//向上调整算法,每push一个数都要调用向上调整算法,保证插入后是一个大堆
	void AdjustUp(int child)
	{
		Compare com;
		int parent = (child - 1) / 2;
		while (child > 0)
		{
			if (com(heap[parent], heap[child]))
			{
				swap(parent, child);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
	//向下调整算法,每次调用pop都要进行向下调整算法重新构成大堆
	void AdjustDwon(int parent)
	{
		Compare com;
		int child = parent * 2 + 1;
		while (child < heapSize)
		{
			if (child + 1 < heapSize && com(heap[child], heap[child + 1]))
			{
				child++;
			}
			if (com(heap[parent], heap[child]))
			{
				swap(parent, child);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	//调整obj所在的位置
	void resign(const T& obj)
	{
		//obj位置的值发生改变,向上和向下调整
		//向上调整和向下调整只会发生一个
		AdjustUp(indexMap[obj]);
		AdjustDwon(indexMap[obj]);
	}
	vector<T> heap;
	//反向索引表
	unordered_map<T, int>indexMap;
	int heapSize = 0;
};
  • 测试代码
#include"HeapGreater.h"

void test_HeapGreater1()
{
	HeapGreater<string, less<string>> heap;
	heap.push("abc");
	heap.push("ccc");
	heap.push("bbb");
	heap.push("dd");
	heap.push("eee");
	heap.push("yyy");
	heap.push("ppp");

	heap.erase("eee");
	heap.set("ccc", "new ccc");
	while (!heap.empty())
	{
		cout << heap.top() << endl;
		heap.pop();
	}
}
struct cmp
{
	bool operator()(string*& l, string*& r)
	{
		return *l < *r;
	}
};
void test_HeapGreater2()
{
	string* s1 = new string("abc");
	string* s2 = new string("ccc");
	string* s3 = new string("bbb");
	string* s4 = new string("dd");
	string* s5 = new string("eee");
	string* s6 = new string("yyy");
	string* s7 = new string("ppp");
	string* s8 = new string("new ccc");

	HeapGreater<string*, cmp> heap;
	heap.push(s1);
	heap.push(s2);
	heap.push(s3);
	heap.push(s4);
	heap.push(s5);
	heap.push(s6);
	heap.push(s7);

	heap.erase(s5);
	heap.set(s2, s8);
	while (!heap.empty())
	{
		cout << *heap.top() << endl;
		heap.pop();
	}
}
void test_HeapGreater3()
{
	vector<string>v = { "abc" ,"ccc" ,"bbb" ,"dd" ,"eee" ,"yyy" ,"ppp" };
	HeapGreater<string, less<string>> heap(v.begin(),v.end());

	heap.erase("eee");
	heap.set("ccc", "new ccc");
	while (!heap.empty())
	{
		
		cout << heap.top() << endl;
		heap.pop();
	}
}
int main()
{
	test_HeapGreater1();
	cout << endl << endl;
	test_HeapGreater2();
	cout << endl << endl;
	test_HeapGreater3();
	return 0;
}

在这里插入图片描述

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值