C++ - Introduction and Simulation of Priority Queue (priority_queue) - Adapter Implementation of Reverse Iterator

 functor

 The so-called functor is actually not a function itself, but a class. The operator () operator is overloaded in this class, so when the member function operator () of this class is used externally, the form used is As if using a function, an object of the functor (function object) class can be used like a function. A functor is defined as follows:

// 简单仿函数的定义
class Less
{
public:
	bool operator()(int x, int y)
	{
		return x < y;
	}
};

Like the above-mentioned less class, an operator() operator overload function is defined . When using this function outside, it is as follows:

Less newLess;
cout << newLess(1 , 2) << endl;

 From the above example, we found that if you don’t look at the newLess object defined more, using the form in stream insertion is like using a function. In fact, the above code is equivalent to the following code :

cout << newLess.operator()(1 , 2) << endl;

The functor defined in C++ only needs to replace the hash pointer in C.

How to use: Because the use of functors is similar to calling functions, then we can define multiple functors (classes), and then use templates to use these classes (because templates need to use types, template parameters are types directly Using functions cannot apply templates), then it is equivalent to using multiple functions in one location. That is to write a certain position alive, instead of simply writing it to death.

for example: 
 

func(bool x)
{
    if(x)
    {
        cout << "yes" << endl;
    }
}

class less
{
    bool operator()(int x , int y)
    {
        return x > y;
    }
}

class greater
{
    bool operator()(int x , int y)
    {
        return x < y;
    }
}

template<class T, class Comapre = less<T>>
class My_class
{
public:
    void My_class_func()
    {
        Comapre com;
        int x = 10, y = 20;
        //func(x < y);  不用这样写死了
        func(com(x , y)); // 这样写看按照传入的Comapre 是什么函数模版来给func函数传入不同值
    }
}

 As shown above, as long as the second template parameter is less when creating an object of My_class externally, then the bool value of x > y is passed to the func function; if the passed in parameter is greater, then to func What the function passes in is the bool value of x < y.

This is the solution here. Only types can be passed in as template parameters. If we want to use templates to apply different functions, we can achieve some code changes. 

priority queue

 Header <queue>  

 The priority queue is essentially a container adapter, and its default container is a vector container:

 The main functions of the container are as follows:

function declaration Interface Description
priority_queue()/priority_queue(first,
last)
Construct an empty priority queue
empty( ) Check whether the priority queue is empty, return true, otherwise return
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 (smallest) element in the priority queue, that is, the top element of the heap

 We found that the implementation of the function function is similar to the heap. In fact, the underlying implementation of the priority queue is the heap in the binary tree.

 According to the characteristics of the heap, the priority queue does not support traversal, nor does it support iterators. It also needs to pop () elements like queues and stacks to get all the data. The following code demonstrates:

void text1()
{
	priority_queue<int> pq;
	pq.push(5);
	pq.push(3);
	pq.push(1);
	pq.push(2);

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

}

 Output:
5 3 2 1

 priority_queue is a large pile by default, which can also be seen in the above template parameters:

class Compare = less<typename Container::value_type>

This parameter controls whether the priority queue is a large heap or a small heap. The default parameter here is a small heap. It’s just that the default parameters are not the same. The above-mentioned less means less than, but the meaning expressed here is a lot, which can be said to be a mistake in the development process.

The controls for small and large heaps are as follows:

priority_queue<int , vector<int> , less<int>>      // 大堆的创建
priority_queue<int , vector<int> , greater<int>>  //  小堆的创建

Here, the control of small heaps and large heaps is implemented using functors .

For the use of the heap, look at the following example: LeetCode official website - the technology growth platform loved by global geeks

 Topic requirements:

Given an array of integers  nums and an integer  k, return the  k largest element in the array.

Please note that what you need to find is the  k largest element after sorting the array, not the  k different element.

You must design and implement an algorithm of time complexity  O(n) to solve this problem.

 Here we can use heaps to solve it, so should we build a large heap or a small heap?

Originally, it is necessary to build a large heap to sort ascending order , but the amount of data here is small, so it is possible to build a small heap or a large heap.

Build a lot of code;

    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.end());

        while(--k)
        {
            pq.pop();
        }

        return pq.top();

    }

