7.3 deque 容器
deque 容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与 vector 不同,deque 支持高效插人和删除容器的头部元素,因此也叫做双端队列。deque 类常用的函数如下。
7.3.1 常用函数
(1)构造函数。
deque():创建一个空 deque。
deque(int nSize):创建一个 deque,元素个数为 nSize。
deque(int nSize,const T& t);创建一个 deque,元素个数为nSize,且值均为t。
deque (const deque &):复制构造函数。
(2)增加函数。
void push_front(const T& x):双端队列头部增加一个元素 x
void push_back(const T& x):双端队列尾部增加一个元素 x
iterator insert(iterator it,const T& x):双端队列中某一元素前增加一个元素 x
void insert(iterator it,int n,const T& x):双端队列中某一元素前增加n个相同元素 x
void insert(iterator it,const_iterator first,const_iterator last): 双端队列中某一元素前插人另一个相同类型向量的[first,last)间的数据。
(3)删除函数。
iterator erase(iterator it):删除双端队列中某一个元素。
iterator erase(iterator first,iterator last):删除双端队列中[first,last)中元素
void pop_front():删除双端队列中最前一个元素。
void pop_back():删除双端队列中最后一个元素。
void clear():删除双端队列中所有元素。
(4)遍历函数。
reference at(int pos);返回 pos 位置元素的引用
reference front():返回首元素的引用
reference back():返回尾元素的引用
iterator begin():返回向量头指针,指向第一个元素
iterator end():返回向量尾指针,不包括最后一个元家,在其下面
reverse_iterator rbegin():反向选代器,最后一个元素的迭代指针
reverse_iterator rend():反向选代器,第一个元素的迭代指针
(5)判断函数。
bool empty()const;向量是否为空,若 true,则向量中无元素
(6)大小函数。
int size()const; 返回向量中元的个数
int max_size()const;返回最大可允许的双端队列元数量值
(7)其他函数。
void swap(vector&);交换两个同类型向量的数据
void assign(int n,const T& x)设置容器大小为n个元案,每个元素值为 x。
void assign(const_iterator first, const_iterator last);容器中[first,last)中元素置成当前双端队列元素。
deque 容器与 vector 容器的许多功能是相似的
相比较特殊的是 push_front、pop_front 函数
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d; // 创建一个双端队列对象d,存储整数类型的元素
d.push_back(10); // 在队列的末尾插入元素10
d.push_back(20); // 在队列的末尾插入元素20
d.push_back(30); // 在队列的末尾插入元素30
cout << "原始双端队列:";
for (int i = 0; i < d.size(); i++) {
cout << d.at(i) << "\t"; // 打印队列中的元素
}
cout << endl;
d.push_front(5); // 在队列的头部插入元素5
d.push_front(3); // 在队列的头部插入元素3
d.push_front(1); // 在队列的头部插入元素1
cout << "push_front(5, 3, 1)后:";
for (int i = 0; i < d.size(); i++) {
cout << d.at(i) << "\t"; // 打印队列中的元素
}
cout << endl;
d.pop_front(); // 移除队列的头部元素
d.pop_front(); // 再次移除队列的头部元素
cout << "两次pop_front后:";
for (int i = 0; i < d.size(); i++) {
cout << d.at(i) << "\t"; // 打印队列中的元素
}
cout << endl;
return 0;
}
可见deque可以通过push_frout直接在容器头增加元素,用pop_frout直接删除容器头元素这一点是 vector 元素不具备的。
例:deque 与 vector 内存分配比较示例
#include<iostream>
#include<vector>
#include<deque>
using namespace std;
int main() {
vector<int> v(2); // 创建一个包含两个元素的整数向量对象v
v[0] = 10; // 将向量v的第一个元素设置为10
int* p = &v[0]; // 创建一个指向v的第一个元素的整型指针p
cout << "vector第1个元素迭代指针*p=" << *p << endl; // 打印指针p所指向的元素值
v.push_back(20); // 在向量v的末尾插入元素20
p = &v[0]; // 更新指针p,因为vector可能会重新分配内存
cout << "vector容量变化后原vector第1个元素迭代指针*p=" << *p << endl; // 打印指针p所指向的元素值
deque<int> d(2); // 创建一个包含两个元素的整数双端队列对象d
d[0] = 10; // 将双端队列d的第一个元素设置为10
int* q = &d[0]; // 创建一个指向d的第一个元素的整型指针q
cout << "deque 第1个元素迭代指针*q=" << *q << endl; // 打印指针q所指向的元素值
d.push_back(20); // 在双端队列d的末尾插入元素20
q = &d[0]; // 更新指针q,因为deque可能会重新分配内存
cout << "deque 容量变化后第1个元素迭代指针*q=" << *q << endl; // 打印指针q所指向的元素值
return 0;}
push_back(20)前如虚线左侧。push_back(20)操作后
对 vector 而言:由于当前有效总空间是2,当增加元素 20后,必须有 3 个空间,这样 vector 就重新分配空间,原先的两个元素复制到新空间中,原先空间释放,因此* p在 push_back 前后是变化的;建立 vector 容器时,一般来说伴随着建立空间一填充数据一重建更大空间一复制原空间数据一删除原空间一添加新数据,保证 vector 始终是一块独立的连续内存空间
对 deque 而言:当前有效总空间是 2,当增加元素 20 后,原有空间仍然保持,再建立一个新的内存块,把 20 放人其中,因此*p在 push_back 前后是不变化的。在建立 deque 容器时,一般伴随着建立空间一-填充数据一-建立新空间--填充新数据,如此反复,没有原空间数据的复制和删除过程,是由多个分段连续的内存空间组成的。
7.3.3综合实例操作
以deque 为基础,编一个先进先出队列应具有下列函数:
(1)队头插人函数 push;(2)队尾删除函数 pop;(3)队列大小函数 size;(4)队列空判定函数empty;(5)获得队头元素函数 front;(6)获得队尾元素函数 back
#include<iostream>
#include<deque>
using namespace std;
template <class T>
class MyQueue {
deque<T> d;
public:
// 添加队尾元素
void push(const T& t) {
d.push_back(t);
}
// 移除队头元素
void pop() {
d.pop_front();
}
// 获得队列大小
int size() {
return d.size();
}
// 队列是否为空
bool empty() {
return d.empty();
}
// 获得队头元素
T& front() {
return d.front();
}
// 获得队尾元素
T& back() {
return d.back();
}
void display() {//显示队列中的所有元素
for (int i = 0; i < d.size(); i++) {
cout << d.at(i) << "\t";
}
cout << endl;}
};
int main() {
MyQueue<int> myqueue;//创建了一个 MyQueue 类的对象 myqueue
for (int i = 1; i <= 5; i++) {//循环将整数 1 到 5 依次添加到队列中
myqueue.push(i);
}
cout << "原队列:";
myqueue.display();
cout << endl;
myqueue.pop();
cout << "删除队头元素后:";
myqueue.display();
cout << endl;
myqueue.push(6);
cout << "插入队尾元素 6后:";
myqueue.display();
cout << endl;
cout << "当前队头元素:";
cout << myqueue.front() << endl;
cout << "当前队尾元素:";
cout << myqueue.back() << endl;
return 0;
}
7.4 list 容器
相对于 vector 的连续线性空间,list 是一个双向链表。它有一个重要性质:插人操作和删除作都不会造成原有的 list 迭代器失效,每次插人或删除一个元素就配置或释放一个元素空间。也就是说,对于任何位置的元素插人或删除,list 永远是常数时间。
7.4.1常用函数
(1)构造函数
list<Elem>c:创建一个空的 list
list<Elem>cl(c2):复制另一个同类型元素的 list
list<Elem>c(n):创建 n 个元案的 list,每个元素值由默认构造函数确定
list<Elem>c(n,elem):创建n个元素的 list,每个元素值为 elem
list<Elem>c(begin,end):由迭代器创建 list,迭代区间为[begin,end)
(2)大小、判断空函数。
int size()const: 返回容器元素个数。
bool empty() const: 判断容器是否空,若返回 true,表明容器已空
(3)增加、删除函数。
void push_back(const T& x):list 容器尾元素后增加一个元素 x。
void push_front(const T& x):list 容器首元素前增加一个元素 x
void pop_back():删除容器尾元素,当且仅当容器不为空。
void pop_front():删除容器首元素,当且仅当容器不为空。
void remove(const T& x):删除容器中所有元素值等于 x 的元素。
void clear():删除容器中所有元素。
iterator insert(iterator it,const T& x=T()):在迭代器指针 it 前插元素 x,返回 x选代器指针。
void insert(iterator it,size_type n,const T& x);在迭代器指针it 前插人n个相同元素 x。
void insert(iterator it, const_iterator first, const_iterator last): 把[first, last)间的元素插入迭代指针 it 前。
iterator erase(iterator it):删除选代器指针it 对应的元素
iterator erase(iterator first,iterator last);删除迭代器指针[first,last)间的元素
(4)遍历函数。
iterator begin():返回首元素的迭代器指针。
iterator end()返回尾元素后的迭代器指针,而不是尾元素的选代器指针。
reverse_iterator rbegin();返回尾元素的逆向代器指针,用于逆向遍历容器
reverse_iterator rend():返回首元素前的逆向选代器指针,用于逆向遍历容器
reference front():返回首元素的引用。
refereice back():返回尾元素的引用。
(5)操作函数。
void sort():容器内所有元素排序,默认是升序
template <class Pred>void sort(Pred pr): 容器内所有元素根据预判定函数pr排序。
void swap(list& str):两 list 容器交换功能。
void unique():容器内相邻元若有重复的,则仅保留一个。
void splice(iterator it,list& x): 队列合并函数,队列 x所有元素插入迭代器指针it,x变成空队列。
void splice(iterator it, list&x, iterator first): 队列 x 中移走[first,end)间元素插入迭代指针 it 前。
void splice(iterator it, list& x, iterator first , iterator last): x 中移走[first,last)间元素插人迭代指针 it 前。
void reverse():反转容器中元素顺序。
例 基本实例(次重点)
#include <iostream>
#include <string>
#include <list>
using namespace std;
typedef list<string> LISTSTR; // 定义LISTSTR为list<string>类型的别名
int main()
{
LISTSTR test; // 创建一个LISTSTR类型的对象test
test.push_back("back"); // 在列表尾部插入元素"back"
test.push_front("middle"); // 在列表头部插入元素"middle"
test.push_front("front"); // 在列表头部插入元素"front"
cout << test.front() << endl; // 输出列表的第一个元素
cout << *test.begin() << endl; // 输出通过迭代器获取的第一个元素
cout << test.back() << endl;// 输出列表的最后一个元素
cout << *(test.rbegin()) << endl;// 输出通过反向迭代器获取的最后一个元素
test.pop_front();// 移除列表的第一个元素
test.pop_back();// 移除列表的最后一个元素
cout << test.front() << endl;// 输出删除元素后的列表的第一个元素
return 0;
}
注意:test.front()相当于 string& s=test,front(),返回了首元的引用test. begin()相当于 list<string>::iterator it = test, begin(),返回了首元素的迭代器指针。因此 testfront()与* test.begin()的结果是一致的
例:顺逆迭代器:
#include <iostream>
#include <list>
using namespace std;
typedef list<int> LISTINT; // 定义LISTINT为list<int>类型的别名
int main()
{
LISTINT test; // 创建一个LISTINT类型的对象test
for(int i=0; i<5; i++) // 循环5次,i从0到4
{
test.push_back(i+1); // 在列表尾部插入元素i+1
}
LISTINT::iterator it = test.begin(); // 创建一个迭代器it并指向列表的第一个元素
for(; it != test.end(); it++) // 遍历列表,直到迭代器指向列表的末尾
{
cout << *it << "\t"; // 输出当前迭代器指向的元素值
}
cout << endl;
LISTINT::reverse_iterator rit = test.rbegin(); // 创建一个反向迭代器rit并指向列表的最后一个元素
for(; rit != test.rend(); rit++) // 反向遍历列表,直到迭代器指向列表的起始位置
{
cout << *rit << "\t"; // 输出当前反向迭代器指向的元素值
}
cout << endl;
return 0; // 程序正常结束,返回0
}
注意:逆向显示并不改变元素在容器中的位置,只是显示逆向了。
对于sort、merge、splice 示例。
#include<iostream>
#include<list>
using namespace std;
typedef list<int> LISTINT;
int main() {
LISTINT tl; // 创建一个列表对象tl
tl.push_back(1); // 在列表尾部插入元素1
tl.push_back(5); // 在列表尾部插入元素5
tl.push_back(3); // 在列表尾部插入元素3
tl.push_back(10); // 在列表尾部插入元素10
LISTINT t2; // 创建一个列表对象t2
t2.push_back(2); // 在列表尾部插入元素2
t2.push_back(8); // 在列表尾部插入元素8
t2.push_back(6); // 在列表尾部插入元素6
t2.push_back(9); // 在列表尾部插入元素9
tl.sort(); // 对tl列表进行排序
t2.sort(); // 对t2列表进行排序
// tl.splice(t1.begin(), t2);
// 注释:将t2列表的所有元素移动到t1列表的指定位置
tl.merge(t2); // 将t2列表合并到tl列表中,合并后t2列表将为空
for (LISTINT::iterator it = tl.begin(); it != tl.end(); it++) {
// 迭代遍历tl列表
cout << *it << "\t"; // 输出当前迭代器指向的元素值
}
cout << endl;
cout << tl.size() << "\t" << t2.size() << endl; // 输出tl列表和t2列表的大小
return 0;
}
(1)两个链表 merge 合并前,一般都已经按升序排好序,合并后的链表元素仍然是升序序列。
(2) merge 操作是数据移动操作,不是复制操作,因此 t1.merge(t2)表示把 t2 中所有元素依次移动并插人到源链表 t1 的适当位置,t1 增加了多少个元素,t2 就减少了多少个元素。
(3) 若用 t1.splice(t1.begin(),t2)代替程序中的 t1. merge(t2),其余不变,就能看出splice 的特点。splice完成的是拼接功能,也是数据移动操作,不是复制操作。t1. splice(t1.begin(),t2)表明把 t2中所有元案整体地移动到原始链表t1的首元素前,t1 增加了多少个元素,t2就减少了多少个元素。
如题中所述t1、t2 排序后,t1={1,3,5,10},t2={2,6,8,9},
t1.splice(t1. begin(),t2)后, t1 = {2, 6, 8, 9, 1,3,5, 10}, t2 = { };
t1. merge ( t2) 后t1={1,2,3,5,6,8,9,10},t2={ }。
例:两个文本文件中包含某中学的高考成绩,包含准考证号、姓名、所考大学名、总成绩信息,准考证号是关键字。但可能由于一些原因,造成两个文件中有重复的记录,现要求把两个文件内容合并在一起,去掉重复记录,并按准考证号升序排列
#include<iostream>
#include<list>
#include<string>
using namespace std;
class Student {
private:
string m_strNO; // 学生学号
string m_strName; // 学生姓名
string m_strUniversity; // 学生所在大学
int m_nTotal; // 学生总分
public:
Student(string strNO, string strName, string strUniversity, int nTotal):
m_strNO(strNO), m_strName(strName),
m_strUniversity(strUniversity), m_nTotal(nTotal) {
}
bool operator <(Student& s) { // 运算符重载,用于比较学生对象的学号
return m_strNO < s.GetNO();
}
bool operator==(Student& s) { // 运算符重载,用于比较学生对象的学号
return m_strNO == s.GetNO();
}
string GetNO() { // 获取学生学号
return m_strNO;
}
string GetName() { // 获取学生姓名
return m_strName;
}
string GetUniversity() { // 获取学生所在大学
return m_strUniversity;
}
int GetTotal() { // 获取学生总分
return m_nTotal;
}
friend ostream& operator<< (ostream& os, Student& s) { // 友元函数,重载输出运算符,用于打印学生信息
os << s.GetNO() << "\t" << s.GetName() << "\t" << s.GetUniversity() << "\t" << s.GetTotal();
return os;
}
};
typedef list<Student> LISTSTUD; // 使用类型别名定义学生列表类型
class StudManage {
private:
LISTSTUD m_stlList; // 学生列表
public:
bool Add(const Student& s) { // 添加学生信息到学生管理对象
m_stlList.push_back(s);
return true;
}
bool Merge(StudManage stud) { // 合并学生管理对象
m_stlList.sort(); // 对当前学生管理对象的学生列表进行排序
stud.m_stlList.sort(); // 对传入的学生管理对象的学生列表进行排序
m_stlList.merge(stud.m_stlList); // 将传入的学生管理对象的学生列表合并到当前学生管理对象的学生列表中
m_stlList.unique(); // 去除重复的学生记录
return true;
}
void Show() { // 显示学生信息
for (LISTSTUD::iterator it = m_stlList.begin(); it != m_stlList.end(); it++) {
cout << *it << endl; // 输出学生信息
}
}
};
int main() {
StudManage sm1; // 创建学生管理对象
StudManage sm2;
Student s1("1001", "zhangsan", "tsinghua", 670); // 创建学生对象
Student s2("1002", "lisi", "beida", 660);
Student s3("1003", "wangwu", "fudan", 650);
Student s4("1004", "zhaoliu", "nankai", 640);
Student s5("1005", "zhouqi", "tongji", 630);
sm1.Add(s1); // 添加学生信息到学生管理对象
sm1.Add(s2);
sm1.Add(s5);
sm2.Add(s5);
sm2.Add(s4);
sm2.Add(s3);
sm2.Add(s1);
sm1.Merge(sm2); // 合并学生管理对象
sm1.Show(); // 显示学生信息
return 0;
}
(1)主要对 list 进行操作,把两个文本文件数据映射成两个 list 容器中的元素采取了仿真,直接写在了主程序 main 中:“第 1个文件”有 3个学生对象 s1,s2,s5,保存在容器sm1中;“第2个文件”有4 个学生对象 s5,s4,s3,s1,保存在容器 sm2 中,两个文件的重复对象是学生对象 s1。
(2)有一个基本类Student,一个集合维护类 StudManage,由于要利用 cout 输出学生对象重载的stream& operator<<(ostream& os,Student& s)函数。
(3)StudManage类 Merge 成员函数封装了两个 list 容器的排序,合并及去掉重复元素功能。
(4)由于对 list 容器均是按学号进行排序、合并的,因此要重载基本类 Student 中的operator<操作符。又由于要按学号是否相同去掉重复记录,因此必须重载基本类 Student中的 operator==操作符
7.5队列和堆栈
队列和栈是常用和重要的数据结构。
队列只允许在表的一端插人,在另一端删除,允许插人的一端叫做队尾,允许删除的一端叫做队头,是一种先进先出线性表。
栈只允许在表的一端进行插人和删除操作,是一种后进先出的线性表。
7.5.1 常用函数
(1)构造函数。
queue(class T,class Container=deque<T>):创建元素类型为T的空队列,默认容器是 deque。
stack(class T,class Container=deque<T>):创建元素类型为 T的空栈,默认容器是 deque。
(2)操作函数。
队列和堆栈共有函数:
bool empty():如果队列(堆)为空返回 true,否则返回 false.
int size():返回队列(堆栈)中元素数量。
void push(const T& t):把t元素压入队尾( 项)。
void pop():当队列(栈)非空情况下,删除队头(栈顶)元素。
队列独有函数:
T& front():当队列非空情况下,返回队头元素引用。
T& back():当队列非空情况下,返回队尾元素引用。
堆栈独有函数:
T& top():当栈非空情况下,返回栈顶元素的应用。
7.5.2 容器配接器
看一段标准模板库中 queue 类的代码。
template<class T,class Container=deque<T>>
class queue
{
protected:
Container c;
public:
void push(T &t) {c.push_back(t);}
...};
可以看出如下特点。
(1)成员变量是标准模板库基础容器类 Container 对应的变量 c,队列的各个元素存在于容器c中。从上述 push 函数中可以得出: 它是通过调用 Container 类中基础函数 push.back完成队列的插人操作的。
当默认容器是 deque,就调用 deque 类中的 pushback 函数;
当传人容器参数是 list,就调用 list 中的 deque 函数。
因此 queue 中各操作函数只是起一个配接作用,几乎没有自己独有的功能。queue 类是对基础容器类Container 的再封装,不是重新定义。表面上操作的是队列类 queue 的各个函数,其实操作的是转接后 Container 类中的函数,queue 只是起一个中介作用,这就是容器配接器的概念
(2)能成为 queue 基本容器类 Container 的条件是它应当支持 size,empty,push backpop_front,front,back 方法,可对数据的两端分别进行插入、删除操作,而 deque、list 都具有这些函数,所以它们可成为 queue 的基本容器类 Container; 能成为 stack 基本容器类Container 的条件是它应当支持 size,empty,push_back, pop_back,back 方法,可对数据的一端进行插人、删除操作,而 deque、list、vector 都具有这些函数所以它们可成为 stack 的基本容器类Container。
注意; vector 不能作为 queue 的基本容器类,因为 vector 没有 popfront方法
例: stack基本函数操作示例。
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<stack>
using namespace std;
void PrintStack(stack<int, vector<int> > & obj) { // 函数模板,打印整型栈中的元素
while (!obj.empty()) {
cout << obj.top() << "\t"; // 输出栈顶元素
obj.pop(); // 弹出栈顶元素
}
}
void PrintStack(stack<string, list<string> > & obj) { // 函数模板,打印字符串栈中的元素
while (!obj.empty()) {
cout << obj.top() << "\t"; // 输出栈顶元素
obj.pop(); // 弹出栈顶元素
}
}
template <class T, class Container>
void PrintStack(stack<T, Container>& obj) { // 函数模板,打印通用类型栈中的元素
while (!obj.empty()) {
cout << obj.top() << "\t"; // 输出栈顶元素
obj.pop(); // 弹出栈顶元素
}
}
int main() {
stack<int, vector<int> > s; // 使用vector作为容器的整型栈
for (int i = 0; i < 4; i++) {
s.push(i + 1); // 将元素压入栈中
}
PrintStack(s); // 打印整型栈中的元素
cout << endl;
string str = "a";
stack<string, list<string> > t; // 使用list作为容器的字符串栈
for (int i = 0; i < 4; i++) {
t.push(str); // 将元素压入栈中
str += "a";
}
PrintStack(t); // 打印字符串栈中的元素
cout << endl;
stack<float, deque<float> > u; // 使用deque作为容器的浮点型栈
for (int i = 0; i < 4; i++) {
u.push(i + 1); // 将元素压入栈中
}
PrintStack(u); // 打印浮点型栈中的元素
return 0;
}
stack是C++标准库中的容器适配器,它提供了一组基本函数用于对栈进行操作。以下是一些常见的stack基本函数的用法:
push(const T& value): 将元素压入栈顶。使用push函数可以向栈中添加新的元素。参数value表示要添加的元素的值。例如,`stack.push(10)`将整数值10推入栈顶。
pop(): 弹出栈顶元素。使用pop函数可以将栈顶的元素移除。它不返回任何值例如,stack.pop()将栈顶元素弹出。
top(): 返回栈顶元素的引用。使用top函数可以获取栈顶元素的值,但不会将其从栈中移除。可以使用引用来修改栈顶元素的值。例如int value = stack.top()将栈顶元素的值赋给变量value。
empty(): 检查栈是否为空。使用empty函数可以检查栈是否为空。如果栈为空,则返回`true,否则返回`false`。
size(): 返回栈中元素的个数。使用`size`函数可以获取栈中元素的个数,并返回一个整数值表示元素数量。
void PrintStack(stack<int, vector<int> > & obj)
在提供的代码中,PrintStack函数用于打印栈中的元素。它接受一个stack<int, vector<int>>类型的参数obj,表示整型栈对象的引用。函数内部通过循环遍历栈中的元素,并使用top函数获取栈顶元素的值,然后使用pop函数将栈顶元素弹出,直到栈为空为止。在循环过程中,每次打印栈顶元素的值。这样,调用PrintStack函数可以打印出整型栈中的所有元素。
注意,基本函数的用法在不同的容器适配器中是相同的,只是容器类型可能不同。
#include <iostream>
#include <string>
#include <list>
#include <deque>
#include <queue>
using namespace std;
template<class T, class Container>
void PrintQueue(queue<T, Container>& obj) //PrintQueue 函数是一个模板函数,用于打印队列中的元素
{
while (!obj.empty()) {
cout << obj.front() << "\t";
obj.pop();
}
}
int main() {
string str = "a"; // 声明并初始化一个字符串变量 str,初始值为 "a"
queue<string, deque<string> > t; // 声明一个队列 t,使用双端队列 deque 作为其容器类型,存储的元素类型为字符串
for (int i = 0; i < 4; i++) {
t.push(str); // 将字符串 str 压入队列 t
str += "a"; // 将字符串 str 进行追加,每次追加一个 "a",用于下一次循环的压入
}
PrintQueue(t); // 调用 PrintQueue 函数打印队列 t 的元素
cout << endl; // 输出一个换行符,表示换行
queue<float, list<float> > u; // 声明一个队列 u,使用链表 list 作为其容器类型,存储的元素类型为浮点数
for (int i = 0; i < 4; i++) {
u.push(i + 1); // 将浮点数 i+1 压入队列 u
}
PrintQueue(u); // 调用 PrintQueue 函数打印队列 u 的元素
return 0;
}
注意:
1.queue 容器不能以vector作为基本容器类
2.void PrintQueue(queue<T, Container>& obj)
PrintQueue:这是函数的名称。
queue<T, Container>& obj:这是函数的参数。
queue<T, Container>:它指定了参数 obj 的类型。它是一个使用两个类型 T 和 Container 进行模板化的队列对象。类型 T 表示队列中存储的元素类型,而 Container 表示用于实现队列的底层容器的类型。
&:它表示参数 obj 是通过引用传递的。通过引用传递,函数内对 obj 的任何修改都会影响到作为参数传递的原始对象。
这行代码声明了一个名为 PrintQueue 的函数,它以一个队列对象 obj 作为引用参数。该函数不返回任何值。
例:编一个固定大小的堆栈类:
#include <iostream>
#include <deque>
#include <stack>
using namespace std;
template<class T, class Container = deque<T> >
//有两个类型参数 T 和 Container,其中 Container 默认为 deque<T>。
class mystack : public stack<T, Container> {
private:
size_t m_nMaxSize; //保存栈的最大容量
public:
mystack(size_t maxsize) { //用于初始化最大容量
m_nMaxSize = maxsize;
}
void push(const T &t) {
if (stack<T, Container>::size() < m_nMaxSize) {
stack<T, Container>::push(t);//还有可用空间可以容纳新元素push 函数,将元素 t 压入栈中
} else {
cout << "stack is full. The term " << t << " is not pushed." << endl;
}
}
};
int main() {
//声明了一个 mystack 类型的对象 obj,使用整型作为元素类型,
//使用双端队列 deque 作为底层容器,初始最大容量为 2
mystack<int, deque<int> > obj(2);
obj.push(1);//整数 1 压入栈中,size=1
obj.push(2);//压入栈,size=2
obj.push(3);//栈满不可入栈了
return 0;
}
注意: stack<T, Container>::size() 来获取当前栈的大小,即栈中元素的数量。
7.6 优先队列
优先队列即 priority_queue类,带优先权的队列,优先权高的元素优先出队。与普通队列相比,共同点:都是对队头做删除操作,队尾做插人操作,但不一定遵循先进先出原则,也可能后进先出。priority_queue 是一个基于某个基本序列容器进行构建的适配器,默认的序列容器是 vector。
7.6.1常用函数
(1)构造函数。
priority_queue(const Pred& pr = Pred( ), const allocator_type& al= alocator.type()):创建元类型为T的空优先队列,Pred 是二元比较函数,默认是 less<T>
priority_queue(const value_type * first, const valuetype * last,const Pred& pr=Pred(),const allocator_type& al=allocator_type()):以选代器[first,last)指向元素,创建元素类型为T的优先队列,Pred 是二元比较函数,默认是 less<T>
(2)操作函数。
bool empty():如果优先队列为空返回 true,否则返回 false。
int size():返回优先队列中元素数量。
void push(const T& t):把t元压人优先队列。
void pop():优先队列非空情况下,删除优先级最高元素
T& top():优先队列非空情况下,返回优先级最高元素的引用。
例: 演示整型序列进出 priority_queue。
#include <iostream>
#include <queue>
#include <algorithm>
#include <iterator>
using namespace std;
int main()
{
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//整型优先队列 pr,并通过构造函数将数组 a 的元素送入队列中。
priority_queue<int> pr(a, a + 10);
cout << "进队顺序:";
//使用 copy 算法将数组 a 的元素复制到标准输出流 cout 中,并以制表符分隔各个元素。
copy(a, a + 10, ostream_iterator<int>(cout, "\t"));
cout << endl;
cout << "出队顺序:";
while (!pr.empty())
{
cout << pr.top() << "\t"; // 获得优先队列队头元素
pr.pop(); // 删除队头元素
}
return 0;
}
例:显示学生信息(学号、姓名、语文、数学),条件是:总成绩由高到低,当总成绩相同时,语文成绩高者优先
#include <iostream>
#include <queue>
#include <string>
using namespace std;
class Student {
int NO;
string name;
int chinese;
int math;
public:
Student(int NO, string name, int chinese, int math) {
this->NO = NO;
this->name = name;
this->chinese = chinese;
this->math = math;
}
int GetNO() const { return NO; }
string GetName() const { return name; }
int GetChinese() const { return chinese; }
int GetMath() const { return math; }
bool operator<(const Student& s) const {
int sum1 = chinese + math;
int chinese2 = s.GetChinese();
int math2 = s.GetMath();
int sum2 = chinese2 + math2;
if (sum1 < sum2)
return true;
if ((sum1 == sum2) && (chinese < chinese2))
return true;
return false;
}
};
int main() {
Student s[] = {
Student(1001, "zhang", 70, 80),
Student(1002, "li", 80, 70),
Student(1003, "wang", 90, 85),
Student(1004, "zhao", 85, 75)
};
//已经在 Student 类中重载了小于运算符 <,定义了学生对象之间的比较规则
priority_queue<Student> pr;//创建一个存储学生对象的优先队列 pr
for (int i = 0; i < 4; i++) {
pr.push(s[i]);
}
cout << "成绩由高到低(当相同时,语文高优先):" << endl;
cout << "学号\t姓名\t语文\t数学" << endl;
while (!pr.empty()) {
const Student t = pr.top();
cout << t.GetNO() << "\t" << t.GetName() << "\t" << t.GetChinese() << "\t" << t.GetMath() << endl;
pr.pop();
}
return 0;
}
例 :设计一个固定大小的优先队列类,即优先队列元素个数只能小于某个值
实现一个固定容量的优先队列,当队列已满时,插入操作将返回 false。
#include <iostream>
#include <queue>
using namespace std;
template <class T, class Cont = vector<T>, class Pred = less<typename Cont::value_type> >
class FixedPriority : public priority_queue<T, Cont, Pred> {//表示一个固定容量的优先队列
int nLimit;
public:
FixedPriority(int nLimit) {//接受一个整数参数 nLimit,用于设置优先队列的限制大小
this->nLimit = nLimit;
}
void SetLimitSize(int nLimit) {//用于通过函数设置优先队列的大小限制
this->nLimit = nLimit;
}
bool Push(T t) {
if (nLimit > priority_queue<T, Cont, Pred>::size()) {//判断优先队列的当前大小是否小于限制大小
priority_queue<T, Cont, Pred>::push(t);//调用基类 priority_queue 的 push 函数,将元素 t 插入优先队列
return true;
}
return false;
}
};
int main() {
FixedPriority<int> fp(10);
for (int i = 0; i < 15; i++) {
if (!fp.Push(i)) {
cout << "优先队列已满,第" << i << "个元素没有插入" << endl;
}
}
return 0;
}
7.7 习题
1.在list容器中,操作函数void splice(iterator it , list& x)的功能是:()
A. 队列 x 所有元素插入迭代器指针 it 前,x 变成空队列
2.以下关于deque的说法哪一个是正确的?
A.当deque的size大于capacity的时候,deque会进行扩容
B.deque没有push_front()方法
C.deque扩容的时候会申请一个新的空间,然后把数据全部迁移过去,再将原空间销毁
D.deque的内存可以是连续的,也可以是不连续的
deque 的扩容并不依赖于 size 和 capacity 的关系,它可以在任何时候进行扩容。
deque 是具有双端插入和删除操作的容器,因此是支持 push_front() 方法的
不会把原空间摧毁,vector会摧毁原空间
3.下面代码实现了对stack的遍历,需要补充完整。
void Print(stack<int> &q) {
while (!q.empty()) {
cout << q.top() << endl;
_q.pop();____________
} }
4. 已知 int a[]={1,2,3,4,5,6}; 利用整型数组a构造一个最大值优先队列pr,请写出代码: ____priority_queue<int> pr(a , a+9);。
5.queue容器适配器后端容器可以使用__deque ,list__。
6.stack容器适配器后端容器可以使用___ vector ,deque ,list____。
7.Priority_queue容器适配器后端容器可以使用__ vector ,deque ,list ____。
8、三种SLT容器适配器是____栈____、___队列_____和____优先级队列____.
9.适配器是不独立的,它依附于一个(2)顺序容器上,它没有自己的(3)构造函数和(4)析构函数而借用其实现类的对应函数
10.STL中容器适配器包含什么?
stack(堆栈类):后进先出 基本容器:vector、deque、list
queue(队列类):先进先出 基本容器:deque、list
11、容器适配器:遍历:p84
对已有的容器进行某些特性的再封装,不是一个真正的新容器
主要有stack:堆栈类(后进先出)
Queue:队列类(先进先出)
Priority_queue 优先队列
12.vector和list的区别?
vector和数组类似,拥有连续的内存空间,支持随机的存取。
list是由双向链表实现的,只能通过数组指针来进行数据访问。
13、List的功能方法
实际上有两种List: 一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。
List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
ArrayList : 由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(),getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用
14.下面代码实现了对stack的遍历,需要补充完整。
void Print(stack<int> &q) {
while (!q.empty()) {
cout << q.top() << endl;
___q.pop();__________
}
}