天天看點

STL之容器STL容器順序容器關聯容器容器擴充卡總結

STL容器

  • STL容器
  • 順序容器
    • vector(向量)
    • list(清單)
    • deque(雙端隊列)
  • 關聯容器
    • set(集合)
    • map(映射)
  • 容器擴充卡
    • stack(棧)
    • queue(隊列)
    • priority_queue(優先隊列)
  • 總結

STL容器

在現實生活中,容器是用來裝其他東西的器具。在C++中,容器就是能容納其他對象的對象。STL中提供了豐富的容器,并且它們都是泛型容器,可以容納不同的資料類型。

STL中定義的容器可分為三類:

類别 容器
順序容器 vector(向量)、list(清單)、deque(隊列)
關聯容器 map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)
容器擴充卡 stack(棧)、queue(隊列)、priority_queue(優先隊列)

順序容器

順序容器中各元素之間有順序關系,順序容器中每個元素均有固定的位置,位置的先後關系由添加進容器的先後順序決定。STL中有三個順序容器,它們分别是vector、list和deque。

vector(向量)

vectors是一種線性順序結構,與數組很類似,但不用預先指定其大小。當資料所需的存儲空間大于目前配置設定的空間時,vector會自動申請一塊更大的記憶體,然後将原來的資料拷貝到新的記憶體中并銷毀原記憶體中的資料,最後将原來的記憶體空間釋放掉。是以,當資料量變化較大時,vector的性能就不是很好。

vector的特點如下:

1.不用預先指定大小,可動态配置設定記憶體

2.由于是線性結構,可對元素進行随機通路

3.插入和删除的效率非常低

4.當動态添加的資料超過預設配置設定的大小時,進行動态記憶體配置設定,資料拷貝等操作非常消耗性能。

建立

vector的操作定義于<vector>頭檔案中。vector提供了很多的構造函數重載,是以可以很靈活的定義并初始化一個vector。此外,vector還是一個模闆容器,是以适用于不同的資料類型。下面是一個建立vector的簡單例子。

#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
	vector<int> vec;
	vector<int> vec1 = vec;
	vector<int> vec2(vec);
	vector<int> vec3(vec.begin(), vec.end());
	vector<int> vec4(10);
	vector<int> vec5(10, 1);
	vector<char> vec6(10, 'X');
	vector<string> vec7(10, "Hello World");
	cout << vec.size() << endl;
	cout << vec1.size() << endl;
	cout << vec5.size() << endl;
	return 0;
}

           
0
0
10

           

增删插入

上面說到,由于vector在記憶體中的存儲特性,是以vector在插入與删除性能很差。是以盡量需要頻繁的在資料中間進行插入和删除操作時,不宜使用vector容器。vector提供了一個在末尾增删元素的方法,當資料長度在目前配置設定資料大小以内時,效果還是不錯的。

#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
void print_vec(const vector<int> &vec);
int main() {
	vector<int> vec;
	for (int i = 0; i < 10; ++i) 
		vec.push_back(i);//添加10個元素{0, 1, ..., 9}
	print_vec(vec);
	vec.pop_back();//删除最後一個元素
	print_vec(vec);
	vec.pop_back();//删除最後一個元素
	print_vec(vec);
	vec.insert(vec.begin(), 2, 0);//效率很低
	print_vec(vec);
	vec.erase(vec.begin(), vec.end());
	//vec.clear();
	print_vec(vec);
	cout <<  "sizeof vec: " << vec.size() << endl;
	return 0;
}
           
vec: 0 1 2 3 4 5 6 7 8 9
vec: 0 1 2 3 4 5 6 7 8
vec: 0 1 2 3 4 5 6 7
vec: 0 0 0 1 2 3 4 5 6 7
vec:
sizeof vec: 0

           

周遊

通路vector中元素的方法一般有三種:

1.由于vector的随機通路特性,是以它可以和數組一樣使用下标通路。

2.使用疊代器通路,疊代器也是STL一個非常重要的内容,這裡先給出周遊方法,後面會詳細讨論。

