1.vector
1.1概念
1)vector是线性容器,其元素保存在一块连续的内存空间,所以可以使用下标运算符对元素进行随机访问
2)vector是变长容器,允许元素的插入和删除
1.2空间配置策略
1)为了提高效率,vector实际配置的空间大小可能比用户需求的大一些(定义时大小是一样的),以便以后的扩充,这也是的给vector添加元素比list要快很多,因为vector已经分配好了空间,不需要再开辟,当然我指的只是在尾部添加
2)size()函数返回已存元素个数,capacity()函数返回的是总空间大小,capacity永远大于等于size
3)插入新元素时,如果超过当时的capacity,则capacity会扩充至两倍(vs是1.5倍,gcc是2倍)
4)所谓扩容,并不是在原空间后面接上新空间(因为无法保证原空间之后还有可用的空间),而是以原capacity的两倍配置另一块空间,然后将原内容拷贝过来,并释放原空间,使用新空间,即:重新配置、拷贝数据、释放原空间
5)max_size()是理论上可以vector容纳的元素的最大数量,vs下值为230
1.3相关操作
两个迭代器减介绍
iterator finish; //finsh:目前使用空间的尾 iterator end() { return finish; }
1)插入元素
push_back()函数
- 直接在尾部插入,时间复杂度O(1)
- 如果无备用空间,则会发生扩容
// 增加一個元素,做為最後元素 void push_back(const T& x) { if (finish != end_of_storage) // 還有備用空間 { construct(finish, x); // 直接在備用空間中建構元素。 ++finish; // 調整水位高度 } else // 已無備用空間 insert_aux(end(), x); } template <class T, class Alloc> void vector<T, Alloc>::insert_aux(iterator position, const T& x) { if (finish != end_of_storage) // 還有備用空間 { // 在備用空間起始處建構一個元素,並以vector 最後一個元素值為其初值。 construct(finish, *(finish - 1)); // 調整水位。 ++finish; // 以下做啥用? T x_copy = x; copy_backward(position, finish - 2, finish - 1); *position = x_copy; } else // 已無備用空間 { const size_type old_size = size(); const size_type len = old_size != 0 ? 2 * old_size : 1; // 以上配置原則:如果原大小為0,則配置 1(個元素大小); // 如果原大小不為0,則配置原大小的兩倍, // 前半段用來放置原資料,後半段準備用來放置新資料。 iterator new_start = data_allocator::allocate(len); // 實際配置 iterator new_finish = new_start; __STL_TRY { // 將原vector 的內容拷貝到新 vector。 new_finish = uninitialized_copy(start, position, new_start); // 為新元素設定初值x construct(new_finish, x); // 調整水位。 ++new_finish; // 將原vector 的備用空間中的內容也忠實拷貝過來(啥用途?) new_finish = uninitialized_copy(position, finish, new_finish); } // 解構並釋放原 vector destroy(begin(), end()); deallocate(); // 調整迭代器,指向新vector start = new_start; finish = new_finish; end_of_storage = new_start + len; } }
insert()函数
- 如果要插入的迭代器指向尾部,则直接插入,时间复杂度O(1)
- 如果要插入的迭代器不指向尾部,则需要移动元素(实质上是复制),时间复杂度O(n)
- 如果无备用空间,则会发生扩容
// 從 position 開始,安插 n 個元素,元素初值為 x template <class T, class Alloc> void vector<T, Alloc>::insert(iterator position, size_type n, const T& x) { if (n != 0) // 當 n != 0 才進行以下所有動作 { if (size_type(end_of_storage - finish) >= n) // 備用空間大於等於「新增元素個數」 { T x_copy = x; // 以下計算安插點之後的現有元素個數 const size_type elems_after = finish - position; iterator old_finish = finish; if (elems_after > n) // 「安插點之後的現有元素個數」大於「新增元素個數」 { uninitialized_copy(finish - n, finish, finish); finish += n; // 將vector 尾端標記後移 copy_backward(position, old_finish - n, old_finish); fill(position, position + n, x_copy); // 從安插點開始填入新值 } else // 「安插點之後的現有元素個數」小於等於「新增元素個數」 { uninitialized_fill_n(finish, n - elems_after, x_copy); finish += n - elems_after; uninitialized_copy(position, old_finish, finish); finish += elems_after; fill(position, old_finish, x_copy); } } else // 備用空間小於「新增元素個數」(那就必須配置額外的記憶體) { // 首先決定新長度:舊長度的兩倍,或舊長度+新增元素個數。 const size_type old_size = size(); const size_type len = old_size + max(old_size, n); // 以下配置新的vector 空間 iterator new_start = data_allocator::allocate(len); iterator new_finish = new_start; __STL_TRY// 以下首先將舊vector 的安插點之前的元素複製到新空間。 { new_finish = uninitialized_copy(start, position, new_start); // 以下再將新增元素(初值皆為 n)填入新空間。 new_finish = uninitialized_fill_n(new_finish, n, x); // 以下再將舊vector 的安插點之後的元素複製到新空間。 new_finish = uninitialized_copy(position, finish, new_finish); } // 以下清除並釋放舊的 vector destroy(start, finish); deallocate(); // 以下調整水位標記 start = new_start; finish = new_finish; end_of_storage = new_start + len; } } }
2)删除元素
pop_back()函数
- 直接在尾部删除,时间复杂度O(1),capacity不受影响
void pop_back() { --finish; destroy(finish); // 全域函式,建構/解構基本工具。 }
erase()函数
- 如果删除尾元素,则直接删除,时间复杂度O(1)
- 如果不是删除尾位置,则需要移动元素(实质上是复制),时间复杂度O(n)
- 返回迭代器:一个指向被删除元素之后的元素的迭代器
- capacity不受影响
//清除区间[first, last)中的所有元素 iterator erase(iterator first, iterator last) { iterator i = copy(last, finish, first);//将区间[last, finish]的元素复制至从first开始的位置 destroy(i, finish);//destroy掉多余的元素 finish = finish - (last - first); return first; } //清除某个位置上的元素 iterator erase(iterator position) { if (position + 1 != end()) copy(position + 1, finish, position);//将区间[position+1, finish]的元素复制至从position开始的位置 --finish; destroy(finish);//destroy掉尾元素 return position; }
clear()函数
- 清空所有元素,capacity不受影响
// 清空所有元素。注意,并未释放空间,以备可能未来还会新加入元素。 void clear() { erase(begin(), end()); }
3)resize()函数
1)resize()函数用来改变vector的size()
2)如果n大于当前的capacity(),则发生扩容
void resize(size_type new_size, const T& x) { if (new_size < size())//如果要缩小size erase(begin() + new_size, end()); else insert(end(), new_size - size(), x); }
4)reserve()函数
1)reserve是容器预留空间,但在空间内不真正创建元素对象,所以不会影响size()
2)如果要reserve的空间比当前capacity()小,则什么都不做
3)如果大概知道数据的数量,习惯上使用vector前会reserve()一下,免得它不断地申请新空间
void reserve(size_type n) { if (capacity() < n) //扩容 { const size_type old_size = size(); iterator tmp = allocate_and_copy(n, start, finish); destroy(start, finish); deallocate(); start = tmp; finish = tmp + old_size; end_of_storage = start + n; } //如果n小于等于capacity,则什么也不干 }
2.vector、内置数组、array的区别
在c++11中,STL中提拱了一个新的容器std::array,针对vector、array和内置数组,做个比较:
2.1相同点
1) 三者均可以使用下标运算符对元素进行随机访问,即vector和array都针对下标运算符[]进行了重载
2.)三者都存储在一块连续的内存空间
2.2不同点
1)vector属于变长容器,允许元素的插入和删除;但array和数组属于定长容器,定义容量后不能插入或删除元素
2)由于vector的空间配置策略,在插入和删除时,需要考虑迭代器是否失效的问题
3)可以将一个vector、array对象赋值给另一个vector、array对象,但是数组不行,数组名是一个常量指针,指向数组的第一个元素,常量指针不可以修改
4)vector和array提供了更丰富的数据访问机制,可以通过迭代访问,也可以使用front()和back()以及at访问方式;而数组只支持下标访问
5) vector和array提供了size()和empty(),而数组只能通过遍历来判空,通过额外的变量记录数组的size
6)array提供了给所有元素赋值(同一个值)的函数fill()
7)vector、array对象可以作为函数的返回值,数组不能被拷贝所以函数不能返回数组,不过可以返回数组的指针或引用
int (*fun(int i))[10]//返回数组指针 { int a[10] = {i}; int(*p)[10] = &a;//指向数组的指针 return p; }
3.list
3.1概念
1)list是一个双向链表,而且还是一个环形的双向链表
2)节点组成:每个节点由元素本身、后向指针、前向指针组成;节点被封装起来了,外部使用者并不能访问到,用迭代器取值时取的是节点的数据值,如下图
3)存储空间:list的元素不保证在内存中连续存储,list对空间的利用绝对精准,每插入或删除一个元素,就配置或释放一个元素的空间
4)为满足STL“前开后闭”区间的规范,list的尾端加上了一个空白节点作为end(),所以空list中其实是含有一个空白节点,它指向它自己
struct __list_node { typedef void* void_pointer; void_pointer next; void_pointer prev; T data; }; list() { empty_initialize(); } void empty_initialize() { node = get_node(); node->next = node; node->prev = node; }
3.2相关操作
1)插入元素
push_front()函数
- 在首部插入元素,时间复杂度O(1)
push_back()函数
- 在尾部插入元素,时间复杂度O(1)
insert()函数
- 在迭代器所指向的位置处插入元素,时间复杂度O(1)
2)删除元素
pop_front()函数
- 删除首元素,时间复杂度O(1)
void pop_front() { erase(begin()); }
pop_back()函数
- 删除尾元素,时间复杂度O(1)
void pop_back() { iterator tmp = end(); erase(--tmp); }
erase()函数
- 删除迭代器所指向的元素,时间复杂度O(1)
- 返回指向被删元素之后的元素的迭代器
iterator erase(iterator position) { link_type next_node = link_type(position.node->next); link_type prev_node = link_type(position.node->prev); prev_node->next = next_node; next_node->prev = prev_node; destroy_node(position.node); return iterator(next_node); } template <class T, class Alloc> list<T, Alloc>::iterator list<T, Alloc>::erase(iterator first, iterator last) { while (first != last) erase(first++); return last; }
remove()函数
- 删除与给定数值相等的所有元素,时间复杂度O(n)
void list<T, Alloc>::remove(const T& value) { iterator first = begin(); iterator last = end(); while (first != last) { iterator next = first; ++next; if (*first == value) erase(first); first = next; } }
clear()函数
删除所有元素,只留下空白节点
void list<T, Alloc>::clear() { link_type cur = (link_type)node->next; while (cur != node) { link_type tmp = cur; cur = (link_type)cur->next; destroy_node(tmp); } node->next = node; node->prev = node; }
4.deque
4.1概念
1)vector是单向开口的连续空间,deque是双向开口的连续空间,所谓双向开口,就是说能够高效地同时在首部和尾部插入和删除元素,vector在首部操作的效率很差
2)deque的连续空间其实是分段的连续空间,deque使用一个叫map(并不是STL容器map)作为中控器,来控制这些分段的连续空间,维持整体连续的假象
4.2中控器
1)deque的中控器叫做map,用来控制deque分段的连续空间
2)map是一块小的连续空间,其中的每个元素(称为节点)都是指针,各自指向一块连续的空间,这些连续的空间称为缓冲区们,deque的元素就存放在这些缓冲区中
3)单个缓冲区的总大小默认值为512字节,也允许用户自己指定缓冲区的大小
4)map需要节点数 =(元素个数 / 每个缓冲区可容纳的元素个数) + 1,如果刚好整除,会多配一个节点;节点的最大个数= max( 8, 需要的节点个数 + 2 )
5)一旦节点不足,就要重新配置一块更大的map,把原map的内容拷贝过来,再释放原map,使用新map
//返回deque的缓冲区的大小 static size_t buffer_size() { return __deque_buf_size(BufSiz, sizeof(T));//T是元素类型 } //如果n不为0,传回n,表示buffer size由用户自己定义 //如果n为0,表示buffer_size采用默认值,那么 // 如果sz(元素大小)小于512,传回512/sz // 如果sz不小于512,传回1 inline size_t __deque_buf_size(size_t n, size_t sz) { return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1)); } //建立map和节点,并配置缓冲区 template <class T, class Alloc, size_t BufSize> void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) { // 需要节点数=(元素个数/每个缓冲区可容纳的元素个数)+1 // 如果刚好整除,会多配一个节点。 size_type num_nodes = num_elements / buffer_size() + 1; // 一个map要管理几个节点。最少8个,最多是“所需节点数加2” // (前后各预留一个,扩充时可用)。 map_size = max(initial_map_size(), num_nodes + 2); map = map_allocator::allocate(map_size); // 以上配置出一个“具有map_size个节点”的map。 // 以上配置出一个“具有map_size个节点”的map。 // 保持在最中央,可使头尾两端的扩充能量一样大。每个节点可对应一个缓冲区。 map_pointer nstart = map + (map_size - num_nodes) / 2; map_pointer nfinish = nstart + num_nodes - 1; map_pointer cur; //配置缓冲区 __STL_TRY { // 为map内的每个现用节点配置缓冲区。所有缓冲区加起来就是deque的空间 // (最后一个缓冲区可能留有一些余裕)。 for (cur = nstart; cur <= nfinish; ++cur) *cur = allocate_node(); } // 为deque内的两个迭代器start和end设定正确的内容。 start.set_node(nstart); finish.set_node(nfinish); start.cur = start.first; // first, cur都是public finish.cur = finish.first + num_elements % buffer_size(); // 前面说过,如果刚好整除,会多配一个节点。 // 此时即令cur指向这多配的一个节点(所对映之缓冲区)的起头处。 } //重新配置一块map template <class T, class Alloc, size_t BufSize> void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add, bool add_at_front) { size_type old_num_nodes = finish.node - start.node + 1; size_type new_num_nodes = old_num_nodes + nodes_to_add; map_pointer new_nstart; if (map_size > 2 * new_num_nodes) { new_nstart = map + (map_size - new_num_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_num_nodes); } else { size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2; // 配置一块空间,准备给新map使用。 map_pointer new_map = map_allocator::allocate(new_map_size); new_nstart = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0); // 把原map内容拷贝过来。 copy(start.node, finish.node + 1, new_nstart); // 釋放原map map_allocator::deallocate(map, map_size); // 设定新map的起始位址与大小 map = new_map; map_size = new_map_size; } // 重新设定迭代器start和finish start.set_node(new_nstart); finish.set_node(new_nstart + old_num_nodes - 1); }
4.3迭代器
1)deque的迭代器包含4个指针:
- cur指针:指向当前缓冲区中的元素
- first指针:指向当前缓冲区的首部,用来方便判定当前缓冲区是否还有可用空间
- last指针:指向当前缓冲区的尾部,用来方便判定当前缓冲区是否还有可用空间
- node指针:指向map的节点
//注意deque的迭代器没有重载STL的iterator template <class T, class Ref, class Ptr, size_t BufSiz> struct __deque_iterator { typedef __deque_iterator<T, T&, T*, BufSiz> iterator; typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator; // 未继承STL的iterator,所以必须自行撰写5个必要的迭代器相应型别 typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T** map_pointer; typedef __deque_iterator self; T* cur; // 指向当前缓冲区中的元素 T* first; // 指向当前缓冲区的首部 T* last; // 指向当前缓冲区的尾部 map_pointer node; // 指向map中的节点 }
2)deque内部有两个名为start和finish的迭代器,分别指向deque的首部和尾部(尾部符合STL规范,是指向末尾元素的下一个位置)
iterator begin() { return start; } iterator end() { return finish; } const_iterator begin() const { return start; } const_iterator end() const { return finish; }
4.4相关操作
1)插入元素
push_back()函数
- 如果尾端缓冲区尚有>1的备用空间,则直接在尾端缓冲区上添加元素,再调整迭代器finish
- 如果没有,则调用push_back_aux():先配置一块新的缓冲区,再添加元素,最后调整迭代器finish
- 备用空间>1的原因是:要调节迭代器finish
- 时间复杂度为O(1)
void push_back(const value_type& t) { if (finish.cur != finish.last - 1) //尾端缓冲区尚有>1的备用空间 { construct(finish.cur, t); //直接在备用空间上添加元素 ++finish.cur; //调整迭代器finish } else //最后缓冲区已无(或只剩一个)元素备用空间。 push_back_aux(t); } // 只有当 finish.cur == finish.last – 1 时才会被呼叫。 // 也就是說只有当最後一个缓冲区只剩一个备用元素空间时才会被呼叫。 template <class T, class Alloc, size_t BufSize> void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) { value_type t_copy = t; reserve_map_at_back(); // 若符合某种条件则必须重换一个map *(finish.node + 1) = allocate_node(); // 配置一个新节点(缓冲区) __STL_TRY { construct(finish.cur, t_copy); // 針對標的元素設值 finish.set_node(finish.node + 1); // 改變finish,令其指向新節點 finish.cur = finish.first; // 設定 finish 的狀態 } __STL_UNWIND(deallocate_node(*(finish.node + 1))); }
push_front()函数
- 过程类似push_back()函数
- 备用空间>=1的原因:迭代器start的指向规则和finish有差异
- 时间复杂度为O(1)
void push_front(const value_type& t) { if (start.cur != start.first) //首端缓冲区尚有>=1的备用空间 { construct(start.cur - 1, t); //直接在备用空间上添加元素 --start.cur; //调整迭代器start } else //第一缓冲区已无备用空间 push_front_aux(t); }
insert()函数
- 当安插点是最前端时,则交给push_front()函数
- 当安插点是最后端时,则交给push_back()函数
- 如果都不是,则交给insert_aux()函数:选择安插点前后元素更少的那一侧进行元素移动(实质是拷贝),最后在安插点处进行赋值更新
- 安插点不在首尾时,时间复杂度为O(n)
// 在position 处安插一个元素,其值为x iterator insert(iterator position, const value_type& x) { if (position.cur == start.cur) // 如果安插点是deque 最前端 { push_front(x); // 交给push_front 去做 return start; } else if (position.cur == finish.cur) // 如果安插点是deque 最尾端 { push_back(x); // 交给push_back 去做 iterator tmp = finish; --tmp; return tmp; } else { return insert_aux(position, x); // 交给 insert_aux 去做 } } template <class T, class Alloc, size_t BufSize> typename deque<T, Alloc, BufSize>::iterator deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) { difference_type index = pos - start; // 安插点之前的元素个數 value_type x_copy = x; if (index < size() / 2) // 如果安插点之前的元素个数比较少 { push_front(front()); // 在最前端加入与第一元素同值的元素。 iterator front1 = start; // 以下标记记号,然后进行元素搬移... ++front1; iterator front2 = front1; ++front2; pos = start + index; iterator pos1 = pos; ++pos1; copy(front2, pos1, front1); // 元素搬移 } else // 安插点之后的元素个数比较少 { push_back(back()); // 在最尾端加入与最后元素同值的元素。 iterator back1 = finish; // 以下标示记号,然后进行元素搬移... --back1; iterator back2 = back1; --back2; pos = start + index; copy_backward(pos, back2, back1); // 元素搬移 } *pos = x_copy; // 在安插点上赋上新值 return pos; }
2)删除元素
pop_back()函数
- 当尾端缓冲区有至少有1个元素时,直接去除末尾元素,调整迭代器finish
- 当尾端缓冲区为空时,交给pop_back_aux()函数:释放尾端缓冲区,去除前一缓冲区的末尾元素,最后调整迭代器finish
- 时间复杂度为O(1)
void pop_back() { if (finish.cur != finish.first) // 尾端缓冲区有一个(或更多)元素 { --finish.cur; // 调整迭代器finish destroy(finish.cur); // 将末尾元素析构 } else // 当尾端缓冲区沒有任何元素 pop_back_aux(); // 这里將进行缓冲区的释放工作 } // 只有当finish.cur == finish.first时才会被呼叫。 template <class T, class Alloc, size_t BufSize> void deque<T, Alloc, BufSize>::pop_back_aux() { deallocate_node(finish.first); // 释放尾端缓冲区 finish.set_node(finish.node - 1); // 调整 finish 的状态,使指向上一个缓冲区的最後一个元素 finish.cur = finish.last - 1; destroy(finish.cur); // 将末尾元素析构 }
pop_front()函数
- 当首端缓冲区有至少有1个元素时,直接去除末尾元素,调整迭代器finish
- 当首端缓冲区为空时,交给pop_back_aux()函数:释放首端缓冲区,去除后一缓冲区的首部元素,最后调整迭代器start
- 时间复杂度为O(1)
void pop_front() { if (start.cur != start.last - 1) // 首端缓冲区有一个(或更多)元素 { destroy(start.cur); // 将首端元素析构 ++start.cur; // 调整迭代器start } else// 首端缓冲区仅有一个元素 pop_front_aux(); // 这里將进行缓冲区的释放工作 } // 只有当start.cur == start.last - 1时才会被呼叫。 template <class T, class Alloc, size_t BufSize> void deque<T, Alloc, BufSize>::pop_front_aux() { destroy(start.cur); // 將首端缓冲区的第一个元素析构。 deallocate_node(start.first); // 释放首端缓冲区。 start.set_node(start.node + 1); // 调整 start 的状态,使指向下一个缓冲区的第一个元素。 start.cur = start.first; }
erase()函数
- 选择清除点前后元素更少的那一侧进行元素移动(实质是拷贝),最后再调用pop_front()或pop_back()去除首部元素或末尾元素
- 清除点不在首尾时,时间复杂度为O(n)
// 清除 pos 所指的元素。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; }
5.vector、list和deque的区别
1)存储空间:vector是一块连续的内存空间,实际配置的空间大小比用户需求的更大一些;list的元素不保证在内存中连续存储,且对空间的利用绝对精准,每插入或删除一个元素,就配置或释放一个元素的空间;deque是分段的连续空间,使用map(并不是STL容器map)作为中控器,来控制这些分段的连续空间,维持整体连续的假象
2)元素:vector的元素就是元素本身;而list的元素是节点,每个节点由元素本身、后向指针、前向指针组成,由于要对额外指针的维护,所以开销更大;deque的元素也是元素本身
3)下标随机访问元素:vector支持;list不支持;deque也支持
4)插入、删除元素的效率:vector在尾部插入或删除元素,时间复杂度为O(1),在其他地方为O(n);list在任何地方均为O(1);deque在首部和尾部插入或删除元素,时间复杂度为O(1),在其他地方为O(n)
5)vector的push_back虽然在内存不足的时候会重新申请内存,但它申请内存是预先申请原来多一倍的空间,所以很多时候均是直接往内存里面写数据即可,效率非常高;list的push_back,虽然是增加一个节点,但每个新增节点的时候,都是new了一块新内存出来,再连到链表上去,new这个操作是非常吃内存的
6)迭代器:vector的迭代器属于随机迭代器,实质上为普通指针;list的迭代器属于双向迭代器;deque的迭代器属于随机访问迭代器,它很复杂,包含四个指针
7)迭代器失效:见迭代器
7.stack
7.1概念
1)stack是一种先进后出的结构,只有一个口,即栈顶,只允许在栈顶压入、弹出元素,不具有随机访问能力,只能取栈顶元素的值
2)stack底层以deque封闭其首端而实现
3)stack这种通过修改既有容器的接口来实现另一种结构的,称为配接器(adapter)
4)因为只有栈顶元素才有机会被外界取走,所以stack没有迭代器
7.2常用操作
// 以下完全利用 Sequence c 的操作,完成 stack 的操作。 bool empty() const { return c.empty(); } size_type size() const { return c.size(); } reference top() { return c.back(); } const_reference top() const { return c.back(); } // deque 是兩頭可進出,stack 是末端進,末端出(所以後進者先出)。 void push(const value_type& x) { c.push_back(x); } void pop() { c.pop_back(); }
7.3以list作为stack的底层容器
stack允许使用其他容器作为它的底层容器,如:和deque一样也是双向开口的list;同时,vector也可以的
stack<int, list<int>> sta; sta.push(5); sta.push(10); cout << sta.size() << endl; cout << sta.top() << endl; sta.pop(); cout << sta.size() << endl; cout << sta.top() << endl; 程序输出: 2 10 1 5
8.queue
8.1概念
1)queue是一种先进先出的结构,有两个口,即队首和队尾,只允许在队尾添加元素,在队首删除元素
2)queue不具有随机访问能力,只能取队首元素和队尾元素的值
3)queue底层以deque封闭其首端入口和尾端出口而实现
4)跟stack一样,queue也是配接器(adapter)
5)因为只有队首元素才有机会被外界取走,所以queue也没有迭代器
8.2常用操作
注意:queue有back()
// 以下完全利用 Sequence c 的操作,完成 queue 的操作。 bool empty() const { return c.empty(); } size_type size() const { return c.size(); } reference front() { return c.front(); } const_reference front() const { return c.front(); } reference back() { return c.back(); } const_reference back() const { return c.back(); } // deque 是兩頭可進出,queue 是末端進,前端出(所以先進者先出)。 void push(const value_type& x) { c.push_back(x); } void pop() { c.pop_front(); }
8.3以list作为queue的底层容器
stack允许使用其他容器作为它的底层容器,如:和deque一样也是双向开口的list;而使用vector作为底层容器时,queue不支持pop()操作,因为vector没有pop_front()
queue<int, list<int>> que; que.push(5); que.push(10); cout << que.size() << endl; cout << que.front() << endl; cout << que.back() << endl; que.pop(); cout << que.size() << endl; cout << que.front() << endl; 程序输出: 2 5 10 1 10
9.priority_queue
9.1概念
1)priority_queue是一个带有权值的队列,只允许在队尾添加元素,在队首删除元素,元素在容器内自动依照权值排列,默认情况下,权值最高者,排在队首
2)默认情况下,priority_queue以最大堆max-heap实现
3)priority_queue归类为容器配接器(container adapter)
4)因为heap没有迭代器,所以priority_queue也没有迭代器
5)priority_queue也可以用最小堆min-heap
priority_queue<int, vector<int>, greater<int>> queMin;
10.heap
10.1概念
1)堆(heap)是具有以下性质的完全二叉树:每个节点的值都大于等于其左右孩子节点的值,称为最大堆(max-heap);或者每个节点的值都小于等于其左右孩子节点的值,称为最小堆(min-heap)
2)heap并不属于STL容器,它是priority_queue的底层处理机制,选择heap的原因:
- 如果使用list,插入为O(1),取极值为O(n)
- 如果使用二叉搜索树,插入和取极值均为O(logn),复杂度低,但有些小题大做,一是二叉搜索树的性能取决于树的形状,要求输入具有足够的随机性,二是二叉搜索树并不容易实现
3)heap由vector(相对与数组有变长容器的优势)和一组heap算法实现
4)heap中所有元素都遵循完全二叉树的排列规则,所以不提供遍历功能,没有迭代器
10.2heap算法
push_heap()函数
- 首先把新元素(如图中50)插入到vector的末尾处(完全二叉树的下一个位置)
- 然后执行上溯程序:用新节点和其父节点比较,如果比父节点大,则对换位置,如此一直上溯,直到不需要对换或者遇上根节点为止
pop_heap()函数
- 将vector的首元素(如图中68)取出其实等同于把首元素和末尾元素交换位置(图中24),然后为这个新的首元素寻找一个合适的位置
- 执行下溯程序来寻找:新的首元素位于根节点,将它和两个儿子比较,和较大的儿子对换位置,直到它比它的两个儿子都大或者下放至叶子节点为止
- 注意:pop_heap()之后,实际上最大元素并未被删除,只是被置放到了vector的尾端
sort_heap()函数
- 每次pop_heap()将获得heap中最大的元素,持续对heap做pop_heap(),则可以得到一个递增序列,sort_heap()便是这个过程
make_heap()函数
- 将现有的数据转化成一个heap
11.slist
11.1概念
1)slist是单向链表,并不在STL标准内
2)slist的迭代器属于前向迭代器
3)slist中默认有一个头节点head
4)根据STL的习惯,插入或删除操作都是发生在指定位置之前,而slist没有方便的办法回到前一个位置,必须从头找起,所以内部insert和erase的实现更为复杂,出于效率考虑,早期slist也只提供push_front()不提供push_back()
5)slist节点和迭代器的设计,比list复杂,运用了继承关系