Learn about container adapters, functors and reverse iterators via stacks/queues/priority queues


Vector and list are called containers, but stack and queue are called container adapters.

insert image description here

This is related to their second template parameter. You can see that the default value of the second template parameter of stack and queue is both, dequethat is, the double-ended queue container. That is to say, both the stack and the queue are built with the container as the main material, and because the second parameter is a default value, we can not only use the dequebuild, but also use vectorand listto build.

Using an existing container to encapsulate and convert a container that does not exist is called the adapter pattern.

With dequethe provided interface, it becomes very simple to implement stacks and queues.

One.stack

The characteristic of the stack is last in, first out, insertion and deletion are performed at the end, and the stack does not provide an iterator (because the stack can only access the elements at the top of the stack).

#include<deque>
namespace wbm
{
	template <class T, class Container = deque<T>>
	class stack
	{
	public:
		bool empty()const
		{
			return _con.empty();
		}

		void push(const T&x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

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

		const T& top()const
		{
			return _con.back();
		}

	private:
		Container _con;
	};

}

The stack can not only dequebe encapsulated by , but also can be encapsulated by vectorand , as long as it supports tail insertion and tail deletionlist

Two.queue

The queue is characterized by first-in first-out, tail-in, and head-out. Data at the head and tail of the queue can be accessed, and no iterators are provided.

#include<deque>

namespace wbm
{
	template<class T,class Container=deque<T>>
	
	class queue
	{
	public:
		bool empty()const
		{
			return _con.empty();
		}

		void push(const T& x)
		{
			_con.push_back();
		}

		void pop()
		{
			_con.pop_front();
		}

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

		const T& fornt()const
		{
			return _con.front();
		}

		const T& back()const
		{
			return _con.back();
		}

	private:
		Container _con;
	};
}

As long as it is an adapter that supports tail plug deletion queue, it is not recommended to use an array, because the head deletion efficiency of the array is too low

3.deque (double-ended queue)

Because both vector and list have their own advantages and disadvantages, the big guys wonder whether it is possible to create a container that has both the advantages of vector and list while avoiding the disadvantages, so there is a double-ended queue deque, although the name of deque has a queue, but It has nothing to do with queues.

insert image description here

Observing the interface it provides, it is found that it not only supports random access, but also supports head-plug deletion and tail-plug deletion. It seems that it is indeed a perfect container. But if it was really that perfect, wouldn't vector and list have been eliminated long ago.

In fact, the bottom layer of the deque is composed of a pointer array + multiple buffer arrays (the size of the buffer array is fixed); this pointer array is a central control array, and the elements stored in it are the addresses of the buffer arrays belonging to the deque.

The address of the buffer array created by inserting data for the first time is stored in the middle element of the central control array. If the buffer array is full and the end insertion is continued, a new buffer array will continue to be opened. At this time, the address of the second buffer array is stored in Next to the middle element of the control array. If you want to plug in, create a new buffer array, and store the address of this buffer array in the previous one of the middle element.

insert image description here

Advantages of deques:

1. The cost of expansion is lower than that of vector: it only needs to be expanded when the central control is full, and there is no need to copy the original data, as long as the mapping relationship between the central control array and the buffer is copied.

2. Because it is a section of space, the CPU cache hit rate is higher than list.

3. Head-to-tail insertion and deletion are highly efficient and support random access

But deque also has its own disadvantages:

1. The efficiency of random access is not as good as that of vector. Its random access must be obtained through calculation. Assume that the size of each buffer array is size, and you want to access the 10th element. First, you need 10/size to determine which buffer array this element is in. , and then use 10% size to determine where this element is in the buffer array, so deque is not suitable for sorting, because sorting requires a lot of random access.

2. Inserting and deleting in the middle is not as good as list, it also needs to move a certain amount of data to insert and delete in the middle

3. The implementation of the iterator is complicated: there are four pointers in its iterator. cur==lastAt that time , it means that the space in this segment is used up and ++nodecontinues to access the next segment of space, and updates firstandlast

insert image description here


4. Priority queue

insert image description here

The characteristic of the priority queue is that the priority is first out. It is also a container adapter that does not provide an iterator. The bottom layer is a heap and the default is a large heap.

priority_queue<int> //小堆
priority_queue<int,vector<int>,greater<int>> //大堆

Functors in priority queues

A functor is a function object, which is an object of a class function. To achieve a class function, it must be overloaded in this class (). The default in the priority queue is a large heap. If we want to change it to a small heap, we need to change the size comparison algorithm in addition to passing the third parameter. In C language, in order to qsortsort any type, the library uses a method of function pointers, allowing users to explicitly write a comparison function, and pass the address of the function as a parameter. One use case for functors is analogous to function pointers.

namespace wbm
{
	template<class T>
	struct less //可以使用struct,也可以使用class,它们都是类,只是默认的访问限定不同
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template <class T>
	struct greater
	{
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};
}
int main()
{
	wbm::less<int> func;
	//两者等价:func(1, 2)==func.operator()(1, 2);
	return 0;
}

hand rubbing priority queue

#include<vector>
#include<iostream>
namespace wbm
{