3.使用range_based for(後面簡稱rangefor),這是C++11新增的,類似于python中的 for in等,不需要指出開始和結束的條件。

void print_vec(const vector<int> &vec) {
	int length = vec.size();
	for (int i=0; i<length; i++)
		cout << vec[i] << " ";
	cout << endl;
}
           
void print_vec(const vector<int> &vec) {
	for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); it++)
		cout << *it << " ";
	cout << endl;
}
           
void print_vec(const vector<int> &vec) {
	for(auto& i:vec)
		cout << i << " ";
	cout << endl;
}
           

相關函數

函數 作用
size() 傳回容器中元素個數
max_size() 傳回容器的最大容量
resize() 改變容器的容量
at() 通路某個下标的元素
capacity() 傳回目前配置設定的空間大小(元素個數)
empty() 判斷容器是否為空
reserve() 改變capacity大小
shrink_to_fit() 減少capacoty,使之與size大小相同
front() 傳回首元素
back() 傳回尾元素
data() 傳回指向容器中數組的指針
assign 給容器指派(完全替換掉以前的内容)
push_back() 在容器末尾增加一個元素
pop_back() 在容器末尾移出一個元素
insert() 在某個位置插入元素
erase() 删除某個位置的元素
swap() 交化兩個容器的内容
clear() 清除容器内容
emplace() insert的優化版本
emplace_back() push_back的優化版本
emplace_back() 在最後插入一個元素
get_allocator() 給容器配置設定空間
(c)(r)begin() 傳回指向第一個元素的(常)(反)疊代器
(c)(r)end() 傳回指向最後一個元素後面的(常)(反)疊代器

list(清單)

list是一種線性連結清單結構,學過資料結構的都知道,連結清單由若幹個結點組成,每個節點包含資料域和指針域。而list就相當于一個雙向連結清單,它的每個結點包含一個指向前驅的指針和一個指向後驅的指針。list在記憶體中不是連續的,是以無法像vector那樣随機存取。但由于list的鍊式結構,其插入和删除的效率很高。

vector的特點如下:

1.不用預先指定大小,可動态配置設定記憶體

2.不能随機通路,查找效率低

3.插入和删除的效率高

4.由于存在指針域,是以比vector占用更多空間

建立

list的操作定義于<list>頭檔案中。list的定義和初始化與vector非常相似,

#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
	list<int> list0;
	list<int> list1 = list0;
	list<int> list2(list0);
	list<int> list3(list0.begin(), list0.end());
	list<int> list4(10);
	list<int> list5(10, 1);
	list<char> list6(10, 'X');
	list<string> list7(10, "Hello World");
	cout << list0.size() << endl;
	cout << list2.size() << endl;
	cout << list6.size() << endl;
	return 0;
}
           
0
0
10

           

增删插入

list的增删插入操作和vector的也十分類似,不過list中一些操作的效率要高很多。

#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
	list<int> list1;
	for (int i = 0; i < 10; ++i)
		list1.push_back(i);//添加10個元素{0, 1, ..., 9}
	print_list(list1);
	list1.pop_back();//删除最後一個元素
	print_list(list1);
	list1.pop_back();//删除最後一個元素
	print_list(list1);
	list1.insert(list1.begin(), 2, 0);
	print_list(list1);
	list1.erase(list1.begin(), list1.end());
	//vec.clear();
	print_list(list1);
	cout << "sizeof vec: " << list1.size() << endl;
	return 0;
}

           

周遊

list容器無法用下标進行周遊,是以隻能用疊代器和rangefor對list進行周遊:

void print_list(const list<int> &contain) {
	for (list<int>::const_iterator it = contain.begin(); it != contain.end(); it++)
		cout << *it << " ";
	cout << endl;
}
           
void print_list(const list<int> &contain) {
	for(auto& it:contain)
		cout << *it << "";
}
           

相關函數

list容器有關的函數大多數都和vector容器的類似,不過其中也有一些不同,少了關于capacity相關的函數,另外還多了幾個特殊的函數。

