<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>青山旧歌谣</title><link>https://www.fullcomb.top/</link><description>Jup的个人博客 - 技术与生活</description><generator>Hugo 0.156.0 &amp; FixIt v0.4.5</generator><language>zh-cn</language><lastBuildDate>Sun, 26 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.fullcomb.top/index.xml" rel="self" type="application/rss+xml"/><item><title>【算法】基础数据结构</title><link>https://www.fullcomb.top/posts/%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E7%AE%97%E6%B3%95/1.%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://www.fullcomb.top/posts/%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E7%AE%97%E6%B3%95/1.%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</guid><category domain="https://www.fullcomb.top/categories/%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E7%AE%97%E6%B3%95/">业务开发算法</category><description>&lt;p&gt;通过分析C++ STL容器，理解基础数据结构。&lt;/p&gt;
&lt;h2 class="heading-element" id="一动态数组-vector"&gt;&lt;span&gt;一、动态数组 vector&lt;/span&gt;
 &lt;a href="#%e4%b8%80%e5%8a%a8%e6%80%81%e6%95%b0%e7%bb%84-vector" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;数组：相同类型、连续内存、顺序存储。因此能基于下标，实现对数组元素O(1)的随机访问：数组的开始地址 + index * 元素大小。&lt;/p&gt;
&lt;p&gt;静态数组需要提前指定数组大小，如果实际使用超过了数组大小，需要手动实现申请一片更大内存，将原来数组的内容拷贝到新内存，并释放原本内存的操作。动态数组把这些细节封装起来，无需用户关注扩容的操作。&lt;/p&gt;
&lt;h3 class="heading-element" id="vector定义和扩容实现"&gt;&lt;span&gt;vector定义和扩容实现&lt;/span&gt;
 &lt;a href="#vector%e5%ae%9a%e4%b9%89%e5%92%8c%e6%89%a9%e5%ae%b9%e5%ae%9e%e7%8e%b0" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) &amp;gt;
