std::deque(雙端隊列)是 STL 中最複雜的序列容器實現。本文基於 SGI STL 設計原理,深度解析 分段連續存儲、中控器、隨機訪問迭代器、擴容/縮容策略、插入刪除算法,並提供完整可編譯的教學級實現代碼與 Google Test 單元測試。
| 容器 | 存儲模型 | 擴容代價 | 頭部操作 | 隨機訪問 |
|---|---|---|---|---|
| vector | 單塊連續內存 | 高(分配新→拷貝→釋放舊) | O(n) | O(1) |
| list | 雙向鏈表 | 低(節點級分配) | O(1) | O(n) |
| deque | 分段連續(緩沖區+中控器) | 低(僅分配新緩沖區/調整中控器) | O(1) 攤還 | O(1) |
deque 以迭代器複雜度換取頭尾 O(1) 操作與無需大塊遷移的擴容,適合頻繁頭尾操作、大對象存儲、需隨機訪問的場景。
BUFFER_SIZE = 512 / sizeof(T))T** map,每個元素(節點)指向一個緩沖區MAP_SIZE = 8 節點,擴容時按 2 倍 + 2 策略重新分配[前備用節點] [有效節點...] [後備用節點]必須維護四個指針以支持跨緩沖區隨機訪問:
template
class DequeIterator {
public:
using iterator_category = random_access_iterator_tag;
using value_type = T;
using pointer = T*;
using reference = T&;
using difference_type = ptrdiff_t;
using size_type = size_t;
using map_pointer = T**;
using self = DequeIterator;
T* cur; // 當前元素指針
T* first; // 所在緩沖區起始
T* last; // 所在緩沖區末尾(含哨兵)
map_pointer node; // 指向中控器中指向本緩沖區的指針
static size_t buffer_size() { return BUFFER_SIZE; }
// 構造/拷貝...
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + buffer_size();
}
// 核心:跨緩沖區隨機移動
self& operator+=(difference_type n) {
difference_type offset = n + (cur - first);
if (offset >= 0 && offset < difference_type(buffer_size())) {
cur += n; // 同緩沖區內
} else {
// 計算目標節點偏移
difference_type node_offset = offset > 0
? offset / difference_type(buffer_size())
: -difference_type((-offset - 1) / buffer_size()) - 1;
set_node(node + node_offset); // 切換緩沖區
cur = first + (offset - node_offset * difference_type(buffer_size()));
}
return *this;
}
// 其他運算符:++, --, +, -, -, [], ==, !=, < 等均基於 operator+= 實現
};

