C++priority_queue模拟实现与仿函数讲解

Posted xiao zhou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++priority_queue模拟实现与仿函数讲解相关的知识,希望对你有一定的参考价值。

C++priority_queue模拟实现

1、priority_queue介绍

xxxx先来一个大爆炸!!

xxxx不要慌,我们慢慢解读,priority_queue(优先级队列),这确实是我们没有听说过的,不是我们在数据结构中讲过的。但是,我们注意到图中的一句话This context is similar to a heap… heap大家应该都知道,heap就是堆,堆应该大家都很熟悉吧,不熟悉请看我之前的文章《堆(Heap)的基本知识和堆排序》墙裂推荐先看懂堆是啥,再看本文,因为后文不会花过多时间在如何建堆,调整堆上

xxxx我们再来看一下上面的介绍,its first element is always the greatest of the elements it contains 它的首个元素是最大的元素。
我要说明一下,这里的最大,并不是数值最大,而是优先级最大,就是人为规定的一个秩序。任何秩序都可以。例如:数值大小,字符串长度等等人为规定的规则。不过,最常见的就是按照大小,大小优先级也是STL中提供的两种内置的规则
xxxx其实这个也是我们能理解的,堆分为大堆、小堆。堆顶元素就是所有数据中最大最小的元素。只不过在这里,将数值的大小扩展成为了优先级这就变得十分宽泛了,你可以自己定义具体的优先级是什么。

xxxx我们再注意一下,priority_queue的模板。template <class T, class Container = vector, class Compare = less< typename Container::value_type> >
1、T: 我们应该非常熟悉,就是这个容器存储的数据类型,会根据显示实例化时自动实例化成对应的容器。
2、vector< T >: 这个就说明,priority_queue底层默认就是vector,但是堆底层一般就是使用数组、vector,因此我们一般不适用其他数据结构
3、Compare = less< typename Container::value_type> 这个是我们重要介绍的,他决定了你的优先级是什么,是什么样的规则。但是,这个我先放在最后讲,作为一个重点,就是我要讲到的仿函数或者成为函数对象

(1)priority_queue的使用

我们先学会priority_queue的使用,再慢慢去学习它的底层实现

这是最简单的push、pop。当然,priority_queue还有其他接口

这些接口都是比较常规的,就不使用了,后期我会将他们模拟实现。基本就是完全封装的底层的数据结构的接口。

模拟实现

大纲:

template<class T, class Container = vector<T>>
class priority_queue

public:
	void push(const T& x);	//就是堆的插入数据,然后向上调整AdjustUp
	void pop();	//就是堆的删除数据,将第一个元素与最后一个元素换,删除后,向下调整AdjustDown
	T top() const;	//return _con.front();
	bool empty();	//return _con.empty();
	size_t size();	//return _con.size();
private:
	Container _con;
;

xxxx其实,这就跟C语言用struct借助数组完成堆数据结构没有任何本质区别,只要那里懂了,就可以轻而易举完成上述的简易版priority_queue。完成了上面的基础操作,我们就可以来进行下一阶段,就是高级版(完整版)priority_queue,在这里,我们就可以自主定义优先级的规则,那么首先,我们需要一个储备知识,就是仿函数 \\ 函数对象

仿函数 \\ 函数对象

何为仿函数?


xxxx这是360百科给出的解释,我觉得概括的非常好。关键字就是“类、使用像函数、重载operator()、函数的行为”
xxxx接下来我就利用STL中仿函数less来讲解,啥是仿函数!

less的介绍


xxxx我们可以总结:less类是一种模板类,是用于比较数据是否小于的,而通过什么呢?就是通过重载operator<( )这样我们就能知道了less的功能,但是一个类为何叫仿函数,我们还是不清楚。下面我先给大家一个例子!

xxxx我们看到,这个compareLess仿佛是一个函数一样,名字后面加(),()里我们可以传入参数。但是实际上,compareLess这是一个less类的对象。那么为什么可以做到这样仿佛像是一个函数在被调用呢?那就是上面提到的,less类中重载了operator( )。
下面我们来模拟实现一下less,大家就可以理解的很透彻了!