函數 作用
size() 傳回容器中元素個數
max_size() 傳回容器的最大容量
resize() 改變容器的容量
at() 通路某個下标的元素
capacity() 傳回目前配置設定的空間大小(元素個數)
empty() 判斷容器是否為空
reserve() 改變capacity大小
shrink_to_fit() 減少capacoty,使之與size大小相同
front() 傳回首元素
back() 傳回尾元素
data() 傳回指向容器中數組的指針
assign 給容器指派(完全替換掉以前的内容)
push_back() 在容器末尾增加一個元素
pop_back() 在容器末尾移出一個元素
insert() 在某個位置插入元素
erase() 删除某個位置的元素
swap() 交化兩個容器的内容
clear() 清除容器内容
emplace() insert的優化版本
emplace_back() push_back的優化版本
emplace_back() 在最後插入一個元素
get_allocator() 給容器配置設定空間
(c)(r)begin() 傳回指向第一個元素的(常)(反)疊代器
(c)(r)end() 傳回指向最後一個元素後面的(常)(反)疊代器
push_front() 容器最前面插入一個元素
pop_front() 容器最前面移除一個元素
merge() 合并兩個容器
splice() 合并兩個容器
reverse() 将容器中的元素進行反轉
sort() 排序
unique() 删除重複元素
remove() 移除某些特定元素
remove_if() 移除某些滿足條件的元素

deque(雙端隊列)

從上面的讨論可以看出,vector的查找效率高,插入和删除的效率低;而list的插入和删除效率低,但查找效率卻很低。而deque則是一種結合了vector和list各自有點的容器,它有着較高的查找效率和插入删除效率。當然,deque在查找上肯定不如vector, 插入删除上不如list,但它兼顧了兩者的優點,在很多情況下是很好的選擇。

是以,deque的特點如下:

1.支援随機通路,但性能不如vector

2.支援在内部進行插入和删除,但性能不如list

3.它也支援雙端push和pop

建立

deque的定義和初始化與二者類似:

#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
	deque<int> deque0;
	deque<int> deque1 = deque0;
	deque<int> deque2(deque0);
	deque<int> deque3(deque0.begin(), deque0.end());
	deque<int> deque4(10);
	deque<int> deque5(10, 1);
	deque<char> deque6(10, 'X');
	deque<string> deque7(10, "Hello World");
	cout << deque0.size() << endl;
	cout << deque2.size() << endl;
	cout << deque6.size() << endl;
	return 0;
}
           

增删插入

list的插入與删除與list差不太多,不過在效率上稍低,它同樣支援在容器頭進行push和pop。

#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
	deque<int> deque1;
	for (int i = 0; i < 10; ++i)
		deque1.push_front(i);//從deque頭添加10個元素{0, 1, ..., 9}
	print_deque(deque1);
	deque1.pop_back();//删除最後一個元素
	print_deque(deque1);
	deque1.pop_front();//删除第一個元素
	print_deque(deque1);
	deque1.insert(deque1.begin(), 2, 0);
	print_deque(deque1);
	deque1.erase(deque1.begin(), deque1.end());
	//vec.clear();
	print_deque(deque1);
	cout << "sizeof vec: " << deque1.size() << endl;
	return 0;
}
           

周遊

由于deque支援下标通路,是以deque也支援下标周遊、疊代器周遊以及rangefor三種方式,此處省略,可參考vector的周遊。

相關函數

deque大多數函數和vector一緻,不過删除了兩個與capacity有關的函數,增加了幾個操作首元素的函數,詳細變動如下:

