文章目录
一、stack的介绍和使用
1.stack的介绍
C++ 中的 stack 是一种数据结构,位于标准模板库(STL)中。它遵循后进先出(LIFO, Last In First Out)的原则,也就是说,最后插入的元素最先被移除。
主要特点:
- LIFO 结构:最后插入的元素最先被取出。
- 限制访问方式:只能访问栈顶元素,无法直接访问其他元素。
2.stack的使用
- stack() 构造栈
#include <iostream>
#include <stack> // 引入stack容器适配器
#include <vector> // 引入vector容器
#include <deque> // 引入deque容器
// deque 动态数组的块链表。其设计结合了数组和链表的优点,以便在两端都能高效地进行插入和删除操作,同时仍保持良好的随机访问性能。
int main()
{
std::deque<int> mydeque(3, 100); // 用3个元素100初始化一个deque
std::vector<int> myvector(2, 200); // 用2个元素200初始化一个vector
std::stack<int> first; // 创建一个空的stack,底层容器默认为deque
std::stack<int> second(mydeque); // 用mydeque初始化一个stack,底层容器为deque
std::stack<int, std::vector<int>> third; // 创建一个空的stack,指定底层容器为vector
std::stack<int, std::vector<int>> fourth(myvector); // 用myvector初始化一个stack,底层容器为vector
std::cout << "size of first: " << first.size() << '\n'; // 输出first栈的大小
std::cout << "size of second: " << second.size() << '\n'; // 输出second栈的大小
std::cout << "size of third: " << third.size() << '\n'; // 输出third栈的大小
std::cout << "size of fourth: " << fourth.size() << '\n'; // 输出fourth栈的大小
return 0;
}
- empty() 检测stack是否为空
#include <iostream>
#include <stack>
int main()
{
std::stack<int> mystack;
int sum(0);
for (int i = 1; i <= 10; i++)
mystack.push(i); // 使用循环将1到10的整数压入mystack中
while (!mystack.empty()) // 当栈不为空时,继续循环
{
sum += mystack.top(); // 将栈顶元素加到sum中
mystack.pop(); // 移除栈顶元素
}
std::cout << "total: " << sum << '\n';
return 0;
}
- size() 返回stack中元素的个数
#include <iostream>
#include <stack>
int main()
{
std::stack<int> myints; // 创建一个空的stack对象myints,存储int类型的元素
std::cout << "0. size: " << myints.size() << '\n'; // 输出当前栈的大小,最初为0,因为栈是空的
for (int i = 0; i < 5; i++)
myints.push(i); // 使用循环将0到4的整数压入栈中,总共压入5个元素
std::cout << "1. size: " << myints.size() << '\n'; // 输出栈的大小,此时栈中有5个元素
myints.pop(); // 弹出栈顶的元素,此时栈顶元素是4
std::cout << "2. size: " << myints.size() << '\n'; // 输出栈的大小,现在栈中剩下4个元素
return 0;
}
- top() 返回栈顶元素的引用
#include <iostream>
#include <stack>
int main()
{
std::stack<int> mystack; // 创建一个空的stack对象mystack,存储int类型的元素
mystack.push(10);
mystack.push(20);
mystack.top() -= 5; // 访问栈顶元素,将栈顶元素减去5,原来的栈顶元素20变为15
std::cout << "mystack.top() is now " << mystack.top() << '\n'; // 输出当前栈顶元素的值,现在是15
return 0;
}
- push() 将元素val压入stack中 pop() 将stack中尾部的元素弹出
#include <iostream>
#include <stack>
int main()
{
std::stack<int> mystack;
for (int i = 0; i < 5; ++i) // 使用循环将0到4的整数压入栈中,总共压入5个元素
mystack.push(i);
std::cout << "Popping out elements...";
while (!mystack.empty()) // 当栈不为空时,持续循环
{
std::cout << ' ' << mystack.top(); // 输出栈顶元素的值
mystack.pop(); // 弹出栈顶元素
}
std::cout << '\n'; // 输出换行符,结束输出
return 0;
}
3.stack的模拟实现
从栈的接口中可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack
#include <vector> // 引入标准库中的 vector 容器,用来实现栈的存储结构
namespace stimulate // 定义命名空间 stimulate,避免名称冲突
{
// 定义模板类 stack,模板参数为 T,T 表示栈中存储的数据类型
template <class T>
class stack
{
public:
stack() {} // 默认构造函数,什么也不做,初始化一个空栈
// push 函数,接收一个常量引用的元素 x,并将其压入栈顶
void push(const T &x)
{
_c.push_back(x); // 使用 vector 的 push_back 方法将元素添加到容器末尾
}
// pop 函数,用于将栈顶元素弹出
void pop()
{
_c.pop_back(); // 使用 vector 的 pop_back 方法移除最后一个元素(即栈顶元素)
}
// top 函数,返回栈顶元素的引用
T &top()
{
return _c.back(); // 使用 vector 的 back 方法返回最后一个元素的引用(即栈顶元素)
}
// const 版本的 top 函数,返回栈顶元素的常量引用,用于不可修改栈的情况
const T &top() const
{
return _c.back(); // 使用 vector 的 back 方法返回最后一个元素的常量引用
}
// size 函数,返回栈的大小(元素个数)
size_t size() const
{
return _c.size(); // 使用 vector 的 size 方法返回元素的个数
}
// empty 函数,判断栈是否为空,返回一个布尔值
bool empty() const
{
return _c.empty(); // 使用 vector 的 empty 方法判断容器是否为空
}
private:
// 私有成员变量 _c,用来存储栈中的元素,底层实现使用 std::vector
std::vector<T> _c;
};
}
二、queue的介绍和使用
1.queue的介绍
队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元
素,另一端提取元素。
2.queue的使用
- queue() 构造空的队列
// constructing queues
#include <iostream> // std::cout
#include <deque> // std::deque
#include <list> // std::list
#include <queue> // std::queue
int main ()
{
std::deque<int> mydeck (3,100); // deque with 3 elements
std::list<int> mylist (2,200); // list with 2 elements
std::queue<int> first; // empty queue
std::queue<int> second (mydeck); // queue initialized to copy of deque
std::queue<int,std::list<int> > third; // empty queue with list as underlying container
std::queue<int,std::list<int> > fourth (mylist);
std::cout << "size of first: " << first.size() << '\n';
std::cout << "size of second: " << second.size() << '\n';
std::cout << "size of third: " << third.size() << '\n';
std::cout << "size of fourth: " << fourth.size() << '\n';
return 0;
}
- empty() 检测队列是否为空,是返回true,否则返回false
// queue::empty
#include <iostream> // std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myqueue;
int sum (0);
for (int i=1;i<=10;i++) myqueue.push(i);
while (!myqueue.empty())
{
sum += myqueue.front();
myqueue.pop();
}
std::cout << "total: " << sum << '\n';
return 0;
}
- size() 返回队列中有效元素的个数
// queue::size
#include <iostream> // std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myints;
std::cout << "0. size: " << myints.size() << '\n';
for (int i=0; i<5; i++) myints.push(i);
std::cout << "1. size: " << myints.size() << '\n';
myints.pop();
std::cout << "2. size: " << myints.size() << '\n';
return 0;
}
- front() 返回队头元素的引用
// queue::front
#include <iostream> // std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myqueue;
myqueue.push(77);
myqueue.push(16);
myqueue.front() -= myqueue.back(); // 77-16=61
std::cout << "myqueue.front() is now " << myqueue.front() << '\n';
return 0;
}
- back() 返回队尾元素的引用
// queue::back
#include <iostream> // std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myqueue;
myqueue.push(12);
myqueue.push(75); // this is now the back
myqueue.back() -= myqueue.front();
std::cout << "myqueue.back() is now " << myqueue.back() << '\n';
return 0;
}
- push() 在队尾将元素val入队列
// queue::push/pop
#include <iostream> // std::cin, std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myqueue;
int myint;
std::cout << "Please enter some integers (enter 0 to end):\n";
do {
std::cin >> myint;
myqueue.push (myint);
} while (myint);
std::cout << "myqueue contains: ";
while (!myqueue.empty())
{
std::cout << ' ' << myqueue.front();
myqueue.pop();
}
std::cout << '\n';
return 0;
}
- pop() 将队头元素出队列
// queue::empty
#include <iostream> // std::cout
#include <queue> // std::queue
int main ()
{
std::queue<int> myqueue;
int sum (0);
for (int i=1;i<=10;i++) myqueue.push(i);
while (!myqueue.empty())
{
sum += myqueue.front();
myqueue.pop();
}
std::cout << "total: " << sum << '\n';
return 0;
}
3.queue的模拟实现
因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,故可以借助list来模拟实现queue
#include <list>
namespace simulate
{
template <class T>
class queue
{
public:
queue() {}
void push(const T &x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T &back()
{
return _c.back();
}
const T &back() const
{
return _c.back();
}
T &front()
{
return _c.front();
}
const T &front() const
{
return _c.front();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
std::list<T> _c;
};
}
三、priority_queue的介绍和使用
1.priority_queue的介绍
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
- 类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
std::priority_queue<int> pq;
pq.push(10);
pq.push(20);
pq.push(5);
std::cout << pq.top(); // 输出 20,最大值在队列顶部
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(10);
min_pq.push(20);
min_pq.push(5);
std::cout << min_pq.top(); // 输出 5,最小值在队列顶部
应用场景:
- 任务调度:在操作系统中,任务可以根据优先级来调度,优先级队列可以根据任务的重要性排序。
- Dijkstra算法:用于最短路径算法中,优先级队列可以帮助快速找到当前最短路径的节点。
- 事件模拟:在模拟系统中,事件可以按照时间顺序(或优先级)处理。
2.priority_queue的使用
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。
- priority_queue()/priority_queue(first,last) 构造一个空的优先级队列
#include <iostream>
#include <queue> // 引入优先队列(priority_queue)库
#include <vector>
#include <functional> // 引入函数对象(greater)库
// 定义比较类,用于自定义优先队列的排序方式
class mycomparison
{
bool reverse; // 是否反向排序的标志
public:
// 构造函数,初始化reverse标志,默认false为升序
mycomparison(const bool &revparam = false)
{
reverse = revparam;
}
// 重载操作符(),用于比较两个整数
bool operator()(const int &lhs, const int &rhs) const
{
if (reverse)
return (lhs > rhs); // 如果reverse为true,进行降序排序
else
return (lhs < rhs); // 否则进行升序排序
}
};
int main()
{
int myints[] = {10, 60, 50, 20}; // 初始化一个整数数组
std::priority_queue<int> first; // 创建一个默认的空优先队列,使用最大堆
std::priority_queue<int> second(myints, myints + 4); // 使用数组中的元素创建优先队列,默认最大堆
std::priority_queue<int, std::vector<int>, std::greater<int>>
third(myints, myints + 4); // 使用std::greater进行最小堆排序的优先队列
// 使用自定义比较类mycomparison:
typedef std::priority_queue<int, std::vector<int>, mycomparison> mypq_type; // 定义自定义类型的优先队列
mypq_type fourth; // 默认使用less-than比较的优先队列(最大堆)
mypq_type fifth(mycomparison(true)); // 使用greater-than比较的优先队列(最小堆)
return 0;
}
- empty( ) 检测优先级队列是否为空,是返回true,否则返回false
#include <iostream>
#include <queue> // 引入优先队列(priority_queue)库
int main()
{
std::priority_queue<int> mypq; // 创建一个空的优先队列,存储int类型元素
int sum(0); // 初始化sum变量,计算总和
for (int i = 1; i <= 10; i++)
mypq.push(i);
// 当优先队列不为空时,执行循环
while (!mypq.empty())
{
sum += mypq.top(); // 获取优先队列顶部元素(最大值)并加到sum中
mypq.pop(); // 移除优先队列顶部的元素
}
std::cout << "total: " << sum << '\n'; // 输出元素总和
return 0; // 返回0,表示程序成功结束
}
- top( ) 返回优先级队列中最大(最小元素),即堆顶元素
#include <iostream>
#include <queue>
int main()
{
std::priority_queue<int> mypq; // 创建一个空的优先队列,存储int类型元素
mypq.push(10);
mypq.push(20);
mypq.push(15);
// 输出当前优先队列的顶部元素(最大值)
std::cout << "mypq.top() is now " << mypq.top() << '\n';
return 0;
}
- push(x) 在优先级队列中插入元素x
pop() 删除优先级队列中最大(最小)元素,即堆顶元素
#include <iostream>
#include <queue>
int main()
{
std::priority_queue<int> mypq;
mypq.push(30);
mypq.push(100);
mypq.push(25);
mypq.push(40);
std::cout << "Popping out elements...";
// 当优先队列不为空时,依次弹出顶部元素
while (!mypq.empty())
{
std::cout << ' ' << mypq.top(); // 输出当前优先队列的顶部元素(最大值)
mypq.pop(); // 移除当前优先队列的顶部元素
}
std::cout << '\n';
return 0;
}
- 默认情况下,priority_queue是大堆
#include <iostream>
#include <vector>
#include <queue>
#include <functional> // 引入greater算法头文件,用于小堆
using namespace std;
void TestPriorityQueue()
{
// 默认情况下,创建的是大顶堆(最大堆),其底层按照小于号比较
vector<int> v{3, 2, 7, 6, 0, 4, 1, 9, 8, 5}; // 初始化向量v,包含10个整数元素
priority_queue<int> q1; // 创建一个默认的大顶堆优先队列,类型为int
for (auto &e : v)
q1.push(e);
cout << q1.top() << endl; // 输出优先队列q1的顶部元素,即最大值
// 如果要创建小顶堆(最小堆),将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
- 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year), _month(month), _day(day)
{
}
// 重载小于运算符,用于比较两个日期
bool operator<(const Date &d) const
{
// 比较年份,如果年相同则比较月份,如果月相同则比较日期
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
// 重载大于运算符,用于比较两个日期
bool operator>(const Date &d) const
{
// 比较年份,如果年相同则比较月份,如果月相同则比较日期
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
// 重载输出运算符,以"年-月-日"的格式输出日期
friend ostream &operator<<(ostream &_cout, const Date &d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 创建一个大顶堆(最大堆),要求自定义类型Date重载"<"运算符
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl; // 输出优先队列顶部的日期(最大值)
// 创建一个小顶堆(最小堆),要求自定义类型Date重载">"运算符
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl; // 输出优先队列顶部的日期(最小值)
}
3.priority_queue的模拟实现
#pragma once // 防止头文件被重复包含
#include <iostream>
using namespace std;
#include <vector>
namespace simulate // 自定义命名空间simulate
{
// less 仿函数,用于小于号比较(用于最大堆)
template <class T>
struct less
{
bool operator()(const T &left, const T &right)
{
return left < right;
}
};
// greater 仿函数,用于大于号比较(用于最小堆)
template <class T>
struct greater
{
bool operator()(const T &left, const T &right)
{
return left > right;
}
};
// 优先级队列的模板类
template <class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
// 构造空的优先级队列
priority_queue() : c() {}
// 使用迭代器构造优先级队列
template <class Iterator>
priority_queue(Iterator first, Iterator last)
: c(first, last)
{
// 将容器c中的元素调整成堆的结构
int count = c.size();
int root = ((count - 2) >> 1); // 找到最后一个非叶子节点
for (; root >= 0; root--)
AdjustDown(root); // 从最后一个非叶子节点开始向下调整
}
// 向优先队列中插入元素
void push(const T &data)
{
c.push_back(data); // 将新元素加入容器末尾
AdjustUP(c.size() - 1); // 从最后一个元素开始向上调整堆
}
// 从优先队列中移除堆顶元素
void pop()
{
if (empty()) // 如果队列为空则直接返回
return;
swap(c.front(), c.back()); // 将堆顶元素和最后一个元素交换
c.pop_back(); // 移除最后一个元素(即原来的堆顶)
AdjustDown(0); // 从堆顶开始向下调整堆
}
// 返回优先队列的大小
size_t size() const
{
return c.size();
}
// 检查优先队列是否为空
bool empty() const
{
return c.empty();
}
// 返回堆顶元素,但不允许修改,因为修改可能会破坏堆的特性
const T &top() const
{
return c.front(); // 返回容器的第一个元素(堆顶)
}
private:
// 向上调整堆
void AdjustUP(int child)
{
int parent = ((child - 1) >> 1); // 计算父节点索引
while (child) // 当子节点不是根节点时
{
if (Compare()(c[parent], c[child])) // 比较父子节点,若不满足堆性质
{
swap(c[child], c[parent]); // 交换父子节点
child = parent; // 更新子节点为父节点
parent = ((child - 1) >> 1); // 更新父节点
}
else
{
return; // 满足堆性质,退出循环
}
}
}
// 向下调整堆
void AdjustDown(int parent)
{
size_t child = parent * 2 + 1; // 计算左子节点索引
while (child < c.size()) // 当子节点存在时
{
// 如果右子节点存在且大于左子节点,选取较大的子节点
if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
child += 1;
// 比较父子节点,若父节点不满足堆性质
if (Compare()(c[parent], c[child]))
{
swap(c[child], c[parent]); // 交换父子节点
parent = child; // 更新父节点为子节点
child = parent * 2 + 1; // 更新子节点索引
}
else
return; // 满足堆性质,退出循环
}
}
private:
Container c; // 底层容器
};
}
// 测试优先队列
void TestQueuePriority()
{
// 创建最大堆(默认使用less比较器)
simulate::priority_queue<int> q1;
q1.push(5);
q1.push(1);
q1.push(4);
q1.push(2);
q1.push(3);
q1.push(6);
cout << q1.top() << endl; // 输出堆顶元素(最大值)
q1.pop(); // 移除堆顶元素
q1.pop(); // 再次移除堆顶元素
cout << q1.top() << endl; // 输出新的堆顶元素
// 创建最小堆(使用greater比较器)
vector<int> v{5, 1, 4, 2, 3, 6};
simulate::priority_queue<int, vector<int>, simulate::greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl; // 输出堆顶元素(最小值)
q2.pop(); // 移除堆顶元素
q2.pop(); // 再次移除堆顶元素
cout << q2.top() << endl; // 输出新的堆顶元素
}
四、容器适配器
1.概念
- 容器适配器是一类特殊的容器,它们并不是从零开始实现数据存储和管理功能,而是基于已有的序列容器(如 vector、deque 等)构建的一层封装,用于提供简化或定制的接口。这种封装可以让我们使用容器更方便,且专注于特定的功能需求。
- 容器适配器通过对已有的容器类型进行适配,提供了不同的接口和行为。主要有三种常见的容器适配器:
stack(栈) queue(队列) priority_queue(优先级队列)
2.STL标准库中stack和queue的底层结构
stack 是一种后进先出(LIFO,Last In First Out)的数据结构,意味着最后插入的元素最先被访问或移除。stack 的底层实现可以使用以下序列容器之一:
- deque(双端队列)
- vector(动态数组)
- list(双向链表)
默认的底层结构:deque stack 默认使用 deque 作为其底层容器。deque提供了快速的双端插入和删除操作,这使得它非常适合实现栈,因为栈的主要操作都发生在顶部(即容器的一端)。
queue 是一种先进先出(FIFO,First In First
Out)的数据结构,意味着最先插入的元素最先被访问或移除。queue 的底层实现可以使用以下序列容器之一:
- deque(双端队列)
- list(双向链表) 默认的底层结构:deque queue 默认使用 deque 作为其底层容器。和栈一样,deque 提供了双端插入和删除的能力,这非常适合队列的操作需求。在队列中,元素从一端插入,从另一端删除,这与 deque 的特点相匹配。
3.为什么选择deque作为stack和queue的底层默认容器
为什么 deque 适合作为 stack 的默认实现?
- 快速的双端插入和删除:deque 支持在头部和尾部进行常数时间的插入和删除操作,这对于栈的 push 和 pop 操作非常重要。
- 灵活性:相比 vector 只能在尾部操作,deque 提供了更灵活的接口,在两端都可以插入和删除元素。
为什么 deque 适合作为 queue 的默认实现?
- 双端操作:deque 允许在两端进行快速的插入和删除操作,这与队列的特点(从尾部插入,从头部删除)非常吻合。
- 灵活性:相比于 list,deque提供了类似数组的连续存储,可能在某些操作中更高效。