STL之容器

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。

猜你喜欢

转载自blog.csdn.net/weixin_43374723/article/details/83958842
今日推荐