函數 作用
size() 傳回容器中元素個數
max_size() 傳回容器的最大容量
resize() 改變容器的容量
at() 通路某個下标的元素
capacity() 傳回目前配置設定的空間大小(元素個數)
empty() 判斷容器是否為空
reserve() 改變capacity大小
shrink_to_fit() 減少capacoty,使之與size大小相同
front() 傳回首元素
back() 傳回尾元素
data() 傳回指向容器中數組的指針
assign 給容器指派(完全替換掉以前的内容)
push_back() 在容器末尾增加一個元素
pop_back() 在容器末尾移出一個元素
insert() 在某個位置插入元素
erase() 删除某個位置的元素
swap() 交化兩個容器的内容
clear() 清除容器内容
emplace() insert的優化版本
emplace_back() push_back的優化版本
get_allocator() 給容器配置設定空間
(c)(r)begin() 傳回指向第一個元素的(常)(反)疊代器
(c)(r)end() 傳回指向最後一個元素後面的(常)(反)疊代器
push_front() 容器最前面插入一個元素
emplace_front() push_front的優化版本
pop_front() 容器最前面移除一個元素

關聯容器

關聯容器内部是一種非線性的樹形結構,具體來說是采用的一種高效的平衡檢索二叉樹-紅黑樹。關聯容器分為兩類,集合(set)和映射(map)。

set(集合)

set是一種非線性結構,是以它不具有随機通路的特性。set中的元素是唯一的,是以一個set中不允許有重複的元素,并且set會根據元素的值進行自動排序,是以set中的元素是有序的。multiset和set非常相似,不過取消元素值唯一這個限制。

set的特點如下:

1.非線性結構,不能随機通路

2.内部元素是唯一的

3.内部元素自動排序

4.具有優秀的檢索以及插入删除特性

建立

#include <iostream>
#include <iterator>
#include <set>
using namespace std;
void print_set(const set<int> &s) {
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
		cout << *it << " " ;
	cout << endl;
}
int main() {
	set<int> set1;
	set<int> set2 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	set<int> set3 = set2;
	set<int> set4(set2);
	cout << "set1: "; print_set(set1);
	cout << "set2: "; print_set(set2);
	cout << "set3: "; print_set(set3);
	cout << "set4: "; print_set(set4);
	return 0;
}
           
set1:
set2: 1 2 3 4 5 6 9
set3: 1 2 3 4 5 6 9
set4: 1 2 3 4 5 6 9

           

從結果可以看出,set中元素是唯一的且預設按升序排序。這裡讀者不妨試試multiset的效果,結果應該如下:

set1:
set2: 1 2 2 2 2 3 3 4 4 5 6 9
set3: 1 2 2 2 2 3 3 4 4 5 6 9
set4: 1 2 2 2 2 3 3 4 4 5 6 9

           

增删插入

由于set的樹形結構,沒有樹尾的說法,是以未提供push、pop等方法進行插入删除。下面是利用insert\emplace和erase進行插入删除的例子:

int main() {
	set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	cout << "origin:     "; print_set(set1);
	set1.insert(10);
	cout << "inset(10):  "; print_set(set1);
	set1.emplace(8);
	cout << "emplace(8): "; print_set(set1);
	set1.erase(2);
	cout << "erase(2):   "; print_set(set1);
	return 0;
}
           
origin:     1 2 3 4 5 6 9
inset(10):  1 2 3 4 5 6 9 10
emplace(8): 1 2 3 4 5 6 8 9 10
erase(2):   1 3 4 5 6 8 9 10

           

注意這裡插入和删除可能不成功,因為set中的元素唯一,如果該元素已經存在,就會插入失敗。是以insert和emplace其實是有傳回值的,它的類型為:pair<set::iterator, bool>,代表傳回一個pair,分别是指向該元素的疊代器和插入成功與否的bool值,用.first和.second來擷取二者,如下例:

int main() {
	set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	cout << *set1.insert(4).first << endl;
	cout << set1.insert(5).second << endl;
	cout << set1.insert(8).second << endl;
	return 0;
}
           
4
0
1

           

運作結果中,4代表傳回的疊代器指向的是4,0代表插入5失敗,因為5已經存在于set中了,1代表插入8成功。

周遊

set隻能通過疊代器和rangefor進行周遊,疊代器方法在上面的例子中其實已經給出,下面給出rangefor方法的示例。

for(auto& it:set)
		cout << *it << " " ;
	cout << endl;
           

相關函數

