The six major components of stl for getting started with C++ - in-depth analysis and simulation implementation of stack and queue source code

Table of contents

Preface

1. Introduction and use of stack

1.Introduction to stack

2.Use of stack

3.Simulation implementation of stack

2. Introduction and use of queue

1.Introduction to queue

2.Use of queue

3.Simulation implementation of queue

3. Introduction and use of priority_queue

1.Introduction to priority_queue

2.Usage of priority_queue

3.Simulation implementation of priority_queue

3.1 Solve a topK problem

4. Container Adapter

1.What is a container adapter?

2.The underlying structure of stack and queue in stl

3.A brief introduction to deque (vector +list)

Summarize


Preface

The source code in this article has been tested under local vs2019 and is correct. Upload the file to gitee at the address: https://gitee.com/a_young/stack_queue_-priority_queue


1. Introduction and use of stack

1.Introduction to stack

By reading the official documentation:

1. Stack is a container adapter that is specially used in contexts with last-in-first-out operations. Its deletion can only insert and extract elements from one end of the container.
2. Stack is implemented as a container adapter. A container adapter encapsulates a specific class as its underlying container , provides a set of specific member functions to access its elements, and uses a specific class as its underlying element at the end of a specific container. (that is, the top of the stack) is pushed and popped.
3. The underlying container of the stack can be any standard container class template or some other specific container classes. These container classes should support the following operations:
  • empty: empty operation
  • back: Get the tail element operation
  • push_back: tail insertion element operation
  • pop_back: tail deletion element operation
4. The standard containers vector, deque, and list all meet these requirements. By default, if no specific underlying container is specified for the stack, deque is used by default .

2.Use of stack

  • stack() constructs an empty stack
  • bool empty() const { return c.empty(); } Check whether the stack is empty
  •  size_type size() const { return c.size(); } Returns the number of elements in the stack
  •  reference top() { return c.back(); } Return the top element of the stack
  •  const_reference top() const { return c.back(); } Return the top element of the stack
  •  void push(const value_type& x) { c.push_back(x); } Push element val onto the stack
  •  void pop() { c.pop_back(); } Pop the tail element of the stack

3.Simulation implementation of stack

The deque used in the source code serves as the stack adapter, so we use deque to simulate the stack.

template<class T, class Contanier = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

		size_t size()
		{
			return _con.size;
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Contanier _con;
	};
	void test_stack()
	{
		//stack<int, vector<int>>st;  //数组栈
		// stack<int,list<int>> st; 链式栈
		stack<int, deque<int>>st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);

		while (!st.empty())
		{
			cout << st.top() << " ";
			st.pop();

		}

		cout << endl;
	}
}

2. Introduction and use of queue

1.Introduction to queue

1. A queue is a container adapter designed to operate in a FIFO context ( first in, first out ) , where elements are inserted from one end of the container and elements are extracted from the other end.
2. The queue is implemented as a container adapter. The container adapter 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 put into the queue from the end of the queue and dequeued from the head.
3. The underlying container can be one of the standard container class templates or other specially designed container classes. The underlying container should support at least the following operations :
empty : Check whether the queue is empty
size : Returns the number of valid elements in the queue
front : Returns a reference to the head element of the queue
back : Returns a reference to the last element of the queue
push_back : queue at the end of the queue
pop_front : Dequeue at the head of the queue
4. The standard container classes deque and list meet these requirements. By default, if no container class is specified for queue instantiation, the standard container deque is used .

2.Use of queue

queue() constructs an empty queue
empty() detects whether the queue is empty and returns true , otherwise it returns false
size()     returns the number of valid elements in the queue
front()    returns a reference to the head element of the queue
back()    returns a reference to the last element of the queue
push()   puts the element val into the queue at the end of the queue
pop()      dequeues the head element of the queue

3.Simulation implementation of queue

#pragma once
#pragma once
#include<iostream>
#include<vector>
#include<stack>
#include<list>

using namespace std;

namespace jellytest
{
	//适配器模式/配接器 
	//队列 先进先出
	template<class T, class Contanier = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

		const T& back()
		{
			return _con.back();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Contanier _con;
	};
	void test_queue()
	{
		//stack<int, vector<int>>st;  //数组栈
		// stack<int,list<int>> st; 链式栈
		queue<int, deque<int>> q;
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);

		while (!q.empty())
		{
			cout << q.front() << " ";
			q.pop();
		}

		cout << endl;
	}
}

3. Introduction and use of priority_queue

1.Introduction to priority_queue

1. 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 .
2. This context is similar to a heap, where elements can be inserted at any time and only the largest heap element ( the top element ) can be retrieved .
3. The priority queue is implemented as a container adapter. The container adapter encapsulates a specific container class as its underlying container class. The queue provides a specific 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.
4. The underlying container can be any standard container class template, or it can be other specifically designed container classes. 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
5. 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 .
6. Need to support random access iterators so that the heap structure is always maintained internally. The container adapter does this automatically by automatically calling the algorithmic functions make_heap , push_heap , and pop_heap when needed .

