优先级队列的模拟实现(包含堆的回顾和仿函数讲解)

因为优先级队列的本质是堆,所以这里先讲堆的知识点

堆的理论

堆的定义:n个关键字序列L[1…n]成为堆,需满足下面其中一个条件:
① L(i) >= L(2i) 且 L(i) >= L(2i + 1) ( 1<=i<=[n/2]) ----大堆
② L(i) <= L(2i) 且 L(i) <= L(2i + 1) ( 1<=i<=[n/2]) ----小堆

可以把堆视为一个完全二叉树,是有条件限制的完全二叉树
关键:父子节点的转换公式
parent = (child - 1 ) / 2
chlid = parent * 2 +1

向上调整算法

逻辑:用child去算parent,child和parent比较,交换实现堆逻辑
使用向上算法建堆:

在这里插入图片描述
插入99,然后计算出他对应的父节点位置,跟他的父节点进行比较,如果99比8要大,则发生交换,交换后,继续执行计算父节点,进行比较,如果比父节点大,则交换,知道child为迭代到根节点的位置。
主要逻辑:
用child节点计算parent节点,逻辑判断是否child如果比parent大就交换,
迭代条件:child下标走到根节点就结束
代码:

		void adjustUp(int childi)
		{
			int parenti = (childi - 1) / 2;
			while (childi > 0)
			{
				if (_con[childi] > _con[parenti])
				{
					std::swap(_con[childi], _con[parenti]);
					childi = parenti;
					parenti = (childi - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

向下调整算法

在这里插入图片描述
从根节点开始,向下计算child节点,找最大的child节点(因为这里要建大堆),如果最大child比parent要大,则交换,然后继续往下计算,知道走到最后一行的父节点就结束。
主要逻辑:
用parent节点计算child节点,逻辑判断是否child如果比parent大就交换,
迭代条件:parent条件走完最后一行的父节位置,即走到最后一行就结束。
代码:

		void adjustDown(int parenti)
		{
			int childi = parenti * 2 + 1;
			if (childi + 1 < _con.size() && _con[childi + 1] > _con[childi])
				//避免右孩子不存在的情况
			{
				++childi;
			}
			while (childi < _con.size())
			{
				if (_con[childi]>_con[parenti] )
				{
					std::swap(_con[childi], _con[parenti]);
					parenti = childi;
					childi = parenti * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

优先级队列的模拟实现

构造函数和析构函数

不用写,因为成员变量是自定义类型,实例化的时候会自动调用该自定义类型的析构和构造函数和拷贝构造函数

迭代器区间构造

向下调整建堆:向下调整的前提是左右子树符合堆的定义,所以要从最后的parent开始向下调整建堆。利用最后一个child计算parent。

		template<class Inputiterator>
		priority_queue(Inputiterator first,Inputiterator last)
			:_con(first,last)
		{
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				adjustDown(i);
			}
		}

pop删除数据

		void push(const T& x)
		{
			_con.push_back(x);
			adjustUp(_con.size() - 1);
		}

push添加数据

		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjustDown(0);
		}

适配器

该容器对其他容器的进行了包装,称为容器适配器。容器适配器包括stack,queue,priority_queue三种,可以让基本的容器类型采用另一种更适配于当前工作要求的工作方式实现。三种适配器都需满足一定的约束条件,也可以可理解为加了限制条件的容器。

代码层面:在类模版位置添加其他容器的模版变量,让该模板作为成员变量,使用该容器的成员函数

仿函数入门

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象。主要的用处是为了替代函数指针

例子1:用仿函数实现向下调整和向上调整
仿函数代码:

	template<class T>
	struct less
	{
		bool operator()(T& x, T& y)
		{
			return x < y;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(T& x, T& y)
		{
			return x > y;
		}
	};

修改后的下上调整代码:

	template<class T,class container = vector<T>,class compare = less<T>>
	class priority_queue
	{
	public:
		compare _com;//仿函数
		//迭代器区间构造
		template<class Inputiterator>
		priority_queue(Inputiterator first,Inputiterator last)
			:_con(first,last)
		{
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				adjustDown(i);
			}
		}
		
		priority_queue(){}//默认构造占位,避免写了迭代器区间构造,默认构造不生成

		void adjustUp(int childi)
		{
			int parenti = (childi - 1) / 2;
			while (childi > 0)
			{
				if (_com(_con[parenti],_con[childi]))
				{
					std::swap(_con[childi], _con[parenti]);
					childi = parenti;
					parenti = (childi - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void adjustDown(int parenti)
		{
			int childi = parenti * 2 + 1;
			if (childi + 1 < _con.size() && _con[childi + 1] > _con[childi])
				//避免右孩子不存在的情况
			{
				++childi;
			}
			while (childi < _con.size())
			{
				if (_com(_con[parenti],_con[childi]))
				{
					std::swap(_con[childi], _con[parenti]);
					parenti = childi;
					childi = parenti * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);
			adjustUp(_con.size() - 1);
		}

		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjustDown(0);
		}

		size_t size()
		{
			return _con.size();
		}

		const T& top() const
		{
			return _con[0];
		}
		private:
			container _con;
		};

例子2:实例一个日期类的优先级队列对象、实例一个日期类指针的优先级队列对象,比较不同,并解决问题?

	Date* d1 = new Date(2024, 5, 20);
	Date* d2 = new Date(2024, 6, 20);
	Date* d3 = new Date(2024, 7, 20);

	priority_queue<Date*> pqd;
	pqd.push(d1);
	pqd.push(d2);
	pqd.push(d3);
	while (pqd.size())
	{
		cout << *pqd.top();
		pqd.pop();
	}

为什么这段代码输出的top是随机的?因为这里的d1,d2,d3是指针,这就导致了用于向上调整的比较仿函数没有不是根据日期比较,而是直接拿了d1,d2,d3比较,d1,d2,d3是什么?是指针,是地址,而每次运行分配的地址都是不一样的,而且new分配的地址位置也是随机的,没有先new则分配高位这些说法?所以我们要写一个用于日期类指针比较的仿函数来帮助这个段代码的成功执行。

修改后:

template<class T>
struct pDateCompare
{
	bool operator()(T& x, T& y)
	{
		return *x < *y;
	}
};

int main()
{
	Jaxsen::test_priority_queue();
	//vector<int> v1 = { 51,2,31,244 };
	//priority_queue<int> pq(v1.begin(), v1.end());
	//while(pq.size())
	//{
	//	cout << pq.top() << " ";
	//	pq.pop();
	//}
	cout << endl;
	Date* d1 = new Date(2024, 5, 20);
	Date* d2 = new Date(2024, 6, 20);
	Date* d3 = new Date(2024, 7, 20);

	Jaxsen::priority_queue<Date*,vector<Date*>,pDateCompare<Date*>> pqd;
	pqd.push(d1);
	pqd.push(d2);
	pqd.push(d3);
	while (pqd.size())
	{
		cout << *pqd.top();
		pqd.pop();
	}

	return 0;
}

在模版参数中传入了一个仿函数进行比较

问题:为什么sort的传参要带括号,而priority_queue不用带括号???
在这里插入图片描述
因为sort是函数参数而priority_queue是模版参数,函数参数接收的是对象,而模版参数接收的是类,这里sort传入的是匿名对象,而这里的sort有函数模版,会自动推出Compare 的类型(根据传参),比如这里传入的是greater< int >, 所以compare被编译器推出的是greater< int >

为什么函数模版可以自己推类型,而类模板要传参?
在这里插入图片描述
因为函数会传参数,编译器可以根据参数去推出类型,而类模板无法根据任何东西推出,所以需要显式调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值