C++教学总目录
deque、stack、queue、priority_queue
- 1、deque
- 2、stack使用介绍
- 3、stack实现
- 4、queue使用介绍
- 5、queue实现
- 6、priority_queue使用介绍
- 7、priority_queue实现
- 8、反向迭代器
1、deque
deque是双端队列,我们学习的队列是先进先出的(First in first out),相比于队列,双端队列是支持头插头删、尾插尾删的。包含于头文件<deque>
我们之前学的vector实际上就是顺序表,它重载了operator[],所以可以随意访问任意一个位置的元素,但是如果扩容需要挪动数据,消耗较大,并且头插头删/中间插入删除都需要挪动数据。
我们之前学的list是带头双向循环链表,它在任意位置的插入删除效率很高,并且不存在扩容问题,按需申请按需释放,但是不支持下标随机访问。
vector和string各有优缺点:而deque双端队列就是结合了vector和list的优点设计出来的,它支持任意位置的下标访问,也支持头插头删/尾插尾删。
下面这张图描述了deque的底层实现原理:
下面看看deque的接口:
用法基本上同之前的vector,这里就不再赘述。由于deque的底层实现比较复杂,我们不模拟实现,有兴趣可以去看看库里是如何实现的。
2、stack使用介绍
栈是先进后出的一种数据结构,STL中的stack是一种容器适配器,包含于头文件<stack>,stack也是类模板,有两个模板参数,第一个T代表数据类型,第二个Container表示容器,模板参数也可以给缺省值,这里默认用deque来作为stack的容器。
下面是stack的核心接口:
主要是:1、size返回栈中元素个数。2、empty判断栈是否为空。3、top返回栈顶元素。4、push元素入栈。5、pop弹出栈顶元素。
stack是没有迭代器的,不能使用迭代器/范围for进行遍历,可以用下面方式遍历:
上面模板参数我们只给了类型,所以默认stack的底层容器就是deque,假设我不想用deque呢?我可以传vector或list:
stack<int, list<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
while (st.size())
{
cout << st.top() << " ";
st.pop();
}
stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
while (st.size())
{
cout << st.top() << " ";
st.pop();
}
3、stack实现
我们直接使用deque作为stack的默认容器:
#pragma once
namespace zzy
{
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
}
4、queue使用介绍
队列的性质是先进先出,STL中的queue和stack一样,都是容器适配器,默认底层容器也是deque。包含于头文件<queue>
下面给出核心接口:1、empty判断队列是否为空。2、size返回队列元素个数。3、front返回队头元素。4、back返回队尾元素。5、push在队尾插入元素。6、pop弹出队头元素。
queue也是没有迭代器的。用法如下:
和上面stack一样,如果不想使用deque作为queue的底层容器,可以自己传模板参数。
5、queue实现
#pragma once
namespace zzy
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
}
6、priority_queue使用介绍
priority_queue是优先级队列(堆),它也是容器适配器,包含于头文件<queue>
可以看到这里有三个模板参数,第三个是仿函数——用来控制比较大小从而实现大堆/小堆。
下面看看优先队列的接口:
1、empty判断堆是否为空。2、size计算堆中元素个数。3、top获取堆顶元素。4、push向堆中插入元素并调整堆。5、pop弹出堆顶元素并调整堆。
用法如下:
如果要用大堆,可以传仿函数的模板参数:
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(10);
pq.push(24);
pq.push(17);
pq.push(30);
pq.push(1);
while (pq.size())
{
cout << pq.top() << " ";
pq.pop();
}
7、priority_queue实现
仿函数/函数对象:这个类对象可以像函数一样使用——实际上是重载了operator()
看下面的类:
template<class T>
struct Less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
int main()
{
Less<int> lessfunc;
cout << lessfunc(1, 2) << endl; // 像函数一样使用
cout << lessfunc.operator()(1, 2) << endl; // 实际上是重载了operator()
return 0;
}
这就是仿函数。官方实现了less和greater两个仿函数,less比较就是<,而greater就是>。
针对内置类型,我们可以直接使用less和greater。如果是自定义类型,也可以使用less和greater,但是要保证自定义类型中重载了operator</operator>。
另外有些场景我们需要自己实现仿函数,例如存放的是指针类型。
下面是priority_queue的实现:
#pragma once
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
namespace zzy
{
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
private:
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++;
}
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else break;
}
}
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
public:
priority_queue()
{}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
++first;
}
for (int i = _con.size() - 1 - 1 >> 1; i >= 0; i--)
{
AdjustDown(i);
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
void test_priority_queue1()
{
priority_queue<int, vector<int>, Greater<int>> pq;
pq.push(3);
pq.push(5);
pq.push(1);
pq.push(4);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
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);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
struct LessPDate
{
bool operator()(const Date* p1, const Date* p2)
{
return *p1 < *p2;
}
};
void test_priority_queue2()
{
// 仿函数控制实现小堆
//priority_queue<Date, vector<Date>, greater<Date>> pq;
//pq.push(Date(2023, 7, 20));
//pq.push(Date(2023, 6, 20));
//pq.push(Date(2023, 8, 20));
//while (!pq.empty())
//{
// cout << pq.top() << " ";
// pq.pop();
//}
//cout << endl;
priority_queue<Date*, vector<Date*>, LessPDate> pq;
pq.push(new Date(2023, 7, 20));
pq.push(new Date(2023, 6, 20));
pq.push(new Date(2023, 8, 20));
while (!pq.empty())
{
cout << *pq.top() << " ";
pq.pop();
}
cout << endl;
}
}
关于模板中的仿函数有一个注意点:
8、反向迭代器
反向迭代器的实现需要创建自定义类型,反向迭代器是一种适配器,通过对正向迭代器的封装来实现。
如图:反向迭代器的rbegin就是正向迭代器的end,反向迭代器的rend就是正向迭代器的begin,之所以这么设计是考虑了镜像对称。
因此,反向迭代器的++就是正向迭代器的–,反向迭代器的–就是正向迭代器的++。由于反向迭代器与容器中元素错位,所以实现operator*的时候需要–再解引用获取元素。
下面先给出模板参数:
template<class Iterator, class T, class Ref, class Ptr>
第一个模板参数是正向迭代器,后面两个Ref和Ptr是为了通过不同的参数实例化出普通反向迭代器和const反向迭代器。之所以还需要给一个T类型的模板参数是因为需要实现普通反向迭代器构造const反向迭代器的函数,否则无法使用const反向迭代器。
template<class Iterator, class T, class Ref, class Ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator, T, Ref, Ptr> self;
typedef ReverseIterator<Iterator, T, T&, T*> reverseiterator;
Iterator _it;
ReverseIterator(Iterator it)
:_it(it)
{}
ReverseIterator(const reverseiterator& it)
:_it(it._it)
{}
Ref operator*()
{
Iterator tmp = _it;
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
self& operator++()
{
--_it;
return *this;
}
self operator++(int)
{
self tmp(_it);
--_it;
return tmp;
}
self& operator--()
{
++_it;
return *this;
}
self& operator--(int)
{
self tmp(_it);
++_it;
return tmp;
}
bool operator!=(const self& s) const
{
return _it != s._it;
}
bool operator==(const self& s) const
{
return _it == s._it;
}
};
然后在容器中加入以下代码:
typedef ReverseIterator<iterator, T, T&, T*> reverse_iterator;
typedef ReverseIterator<iterator, T, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin() { return end(); }
reverse_iterator rend() { return begin(); }
const_reverse_iterator rbegin() const { return end(); }
const_reverse_iterator rend() const { return begin(); }
以上是实现方式之一,对于之前写的string和vector都可以使用。库里的实现方式是迭代器萃取,有兴趣可以了解一下。