Build a small pile of code:

          int findKthLargest(vector<int>& nums, int k) {

    priority_queue<int , vector<int> , greater<int>> pq(nums.begin(), nums.begin() + k);

       for(int i = k;i < nums.size();i++)
       {
           if(nums[i] > pq.top())
           {
               pq.pop();
               pq.push(nums[i]);
           }
       }

        return pq.top();
    }

priority_queue Analog implementation of priority queue

 For an introduction to container adapters, please see the following blog, and I won’t talk about it here:

 general framework

#pragma once
#include<vector>

namespace My_priority_queue
{
	template<class T , class Container = vector<T>>
	class priority_queue
	{
	private:

    private:
		Container _con;
	};
}

Constructor

 Constructors that support iterator ranges for themselves or other containers

	private:
		void Adjustdown(int parent)
		{
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}

				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	public:
		template<class Inputiterator>
		priority_queue(priority_queue first, priority_queue last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			// 建堆 向下调整建大堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				Adjustdown(i);
			}
		}

The main implementation here is to first copy the data from other containers to the storage heap container, and then make downward adjustments to build a large heap. The specific implementation process of the heap can refer to the introduction of heap sorting in the following blog: (1 message) Data structure (c language version)-5_Provide a sorting example that maximizes p ai 5 for this query_chihiro1122's blog-CSDN blog

 CRUD

 pop()

 The deletion of the heap cannot directly delete the elements at the top of the heap, and then move the elements forward in the form of an array. This is not acceptable. If you do this, the relationship between the father and the child in the whole pile will be completely messed up.

We need to exchange the top element of the heap with the last element of the array, then delete the last element of the array, and adjust the top element of the heap downward to build a heap .

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

push()

 Insert to the last position of the array, and then adjust the element at the last position of the array upwards:

// 向上调整
		void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		// 插入元素
		void push(const T& x)
		{
			_con.push_back(x);
			adjustup(_con.size());
		}

other functions

 Including top () function size () function empty () function.

		// 取堆顶的元素
		const T& top()
		{
			return _con[0];
		}

		// 堆元素个数
		size_t size()
		{
			return _con.size();
		}

		// 堆的判空
		bool empty()
		{
			return _con.empty();
		}

The functor realizes the selection of large and small heaps of priority_queue

 We have already mentioned the use of functors above, so here we directly use priority_queue, because the difference between building a large heap and building a small heap lies in the child node and parent node that adjust the two functions up and down. The size of the point is relatively related, so replace these two functions in:

Two functors, the Less class implements a large heap; the Greater class implements a small heap:

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

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

Here are the improvements for scaling up and scaling down:
 