模拟实现:

template<class T>
struct less

	bool operator()(const T& x, const T& y)
	
		return x<y;
	
;

xxxx值得注意的是,在这里,当我们调用less类对象的operator( )时,最正规的应该是compareLess.operator()(x, y) 但是经过编译器的优化后,就变成了**compareLess(x, y)**所以,他才有像函数调用一样的效果。

其他函数对象


greater,就是less的反义。如果我们在priority_queue中把less换成greater,那么priority_queue的底层就变成了小堆。

(2)补充priority_queue的实现

xxxx接下来,我们就实现一下less这个函数对象,并把它加入到函数模板中即可

template<class T>
struct less

	bool operator()(const T& x, const T& y)
	
		return x<y?
	
;

template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
class priority_queue
;

解释:
1、我们将Compare搞成模板,这样,就可以接受不同的优先级规则,当我们在priority_queue实现的时候,遇到优先级的比较,我们就可以用这个函数对象Compare(x,y)这样就可以进行比较。
2、为什么使用typename Container::value_type?在低版本编译器,使用T类型,或者typename Container::value_type都可以,都是代表了T的含义,都是一种数据类型,但是直接使用T是有问题的,在程序编译时,由于模板还没有实例化,我们根本就不知道T是什么,所以我们就要用Container::value_type告诉编译器这是一个数据类型,而typename就是修饰Container::value_type这是一个类型名称,好让编译器先通过编译。直接使用T是不对的,只是低版本编译器没有进行检查。

下面就是priority_queue完整的实现代码

namespace zzk

	template<class T>
	struct Less
	
		bool operator()(const T& l, const T& r)
		
			return l < r;
		
	;
	template<class T>
	struct Greater
	
		bool operator()(const T& l, const T& r)
		
			return l > r;
		
	;
	//底层是堆
	template<class T, class Container = vector<T>, class Compare = Greater<typename Container::value_type>>	//question1
	class priority_queue
	

	public:
		typedef typename Container::value_type CVT;//question:类模板没实例化,就不知道vector<T>是啥,它的value_type是什么,所以用typename让他成为一种类型
		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);
		
		T top() const
		
			return _con.front();	
		
		bool empty()
		
			return _con.empty();
		
		size_t size()
		
			return _con.size();
		
		void AdjustUp(size_t child)
		
			Compare compare;
			size_t parent = (child-1)>>1;
			while (child > 0)
			
				if (compare(_con[parent] , _con[child]))
				
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) >> 1;
				
				else
				
					break;
				
			
		
		void AdjustDown(size_t parent)
		
			Compare compare;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			
				if (child + 1 < _con.size() && compare(_con[child] , _con[child + 1]))
					child++;
				else
				
					if (compare(_con[parent],_con[child]))
					
						std::swap(_con[parent], _con[child]);
						parent = child;
						int child = parent * 2 + 1;
					
					else
					
						break;
					
				
			
		
	private:
		Container _con;
	;

总结

xxxxpriority_queue(优先级队列)是以堆为基础的数据结构,从堆上发展而来,基本思想就是堆的思想,不过现在我们可以自己来规定优先级的规则。STL中使用函数对象/仿函数来传入优先级,这里我们就学习到了基本的函数对象的使用,其实函数对象永不止这些内容,后期博主还会仔细谈一下函数对象和函数对象与函数指针的比较。。。敬请期待!
xxxx如果本文出现了错误或者各位读者有什么想说的,都可以在评论处指出。让我们一起学习,共同进步!

以上是关于C++priority_queue模拟实现与仿函数讲解的主要内容,如果未能解决你的问题,请参考以下文章

C++priority_queue模拟实现

C++priority_queue模拟实现

C++STL之优先级队列详解

C++STL之优先级队列详解

C++ STL:优先级队列priority_queue的使用方法和模拟实现

C++初阶----priority_queue模拟实现+仿函数