前言
容器適配器是一個封裝了序列容器的類模板,它在一般序列容器的基礎上提供了一些不同的功能,之所以稱之爲容器適配器,是因爲它可以通過適配容器的現有接口來提供不同的功能。
簡單的理解容器適配器,其就是將不適用的序列式容器(包括 vector、deque 和 list)變得適用。
STL中的三大容器適配器:stack、queue、priority_queue。
一:deque(雙端隊列)
在學習容器適配器之前我們首先了解一個叫做deque的序列容器。
deque(雙端隊列): 是一個雙端操作,任意位置O(1)插入刪除加上隨機訪問的序列容器,並且不需要增容,deque集合了vector和list的優點,但是deque隨機訪問的效率低(排序)。
- 雙端隊列的底層:
雙端隊列底層是一段假想的連續空間,實際是分段連續的 ,由一段段連續的小空間組成。
問題一: deque怎麼管理這一段段連續的小空間呢?
這裏deque通過中控映射的方式管理這一段段連續的小空間,中控映射其實就是一個指針數組,通過指針指向一段段連續的小空間,如果擴容,只需要考慮指針數組的擴容,這樣代價就會小很多。
問題二: deque如何實現隨機訪問呢?
需要計算訪問的數據在哪個小空間中,數據量較大的場景下耗時會比較長。
問題三: deque的迭代器結構?
first指向一段小空間的開始,last指向一段小空間的結束,cur指向迭代器當前位置,node指向中控指針數組中的結點用於切換小空間。
注意:雙端隊列並不符合先進先出的序列特點。
- 雙端隊列的應用:
雙端隊列被用於STL容器stack和queue的默認底層數據結構
1.stack和queue不需要遍歷,只需要在固定的一端或者兩端進行操作。
2.在stack中元素增長時,deque比vector的效率高(擴容時不需要搬移大量數據);queue中的元素增長
時,deque不僅效率高,而且內存使用率高。
二:stack(棧)
stack類的基本使用這裏就不多做講解,我們主要來研究一下stack類的模擬實現。
stack是一個封裝了deque< T >容器的適配器類模板,默認實現的是一個後入先出(Last-In-First-Out,LIFO)的壓入棧。stack< T > 模板定義在頭文件 stack 中。
#pragma once
#include<vector>
#include<list>
// 用STL容器封裝適配轉換實現出來的stack(複用性)
namespace WJL{
// Container:容器
template<class T, class Container>
class Stack{
public:
void push(const T& x){
_con.push_back(x);
}
void pop(){
_con.pop_back();
}
size_t size(){
return _con.size();
}
bool empty(){
return _con.empty();
}
T& top(){
return _con.back();
}
private:
Container _con;
};
void Test(){
Stack<int, vector<int>> st;
//Stack<int, list<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
while (!st.empty()){
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
}
輸出結果:5 4 3 2 1
三:queue(隊列)
queue類的基本使用這裏就不多做講解,我們主要來研究一下queue類的模擬實現。
queue是一個封裝了deque< T >容器的適配器類模板,默認實現的是一個先入先出(First-In-First-Out,LIFO)的隊列。queue< T > 模板定義在頭文件queue中。
注意:vector不提供支持頭部操作的接口,所以vector不能作爲queue的適配容器。
#pragma once
#include<list>
// 用STL容器封裝適配轉換實現出來的queue(複用性)
namespace WJL{
// Container:容器
template<class T, class Container>
class Queue{
public:
void push(const T& x){
_con.push_back(x);
}
void pop(){
// vector不提供pop_front()接口
_con.pop_front();
}
size_t size(){
return _con.size();
}
bool empty(){
return _con.empty();
}
T& front(){
return _con.front();
}
T& back(){
return _con_back();
}
private:
Container _con;
};
void Test(){
Queue<int, list<int>> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
q.push(5);
while (!q.empty()){
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
}
輸出結果:1 2 3 4 5
四:priority_queue(優先級隊列)
優先級隊列是一種容器適配器,它的第一個元素總是它包含元素中優先級最高的。
4.1 優先級隊列的使用
優先級隊列默認使用vector作爲其底層存儲數據的容器,在vector上又使用了堆算法將vector中元素構造成堆的結構,默認情況下priority_queue是大堆。
#include<iostream>
#include<queue>
#include<functional> // 仿函數頭文件
using namespace std;
void test_priority_queue(){
1. 默認大的優先級高,優先級隊列底層實際是一個大堆
//priority_queue<int> pq;
2. 應用仿函數使小的優先級高
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(3);
pq.push(1);
pq.push(9);
pq.push(4);
pq.push(2);
注:容器適配器不支持迭代器,容器適配器通常包含特殊的性質,迭代器會破壞容器適配器原有性質
while (!pq.empty()){
cout << pq.top() << " ";
pq.pop();
}
}
int main(){
test_priority_queue();
return 0;
}
4.2 優先級隊列的模擬實現
優先級隊列運用仿函數控制優先級大小(大小堆)。
#include<vector>
namespace WJL{
// 仿函數(函數對象)
template<class T>
// class:成員部分公有 部分私有
// struct:成員全部公有
struct less{
bool operator()(const T& x1, const T& x2){
return x1 < x2;
}
};
template<class T>
struct greater{
bool operator()(const T& x1, const T& x2){
return x1 > x2;
}
};
// 默認大堆
template<class T,class Container = vector<T>,class Compare = less<T>>
class priority_queue{
public:
// 向上調整算法
void AdjustUp(int child){
Compare com;
int parent = (child - 1) / 2;
while (child > 0){
// child > parent
if (com(_con[parent], _con[child])){
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else{
break;
}
}
}
void push(const T& x){
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
//向下調整算法
void AdjustDown(int root){
Compare com;
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < _con.size()){
// 選出左右孩子中較大的
if (child+1 < _con.size()&&com(_con[child],_con[child + 1])){
child++;
}
// child > parent
if (com(_con[parent], _con[child])){
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else{
break;
}
}
}
void pop(){
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
T& top(){
return _con[0];
}
size_t size(){
return _con.size();
}
bool empty(){
return _con.empty();
}
private:
Container _con;
};
void test_priority(){
//priority_queue<int> pq;
priority_queue<int,vector<int>,greater<int>> pq;
pq.push(3);
pq.push(1);
pq.push(9);
pq.push(4);
pq.push(2);
while (!pq.empty()){
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
}
運行結果:1 2 3 4 9
小結
STL中的容器適配器都是通過基礎序列容器適配轉換而來的,並不是原生實現的 , 提高了代碼的複用性。
需要注意的是,STL 中的容器適配器,其內部使用的基礎序列容器並不是固定的,用戶可以在滿足特定條件的多個基礎容器中自由選擇。並且,容器適配器不支持迭代器,因爲支持迭代器會導致適配器的性質改變。