C++STL详解2:stack和queue
- 一、stack和queue
-
- 1.1容器适配器
- [1.2 stack](#1.2 stack)
-
- [1.2.1 stack的简介](#1.2.1 stack的简介)
- [1.2.2 stack的简单实现](#1.2.2 stack的简单实现)
- [1.3 queue](#1.3 queue)
-
- [1.3.1 queue的简介](#1.3.1 queue的简介)
- 1.3.2queue的简单实现
- [1.4 deque详解](#1.4 deque详解)
-
- 1.4.1deque简介
- [1.4.2 结构](#1.4.2 结构)
-
- [1.4.2.1 deque容器的成员变量](#1.4.2.1 deque容器的成员变量)
- 1.4.2.2头插和尾插操作
- 1.4.2.1支不支持下标访问
- [1.4.3 迭代器的组成和工作原理](#1.4.3 迭代器的组成和工作原理)
-
- [1.4.3.1 组成](#1.4.3.1 组成)
- [1.4.3.2 移动逻辑](#1.4.3.2 移动逻辑)
- [1.4.4 头插尾插和中间插入](#1.4.4 头插尾插和中间插入)
- [1.4.5 优缺点分析](#1.4.5 优缺点分析)
- 二、仿函数
-
- 2.1仿函数的概念
- [2.2 仿函数的使用](#2.2 仿函数的使用)
- 三、priority_queue(优先级队列)
-
- [3.1 优先级队列的简单介绍](#3.1 优先级队列的简单介绍)
- [3.2 实现](#3.2 实现)

递归何不归:个人主页
个人专栏 : 《C++庖丁解牛》《数据结构详解》
在广袤的空间和无限的时间中,能与你共享同一颗行星和同一段时光,是我莫大的荣幸
一、stack和queue
1.1容器适配器
在STL中,stack和queue包括priority_queue其实都不是独立的容器 ,而是其他容器封装得来的,我们将他们称为容器适配器。
1.2 stack
1.2.1 stack的简介
stack中文名为栈,遵循先进后出原则 ,是一个被广泛使用的数据结构。

这是它的接口:

1.2.2 stack的简单实现
由于我们要实现的是栈,只需要在一端进行操作 ,所以我们可以将vector封装,然后在vector的尾部操作,此时可以做到效率最大化
我们实现的栈默认是将vector封装,故在模版声明处给出缺省值
cpp
template<class T, class Con = vector<T>>
class stack
{
public:
stack()
{
Con();
}
void push(const T& t)
{
_con.push_back(t);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top()const
{
return _con.back();
}
size_t size()const
{
return _con.size();
}
bool empty()const
{
return _con.empty();
}
private:
Con _con;
};
1.3 queue
1.3.1 queue的简介
队列是一种**先进先出(FIFO)**的数据结构

这是他的接口 :

1.3.2queue的简单实现
由于队列是先进先出的的数据结构 ,使用vector时难免会出现挪动数据的低效行为 ,所以这里我们封装可以自由插入和删除的list来实现队列
cpp
template<class T, class Con = list<T>>
class queue
{
public:
queue()
{
Con _con;
}
void push(const T& x)
{
_con.push_front(x);
}
void pop()
{
_con.pop_back();
}
T& back()
{
return _con.back();
}
const T& back()const
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& front()const
{
return _con.front();
}
size_t size()const
{
return _con.size();
}
bool empty()const
{
return _con.empty();
}
private:
Con _con;
};
1.4 deque详解


我们之前在实现stack和queue的时候分别使用了vector 和 list,但是如果我们仔细观查他们的函数声明就会发现:
库函数中的stack和queue都是使用一个叫deque的容器封装的
那这个deque是何方神圣呢?
1.4.1deque简介
我们先查一下文档:
deque(这里图片比较长,就直接挂链接了)
我们可以发现:deque包含了vector和list的所有功能
但这又是怎么实现的呢?
1.4.2 结构
其实,deque就是一个缝合怪 ,他身上既有vector 的影子,也有list的影子

可以看到,deque 的本体是一个指针数组,指针数组中的指针指向等大的空间
1.4.2.1 deque容器的成员变量


1.4.2.2头插和尾插操作
当deque需要头插时,就在中控数组前面再添加一个数组并插入 ,需要在尾部插入时,就在尾部的数组内插入,如果已满,就再添加一个新的数组。
我们这里只是将一个大概,具体的在下面讲
1.4.2.1支不支持下标访问
deque是支持下标访问的,但是效率相较于vector 来说是较低的 (毕竟deque中的迭代器的移动逻辑是比较复杂的)
可以看出,deque这个结构保留了stack内存连续、缓存利用率高和支持下标访问的优点 ,也保留了list头插不需要挪动数据的优点,可谓是真正的缝合怪。
1.4.3 迭代器的组成和工作原理

1.4.3.1 组成
- first:指向数组的开头
- last:指向数组的结尾
- cur:指向现在的位置
- node:指向中控数组中的指针位置
1.4.3.2 移动逻辑
我们先看源码

分情况考虑:
- cur<last:连续地址,直接++
- cur==last:开一段新的数组,然后移动迭代器,再将数据写入
1.4.4 头插尾插和中间插入
尾插没什么好说的,迭代器++即可
1.4.4.1头插
头插的操作相对来说还是难一点,如果start指向的区域是满的(cur==last) ,就会在start之前 开一个数组,并在start指向的数组中从后往前(即从last位置开始放置)放置数据
具体的代码还是比较难解释的,所以这里只说思路
1.4.4.2中间插入
中间插入操作就更复杂了,因为deque的数据还是存储在vector的顺序结构中 ,中间插入不可避免的需要挪动数据 ,时间复杂度是O(N)
1.4.5 优缺点分析

- 1、deque头插尾插还是挺高效的(vector头插需要挪动数据 ,list 每次都要申请空间,而deque可以一次申请一块空间)
- 2、支持下标随机访问,但是效率低于vector
- 2、中间插入数据效率很低,要挪动数据,时间复杂度是O(N)
二、仿函数
2.1仿函数的概念
仿函数(Functor) 是一个重载了 operator() 的类或结构体。它的实例可以像函数一样被调用,因此也被称为函数对象
2.2 仿函数的使用
cpp
template<class T>
class less_func
{
public:
bool operator()(const T& a,const T& b)
{
return a < b;
}
};
jyy::less_func<int> LessFunc;
cout << LessFunc(1, 2) << endl;
// cout << LessFunc.operator()(1, 2) << endl;
注意,这里的仿函数的声明处不要忘记加上public:
三、priority_queue(优先级队列)
优先级队列的底层就是堆 ,默认的顺序是大的数据优先级高(即大堆)
3.1 优先级队列的简单介绍

我们可以看到,优先级队列Compare的缺省值是仿函数less(即<)
cpp
int main()
{
//jyy::test_queue1();
//这里是传了一个仿函数,用于指示容器按照相应的方式
priority_queue<int,vector<int> > pq;
pq.push(1);
pq.push(4);
pq.push(3);
pq.push(5);
pq.push(6);
pq.push(9);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
return 0;
}
运行可得:
可以发现:使用仿函数less的时候,组成的是大堆
我们将仿函数换成greater试试
cpp
priority_queue<int,vector<int>,greater<int> > pq;
得出:

可知建的是小堆
3.2 实现
今天实在是燃尽了,这里就不细讲了,啥时候再说吧
cpp
#pragma once
#include<iostream>
#include<vector>
#include<algorithm>
#include<cassert>
using namespace std;
namespace jyy
{
template<class T>
class less_func
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
class greater_func
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template<class T ,class Con = vector<T> ,class Compare = less_func<T>>
class priority_queue
{
public:
friend class greater_func<T>;
friend class less_func<T>;
priority_queue()
{
Con();
}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
Con(first, last);
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.empty();
}
const T& top() const
{
return _con[0];
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
private:
Con _con;
Compare _compare;
void AdjustUp(size_t child)
{
assert(child < _con.size());
int parent = (child - 1) / 2;
//先是建大堆,那就是如果child 如果是child大就向上调整
while (parent >= 0)
{
//if(_con[parent] < _con[child])
if (_compare(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(size_t parent)
{
assert(parent >= 0);
//这里是建大堆,向下调整肯定是先找到下面的较小的值
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (parent * 2 + 2 < _con.size() && _compare(_con[parent * 2 + 1], _con[parent * 2 + 2]))
{
child = parent * 2 + 2;
}
//if(_con[parent]<_con[child])
if (_compare(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
//这里还是说明符合规则
break;
}
}
}
};
}