C++9:优先级队列以及仿函数和反向迭代器

目录

 优先级队列的基本增删查改实现

仿函数

反向迭代器


 优先级队列的本质其实是一个堆,具体到底层的数据结构其实是有数学关系所形成的一个类似二叉树的结构

至于其优先级的这个特性,跟大堆小堆的性质是相同的,只不过它使用了仿函数来控制生成大堆还是小堆。

 优先级队列的基本增删查改实现

 具体的实现以及逻辑都是堆的,没啥可以记述的,想不起来点这里

数据结构6:二叉树与堆_数据结构堆和二叉树_lanload的博客-CSDN博客

 底层是一个vector非常合理,写起来也没有C语言那么蛋疼,但是由于本身vector对越界行文会进行报错,所以说逻辑还是需要处理好,调整求取父节点下标的时候很有可能越界,比如向下调整的时候,whil循环里头的chil是否大于0需要先行被检测,不然就报错了

#pragma once
#include <iostream>
#include<vector>
using namespace std;
template<class T, class Container = vector<T>>
class Mypriority_queue
{
	// 优先级队列的本质是一个堆。而堆的本质则是有数学关系所形成的一个二叉树
	//       0
	//    1     2
	// 3    4 5    6
	//一个子节点的-1/2将会得到父节点的下标,同理,父节点*2+1得到左孩子,*2+2得到右孩子
	//

public:
	//构造函数
	Mypriority_queue()
	{}
	//迭代器构造函数
	template<class iterator>
	Mypriority_queue(iterator begin, iterator end)
	{
		while (begin != end)
		{
			push_back(*begin);
			++begin;
		}
	}

	void Adjustdown(size_t parent)
	{
		size_t minchild = parent * 2 + 1;

		while (minchild <_a.size())
		{
			if (minchild + 1 < _a.size() && _a[minchild] >_a[minchild + 1])
			{
				++minchild;
			}

			if (_a[parent] > _a[minchild])
			{
				std::swap(_a[parent], _a[minchild]);
				parent = minchild;
				minchild = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}





	//向上调整,每次插入就向上调整一次
	//传递一个当前插入位置的下标,那么先求出其父节点的下标,默认建一个小堆。
	void Adjustup(size_t child)
	{
		size_t parent = (child - 1) / 2;


		while (child > 0 && _a[parent] > _a[child]  )
		{
			std::swap(_a[parent], _a[child]);
			child = parent;
			parent=(child - 1) / 2;
		}
	}
	//void Adjustup(size_t child)
	//{
	//	size_t parent = (child - 1) / 2;
	//	while (child > 0)
	//	{
	//		if (_a[parent] < _a[child])
	//		{
	//			std::swap(_a[child], _a[parent]);
	//			child = parent;
	//			parent = (child - 1) / 2;
	//		}
	//		else
	//		{
	//			break;
	//		}
	//	}
	//}


	void push_back(const T& val)
	{
		_a.push_back(val);


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

		Adjustdown(0);
	}


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

	//T& operater[](size_t pos) 
	//{
	//	return _a[pos];
	//}
	const T& top() const
	{
		return _a[0];
	}


private:
	Container _a;

};

仿函数

 仿函数是对某一个类中的()运算符进行重载,其运作原理类似于函数的一个类。

如下:

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

用起来就像函数调用

int main()
{
	text::less<int> Less;

	Less(1, 2);
}

等价于

Less.operator()(x,y);
  • 其主要的特征就是,仿函数的对象使用起来与函数的方法一致,虽然其并不是函数,而是一个对象。
  • 但这么但看还是蛮鸡肋的,我单独写一个函数再套一个函数模板不是更方便吗?为什么要用仿函数这个玩意?写起来还麻烦
  • 以当前的优先级队列举例,我们在实现的时候会遇到不得不写死逻辑的情况,发生于向上调整以及向下调整的过程中建立的是大堆还是小堆的问题。
	void Adjustup(size_t child)
	{
		size_t parent = (child - 1) / 2;


		while (child > 0 && _a[parent] > _a[child]  )
		{
			std::swap(_a[parent], _a[child]);
			child = parent;
			parent=(child - 1) / 2;
		}
	}
  • 那么在C语言的阶段,如果我们希望解决这个问题,可以额外的写两个函数来解决这个问题,一个比较小于的布尔值,另一个返回大于的布尔值。但是这也引申出非常尴尬的问题,C语言只能传递我们平常见都不想见的函数指针,可读性差不说,还难以掌控。
  • 那么我们刚才实现的仿函数就可以直接使用了,具体用法如下。
  • 我们先把两个仿函数的类创建出来,一个返回小于,另一个返回大于的比较情况

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


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

由于传递的是一个类对象,我们在优先级队列的模板参数列表处新增一个模板参数,称之为Compare,缺省值给一个小于,也就是默认生成小堆。

template<class T, class Container = vector<T>,class Compare =Less<T>>

 然后更改一下调整函数内部的比较逻辑。

具体代码就不完全罗列了,已上传至仓库。模拟实现优先级队列 · Lanload/C++学习代码记录 - Gitee.com

小堆

 大堆,注意的是,大堆的适配器类型需要显示声明,不然会报错,因为这个时候和缺省值的语法冲突,要么全部使用缺省值,要么全部显示调用,本人犯了这个错误,记录一下。

 仿函数还有一系列及其有用的其他用法,官方库内部实现的less以及greater不能比较指针,这是我们也可以自己写一个。

综上所述,仿函数的出现为我们使用类和对象的时候提供了一个逻辑模板,通过与模板结合我们可以成功的借助仿函数控制逻辑。

目前的仿函数也只是吃了个甜品,之后还会有更加复杂的使用。


反向迭代器

 反向迭代器的实现模式等同于适配器设计模式,传入一个正向迭代器,适配出反向迭代器。

那么这样的结构有点像list的迭代器,那么我们简单实现一个

由于STL源码中涉及的萃取过于复杂,我们统一模板参数,以获取到模板参数T,也就是当前对象数据类型。

#pragma once
//反向迭代器,通过传入一个正向迭代器适配出一个反向版本
//不涉及萃取,获取T类型就用简单粗暴的方式
template<class Iterator,class Ref,class Ptr>
class Reverse_iterator
{
public:
	Reverse_iterator(Iterator it)
		:_it(it)
	{}

	typedef Reverse_iterator<Iterator,Ref,Ptr> Self;


	//++变--
	Self& operator++()
	{
		--_it;
		return *this;
	}

	Self& operator--()
	{
		++_it;
		return *this;
	}

	//解引用,保持堆成时需要特殊处理一下,返回当前迭代器指向数据的引用
	Ref operator *()
	{
		Iterator tmp = _it;
		return *--tmp;
	}


	//p->等价于(*p).内部变量
	Ptr operator->()
	{
		return &(operator*());
	}

	bool operator!= (const Self& s) const
	{
		return _it != s._it;
	}


private:
	Iterator _it;
};

其中的解引用操作之所以--是为了同源码实现以下对齐,源码中传递的参数是对齐传递的。

假若没有--,那么数据的访问将会是一个随机值然后访问不到rend处。

反向迭代器仅做了解,不过多记述了。

 当笔记做

猜你喜欢

转载自blog.csdn.net/m0_53607711/article/details/129213571