2.Usage 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 .
priority_queue()/priority_queue(fifirst,last)   constructs an empty priority queue
empty() detects whether the priority queue is empty and returns true , otherwise it returns false
top()  returns the largest ( smallest element ) in the priority queue , that is, the top element of the heap
push(x)  inserts element x into the priority queue
pop () deletes the largest ( smallest ) element in the priority queue , that is, the top element of the heap

3.Simulation implementation of priority_queue

Through the above introduction, the underlying structure of priority_queue is the heap, so here you only need to encapsulate the heap. The default implementation in stl is a large heap.

Here we first introduce a new concept - functor. Objects defined using functors can be used like functions. The essence is to call operator overloading , as defined below:

struct less
{
    bool operator()(int x, int y)
    {
        return x<y;
    }
}

struct greater
{
    bool operator()(int x,int y)
    {
        return x>y;
    }
}


int main()
{
    Less lessfun; //声明一个对象
    cout<<lessfun(1,2)<<endl; // 1
}

Using template classes in stl can compare more than just built-in types

3.1 Solve a topK problem

The topK problem is actually to find the top K largest numbers of all numbers in the file.

Method 1: You can construct a large pile and pop out the first K items.

Analysis method 1: Assuming that N is very large and K is very small, all N numbers need to be stored in the memory, which is a waste of resources.

Method 2: Construct a small heap of K numbers. The following numbers are compared with the top element of the heap in turn. If the number is greater than the top element of the heap, it is put into the heap, the top element of the heap is replaced, and then adjusted downward. In the end, all the top K largest numbers are in the heap.

Analysis method 2: Time complexity O(N*logK), space complexity O(K)

In summary, after comparing the two methods, we choose method 2 to implement

template<class T>
struct less
{
    bool operator()(T & x, T& y)
    {
        //如果是自定义类型这里需要自己实现运算符重载
        return x<y;
    }
}

//创建一个小堆
template<class T,class Container = deque<T> ,class Compare = greter<T>>
class priority_queue
{
    
    public:
    //向下调整
    void adjust_down(int parent)
    {
        Compare com;
        int child = 2*parent +1;
        while(chile < _con.size())
        {
          //  if(child+1 <_con.size() && _con[child] < _con[child+1])
              if(child+1 <_con.size() && com(_con[child] , _con[child+1]))
                {
                    ++child;
                }
          //  if(_con[parent] < _con[child])
            if( com(_con[parent] , _con[child])
            {
                swap(_con[child] , _con[parent]);
                child = parent;
                parent = 2*child+1;
            }
        
            else
            {
                break;
             }
        }
            
    }
    //向上调整
    void adjust_up(int child)
    {
        	Comapre com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				//if (Comapre()(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}

    }

    //堆的一些成员函数 封装一些接口
    void push()
    {
        _con.push_back(x);
        adjust_up(_con.size()-1);
    }

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


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

private:
    Container _con;
}


void test_priority_queue()
{
    priority_queue<int,deque<int>,less<int>> pq;
    pq.push(0);
    pq.push(1);
    pq.push(2);
    pq.push(3);

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

4. Container Adapter

1.What is a container adapter?

2.The underlying structure of stack and queue in stl

3.A brief introduction to deque (vector +list)

deque (double-ended queue): It is a double-opening "continuous" space data structure. The meaning of double-opening is that insertion and deletion operations can be performed at both ends, and the time complexity is O(1), which is the same as vector In comparison, header insertion is more efficient, as there is no need to move elements. Compared with lists, space utilization is higher. The disadvantage is: it is troublesome to insert and delete in the middle.

Deque is not a truly continuous space, but is made up of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array. When inserting the head, insert a pointer in front of the central control array, and insert a pointer at the tail of the central control array.

The bottom layer of the double-ended queue is an imaginary continuous space, which is actually segmented and continuous. In order to maintain the illusion of "overall continuity" and random access, it falls on the deque iterator, so the deque iterator design is more complicated.

Why choose deque as the underlying default container for stack and queue ?
stack is a special linear data structure that is last in first out, so as long as it has a linear structure with push_back() and pop_back() operations, it can be used as the underlying container of the stack , such as vector and list ; queue is a special first in first out Linear data structures, as long as they have push_back and pop_front operations, can be used as the underlying container of queue , such as list . However, in STL, deque is selected as the underlying container by default for stack and queue .
Mainly because:
1. Stack and queue do not need to be traversed ( so stack and queue do not have iterators ) , they only need to operate on one or both fixed ends.
2. When the elements in the stack grow, deque is more efficient than vector ( there is no need to move a large amount of data when expanding ) ; when the elements in the queue grow , deque is not only efficient, but also has high memory usage.
It combines the advantages of deque and perfectly avoids its shortcomings.

Summarize

This article mainly simulates and implements some interfaces of stack, queue, and priority_queue, and briefly introduces what a container adapter is.

Supongo que te gusta

Origin blog.csdn.net/jolly0514/article/details/132134604
Recomendado
Clasificación