函數 作用
(c/r)begin() 傳回指向第一個元素的(常/反)疊代器
(c/r)end() 傳回指向最後一個元素後面的(常/反)疊代器
clear() 清空容器
count() 對某個元素進行計數
empty() 判斷容器是否為空
emplace() insert的優化版本
emplace_hint() 依據提示插入(提示可以是eplace()傳回的疊代器)
erase() 删除元素
equal_range() 傳回pair<lower_bound, upper_bound>
find() 查找元素傳回指向該元素的疊代器
get_allocator() 擷取配置設定器
insert() 插入元素傳回pair<set<type>::iter, bool>
key_comp() 傳回比較準則
value_comp() 傳回對value的比較準則
size() 傳回容器中元素個數
max_size() 傳回可能的最大容量
swap() 交換兩個容器
lower_bound() 傳回lower_bound
upper_bound() 傳回upper_bound

map(映射)

map和set一樣是非線性樹形結構,其内部實作為紅黑樹,與set不同的是,map以pair<key/value>作為元素進行管理。map以key的排序準則将元素進行排序。此外,map的元素也是唯一的,當然,這個限制也可以靠multimap來取消。

map的特點如下:

1.非線性結構,不能随機通路

2.内部元素有序排列

3.key-value一一映射

建立

容器的建立方法其實大多相似,不過map由于是以key-value的方式存儲元素,是以在建立map時需要以<key/value>pair進行初始化。

#include <iostream>
#include <iterator>
#include <string>
#include <map>
using namespace std;
void print_map(const map<int, string> &map1);
int main() {
	map<int, string> map1 = {{1, "Hello"},{2, "World" }};
	map<int, string> map2 = map1;
	map<int, string> map3(map1);
	cout << "map1: "; print_map(map1);
	cout << "map2: "; print_map(map2);
	cout << "map3: "; print_map(map3);
	return 0;
}
           

增删插入

map提供了和set類似的插入與删除的方法,不同的是map的操作元素和set不同。

#include <iostream>
#include <iterator>
#include <string>
#include <map>
using namespace std;
int main() {
	map<int, string> map1 = { {1, "Hello"},{2, "World" } };
	map1.insert({ 2, "C++" });//用insert插入{ 2, "C++" }
	print_map(map1);
	map1.insert({ 3, "!" });//用insert插入{ 3, "!" }
	print_map(map1);
	map1.emplace(2, "C++");//用emplace插入{ 2, "C++" }
	print_map(map1);
	map1.emplace(3, "!");//用emplace插入{ 3, "!" }
	print_map(map1);	
	map1.erase(2);//移除{2, "World"}
	print_map(map1);
	return 0;
}
           
{1:Hello} {2:World}
{1:Hello} {2:World} {3:!}
{1:Hello} {2:World} {3:!}
{1:Hello} {2:World} {3:!}
{1:Hello} {3:!}

           

從結果可以看出,當用insert/emplace進行插入時,如果key不存在,則插入成功,否則插入失敗。是以map1中的{2:World}沒變為{2:C++},而{3:!}則成功插入。注意map中insert()和emplace()傳入參數的方式不同。

STL提供了另一個函數來解決這個問題:insert_or_assign() 從名字可以看出它的作用是在map中要插入的key已經存在時,直接将value的值指派為要插入的值。

int main() {
	map<int,string> map1 = {{ 1,"Hello"},{ 2,"World" }};
	print_map(map1);
	map1.insert_or_assign(2, "C++");
	print_map(map1);
	return 0;
}
           
{1:Hello} {2:World}
{1:Hello} {2:C++}

           

另外,由于map中key的唯一性,是以C++提供了下标的方式來索引元素,并且這種方式可以更改value的值。示例如下:

int main() {
	map<int, string> map1 = { {1, "Hello"},{2, "World" } };
	print_map(map1);
	map1.insert({ 2, "C++" });//用insert插入{ 2, "C++" }
	print_map(map1);
	map1[2] = "C++";
	print_map(map1);
	return 0;
}
           
{1:Hello} {2:World}
{1:Hello} {2:World}
{1:Hello} {2:C++}

           

