STL Quick Start 1-Container

The six components of STL

This article will introduce the first part, the principle and characteristics of containers

  1. Containers: including vectors, lists, sets, maps, etc.

  2. Algorithms: including sorting, searching, merging, exchanging elements, and many other algorithms

  3. Iterators: provide a unified mechanism to traverse the elements in the container

  4. Adapters (Adapters): such as stack and queue, they use the interface provided by the underlying container (vector/list, etc.) to implement stacks and queues.

  5. Function objects: Classes that implement function call operations. Used to provide comparison functions, calculation functions, etc. to the algorithm.

  6. Allocators: Manage memory allocation and release


Main Container Classification

The C++ standard library provides a wealth of containers, the main containers are:

  1. Sequential container: store elements in linear order
  • vector: Dynamic array with random access to elements.
  • deque: Double-ended queue, which is more efficient than vector, but cannot be accessed randomly.
  • list: Doubly linked list, suitable for frequent insertion and deletion.
  • array: ordinary array, fixed capacity.
  1. Associative containers: access elements by key
  • set/multiset: Each element is unique/not unique, sorted by key value.
  • map/multimap: The key value corresponds to the value, the key is unique/not unique, and the key value is sorted.
  1. Container adapters: containers that provide additional functionality
  • stack: Computing stack, which implements last-in-first-out.
  • queue: queue, first in first out.
  • priority_queue: priority queue.

These containers are classified into serial, associative, and container adapters.

The sequential container stores elements in order, and the elements are directly accessed by index;

Associative containers access elements through keys, and there is an ordering relationship between elements;

Container adapters can convert other containers into stacks, queues, etc. to provide additional functions.

Besides that, there are some standard containers:

  • bitset: bit set, efficiently operate and store a set of bits.
  • forward_list: singly linked list, which implements a subset of list.
  • unordered_set/unordered_map: Unordered collection, implemented by hash table.

vector


vector general features

Vector is one of the most commonly used containers in STL, very similar to array in C language, but provides more functions.

main feature:

  • Dynamic array: It can grow freely at runtime, and does not need to specify the length in advance.
  • Random access: supports fast access to arbitrary elements through the [] operator.
  • Automatic expansion: When the element exceeds the capacity, the vector will automatically reallocate the memory, and the size is generally doubled.
  • Sequential storage: elements are stored sequentially in memory.

Commonly used APIs:

  • push_back(): Insert an element at the end.
  • pop_back(): Delete the element at the end.
  • size(): Returns the number of elements in the container.
  • capacity(): Returns the currently allocated storage capacity.
  • max_size(): Returns the maximum capacity allowed.
  • empty(): Determine whether the container is empty.
  • array accessor:[] for indexed access.
  • begin()/end(): returns an iterator.
  • insert(): Insert an element at any position.
  • erase(): Deletes an element at any position.
  • resize(): Resize, add or delete elements.

Advantages and disadvantages:

  • Advantages: high random access efficiency; automatic expansion, easy to use.
  • Disadvantages: Inefficient expansion and deletion, average complexity O(n); takes up extra memory.

initialization:

  • Direct initialization:
vector<int> v1;
vector<int> v2(10); // 长度为10,元素默认初始值为0
  • List initialization:
vector<int> v3 = {
    
    1,2,3};
vector<int> v4(10, 8); //长度为10,元素默认初始值为8

vector implementation principle

  1. Three key members of vector:
  • data: points to the first address of the element.
  • first: points to the first element.
  • last: Points to the next position of the last element.

Among them, first and last are used to judge the range of available elements.

  1. Allocate memory space:
  • A memory space of a specified capacity is allocated when vector is initialized.
  • When the number of elements exceeds the existing capacity, it is necessary to reallocate twice the memory space.
  1. Reallocate memory:
  • Check capacity. Reallocation is required when size() > capacity().
  • Allocate new memory twice the size of capacity.
  • Copy the original elements to the new memory.
  • Release the original memory.
  • Update first, last, data pointers.
  1. Add and delete elements:
  • Insertion: Need to move subsequent elements, time complexity O(n)
  • Delete: Need to move subsequent elements to fill the deleted position, time complexity O(n)
  1. random access:

Use the data pointer and subscript [] to achieve.

  1. iterators:
  • begin(): returns an iterator pointing to first.

  • end(): returns an iterator pointing to last.

The iterator is actually a left-closed right-open range, that is [begin,end), so end does not point to a valid element

  1. free memory
  • The recovery of vector memory can only be recovered by the system when the vector calls the destructor
  • You can also use swap to release memory, such asv1.swap(v1)

stack


stack feature

