list (Doubly Linked List)
<span>list</span> is a doubly linked list where elements are connected through pointers and does not support random access.Features:
- Elements are not contiguous in memory
- High efficiency for insertion/deletion at any position (O(1))
- No support for random access (no [] operator)
- Additional storage for pointer information, resulting in higher memory overhead
Defining and initializing a list
list<int> myList = { 1, 2, 3 };
Inserting elements
// Insert at the head
myList.push_front(0);
// Insert at the tail
myList.push_back(5);
Inserting at a specific position
// Insert at a specific position
auto it = myList.begin();
advance(it, 2); // Move iterator to the 3rd element
myList.insert(it, 3);
Traversing elements
// Traversing
for (int num : myList) {
cout << num << " ";
}
Deleting elements
// Remove all elements with value 3
myList.remove(3);
Removing from head and tail
// Remove the first element
myList.pop_front();
// Remove the last element
myList.pop_back();
Deleting an element at a specific position
auto it = myList.begin();
advance(it, 1); // Move iterator to the 2nd element
myList.erase(it);
Applicable Scenarios:
- Frequent insertions and deletions at any position are required
- No need for random access to elements
- Scenarios where the length of the list is uncertain
Comparison with the previous section’s vector
1. Internal Implementation Principles
1. std::vector
- Implementation based on dynamic arrays, using contiguous memory space to store elements
- Memory Management automatically reallocates a larger memory block and copies existing elements when the number of elements exceeds the current capacity
- Iterators random access iterators (RandomAccessIterator), support arithmetic operations
2. std::list
- Implementation doubly linked list, each element contains data and two pointers (predecessor and successor)
- Memory Management elements are not contiguous in memory, each element is allocated memory separately
- Iterators bidirectional iterators (BidirectionalIterator), only support ++ and — operations
2. Performance Comparison
| Operation | vector | list | Reason for Performance Difference |
|---|---|---|---|
| Random access (by index) | O(1) | O(n) | vector uses contiguous memory, allowing direct address calculation; list requires traversal from the head |
| Insertion/Deletion at the head | O(n) | O(1) | vector needs to move all elements; list only needs to modify pointers |
| Insertion/Deletion at the tail | O(1) (amortized) | O(1) | vector may need to expand; list only needs to modify the tail pointer |
| Insertion/Deletion in the middle | O(n) | O(1) (when position is known) | vector needs to move all elements after the insertion point; list only needs to modify adjacent pointers |
| Finding elements | O(n) | O(n) | Both require linear traversal (in unsorted cases) |
| Memory usage | lower (only stores data) | higher (additional storage for pointers) | list requires extra pointer space for each element |
| Iterator invalidation | invalidated during expansion or middle operations | only the iterators of deleted elements are invalidated | vector’s memory reallocation causes all iterators to be invalidated; list’s structure remains stable |
3. Applicable Scenarios
Scenarios where vector is preferred
- Frequent random access to elements (access by index) is required
- Elements are mainly inserted and deleted at the tail
- The amount of data to be stored is small or can be estimated in advance
- High memory cache utilization is needed (contiguous memory access is faster)
- Algorithms require support for random access iterators (e.g., std::sort)
Scenarios where list is preferred
- Frequent insertions and deletions at any position (especially at the head and middle) are required
- Uncertain number of elements, and insertions and deletions are very frequent
- No need for random access to elements
- Avoiding the overhead of moving elements (e.g., large objects)
- Stable iterators are needed (insertions and deletions do not affect other iterators)
5. Other Factors
-
Memory Fragmentation:
- vector allocates large blocks of memory at once, resulting in less memory fragmentation
- list frequently allocates and releases small blocks of memory, which may lead to more memory fragmentation
Cost of Expansion:
- vector incurs performance overhead when expanding as it needs to copy all elements
- Can reduce the number of expansions by using reserve()
Iterator Usage:
- vector iterators can perform arithmetic operations (e.g., it + 5)
- list iterators can only increment and decrement (e.g., ++it, –it)
Compatibility with Algorithms:
- Many STL algorithms (e.g., sort, binary_search) require random access iterators and can only be used with vector
- list has its own member function sort(), which is more efficient than general algorithms
6. Summary and Recommendations
- vector is the default choice when unsure which container to use, vector is usually the safer choice as it performs better in most common scenarios
- list is suitable for special scenarios when frequent insertions and deletions in the middle are needed, and random access is rare, list has advantages
- Performance testing is important for critical path code, performance testing should be conducted based on specific usage scenarios before making a decision