void create_map_and_nodes(size_type n) {
size_type num_nodes = n / BUFFER_SIZE + 1;
map_size = max(initial_map_size(), num_nodes + 2); // 頭尾各預留 1 節點
map = map_allocator.allocate(map_size);
map_pointer nstart = map + (map_size - num_nodes) / 2; // 置中
map_pointer nfinish = nstart + num_nodes - 1;
for (map_pointer cur = nstart; cur <= nfinish; ++cur)
*cur = allocate_node();
start.set_node(nstart); start.cur = start.first;
finish.set_node(nfinish); finish.cur = finish.first + n % BUFFER_SIZE;
}
reallocate_mapvoid reallocate_map(size_type nodes_to_add, bool add_at_front) {
size_type old_nodes = finish.node - start.node + 1;
size_type new_nodes = old_nodes + nodes_to_add;
map_pointer new_nstart;
if (map_size > 2 * new_nodes) { // 當前 map 足夠大,只需移動節點指針
new_nstart = map + (map_size - new_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
if (new_nstart < start.node)
copy(start.node, finish.node + 1, new_nstart);
else
copy_backward(start.node, finish.node + 1, new_nstart + old_nodes);
} else { // 需分配更大 map
size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;
map_pointer new_map = map_allocator.allocate(new_map_size);
new_nstart = new_map + (new_map_size - new_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
copy(start.node, finish.node + 1, new_nstart);
map_allocator.deallocate(map, map_size);
map = new_map; map_size = new_map_size;
}
start.set_node(new_nstart);
finish.set_node(new_nstart + old_nodes - 1);
}
push_back / push_back_auxvoid push_back(const value_type& val) {
if (finish.cur != finish.last - 1) { // 當前緩沖區尚有空間(預留 1 位作哨兵)
data_allocator.construct(finish.cur, val);
++finish.cur;
} else {
push_back_aux(val);
}
}
void push_back_aux(const value_type& val) {
value_type val_copy = val;
reserve_map_at_back(); // 確保中控器尾部有備用節點
*(finish.node + 1) = allocate_node(); // 分配新緩沖區
data_allocator.construct(finish.cur, val_copy);
finish.set_node(finish.node + 1);
finish.cur = finish.first;
}
pop_back / pop_back_auxvoid pop_back() {
if (finish.cur != finish.first) {
--finish.cur;
data_allocator.destroy(finish.cur);
} else {
pop_back_aux();
}
}
void pop_back_aux() {
deallocate_node(finish.first); // 釋放空緩沖區
finish.set_node(finish.node - 1);
finish.cur = finish.last - 1;
data_allocator.destroy(finish.cur);
}
insert / erase:移動較少端原則iterator insert_aux(iterator pos, const value_type& val) {
difference_type index = pos - start;
value_type val_copy = val;
if (index < size() / 2) { // 前半段少,頭部元素前移
push_front(front());
iterator f1 = start; ++f1;
iterator f2 = f1; ++f2;
pos = start + index;
iterator p1 = pos; ++p1;
copy(f2, p1, f1); // 向後移動 [f2, p1) 到 [f1, ...)
} else { // 後半段少,尾部元素後移
push_back(back());
iterator b1 = finish; --b1;
iterator b2 = b1; --b2;
pos = start + index;
copy_backward(pos, b2, b1); // 向前移動 [pos, b2) 到 [..., b1)
}
*pos = val_copy;
return pos;
}
iterator erase(iterator pos) {
iterator next = pos; ++next;
difference_type index = pos - start;
if (index < (size() >> 1)) { // 前段少
copy_backward(start, pos, next);
pop_front();
} else { // 後段少
copy(next, finish, pos);
pop_back();
}
return start + index;
}
clear 與內存釋放策略finish = start文件 Deque.h 包含:
DequeIterator:隨機訪問迭代器(含 const_iterator 別名)deque:容器主類(構造/析構、迭代器、容量、元素訪問、修改操作)allocate_node、deallocate_node、create_map_and_nodes、reallocate_map、reserve_map_at_front/backdestroy、copy、copy_backward(模擬 std::uninitialized_copy 等)#include
#include
#include "Deque.h"
using namespace std;
deque std_deq; Deque::deque my_deq;
TEST(TestOperation, Test_push_back) {
int n; cin >> n;
for (int i = 0; i < n; ++i) { std_deq.push_back(i); my_deq.push_back(i); }
EXPECT_EQ(std_deq, my_deq);
}
TEST(TestOperation, Test_push_front) { /* 類似 */ }
TEST(TestOperation, Test_insert) { /* 測試 begin/end/middle 插入 */ }
TEST(TestOperation, Test_erase) { /* 測試單點/區間刪除 */ }
TEST(TestOperation, Test_clear) { /* 測試清空後狀態 */ }
TEST(Test_all, test_all) {
// 綜合測試:迭代器算術、下標、比較、正負偏移、解引用
int m, n; cin >> m >> n;
for (int i = 0; i < m; ++i) { std_deq.push_front(i); my_deq.push_front(i); }
EXPECT_EQ(std_deq, my_deq);
// 迭代器隨機訪問驗證
auto it1 = std_deq.begin() + 5; auto it2 = my_deq.begin() + 5;
EXPECT_EQ(*it1, *it2);
EXPECT_EQ(*(it1 + 25), *(it2 + 25));
EXPECT_EQ(*(it1 - 2), *(it2 - 2));
EXPECT_EQ(it1[8], it2[8]);
EXPECT_EQ(it1[-3], it2[-3]);
}
測試結果:所有用例通過,Valgrind 無內存泄漏/非法訪問。
| 操作 | 複雜度 | 備註 |
|---|---|---|
隨機訪問 []/at | O(1) | 兩次指釖解引用(中控器→緩沖區→元素) |
push_back/push_front | O(1) 攤還 | 極少數觸發中控器擴容/新緩沖區分配 |
pop_back/pop_front | O(1) | 釋放空緩沖區時才釋放內存 |
insert/erase (中間) | O(min(k, n-k)) | 移動較少端元素,k 為距離頭部位置 |
sort | O(n log n) | 迭代器非連續,**建議拷貝到 vector 排序後再拷回** |
deque 通過分段連續存儲 + 中控器索引實現了頭尾 O(1) 操作與隨機訪問的平衡。核心難點在於:
理解 deque 實現有助於:正確選擇容器、診斷迭代器失效問題、優化高性能場景內存佈局、面試核心 STL 源碼考點。