	template<class T>
	struct less //可以使用struct,也可以使用class,它们都是类,只是默认的访问限定不同
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

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

	template<class T, class Container =std:: vector<T>,class Compare = less<T>>
	class priority_queue
	{
	private:
		void Adjustdown(size_t parent)
		{
			Compare com;//构造一个仿函数对象
			size_t child = parent * 2 + 1;//先默认是左孩子大,如果右孩子更大,把孩子节点更新成右孩子

			while (child < _con.size())
			{
				if (child+1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;//默认是less,也就是左孩子比右孩子小
				}

				//将孩子节点和父节点做比较
				if (com(_con[parent], _con[child])) 
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
				
			}
		}

		void Adjustup(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;

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

	public:
		priority_queue()
		{
			//要有一个默认构造,不写就会报错
		}

		//迭代器区间构造,直接构造出一个堆,使用向下调整更佳
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)  //初始化列表,vector支持迭代器区间构造
		{
			//初始化建堆,使用向下调整,从第一个非叶子节点开始
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				Adjustdown(i);
			}
		}

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

		void pop()
		{
			//将首元素和末尾元素换位置删除后调整

			std::swap(_con.front(), _con.back());
			_con.pop_back();
			Adjustdown(0);
		}

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

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

		const T& top()const
		{
			return _con.front();
		}

	private:
		Container _con;
	};
}

If the address of a certain class is stored in the priority queue, and we need to compare the priority of the value in the address, then we need to use the functor for special processing. Otherwise it will only be compared by address.

5. Reverse iterator

The reverse iterator adopts the adapter mode, which is realized by repackaging the forward iterator. If you give it the forward iterator of a certain container, it will generate the reverse iterator of the container. It is the same as the forward iterator. The positions of the switches are symmetrical and just opposite. ++So to control the reverse iterator, you only need to use operator overloading and tamper with --the rules for neutralizing the direction iterator .

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

insert image description here

Like end, rbegin points to the next position of the last element. If you want to access 3, you must --visit it first. This problem can be *solved by overloading

template<class Iterator,class Ref,class Ptr>
Ref operator*()
{
    Iterator tmp=it;
    return *(--tmp);
}

hand rub reverse iterator

namespace wbm
{
    //反向迭代器,使用正向迭代器封装
    template<class Iterator,class Ref,class Ptr>  //迭代器,T&/const T&,T*/const T*

    class ReverseIterator
    {
        typedef ReverseIterator<Iterator, Ref, Ptr> Self;
    public:
        ReverseIterator(Iterator it)
            :_it(it)
        {}

        Ref operator*()
        {
            Iterator tmp;
            return *(--tmp);
        }

        Ptr operator->()
        {
            return &(operator*())
        }

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

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

        bool operator!=(const Ref& rit)const//两个迭代器之间的比较
        {
            return _it != rit;
        }

    private:
        Iterator _it;//这里给的参数是一个正向迭代器
    };
}

Guess you like

Origin blog.csdn.net/m0_62633482/article/details/130547937