STL commonly used carding - STACK, QUEUE

1、List

Introduction to STL list container

STL list container, also known as doubly linked list container, means that the bottom layer of the container is implemented in the form of doubly linked list. This means that the elements in the list container can be scattered and stored in the memory space, instead of having to be stored in a whole contiguous memory space.
insert image description here
It can be seen that the order of each element in the list container is maintained by pointers, and each element is equipped with 2 pointers, pointing to its previous element and the next element respectively. The forward pointer of the first element is always null, because there is no element before it; likewise, the backward pointer of the tail element is always null.
Based on this storage structure, the list container has some advantages that other containers (array, vector, and deque) do not have, that is, it can quickly insert or delete elements at any position where the sequence is known (time complexity is O(1)) . And moving elements in the list container is also more efficient than other containers.
The disadvantage of using a list container is that it cannot directly access elements by position like array and vector.

list uses

1. List construction

Constructor Interface introduction
list (size_type n, const value_type& val = value_type()) The constructed list contains n elements whose value is val
list() Construct an empty list
list (const list& x) copy constructor
list (InputIterator first, InputIterator last) Constructs a list with elements in the range [first, last)
 	list<int> l1;                         // 构造空的l1
    list<int> l2(4, 100);                 // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

2. list iterator

begin + end Returns an iterator to the first element + returns an iterator to the next position of the last element
rbegin + rend Return the reverse_iterator of the first element, which is the end position, and return the reverse_iterator of the next position of the last element, which is the begin position
void PrintList(const list<int>& l)
{
    
    
    // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
    for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
    
    
        cout << *it << " ";
       
    }

    cout << endl;
}

void TestList2()
{
    
    
    int array[] = {
    
     1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin();   // C++98中语法
    auto it = l.begin();                     // C++11之后推荐写法
    while (it != l.end())
    {
    
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
    
    
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

3. Add, delete, check and modify
insert image description here

int main()
{
    
    
	
	int arr[] = {
    
    0 };
	int n = sizeof(arr) / sizeof(arr[0]);
	list<int> lt(arr, arr + n);
	list<int> list(10,1);//使用10个1初始化list

	lt.push_front(2);
	PrintList(lt);

	lt.push_back(1);
	PrintList(lt);
	
	lt.pop_back();
	PrintList(lt);

	lt.pop_front();
	PrintList(lt);

	lt.insert(lt.begin()++, 2);
	PrintList(lt);

	lt.erase(lt.begin());
	PrintList(lt);

	lt.swap(list);
	PrintList(lt);

	lt.clear();
	PrintList(lt);

	return 0;
}

Operation result reference:
insert image description here

2. Adapter Introduction

An adapter is an interface that wraps containers, iterators, and algorithms, but its essence is still containers, iterators, and algorithms, but it does not depend on specific standard container, iterator, and algorithm types. The concept comes from the adapter pattern in the design pattern: convert the interface of one class into the interface of another class, so that classes that are originally incompatible and cannot cooperate can work together.

Container adapters can be understood as container templates, iterator adapters can be understood as iterator templates, and algorithm adapters can be understood as algorithm templates.
In the data structure, there are structural implementations for stacks and queues, and there are physical structures that are continuous (array structure) and discontinuous (linked list structure). The underlying structure determines the efficiency issue. For the OPP language, it is a little inconvenient to use.

3. Deque container

Specific libraries may implement deques differently, usually some form of dynamic array. But in any case, they allow direct access to individual elements via random-access iterators, and automatically handle storage by expanding and shrinking the container as needed. Thus, they provide vector-like functionality, but with efficient insertion and deletion of elements at the beginning of the sequence, not just at the end. However, unlike a vector, a deque is not guaranteed to store all of its elements in contiguous memory locations: accessing an element in a deque by offsetting a pointer to another element results in undefined behavior. Both vector and deque provide a very similar interface and can be used for similar purposes, but internally the two work quite differently: vector uses a single array that occasionally needs to be reallocated for growth, while deque's elements can be scattered among In different memory blocks, the container internally keeps the necessary information to directly access any of its elements in constant time using a uniform sequential interface (via iterators). So deques are more complex internally than vectors, but this makes them more efficient to grow in some cases, especially for very long sequences where reallocation becomes more expensive. For operations involving frequent insertion or deletion of elements other than the start or end position, deque performs worse, and iterators and references are less consistent than lists and forward lists.

insert image description here
Deque is not a real continuous space, but is spliced ​​by a series of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array
insert image description here

Compared with vector, the advantage of deque is that when the head is inserted and deleted, there is no need to move elements, and the efficiency is particularly high, and when expanding, there is no need to move a large number of elements, so its efficiency must be high.
Compared with list, its bottom layer is a continuous space, the space utilization rate is relatively high, and no additional fields need to be stored.
However, deque has a fatal flaw: it is not suitable for traversal, because when traversing, the iterator of deque needs to frequently check whether it has moved to the boundary of a small space, resulting in low efficiency. In sequential scenarios, it may be necessary to frequently Traversal, so in practice, when a linear structure is required, vector and list are given priority in most cases. There are not many applications of deque, and one application that can be seen so far is that STL uses it as the underlying data structure of stack and queue .

Stack, Queue adapter implementation

namespace uu

{
    
    

#include<deque>
//第一个模板参数是类型,第二个是容器类型
    template<class T, class Con = deque<T>>
    class stack
    {
    
    

    public:

        stack()
        {
    
    
            
        }

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

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

        T& top()
        {
    
    
            return _c.back();
        }

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

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

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

    private:

        Con _c;

    };
  

    template<class T, class Con = deque<T>>
    class queue
    {
    
    

    public:

        queue()
        {
    
    

        }

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

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

        T& back()
        {
    
    
            return _c.back();
        }

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

        T& front()
        {
    
    
            return _c.front();
        }

        const T& front()const
        {
    
    
            return _c.front();

        }

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

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

    private:

        Con _c;

    };

    void test_queue()
    {
    
    
        queue<int> q;
        q.push(1);
        q.push(2);
        q.push(3);
        q.push(4);

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

    }

    void test_stack()
    {
    
    
        stack<int> st;
        st.push(1);
        st.push(2);
        st.push(3);
        st.push(4);
        st.push(5);

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

};

Running results:
insert image description here
Why choose deque as the underlying default container of stack and queue?
Stack is a special last-in-first-out linear data structure. Therefore, as long as it has a linear structure with push_back() and pop_back() operations, it can be used as the underlying container of stack. For example, both vector and list are available; queue is a special linear data structure of first-in-first-out, as long as it has a linear structure with push_back and pop_front operations, it 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 have no iterators), and only need to operate at one or both ends of the fixed.
  2. When the elements in the stack grow, deque is more efficient than vector (no need to move a lot of data when expanding); when the elements in the queue grow, deque is not only efficient, but also has high memory usage.

Combining the advantages of deque, it perfectly avoids its defects.

Guess you like

Origin blog.csdn.net/github_73587650/article/details/130542512