這裡利用insert插入改變不了key=2對應的value=“World”,而利用下标的方式成功改變value=“C++”。值得提及的是key的類型不是int時也可以作為下标來索引元素,讀者可以嘗試驗證。

周遊

雖然map可以用下标來索引元素,但在不知道key的取值範圍的時候是無法用下标來周遊map的,是以常用的周遊map的方式是疊代器方式和rangefor的方式。

//for (map<int, string>::const_iterator it = map1.begin(); it != map1.end(); it++)
for (auto it = map1.begin(); it != map1.end(); it++)
		cout << "{" << it->first << ":" << it->second << "}" << " ";
	cout << endl; 
           
for(auto& it:map1)
		cout << "{" << it.first << ":"<< it.second << "}" << " " ;
	cout << endl;
           

相關函數

函數 作用
(c)(r)begin() 傳回指向第一個元素的(常)(反)疊代器
(c)(r)end() 傳回指向最後一個元素後面的(常)(反)疊代器
clear() 清空容器
count() 對某個元素進行計數
empty() 判斷容器是否為空
emplace() insert的優化版本
emplace_hint() 依據提示插入(提示可以是eplace()傳回的疊代器)
try_emplace()
erase() 删除元素
equal_range() 傳回pair<lower_bound, upper_bound>
find() 查找元素傳回指向該元素的疊代器
get_allocator() 擷取配置設定器
insert() 插入元素傳回pair<set<type>::iter, bool>
inset_or_assign() key已經存在的時候直接給value指派
key_comp() 傳回比較準則
value_comp() 傳回對value的比較準則
size() 傳回容器中元素個數
max_size() 傳回可能的最大容量
swap() 交換兩個容器
lower_bound() 傳回lower_bound
upper_bound() 傳回upper_bound
at() 下标通路,傳回value

容器擴充卡

容器擴充卡的英文全稱為container adapter,它們本身不是容器,通過改造标準的STL容器來适應特定的場合。其中stack(棧)和queue(隊列)是基于deque,而priority_queue(優先隊列)則是基于vector實作的。

stack(棧)

stack(棧)的特點是後進先出(LIFO),是以所有的順序容器都滿足它的操作要求,而stack預設是建立在deque容器上的。

建立

stack的初始化非常的簡單。

#include <iostream>
#include <iterator>
#include <stack>
using namespace std;
int main() {
	stack<int> stack1;
	stack<int> stack2(stack1);
	stack<int> stack3 = stack1;
	return 0;
}
           

相關函數

stack的函數也很簡單。

函數 作用
push() 壓棧
emplace() 壓棧
pop() 彈棧
top() 傳回棧頂元素
size() 棧中元素個數
empty() 判斷棧是否為空
swap() 交換兩個棧的内容
#include <iostream>
#include <iterator>
#include <stack>
using namespace std;
int main() {
	stack<int> stack1;
	stack<int> stack2(stack1);
	stack1.push(1);
	stack1.emplace(2);
	stack1.push(3);
	stack1.emplace(4);
	while (stack1.size())//或者while(!stack1.empty())
	{
		cout << stack1.top() << " ";//通路棧頂元素
		stack1.pop();//出棧
	}
	stack1.swap(stack2);
	return 0;
}
           

黑人問号臉

queue(隊列)

queue(隊列)的特點是先進先出(FIFO),需要操作首元素,順序容器中vector不能滿足它的操作要求,而queue預設是建立在deque容器上的。

建立

queue的初始化與stack類似。

#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
int main() {
	queue<int> queue1;
	queue<int> queue2(queue1);
	queue<int> queue3 = queue1;
	return 0;
}
           

相關函數

quque的函數和stack也很類似,不過把top()換成了front()和back()。

函數 作用
push() 入隊
emplace() 入隊
pop() 出隊
front() 傳回隊首元素
back() 傳回隊尾元素
size() 隊列中元素個數
empty() 判斷隊列是否為空
swap() 交換兩個隊列的内容
#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
int main() {
	queue<int> queue1;
	queue<int> queue2(queue1);
	queue1.push(1);
	queue1.emplace(2);
	queue1.push(3);
	queue1.emplace(4);
	while (queue1.size())//或者while(!stack1.empty())
	{
		cout << queue1.front() << " ";//通路隊首元素
		queue1.pop();//出隊
	}
	queue1.swap(queue2);
	return 0;
}
           

