[C++ Grocery Store] Priority Queue Usage Guide and Simulation Implementation

Insert image description here

1. Introduction to priority_queue

  • A priority queue is a container adapter whose first element is always the largest of the elements it contains according to strict weak ordering criteria.

  • This context is similar to a heap, where elements can be inserted at any time, and only the largest heap element (the element at the top of the priority queue) can be retrieved.

  • The priority queue is implemented as a container adapter, which encapsulates a specific container class as its underlying container class. The queue provides a specific set of member functions to access its elements. Elements are popped from the "tail" of a specific container, which is called the top of the priority queue.

  • The underlying container can be any standard container class template or other specifically designed container class. The container should be accessible via random access, iterators, and support the following operations:

    • empty(): Check whether the container is empty

    • size(): Returns the number of valid elements in the container

    • front(): Returns a reference to the first element in the container

    • push_back(): Insert elements at the end of the container

    • pop_back(): delete the element at the end of the container

  • The standard container classes vector and deque meet these needs. By default, vector is used if no container class is specified for a particular priority_queue class instantiation.

  • Random access iterators need to be supported so that the heap structure is always maintained internally. The container adapter automatically completes this operation by automatically calling the algorithm functions make_heap, push_heap, and pop_heap when needed.

2. Use of priority_queue

By default, the priority queue uses vector as its underlying container for storing data. The heap algorithm is used on the vector to construct the elements in the vector into a heap structure. Therefore, priority_queue is a heap. You can consider using priority_queue wherever a heap is needed. . Note: By default priority_queue is a large heap.

function declaration Interface Description
priority_queue() Construct an empty priority queue
empty() Checks whether the priority queue is empty, returns true if it is, otherwise returns false
top() Returns the largest (smallest) element in the priority queue, that is, the top element of the heap
push(x) Insert element x into the priority queue
pop() Delete the largest (lowest) element in the priority queue, that is, the top element of the heap

Small Tips : By default, priority_queue is a large heap (the default third template parameter is less); if you put custom type data in priority_queue, the user needs to provide > or < overloading in the custom type.

2.1 The kth largest element in the array

Insert image description here

class Solution {
    
    
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    
    
        //建一个大堆,向下调整建堆的时间复杂度是O(N)
        priority_queue<int> pq(nums.begin(), nums.end());

        //pop k-1次,时间复杂度是O(K*logN)
        while(--k)
        {
    
    
            pq.pop();
        }

        return pq.top();
    }
};

When the value of K is close to N, the time complexity of the above approach is O(N*logN), which does not meet the requirements of the question. We will use another method to solve it below.

lass Solution {
    
    
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    
    
        //用前k个数建一个小堆
        priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);

        //遍历剩下的 N-k 个,比堆顶大就换进去
        //时间复杂度是 (N-K)logN
        for(size_t i = k; i < nums.size(); i++)
        {
    
    
            if(nums[i] > pq.top())
            {
    
    
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }
};

The above solution is to first build a small heap with the first K elements in the array, and then traverse backwards starting from the K+1 element of the array. When encountering an element larger than the top element of the heap, pop out the top element of the heap. , push the current element into it. The time complexity of building a heap is O(K), and the time complexity of updating a small heap is O((NK)logK). When K is small, the time complexity of this approach can be approximated as O(NlogK). When When K is large, the time complexity can be approximated as O(logK).

3. priority_queue simulation implementation

3.1 Functor

A functor is essentially a class. It is called a functor because objects of this class can be used like functions. Examples are as follows:

//一个仿函数
template<class T>
class Less
{
    
    
public:
	bool operator()(const T& x, const T& y)
	{
    
    
		return x < y;
	}
};


int main()
{
    
    
	Less<int> lessfunc;//定义一个对象
	cout << lessfunc(1, 2) << endl;//该类型对象可以像函数一样去使用
	//cout << lessfunc.operator()(1, 2) << endl;//和上面等价
	return 0;
}

Small Tips : Functors are generally class templates, so they can support size comparisons of different types, provided that the type overload implements the comparison operator. Functors were born to replace function pointers, which are less readable.

3.2 Member variables

template<class T, class Container=std::vector<T>, class Compare = Less<T>>
	class priority_queue
	{
    
    
	public:
		//成员
	private:
		Container _con;
	};

Tips : Please note that there are three template parameters here. The first template parameter indicates the data type to be stored in the priority queue; the priority queue is essentially a container adapter, so the second template parameter indicates that the priority queue should be used The underlying container of The size comparison can only be written to death, which is not good.

3.3 Member functions

3.3.1 Constructor

priority_queue()
{
    
    }

template<class InputeIterator>
priority_queue(InputeIterator first, InputeIterator last)
{
    
    
	//先将数据插入
	while (first != last)
	{
    
    
		_con.push_back(*first);
		++first;
	}

	//建堆,从最后一个非叶子节点开始向下调整
	for (int i = (_con.size() - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(i);
	}
}

Small Tips : The iterator interval constructor is a function template, as long as it is an iterator interval that can support ++ operations, that is, one-way iterator, two-way iterator, and random iteration are all acceptable. Secondly, after inserting data into the container, a heap needs to be built. Here, downward adjustment is used to build the heap. Its time complexity is O(N).

3.3.2 AdjustDown

void AdjustDown(int parent)
{
    
    
	Compare com;
	while (parent * 2 + 1 < (int)_con.size())
	{
    
    
		//找到最大的孩子
		int maxchild = parent * 2 + 1;
		if (maxchild + 1 < (int)_con.size() && com(_con[maxchild], _con[maxchild + 1]))
		{
    
    
			maxchild++;
		}

		//和父节点比较
		if (com(_con[parent], _con[maxchild]))
		{
    
    
			std::swap(_con[maxchild], _con[parent]);
			parent = maxchild;
		}
		else
		{
    
    
			break;
		}
	}
}

Small Tips : With the support of functors, the size comparison in downward adjustment code is no longer fixed. It is enough to build a large heap and a small heap. In the end, whether it is a large heap or a small heap is controlled by the functor. .

3.3.3 push

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

Small Tips : Appending data at the end of the priority queue will involve upward adjustment. The code for upward adjustment is as follows.

3.3.4 AdjustUp

void AdjustUp(int child)
{
    
    
	Compare com;
	int parent = (child - 1) / 2;
	while (child > 0)//父亲不会小于0,因此这里的判断条件要用孩子大于0,孩子等于0说明已经调整到根节点,就无需继续调整了
	{
    
    
		if (com(_con[parent], _con[child]))
		{
    
    
			std::swap(_con[child], _con[parent]);
			child = parent;
			parent = (child - 1) / 2;//这里parent不会小于0
		}
		else
		{
    
    
			break;
		}
	}
}

3.3.5 pop

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

Tips : When data comes out of the priority queue, the data on the top of the heap is the data on the top of the heap. The data on the top of the heap is also the first data in the container. If the underlying container is a vector, then the data on the top of the heap is the data with the subscript 0. The data coming out of the top of the heap cannot be directly deleted using the header. This will cause the parent-child relationship of the subsequent data to be completely messed up. The correct approach is to exchange the data on the top of the heap with the last data in the container, and then execute pop_back to delete it. Finally, you need to perform a downward adjustment from the root node so that the data exchanged to the top of the heap goes where it should go. The place.

3.3.6 empty

bool empty()
{
    
    
	return _con.size() == 0;
}

3.3.7 size

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

4. Conclusion

Today’s sharing ends here! If you think the article is good, you can support it three times . There are many interesting articles on Chunren's homepage . Friends are welcome to comment. Your support is the driving force for Chunren to move forward!

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_63115236/article/details/132717178