stack是一種先進後出的資料結構,只有一個出口,類似於棧。stack容器哦允許新增元素,移除元素,取得棧頂元素,但是除了最頂端之後,沒有任何其他辦法可以存取stack的其他元素,換句話說,stack不允許有遍歷的行為。
元素推入棧的操作稱為:push 元素推出棧的操作稱為:pop
總結:
//建構函式
stack<T> stkT;//stack採用模板類實現,stack物件的預設構造形式
stack(const stack &stk);//拷貝建構函式
//賦值操作
stack&operator=(const stack &stk)//過載等號操作符
//資料存取操作
push(elem);//向棧頂新增元素
pop();//從棧頂移除一個元素
top();//返回棧頂元素
//容量大小操作
empty();//判斷堆疊是否為空
size();//返回堆疊的大小
queue是一種先進後出的資料結構(佇列),它有兩個出口,queue容器允許從一端新增元素,另一端移除元素
總結:
//建構函式
queue<T> queT;//queue採用模板類實現,queue物件的預設建構函式
queue(const queue &que);//拷貝建構函式
//存取、插入和刪除操作
push(elem);//往隊尾新增元素
pop();//從對頭移除第一個元素
back();//返回最後一個元素
front();//返回第一個元素
//賦值操作
queue&operator=(const queue &que);//過載等號操作符
//容量大小操作
empty();//判斷佇列是否為空
size();//返回佇列的大小
介面卡: 一種設計模式,該種模式是將一個類的介面轉換成客戶希望的另外一個介面。
可以看出的是,這兩個容器相比我們之間見過的容器多了一個模板引數,也就是容器類的模板引數,他們在STL中並沒有將其劃分在容器的行列,而是將其稱為容器介面卡,它們的底層是其他容器,對其他容器的介面進行了包裝,它們的預設是使用deque(雙端佇列)
vector容器時單向開口的連續記憶體空間,deque則是一種雙向開口的連續線性空間。雙開口的含義是:可以在頭尾兩端進行插入和刪除操作,且時間複雜度為O(1),與vector比較,頭插效率高,不需要搬移元素;與list比較,空間利用率比較高。
deque底層結構
它並不是一段連續的空間,而是由多個連續的小空間拼接而成,相當於一個動態的二維陣列。
Deque容器是連續的空間,至少邏輯上看來如此,連續現行空間總是令我們聯想到 array和vector,array無法成長,vector雖可成長,卻只能向尾端成長,而且其成長其實 是一個假象,事實上(1)申請更大空間(2)原資料複製新空間(3)釋放原空間三步驟,如果不是vector每次設定新的空間時都留有餘裕,其成長假象所帶來的代價是非常昂貴的。Deque是由一段一段的定量的連續空間構成。一旦有必要在前端或者尾端增加新的空間,便設定一段連續定量的空間,串接在deque的頭端或者尾端。Deque最大的工作就是維護這些分段連續的記憶體空間的整體性的假象並提供隨機存取的介面,避開了重新設定空間,複製,釋放的輪迴,代價就是複雜的迭代器架構。 既然deque是分段連續記憶體空間,那麼就必須有中央控制,維持整體連續的假象資料結構的設計及迭代器的前進後退操作頗為繁瑣。Deque程式碼的實現遠比vector或list都多得多。
Deque採取一塊所謂的map作為主控,這裡所謂的map是一小塊連續的記憶體空間,其中每一個元素(此時成為一個結點)都是一個指標,指向另一段連續的記憶體空間,稱作緩衝區,緩衝區才是deque的儲存空間的主體。
deque的迭代器:
deque的優點:
deque的缺點:
deque可以作為stack和queue底層預設容器的原因:
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>>
class priority_queue
priority_queue 範例預設有一個 vector 容器。函數物件型別 less
總結幾點:
push(const T& obj);//將obj的副本放到容器的適當位置,這通常會包含一個排序操作。
push(T&& obj);//將obj放到容器的適當位置,這通常會包含一個排序操作。
emplace(T constructor a rgs...);//通過呼叫傳入引數的建構函式,在序列的適當位置構造一個T物件。為了維持優先順序,通常需要一個排序操作。
top();//返回優先順序佇列中第一個元素的參照。
pop();//移除第一個元素。
size();//返回佇列中元素的個數。
empty();//如果佇列為空的話,返回true。
swap(priority_queue<T>& other);//和引數的元素進行交換,所包含物件的型別必須相同。
void test_priority_queue()
{
priority_queue<int, vector<int>> pq;
pq.push(5);
pq.push(7);
pq.push(4);
pq.push(2);
pq.push(6);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
其中模板中有三個引數,最後一個引數是仿函數,也就是指明優先順序佇列是按照升序還是降序來存資料的
template<class T, class Container = vector<T>, class Compare = less<T>>// 預設是小於
class priority_queue
{
public:
private:
Container _con;
Compare _com;
};
仿函數(functor),就是使一個類的使用看上去像一個函數。其實現就是類中實現一個operator(),這個類就有了類似函數的行為,就是一個仿函數類了。
// 仿函數 就是一個類過載了一個(),operator(),可以像函數一樣使用
template<class T>
struct greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
template<class T>
struct less
{
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
可以看出,仿函數就是用一個類封裝一個成員函數operator(),使得這個類的物件可以像函數一樣去呼叫。
範例演示:
template<class T>
struct IsEqual
{
bool operator()(const T& a, const T& b)
{
return a == b;
}
};
void test()
{
IsEqual<int> ie;
cout << ie(2, 3) << endl;// 該類範例化出的物件可以具有函數行為
}
向上調整: 從最後一個數往上調整
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent])//< 建小堆 > 建大堆
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下調整: 從第一個往下調整
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < (int)size())
{
if (child + 1 < (int)size() && _con[child + 1] > _con[child])
{
++child;
}
if (_con[child] > _con[parent])// 建小堆
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
這兩個函數用仿函數實現後如下:
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_com(_con[parent], _con[child]))// _con[child] > _con[parent]
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < (int)size())
{
if (child + 1 < (int)size() && _com(_con[child], _con[child + 1]))// _con[child + 1] > _con[child]
{
++child;
}
if (_com(_con[parent], _con[child]))// _con[child] > _con[parent]
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
push 先在隊尾插入資料,然後用向上調整演演算法使得堆是大堆或小堆
void push(const T& x)
{
_con.push_back(x);
AdjustUp((int)size() - 1);
}
pop 先將堆頂的元素和隊尾的元素交換,再刪去隊尾元素(而不是直接刪去堆頂元素,這樣會破壞堆的結構,然後又要建堆),然後再使用向下調整演演算法使得堆是大堆或小堆
void pop()
{
assert(!empty());
swap(_con[0], _con[(int)size() - 1]);
_con.pop_back();
AdjustDown(0);
}
//top 返回堆頂元素
T& top()
{
assert(!empty());
return _con[0];
}
//size 返回優先順序佇列元素個數
size_t size()
{
return _con.size();
}
//empty 判斷優先順序佇列是否為空
bool empty()
{
return size() == 0;
}