A stack is a type of container adapter that uses a stack data structure implemented by the underlying container (usually a vector or deque).

main feature:

  • Follow the LIFO (Last In First Out) principle.
  • Provide common stack operations, such as push (push), pop (pop), view the top of the stack (top), etc.

Common functions:

  • push(): Inserts an element to the top of the stack.
  • pop(): Removes the first element from the top of the stack.
  • top(): returns the top element of the stack.
  • empty(): Determine whether the stack is empty.
  • size(): Returns the number of elements in the stack.

Bottom container:

Stack uses deque as the underlying container by default, and deque effectively supports operations at both ends of the stack.
But it is also possible to use vector as the underlying container.

Stack implementation:

The bottom layer of stack is actually a container (usually deque or vector), and provides a stack-oriented interface.
The stack function can be realized by using the insertion and deletion operations of the container.

initialization:

stack<int> s;  // 使用deque作为默认容器

stack<int, vector<int>> s;  // 使用 vector 作为容器

The underlying implementation of stack

stack can use two kinds of underlying containers:

  • vector: as the default implementation of the underlying container.
  • deque: Compared with vector, deque supports more efficient insertion and deletion operations at both ends, and is more suitable as an underlying container.

stack can be implemented directly by using the following three functions in vector and deque

  • push(): call push_back() to insert elements
  • pop(): call pop_back() to delete the element
  • top(): call back() to access the top element of the stack

stack is essentially an adapter, similar to the following form

template<class T, class Container = deque<T> >
class stack {
    
    
public:
    void push(const T&);
    void pop();
    // ...
private:
    Container c;  // 底层容器
};

queue

The queue implementation is similar to the stack, and also uses the underlying container (deque or list) to implement the queue-oriented interface.

Bottom container:

  • By default, deque is used as the underlying container.
  • A deque supports efficient insertion and deletion from both ends.
  • It is also possible to use list as the underlying container.

time complexity:

  • Use deque as the underlying container:
    • push: O(1)
    • pop: O(1)
  • Use list as the underlying container:
    • push: O(1)
    • pop: O(1)

Implement the queue interface:

  • push(): Call the push_back() implementation of the underlying container
  • pop(): Call the pop_front() implementation of the underlying container
  • front(): Call the front() implementation of the underlying container
  • back(): Call the back() implementation of the underlying container

Queue class:

The general structure is as follows:

template <class T, class Container = deque<T>>
class queue {
    
    
public:
    void push(const T& x) {
    
     c.push_back(x); }
    void pop() {
    
     c.pop_front(); }
    // ...
private:
    Container c;  //底层容器
};

Use queues:

queue<int> q;
q.push(1);
q.push(2);
q.pop();

therefore


deque introduction

Deque is one of the STL containers, a double-ended array, which is divided into two types: double-ended queue and segment double-ended queue in the strict sense.

main feature:

  • Supports efficient insert and delete operations on both ends. The time complexity is O(1).
  • Support random access, but not as efficient as vector.
  • Actual data movement does not have to occur on insertion and deletion.
  • Internal data is allocated as a series of segment arrays, and new segments are allocated as needed.

Common functions:

  • push_back(): Insert elements at the end
  • push_front(): Insert elements at the head of the queue
  • pop_back(): delete the end element
  • pop_front(): Delete the first element of the team
  • front(): returns the first element of the queue
  • back(): returns the element at the end of the queue
  • [] : supports random access
  • begin()/end(): return iterator
  • size(): returns the number of elements

Advantages and disadvantages:

  • Advantages: high efficiency of insertion and deletion at both ends; no need to reallocate memory
  • Disadvantages: random access efficiency is lower than vector

Underlying implementation:

A deque internally uses a series of contiguous blocks of memory to store elements.
A new block of memory is allocated each time it is needed.

initialization:

  • Direct initialization:
deque<int> d;
deque<int> d(10, 1); //长度10,默认元素1
  • List initialization:
deque<int> d = {
    
    1,2,3};

The underlying implementation principle of deque

Deque (double-ended queue) is a container in the C++ standard library. It is a sequence container with double-ended openings, which can perform efficient insertion and deletion operations at both ends.

The underlying implementation of a deque is based on a data structure called a "segmented continuous space".
It divides a large continuous space into multiple small continuous spaces according to a certain size. Each small continuous space is called a "buffer", and the internal elements of each buffer are stored continuously.
Deque maintains a doubly linked list, each node points to a buffer, and records the start address, end address, and pointer of the next buffer at the same time.