class vector : protected _Vector_base&amp;lt;_Tp, _Alloc&amp;gt; 
{
 ...
protected:
 _Tp* _M_start; //表示目前使用空间的头
 _Tp* _M_finish; //表示目前使用空间的尾
 _Tp* _M_end_of_storage; //表示目前可用空间的尾 
 ...
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;存了3个指针，分别表示数组的开始、结束、可用空间的结束。据此可以计算出size和capacity。&lt;/p&gt;
&lt;p&gt;当capacity不足时，会触发扩容，测试扩容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
int main() {
 std::vector&amp;lt;int&amp;gt; v;
 for (int i = 0; i &amp;lt; 20; i&amp;#43;&amp;#43;) {
 std::cout &amp;lt;&amp;lt; &amp;#34;size: &amp;#34; &amp;lt;&amp;lt; v.size() &amp;lt;&amp;lt; &amp;#34; capacity &amp;#34; &amp;lt;&amp;lt; v.capacity() &amp;lt;&amp;lt; std::endl;
 v.push_back(i);
 }
 return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;size: 0 capacity 0
size: 1 capacity 1
size: 2 capacity 2
size: 3 capacity 4
size: 4 capacity 4
size: 5 capacity 8
size: 6 capacity 8
size: 7 capacity 8
size: 8 capacity 8
size: 9 capacity 16
size: 10 capacity 16
size: 11 capacity 16
size: 12 capacity 16
size: 13 capacity 16
size: 14 capacity 16
size: 15 capacity 16
size: 16 capacity 16
size: 17 capacity 32
size: 18 capacity 32
size: 19 capacity 32&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;观察到当size &amp;gt; capacity时会触发扩容。并且capacity按2的指数倍增长。&lt;/p&gt;
&lt;p&gt;vector扩容逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; void push_back(const _Tp&amp;amp; __x) {//在最尾端插入元素
 if (_M_finish != _M_end_of_storage) {//若有可用的内存空间
 construct(_M_finish, __x);//构造对象
 &amp;#43;&amp;#43;_M_finish;
 }
 else//若没有可用的内存空间,调用以下函数，把x插入到指定位置
 _M_insert_aux(end(), __x);
 }
 
template &amp;lt;class _Tp, class _Alloc&amp;gt;
 void 
 vector&amp;lt;_Tp, _Alloc&amp;gt;::_M_insert_aux(iterator __position, const _Tp&amp;amp; __x)
 {
 if (_M_finish != _M_end_of_storage) {
 construct(_M_finish, *(_M_finish - 1));
 &amp;#43;&amp;#43;_M_finish;
 _Tp __x_copy = __x;
 copy_backward(__position, _M_finish - 2, _M_finish - 1);
 *__position = __x_copy;
 }
 else {
 const size_type __old_size = size();
 const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
 iterator __new_start = _M_allocate(__len);
 iterator __new_finish = __new_start;
 __STL_TRY {
 __new_finish = uninitialized_copy(_M_start, __position, __new_start);
 construct(__new_finish, __x);
 &amp;#43;&amp;#43;__new_finish;
 __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
 }
 __STL_UNWIND((destroy(__new_start,__new_finish), 
 _M_deallocate(__new_start,__len)));
 destroy(begin(), end());
 _M_deallocate(_M_start, _M_end_of_storage - _M_start);
 _M_start = __new_start;
 _M_finish = __new_finish;
 _M_end_of_storage = __new_start &amp;#43; __len;
 }
 }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;扩容在&lt;code&gt;_M_insert_aux&lt;/code&gt;中实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确保当前size等于capacity&lt;/li&gt;
&lt;li&gt;申请当前size两倍的内存空间&lt;/li&gt;
&lt;li&gt;深拷贝&lt;/li&gt;
&lt;li&gt;释放原有内存空间&lt;/li&gt;
&lt;li&gt;更新vector中的指针&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 class="heading-element" id="思考为什么会设计成倍增"&gt;&lt;span&gt;思考：为什么会设计成倍增？&lt;/span&gt;
 &lt;a href="#%e6%80%9d%e8%80%83%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e8%ae%be%e8%ae%a1%e6%88%90%e5%80%8d%e5%a2%9e" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;若每次扩容固定增加 k 个空间，连续插入 n 个元素的总拷贝次数为：
&lt;code&gt;k + 2k + 3k + ... + n ≈ n²/(2k) = O(n²)&lt;/code&gt;，
均摊到每次 push_back 是 O(n)，性能随规模急剧恶化。&lt;/p&gt;
&lt;p&gt;容量按 1, 2, 4, 8, &amp;hellip;, n 增长，总拷贝次数：
&lt;code&gt;1 + 2 + 4 + ... + n ≈ 2n = O(n)&lt;/code&gt;，
均摊到每次 push_back 是 O(1)。这就是「push_back 均摊 O(1)」的来源。&lt;/p&gt;
&lt;p&gt;所以，vector扩容设计成倍增，是为了保证push_back的均摊复杂度为O(1)。不同的编译器扩容因子可能不同，gcc和clang默认是2，msvc默认是1.5。选2可以做到时间最优，但是为什么有的编译器选择1.5？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1.5的最差空间利用率更高&lt;/li&gt;
&lt;li&gt;考虑到内存复用的问题，假设扩容因子为2，已分配块大小依次是 1, 2, 4, 8, 16&amp;hellip;：1+2+4+&amp;hellip;+2^(k-1)=2^k-1&amp;lt;2^k，前面所有已释放内存加起来，永远不够装下下一次扩容的大小。如果使用对齐内存池，会导致数组扩容只能往堆的高地址不断延伸，对内存分配器不友好。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 class="heading-element" id="思考为什么pop不会触发缩容"&gt;&lt;span&gt;思考：为什么pop不会触发缩容？&lt;/span&gt;
 &lt;a href="#%e6%80%9d%e8%80%83%e4%b8%ba%e4%bb%80%e4%b9%88pop%e4%b8%8d%e4%bc%9a%e8%a7%a6%e5%8f%91%e7%bc%a9%e5%ae%b9" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;扩容是必要性问题，而缩容不是必须的。有可能用户就是需要保持原本的容量，所以是否缩容的选择应该交给使用者。这是STL的设计哲学：零开销抽象，把策略留给用户。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对象池 / 复用：vector 被反复清空再填充，保留 capacity 反而避免了反复申请释放，性能更好。&lt;/li&gt;
&lt;li&gt;容量稳定抖动：size 在某个区间波动，释放只会带来无用拷贝。&lt;/li&gt;
&lt;li&gt;明确知道不再增长：用户可以主动调用 &lt;code&gt;shrink_to_fit()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时C++标准层面的硬约束：&lt;code&gt;pop_back&lt;/code&gt;、&lt;code&gt;clear&lt;/code&gt;、&lt;code&gt;erase&lt;/code&gt; 在标准里都被视为不应失败的操作。如果执行缩容，需要重新申请内存，可能会分配失败，就不符合规范（影响整个RAII析构链）。&lt;/p&gt;
&lt;h2 class="heading-element" id="二双向链表-list"&gt;&lt;span&gt;二、双向链表 list&lt;/span&gt;
 &lt;a href="#%e4%ba%8c%e5%8f%8c%e5%90%91%e9%93%be%e8%a1%a8-list" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;链表：顺序结构，使用指针关联节点地址，无需连续存储，一般来说比数组的内存使用率更高（无需提前分配）。链表更适用于删除、插入、遍历操作频繁的场景，而不适用于随机访问索引频繁的场景，如内存池、LRU。&lt;/p&gt;
&lt;p&gt;链表主要包括单链表、双向链表、循环链表。STL中的list则是循环双向链表: 实现双向迭代器，支持&lt;code&gt;std::reverse&lt;/code&gt;、&lt;code&gt;list::reverse()&lt;/code&gt;、&lt;code&gt;list::sort()&lt;/code&gt;等算法的实现。&lt;/p&gt;
&lt;h3 class="heading-element" id="list定义和实现"&gt;&lt;span&gt;list定义和实现&lt;/span&gt;
 &lt;a href="#list%e5%ae%9a%e4%b9%89%e5%92%8c%e5%ae%9e%e7%8e%b0" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;链表节点的定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class T&amp;gt;
struct __list_node {
 __list_node&amp;lt;T&amp;gt;* next; // 前驱节点指针
 __list_node&amp;lt;T&amp;gt;* prev; // 后继节点指针
 T data; //存储数据
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;list定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
class list{
 protected:
 typedef __list_node&amp;lt;T&amp;gt; list_node; // 显示定义list_node类型
 typedef allocator&amp;lt;list_node&amp;gt; nodeAllocator; // 定义allocator类型
 public:
 typedef T value_type;
 typedef T&amp;amp; reference;
 typedef value_type* pointer;
 typedef list_node* link_type;
 typedef const value_type* const_pointer;
 typedef size_t size_type;
 public:
 typedef __list_iterator&amp;lt;value_type&amp;gt; iterator; // 迭代器类型重写
 private:
 link_type node; // dummy节点
 // ......
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每个list都有一个虚拟节点，用于标记整个循环链表的首位连接处：&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src='https://www.fullcomb.top/posts/%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E7%AE%97%E6%B3%95/1.%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/image.png' alt="alt text" height="1106" width="1526"&gt;&lt;/p&gt;
&lt;p&gt;dummy节点的引入是为了统一end语义：end指向的都是无效值。因此遍历list写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (std::list&amp;lt;int&amp;gt;::iterator it=mylist.begin(); it != mylist.end(); &amp;#43;&amp;#43;it) {
 std::cout &amp;lt;&amp;lt; &amp;#39; &amp;#39; &amp;lt;&amp;lt; *it;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;insert和erase实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在position之前插入节点
iterator insert(iterator position, const T&amp;amp; x) {
 lik_type tmp = create_node(x); // 创建一个临时节点
 tmp-&amp;gt;next = position.node; // 将该节点的后继指针指向当前位置的节点
 tmp-&amp;gt;prev = position.node-&amp;gt;prev; // 将该节点的前驱指针指向当前位置的前驱节点
 (link_type(position.node-&amp;gt;prev))-&amp;gt;next = tmp; // 将前驱节点本来指向当前节点的后继指针改为指向该临时节点
 position.node-&amp;gt;prev = tmp; // 同样，当前位置的前驱指针也要修改为指向该临时节点
 return tmp;
}
// 删除position的节点
iterator erase(iterator position) {
 link_type next_node = link_type(position.node-&amp;gt;next);
 link_type prev_node = link_type(position.node-&amp;gt;prev);
 prev_node-&amp;gt;next = next_node;
 next_node-&amp;gt;prev = prev_node;
 destroy_node(position.node);
 return iterator(next_node);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;据此可以实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;push_back(x)&lt;/code&gt;: &lt;code&gt;insert(end(), x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push_front(x)&lt;/code&gt;: &lt;code&gt;insert(begin(), x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop_back()&lt;/code&gt;: &lt;code&gt;erase(--end())&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop_front()&lt;/code&gt;: &lt;code&gt;erase(begin())&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 class="heading-element" id="注意事项"&gt;&lt;span&gt;注意事项&lt;/span&gt;
 &lt;a href="#%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;遍历list的缓存命中率极差，实际遍历速度比vector慢一个数量级。&lt;/li&gt;
&lt;li&gt;list每个节点额外存了两个指针，16字节，不一定比vector利用率更高。&lt;/li&gt;
&lt;li&gt;list比vector的优势在于可以快速进行头插入和头删除，其他场景都建议使用vector。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 class="heading-element" id="三双端队列-deque"&gt;&lt;span&gt;三、双端队列 deque&lt;/span&gt;
 &lt;a href="#%e4%b8%89%e5%8f%8c%e7%ab%af%e9%98%9f%e5%88%97-deque" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;队列的所有操作都发生在队列的两端。双端队列（double ended queue）在队列两端都可以进行插入和删除操作，更加灵活。&lt;/p&gt;
&lt;h3 class="heading-element" id="deque定义和实现"&gt;&lt;span&gt;deque定义和实现&lt;/span&gt;
 &lt;a href="#deque%e5%ae%9a%e4%b9%89%e5%92%8c%e5%ae%9e%e7%8e%b0" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;deque使用了分级的思想，使用map管理多段连续内存（&lt;strong&gt;分段连续空间&lt;/strong&gt;）：&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src='https://www.fullcomb.top/posts/%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E7%AE%97%E6%B3%95/1.%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/image-1.png' alt="alt text" height="1316" width="1518"&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Tp, class _Alloc&amp;gt;
class _Deque_base {
 ...
protected:
 _Tp** _M_map;
 size_t _M_map_size; 
 iterator _M_start;
 iterator _M_finish;
 ...
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;deque的迭代器屏蔽了底层分块连续的细节，对外可以认为deque的内存空间是连续的。可以直接队deque的元素进行随机访问，时间复杂度是O(1)。&lt;/p&gt;
&lt;p&gt;以push_back为例，看push操作的实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果当前node下面还有空间，直接使用下面的空间即可。&lt;/li&gt;
&lt;li&gt;如果当前node已经占满了，查看map。&lt;/li&gt;
&lt;li&gt;如果map下面还有没有使用的node，使用这个node。&lt;/li&gt;
&lt;li&gt;如果map下面没有剩余node，查看map使用量。&lt;/li&gt;
&lt;li&gt;如果map使用率没有过一半，把当前map上已用区间放到中间位置，然后执行3。&lt;/li&gt;
&lt;li&gt;如果map使用率超过一半，重新申请一个map，然后执行3。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;pop操作则是在一个node空闲后，回收这段内存。所以在临界点反复push和pop每次都会触发node内存的申请和释放（对象池复用vector比deque更香）。&lt;/p&gt;
&lt;h3 class="heading-element" id="思考为什么不使用vector或list"&gt;&lt;span&gt;思考：为什么不使用vector或list？&lt;/span&gt;
 &lt;a href="#%e6%80%9d%e8%80%83%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e4%bd%bf%e7%94%a8vector%e6%88%96list" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;vector在头部插入元素的时间复杂度是O(n)，不符合队列两端操作都要高效的需求。如果是大小确定的循环队列，用vector实现就比较合适。&lt;/li&gt;
&lt;li&gt;list不支持随机访问，并且list每push一个元素就需要去申请内存，相比当前的实现一次性可以申请一段连续内存，list方案申请内存的频率更高，性能更差。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;deque的随机访问也是O(1)，扩容也不需要深拷贝，实现看起来比vector更加全面。但是每次随机访问需要计算map位置和node中偏移，并且缓存命中率更低，实际性能不如vector。&amp;ldquo;只有在真的需要两端高效&amp;rdquo; 的场景才用 deque，否则 vector 永远是更快的选择。&lt;/p&gt;
&lt;h3 class="heading-element" id="思考node该开多大"&gt;&lt;span&gt;思考：node该开多大？&lt;/span&gt;
 &lt;a href="#%e6%80%9d%e8%80%83node%e8%af%a5%e5%bc%80%e5%a4%9a%e5%a4%a7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;STL的策略：每个node 512字节，或者每个node固定16个元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 每个 node 的元素数：若 sizeof(T) &amp;lt; 512，则为 512/sizeof(T)，否则为 1
inline size_t __deque_buf_size(size_t n, size_t sz) {
 return n != 0 ? n : (sz &amp;lt; 512 ? size_t(512 / sz) : size_t(1));
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;node太小：map大，缓存命中率低&lt;/li&gt;
&lt;li&gt;node太大：单node内存浪费严重（和vector的缺点类似）&lt;/li&gt;
&lt;li&gt;512字节接近一个cpu cache line的大小，是性能拐点。&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>毕棚沟之行</title><link>https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/</link><pubDate>Sat, 08 Nov 2025 00:00:00 +0000</pubDate><guid>https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/</guid><category domain="https://www.fullcomb.top/categories/hello-world/">Hello World</category><description>&lt;p&gt;刚好是秋冬交替，红叶的最后窗口，搭同事的顺风车一起去毕棚沟散散心。
因为是临时计划没有提前准备，比较匆忙。但是还好订到了一间民宿，在这个旺季价格也比较良心。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image.png' alt="alt text" height="994" width="1670"&gt;&lt;/p&gt;
&lt;p&gt;周五傍晚出发，抵达理县；第二天一早就步行到景区。&lt;/p&gt;
&lt;p&gt;先是坐大巴到了第一个点，这时候天灰蒙蒙的，云雾缭绕。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image-1.png' alt="alt text" height="1076" width="1614"&gt;&lt;/p&gt;
&lt;p&gt;沿着湖畔走，一般是金黄的树林，一边是绿白相间的群山，甚是养眼。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image-2.png' alt="alt text" height="538" width="954"&gt;&lt;/p&gt;
&lt;p&gt;后面几个点都是坐电瓶车，一弯又一弯。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image-4.png' alt="alt text" height="432" width="766"&gt;&lt;/p&gt;
&lt;p&gt;到山顶差不多是正午的时候，那叫一个阳光明媚。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image-3.png' alt="alt text" height="1132" width="1670"&gt;&lt;/p&gt;
&lt;p&gt;在山顶拍拍照，晒晒太阳，就走“原始深林”的小道下山了。小道晒不到太阳，路上结了很多冰，非常滑，得非常小心。
&lt;img loading="lazy" src='https://www.fullcomb.top/posts/helloworld/%E6%AF%95%E6%A3%9A%E6%B2%9F/image-5.png' alt="alt text" height="1182" width="954"&gt;&lt;/p&gt;
&lt;p&gt;下山后有那么一点高反，和发烧差不多的感觉，吃完饭基本上就好了。&lt;/p&gt;</description></item></channel></rss>