[C++] STL uses functors to control the priority queue priority_queue



foreword

This article explains the C++ STL container adapter: the implementation of priority_queue, and implements the functor to control the bottom layer of priority_queue.


1. The underlying implementation of priority_queue

priority_queue is called a priority queue, and its underlying structure is a heap. In the library, a large heap is generated by default.
insert image description here
In the implementation of the library, vector is used as the adaptation container of the priority queue.

Since priority_queue is also an adapter, its interface functions can also be used to encapsulate functions of other containers.

insert image description here
Let's simulate the implementation of priority_queue.


#pragma once

//优先级队列底层是堆,heap
namespace bit
{
    
    
	//仿函数
	template<class T>
	class Less
	{
    
    
	public:
		bool operator()(const T& t1, const T& t2)
		{
    
    
			return t1 < t2;
		}

	};

	template<class T>
	class Greater
	{
    
    
	public:
		bool operator()(const T& t1, const T& t2)
		{
    
    
			return t1 > t2;
		}

	};

	//类名不是类型
	template<class T, class Container = vector<T>, class Compare = Less<T> >
	//默认大堆
	class PriorityQueue
	{
    
    
		//com是一个仿函数
		Compare com;
		void AdjustDown(int parent)
		{
    
    
			int child = parent * 2 + 1;
			while (child > 0)
			{
    
    
				//可能右孩子存在且大于左孩子
				if (child + 1 < _con.size() && com(_con[child],_con[child + 1]))
				{
    
    
					++child;
				}
				//如果孩子存在且孩子大于父亲,交换
				if (child < _con.size() && com(_con[parent],_con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
    
    
					break;
				}
			}
		}

		void AdjustUp(int child)
		{
    
    
			Compare com;
			//类名实例化类对象,该类型是一个仿函数,实例化的com可以调用仿函数的比较方法
			//记录父亲的下标
			int parent = (child - 1) / 2;

			while (child > 0)
			{
    
    	
				if (com(_con[parent], _con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
				}
				child = parent;
				parent = (child - 1) / 2;
			}
		}

	public:

		PriorityQueue()
		{
    
    }

		//默认建大堆
		template<class InputIterator>
		PriorityQueue(InputIterator first, InputIterator end)
		{
    
    
			//插入数据
			while (first != end)
			{
    
    
				_con.push_back(*first);
				++first;
			}
			//向下调整建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
			{
    
    
				AdjustDown(i);
			}
		}

		void push(const T& val)
		{
    
    
			//插入元素
			_con.push_back(val);
			//然后向上调整
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
    
    
			//1.堆顶和堆尾交换
			swap(_con[0], _con[_con.size() - 1]);

			//2.删除堆尾
			_con.pop_back();

			//3.向下调整,恢复堆
			AdjustDown(0);
		}

		T& top() 
		{
    
    
			 return _con[0];
		}
		
		size_t size() const
		{
    
    
			return _con.size();
		}

		bool empty() const
		{
    
    
			return _con.empty();
		}

	private:
		Container _con;

annotation:

  • 1. When constructing, we use iterative intervals for construction
    • (1) Insert data into the space
    • (2) Adjust the heap downwards, build N data, start to build the heap from the first non-leaf node, and adjust downwards every time, the time complexity is O(N).

The implementation of the push function:
Insert an element to the end of the heap. The inserted element may change the structure of the heap, so we need to adjust the element upwards to maintain the characteristics of the heap.

Implementation of the pop function:
To delete the top element of the heap, first exchange the top element with the last element of the heap, and then perform tail deletion. After deletion, the element at the end of the heap is now at the top of the heap, and we need to adjust it downward to maintain the state of the heap.

empty function:
just judge whether the heap is empty.

size function:
Calculate the number of elements in the heap.

top function:
Take the top element of the heap, that is, take the first element.

The role of the priority_queue container: the container can be used where there is a need to use the heap.

2. Use the functor to control the bottom layer of priority_queue

What is a functor?
Functor: A fake function that is not a real function. Can overload operator(), implement the method you want internally, and then instantiate a method object, call the object as a parameter, then you can call the operator() method implemented inside the method class object.

Since the underlying structure of the priority queue in the library is a heap, and the default is a large heap, when we simulate implementation and use, if we encounter a scenario where a small heap is required, there are many things that need to be changed, such as the comparison method between the upward adjustment algorithm and the downward adjustment algorithm.
Now we need to specify a method, which can be controlled by us, so as to achieve the size of the heap.

functor:

//仿函数
template<class T>
class Less
{
    
    
public:
	bool operator()(const T& t1, const T& t2)
	{
    
    
		return t1 < t2;
	}

};

template<class T>
class Greater
{
    
    
public:
	bool operator()(const T& t1, const T& t2)
	{
    
    
		return t1 > t2;
	}

};

Two classes are implemented here, and a comparison method is overloaded.
In the implementation of the priority_queue template, we can pass one more template parameter: class Compare, that is, pass one more comparison method. Then we write a comparison method and pass it in, and then instantiate a method object in the implementation of priority_queue to control the generation of the heap.


Summarize

This article explains the process of functor controlling the underlying implementation of the priority_queue container and the underlying implementation of priority_queue.

The priority_queue container can be used wherever there is a heap.

Guess you like

Origin blog.csdn.net/w2915w/article/details/131872310