When inserting an element at the head, deque will add a new node at the head of the linked list and insert the new element in front of the head buffer; when
inserting an element at the end, deque will add a new node at the end of the linked list and insert the new element Elements are inserted at the end of the tail buffer.
Delete elements at the head or tail, and the deque will adjust its internal structure as needed to ensure that the operation is completed in constant time.

In implementation, deque uses a central controller called map, which is an array of pointers, each of which points to a buffer.
The size of the map is fixed, as is the size of each buffer, and these sizes are specified at compile time.
When we need to insert elements at the head or tail of the deque, the deque will first check whether a new buffer needs to be added, and if necessary, a new pointer will be allocated in the map and point to a new buffer to ensure that accommodate new elements.


The impact of deque underlying implementation on performance

  1. The performance of insertion and deletion at both ends is extremely high. Deque is implemented by allocating a series of contiguous memory blocks, and there is no need to move existing elements when inserting and deleting at both ends. So the time complexity is O(1).

  2. Random access performance is not as good as vector. The internal elements of deque are not stored sequentially. In order to randomly access an element, it is necessary to locate the corresponding memory block first, and then search in the memory block. So the efficiency is not as good as vector.

  3. The memory overhead is large. Deque needs to maintain an array of memory blocks, and each allocation of a new memory block also requires additional overhead. The total memory overhead is more than vector.

  4. The expansion methods are different. Deque only allocates a fixed-capacity memory block at a time, instead of doubling the capacity like vector.

  5. The iterator is invalid. A deque's iterators cannot span memory blocks. Once the deque reallocates memory blocks, all iterators will be invalidated.

Based on these characteristics, we can get the following performance indicators:

  • Insertion and deletion performance: deque >> vector
  • Random access performance: vector >> deque
  • Memory overhead: vector < deque
  • Iterator stability: vector > deque

so:

  • If you need frequent insertion and deletion at both ends, you should choose deque.
  • If efficient random access is required, vector should be chosen.

Application scenarios of deque

  1. Implement command history (command line interface)
  2. Event loop implemented based on double-ended queue
  3. As the underlying container of stack and queue
  4. When traversing hierarchies (such as XML files) use
  5. A collection that requires efficient insertion and deletion

deque thread safety issues

The deque container in the C++ standard library is a thread-unsafe container, and its underlying implementation does not support multi-threaded operations

If you use it in a multi-threaded environment, you can try the following methods

  1. Use synchronization mechanisms (such as mutexes, read-write locks, etc.) to ensure thread safety
  2. Call the thread-safe deque implementation in the boost library

The C++11 standard introduces some atomic operations and synchronization mechanisms (such as std::atomic、std::mutexetc. ), which can be used to implement thread-safe data structures


list


brief description

list is one of the sequential containers in STL, which implements a doubly linked list.

main feature:

  • The elements of list do not require contiguous memory storage.
  • Nodes are discrete and linked by pointers.
  • Supports efficient insertion and deletion operations.

Common functions:

  • push_back(): Insert elements at the end
  • push_front(): Insert elements at the head
  • pop_back(): delete the tail element
  • pop_front(): delete the head element
  • erase(): delete the specified element
  • insert(): Insert an element at the specified position
  • sort(): sorting
  • merge(): Merge
  • remove(): delete the specified value element

Advantages and disadvantages:

  • Advantages: High efficiency for insertion and deletion operations.
  • Disadvantages: does not support random access, low efficiency.

Allocation:

The nodes of the list are distributed, and adjacent nodes are linked together by pointers.

initialization:

list<int> lst;
list<int> lst = {
    
    1, 2, 3};

time complexity:

  • Insertion and deletion: O(1)
  • Lookup: O(n)
  • Access: O(n)

list implementation

The underlying implementation of List is a doubly linked list, each node contains three pointers: one pointing to the previous node, one pointing to the next node, and one pointing to the element stored in the node

List maintains two pointers, one pointing to the head node of the linked list and one pointing to the tail node of the linked list. When we need to perform insertion or deletion operations at the head or tail of the linked list, use new to add new nodes at the corresponding head or tail, or use delete to delete nodes

For basic types (such as int, char, etc.), List will store them directly in nodes; for complex types (such as custom classes, structures, etc.), List will store their pointers or references

The insertion and deletion operations of List are very efficient, because it only needs to modify the pointer of the node


list multithreading

Similarly, it is not a thread-safe container. To ensure thread safety, you can refer to the corresponding method of deque


priority_queue


Introduction to Priority Queues

Priority queue ( Priority Queue) is an important data structure in STL. It is different from the standard queue and has the following characteristics:

  • The elements in the queue have priority, and the elements can be sorted according to the priority.
  • The dequeue operation selects the element with the highest priority.
  • The insertion and deletion operations of the priority queue are implemented based on the heap data structure.