priority_queue(優先隊列)

priority_queue(優先隊列)中元素并不像queue那樣元素先進先出,而是具有最高優先級的元素最先出隊。其中元素的大小可按預設大小關系,也可以通過重載"<"來規定大小關系。優先隊列需要隊元素進行随機通路,是以list不能滿足要求,隻能通過vector和deque來實作,實際上優先隊列預設是靠vector來實作的。

建立

priority_queue的初始化比stack和queue要靈活一些,因為其涉及到元素的優先級,是以需要元素能夠提供優先級的概念。實際上,就是需要元素能夠進行大小比較。C++内置資料結構都有預設的大小比較方法,而自定義資料結構需要通過重載"<“來提供優先級。下面是具體的例子:

#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
struct Point{
	int x, y;
	Point(int a, int b) :x(a), y(b) {};//構造函數
};
bool operator<(const Point &A,const Point &B) {//重載操作符<
	return A.x < B.x;
}
ostream& operator<<(ostream &out, const Point &A) {//重載操作符<<
	out << "(" << A.x << "," << A.y << ")" << " ";
	return out;
}
template<typename T>
void pop_queue(T &q) {//資料出隊
	while (q.size())
	{
		cout << q.top() << " ";//通路隊首元素
		q.pop();//出隊
	}
	cout << endl;
}
int main() {
	Point p1(5, 1), p2(1, 4), p3(2, 2), p4(3, 2);

	priority_queue<int, vector<int>> queue1;//使用預設優先級less<int>
	priority_queue<int, vector<int>, greater<int>> queue2;//更改優先級為greater<int>
	priority_queue<int, deque<int>> queue3;//使用deque<int>
	priority_queue<Point> queue4;//使用自定義資料結構

	//資料入隊
	queue1.push(3); queue1.push(2); queue1.push(1); queue1.push(4);
	queue2.push(3); queue2.push(2); queue2.push(1); queue2.push(4);
	queue3.push(3); queue3.push(2); queue3.push(1); queue3.push(4);
	queue4.push(p1); queue4.push(p2); queue4.push(p3); queue4.push(p4);

	//資料出隊
	cout << "pop_queue1: "; pop_queue(queue1);
	cout << "pop_queue2: "; pop_queue(queue2);
	cout << "pop_queue3: "; pop_queue(queue3);
	cout << "pop_queue4: "; pop_queue(queue4);
	
	return 0;
}

           

上面的例子初始化了四個不同的優先隊列:

priority_queue<int, vector<int> > queue1;//使用預設優先級less

priority_queue<int, vector<int>, greater<int> > queue2;//更改優先級為greater

priority_queue<int, deque<int> > queue3;//使用deque

priority_queue<Point> queue4;//使用自定義資料結構

其中,第一個優先隊列采用vector作為容器,優先規則為預設的less(從大到小)。第二個隊列也采用vector作為容器,但配置優先規則為greater(從小到大)。第三個隊列采用deque作為容器,優先規則采用預設的less。最後一個隊列的元素為自定義資料結構Point,采用預設優先級less,但仍需要對”<"進行操作符重載。

從程式中對"<"進行重載的函數裡面可以看到,程式采用的x的值作為優先級的判據,是以x的值越大,優先級越高。下面是運作的結果:

pop_queue1: 4 3 2 1
pop_queue2: 1 2 3 4
pop_queue3: 4 3 2 1
pop_queue4: (5,1)  (3,2)  (2,2)  (1,4)

           

從結果可以看到,當優先級規則為less時,元素的值越大優先級越高,是以出隊列的順序時從大到小。反之,則按從小到大的順序出隊。從queue4的出隊順序可以看出,出隊的順序是按照x值的大小進行排列的。

相關操作