// 向下调整
		void Adjustdown(int parent)
		{
			Comapre com;

			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				//
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				// 
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		// 向上调整
		void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//_con[parent] < _con[child]
				if (com(_con[parent] < _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

Note : Because the functor we implemented above is just a simple comparison of size, if it is a built-in type, then there is no problem; but if it is a custom type, then this type must overload the two overloaded functions operator< and operator>, otherwise Will compile and report an error.

In addition to implementing the two overloaded functions of operator< and operator>, the functors implemented above have actually been implemented in the library, and are also calculated with "< " " > " in the library, so, in fact, this functor Instead of implementing it ourselves, we can directly use the functors in the library.

However, using the functor in the library is still a disadvantage. Assuming that the custom class used does not implement the two overloaded functions operator< and operator>, then we need to implement it ourselves in the functor; in fact, this situation can be avoided. Generally speaking, the two overloaded functions operator< and operator> should be implemented in custom types.

In addition to the one situation mentioned above, there is another situation , as shown in the following code:

	My_priority_queue::priority_queue<Date*> pq;
	pq.push(new Date(2023, 7, 1));
	pq.push(new Date(2023, 6, 2));
	pq.push(new Date(2023, 8, 3));

The storage in the above priority_queue container is not a Date custom type object, but a pointer to the object, which is a newly opened space during push().

The output in this case is very strange. We run the executable program multiple times without changing the code, and the output is different each time :

 At this time, the template parameter passed in is similar to a pointer, so when going to the functor for comparison, it is sorted according to the size of the address, and every time a space is opened in the space, the position of this space is not OK, so the result is different.

If in this case, if you want to sort according to the calendar, you can't go according to the functor in the library, you have to judge the date in the functor yourself.

Note: You cannot directly use the overloaded operator function in the class, you can only implement/use it in the functor that you control. Because the pointer is a built-in type, the built-in type cannot overload the operator . The overloaded operator function implemented in the Date class belongs to this custom type, not to the pointer. As shown in the following code:

struct LessPDate
{
    bool operator() (const Date* d1, const Date* d2)
    {
        return *d1 < *d2
    }
}

It is necessary to call the overloaded operator function in the Date class as above.

reverse iterator

 The forward iterator of list was implemented before. The iterator of list no longer uses raw pointers as iterators like vector and string, but wraps/encapsulates the pointer with a class, so that this iterator can be used It looks the same as vector, using dereferencing (*), iterating backwards (++), and so on.

The difference between a reverse iterator and a forward iterator is that (++) is a forward iterator, and the start position and end position are reversed. Then, we can directly copy the code of the forward iterator. Then change the two functions of operator++ and operator--, and then improve some typedef work, etc. It can be realized, but it is redundant .

The reverse iterator of the list in the library is not implemented in this way : the implementation in the library is simply, in the list environment, use a reverse iterator class to encapsulate the forward iterator class, (++) Call (--) in the forward iterator, (--) calls (++);

 But it's not that simple. In the library is a class template that implements a reverse iterator ( adapter for a reverse iterator ). As long as I use a reverse iterator of a certain class, I will call the class template of this reverse iterator; that is to say , the library uses a class template to implement the reverse iterator of all containers.

 It is not a reverse iterator implemented for a certain class, but for all containers.

 In STL, the code for the reverse iterator is in the file stl_iterator.h :

Probably something like this;

 Moreover, for rend(), the implementation of rbegin() is mirror-symmetrical to end() and rbegin(), so we can see that the dereference operation in the reverse iterator is first (--) and then dereferenced. That is, the previous position of the current position is accessed:

 

 The iterator relationship is as follows:

 It can be seen that it is completely mirrored, and the positions of the reverse iterator and forward iterator are symmetrical.

Adapter implementation for reverse iterators

 According to the above description, we implement the following reverse iterator in the list by referring to the logic in the library.

Problem : the return value of operator*, because the reverse iterator does not want the forward iterator to be implemented in each container, the reverse iterator does not have the data type stored in this container, and the forward iterator uses template parameters to know the container storage The type of data.

In the official library , the data types stored in the container are extracted by means of extraction , but this method is very complicated.

Another way is relatively simple, which is to let the user pass the data type of the container through the template parameter .

 Code:

#pragma once

#include<iostream>

using namespace std;

namespace reverse_iterator
{
	//			迭代器类型     T*         T&
	template<class iterator, class Ref, class ptr>
	class ReverseIterator
	{
		typedef ReverseIterator< iterator, Ref, ptr> self;
		iterator _it;

		ReverseIterator(it)
			:_it(it)
		{}

		Ref operator*()
		{
			iterator tmp = it;
			return *(--tmp);
		}

		ptr operator->()
		{
			return &(operator*());
		}

		self operator++()
		{
			--_it;
			return _it;
		}

		self operator--()
		{
			++_it;
			return _it;
		}

		bool operator!= (const T& x) const
		{
			return _it != x._it;
		}
	};
}

For the implementation in a class, just typedef it:

template<class T, class Ref, class ptr>
class xxx
{
public:
    // 非 const迭代器
    typedef ReverseIterator<T, T* , T&> reverse_iterator;
    // const 迭代器
    typedef ReverseIterator<T, const T* , const T&> const_reverse_iterator;

    reverse_iterator rbegin()
    {
        return reverse_iterator(end());
    }

    reverse_iterator rend()
    {
        return reverse_iterator(begin());
    }

    const_reverse_iteratorrbegin()
    {
        return const_reverse_iterator(end());
    }

    const_reverse_iteratorrend()
    {
        return const_reverse_iterator(begin());
    }

}

This reverse iterator, according to the forward iterator of which container is given, will adapt to which reverse iterator.

Guess you like

Origin blog.csdn.net/chihiro1122/article/details/131954198