Common interface:

  • push(): Insert an element into the queue
  • pop(): Remove the element with the highest priority
  • top(): Get the element with the highest priority
  • empty(): Determine whether the queue is empty
  • size(): returns the number of elements in the queue

Underlying implementation:

The priority queue of STL is implemented by binary heap by default.
Heaps can also be of other types, including bullpen heaps, Fibonacci heaps, etc.

Binary heap:

  • complete binary tree. All elements of the parent node are less than or greater than the child nodes.
  • Provides efficient insertion and deletion operations. The time complexity is O(logn).

initialization:

priority_queue<int> max_pq;  // 最大堆,默认
priority_queue<int,vector<int>,greater<int>> min_pq; // 最小堆

application:

Priority queues are mainly used in scenarios that require efficient sorting, such as:

  • Huffman coding
  • Dijkstra's shortest path algorithm
  • single source shortest path
  • High Frequency Element Statistics

Priority Queue Principle

The underlying implementation of the priority queue is to use the heap (Heap) data structure.
A heap is a complete binary tree and can be divided into two types: max-heap and min-heap.
In a max-heap, each node's value is greater than or equal to the value of its child nodes; in a min-heap, each node's value is less than or equal to the value of its child nodes.
In the priority queue, the maximum heap is usually used to maintain the priority relationship of elements

When a new element is added to the priority queue, it will be inserted at the end of the heap, and then moved to the correct position through the percolate up operation to ensure that the nature of the heap remains unchanged.
When the element with the highest priority in the queue needs to be extracted, the root node of the heap is the element with the highest priority, which will be popped and returned.

Priority queues usually use vector or deque containers to implement heaps

Priority queues are still thread-unsafe


set


Introduction to set

set is an important associative container in STL, which has the following characteristics:

Features:

  • Duplicate elements are not allowed in a set.
  • Elements are automatically sorted according to their values.
  • Elements can be found quickly, but random access is not supported.

Common interface:

  • insert(): Insert an element
  • erase(): delete elements
  • find(): Find elements
  • count(): Count the number of elements
  • begin()/rend(): return iterator
  • size(): returns the number of elements
  • empty(): determine whether it is empty

Underlying implementation:

Set is implemented by red-black tree by default.

time complexity:

  • Insertion and deletion: O(logN)
  • Lookup: O(logN)
  • Traversal: O(N)

traverse:

set provides iterator traversal methods and built-in iterator traversal methods:

// 迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it){
    
    
    cout << *it << endl;
}

// 内置遍历
for (int x : s) {
    
    
    cout << x << endl;
}

initialization:

set<int> s;
set<int> s = {
    
    1, 2, 3};
set<string> s2;

The underlying implementation of set

The underlying implementation of the set container usually uses 红黑树(Red-Black Tree)a data structure. The red-black tree is a self-balancing binary search tree, which can guarantee that the time complexity of the search, insertion and deletion operations in the worst case isO(log n)

The elements in the set container are unique, so when inserting elements, the red-black tree will automatically deduplicate to ensure that each element appears only once

For the Set container, the red-black tree implemented by STL is a variant of the Leftist Tree, called RB-tree (Red-Black Tree)

The set container also provides some other operations, such as lower_bound(), upper_bound(), equal_range(), etc. These operations are all implemented based on RB-tree


map


map introduction

map is one of the associative containers in STL, which supports dynamic initial size and allows user-defined key types.

main feature:

  • It stores key-value pairs (key-value pairs).
  • The data can be quickly located by the key, and the same key can only appear once.
  • Internally, a red-black tree is used for self-balancing.

Common functions:

  • insert(): Insert an element
  • erase(): delete an element
  • find(): find element
  • at(): get element by key
  • begin()/end(): returns an iterator
  • size(): returns the number of elements
  • empty(): Determine whether the container is empty

time complexity:

  • Insertion and deletion: O(logn)
  • Lookup: O(logn)
  • Iterations: O(n)

initialization:

map<string, int> m;
map<string, int> m = {
    
    
   {
    
    "apple", 1},
   {
    
    "banana", 2}
};

use:

m["apple"] = 100;  // 通过键设置值
int appleValue = m["apple"];

The underlying implementation of map

The underlying implementation of the map container usually uses the Red-Black Tree data structure

In the map container, the elements are composed of key-value pairs, sorted according to the size relationship of the key. When inserting elements, the map container will insert the elements into the appropriate position in the red-black tree, and keep the nature of the red-black tree unchanged


Guess you like

Origin blog.csdn.net/delete_you/article/details/130829683