priority_quque的相關函數和stack是一樣的,它不像queue那樣提供通路隊首和隊尾的方法,隻提供了top()一個方法來讀取隊首的元素。具體的函數清單如下,函數的用法也不給出執行個體了。

函數 作用
push() 入隊
emplace() 入隊
pop() 出隊
top() 傳回隊首元素
size() 隊列中元素個數
empty() 判斷隊列是否為空
swap() 交換兩個隊列的内容

總結

至此,我們一共讨論了約5種容器:vector、list、deque、set(multiset)、map(multimap)。以及3種容器擴充卡:stack、queue、priority_queue。

容器按照在記憶體中的存儲形式分為線性和非線性兩類,線性的包括:vector、list、deque;非線性的包括set(multiset)和map(multimap)。

  • 從通路機制上來看,vector和deque支援随機通路,可通過數字下标來通路容器中的元素;map雖然不支援随機通路,卻可以通過key作為下表來通路容器中的元素;其他的,list和map不支援随機通路,是以隻能通過疊代器對容器中的元素進行通路。當然,所有的容器提供了對應的疊代器來進行元素通路。
  • 從資料結構上來看,vector是利用線性順序表來實作的,是以在記憶體中的存儲是連續的;list是通過線性連結清單實作,在記憶體中的間斷的;而deque是介于vector和list之間,在記憶體中是多個連續的片段組成;map和set和上述三種線性結構的容器不同,它是通過紅黑樹結構來實作的,紅黑樹是一種搞笑高效的的自平衡查找二叉樹,是以在記憶體中顯然是間斷的。從這裡我們也可以看出,為什麼隻有vector和deque支援随機通路,因為它們在記憶體中的存儲形式是連續的。
  • 從應用上來看,vector支援随機通路,但進行插入和删除的效率很低,是以多适用于隻在末尾進行添加和删除的場合。list不支援随機通路,但由于其鍊式結構在插入和删除上效率很高,是以适用于經常進行插入和删除的場合。deque既支援随機通路,也有較高的插入删除效率,是以在複雜的情況下,deque作為一種折中也是不錯的選擇,但大多數情況下,為了追求極緻的性能,我們會選擇vector或list兩種極端的資料結構。而set和map是兩個有着特殊功能的關聯容器,set是有序集合,其中的元素唯一且有序排列;map是有序映射,它的key/value特性使得我們可以通過鍵(key)來擷取對應的值(value)。

容器擴充卡實質上就是容器的一種接口轉換器,它通過一定的轉換機理,将STL中的上述5類容易轉換為有特定用途的資料結構提供給開發者更友善的使用。換句話說,容器擴充卡本身不是容器,它是對容器的一種改造,使之滿足我們的特殊要求。

容器擴充卡為我們提供了三種非常重要的資料結構:stack(棧)、quque(隊列)、優先隊列(priority_queue),它們都是以依托于現成的容器來實作。

  • 棧是一種先進後出的資料結構,是以隻需要在容器的一端進行增加和删除,由上面的分析可知,vector、deque、list三種線性容器都能滿足這個要求。由于vector在記憶體中線性存儲,相對于dque和list更加節省空間,是以大多數情況下将vector作為是實作棧的基本容器。
  • 隊列是一種先進先出的資料結構,需要在容器的兩端進行增加和删除,隻有list和deque滿足這個要求,但由于不需要在容器中間進行插入和删除,是以選擇deque在空間的利用上更加合理,是以隊列經常選擇deque來實作。
  • 優先隊列中元素出隊的順序是依據優先級來定的,是以需要一種排序算法來計算出各個元素的優先級。在C++中利用堆排序來對優先隊列中的元素進行排序。堆排序在這裡不展開闡述,但堆排序的實作的依據線性順序序列,許多基本容器能夠提供随機通路的功能,是以使用vector來實作是最好的。當然,也可以利用deque來實作優先隊列,但效率上肯定不如vector實作的優先隊列好。

綜上,我們預設情況下,利用vector來實作stack和priority_quque,而利用deque來實作queue。

繼續閱讀