目录
一,栈_刷题必备
二,stack实现
1.什么是容器适配器
2.STL标准库中stack和queue的底层结构
了解补充:容器——deque
1. deque的缺陷
2. 为什么选择deque作为stack和queue的底层默认容器
三,queue实现
1. 普通queue
2,优先级队列(有难度)
<1>. 功能
<2>. 模拟实现
1). 利用迭代器_构造
2).仿函数
sort函数中的仿函数使用理解
四. 反向迭代器(以list为例)
2. 关于迭代器文档一个小细节
结语
一,栈_刷题必备
常见接口:
stack() 造空的栈empty() 检测 stack 是否为空size() 返回 stack 中元素的个数top() 返回栈顶元素的引用push() 将元素 val 压入 stack 中pop() 将 stack 中尾部的元素弹出
简单应用,刷点题:
155. 最小栈
栈的压入、弹出序列_牛客题霸_牛客网
150. 逆波兰表达式求值
二,stack实现
思路:在C语言期间,我们可以通过链表,数组形式实现过stack,而数组形式效率更高,所以stack的实现可以直接复用vector接口,包装成栈先进后出的特性。
#pragma once
#include <iostream>
#include <vector>
#include <list>
using namespace std;
namespace my_s_qu
{
template <class T >
class stack
{
public:
void push_back(const T& x)
{
Data.push_back(x);
}
void Pop()
{
Data.pop_back();
}
T& top()
{
return Data.back();
}
const T& top() const
{
return Data.back();
}
bool empty() const
{
return Data.empty();
}
size_t size() const
{
return Data.size();
}
private:
vector<T> Data;
};
}
很简单不是吗? 到这里并没有结束,我们发现,我们的栈只能通过vector实现。根据c++标准库,我们还差个适配器的实现,换句话说,我们实现的栈不支持容器适配器。
1.什么是容器适配器
2.STL标准库中stack和queue的底层结构
回到我们栈的实现,我们仅支持vector模式设计,优化为成容器适配器支持所有容器,都能支持栈的先进后出的特性。(在我看来这中思想更为重要)
namespace my_s_qu
{
template <class T, class container = deque<T> > // 添加容器模板
{
public:
void push_back(const T& x)
{
Data.push_back(x);
}
......
private:
container Data; // 容器换成模板
};
}
其中deque又是什么?
了解补充:容器——deque
那deque是如何借助其迭代器维护其假想连续的结构呢?
1. deque的缺陷
优势:
与vector比较,deque的 优势是: 头部插入和删除效率高。 不需要搬移元素,效率特别高,而且在 扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较, 优势: 支持随机访问。其底层是连续空间, 空间利用率比较高,不需要存储额外字段。
缺陷:
不适合遍历。因为在遍历时,deque的迭代器要 频繁的去 计算每个数据的位置,导致 效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list, deque的应用并不多,而目前能看到的一个应用就是, STL用其作为stack和queue的底层数据结构。
2. 为什么选择deque作为stack和queue的底层默认容器
1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
三,queue实现
1. 普通queue
queue实现,我们这次以容器适配器进行。
template <class T, class container = deque<T>>
class queue
{
public:
void push_back(const T& x)
{
Data.push_back(x);
}
void Pop()
{
Data.pop_front();
}
T& back()
{
return Data.back();
}
const T& back() const
{
return Data.back();
}
T& front()
{
return Data.front();
}
const T& front() const
{
return Data.front();
}
bool empty() const
{
return Data.empty();
}
size_t size() const
{
return Data.size();
}
private:
container Data;
};
2,优先级队列(有难度)
简介:
<1>. 功能
priority_queue()/priority_queue(fifirst, last) 构造一个空的优先级队列empty( ) 检测优先级队列是否为空,是返回true,否则返回falsetop( ) 返回优先级队列中最大(最小元素),即堆顶元素push(x) 在优先级队列中插入元素xpop() 删除优先级队列中最大(最小)元素,即堆顶元素pop_back() 删除容器尾部元素
<2>. 模拟实现
完成优先级队列,需要用到堆方面的知识,如果堆大家不太熟悉了,建议将堆实现内容复习一遍:
详解树与二叉树的概念,结构,及实现(上篇)_花果山~~程序猿的博客-CSDN博客
利用堆方面知识,完成最基础的框架:
namespace my_priority_queue
{
template <class T, class container = vector<T>>
class priority_queue
{
public:
// 自定义类型,不需要给他初始化
void ajust_up(size_t child)
{
int parent;
while (child > 0)
{
parent = child / 2;
if (_pri_queue[child] > _pri_queue[parent]) // 写大堆
{
std::swap(_pri_queue[child], _pri_queue[parent]);
child = parent;
parent = child / 2;
}
else
{
break;
}
}
}
T& top()
{
return _pri_queue[0];
}
bool empty()
{
return _pri_queue.empty();
}
void push(const T& x)
{
_pri_queue.push_back(x);
// 向上调整
ajust_up(_pri_queue.size() - 1);
}
size_t size()
{
return _pri_queue.size();
}
void ajust_down()
{
size_t parent = 0;
size_t child = 2 * parent + 1;
while (child < _pri_queue.size())
{
if (child + 1 < _pri_queue.size() && _pri_queue[child + 1] > _pri_queue[child])
{
child++;
}
if (_pri_queue[parent] < _pri_queue[child])
{
std::swap(_pri_queue[parent], _pri_queue[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_pri_queue[0], _pri_queue[size() - 1]);
// 向下调整
_pri_queue.pop_back();
ajust_down();
}
private:
container _pri_queue;
};
}
这里我们会有一个疑问,那我们要构建升序怎么办? 仿函数会解释
1). 利用迭代器_构造
// 自定义类型,不需要给他初始化
priority_queue()
{}
template <class newiterator>
priority_queue(newiterator begin, newiterator end)
: _pri_queue()
{
while (begin != end)
{
push(*begin);
begin++;
}
}
2).仿函数
在该场景下,我们的优先级队列已经实现了降序的功能,那我们如何实现升序? 什么你说再写一段,改一下符号??
这里仿函数就得引出了,仿函数,那么它并不是函数,那是什么? (仿函数比如:less, greater)
仿函数本质是一个类,根据上图中,compare 模板,说明这里的仿函数,就是用来灵活改变大小堆符号的。
我们直接实现结果:
template <class T>
struct less
{
bool operator()(const T& left, const T& right)const
{
return left < right;
}
};
template <class T>
struct greater
{
bool operator()(const T& left, const T& right)const
{
return left > right;
}
};
这个两个类都是重载了operator(),因此我们在实例化对象后,使用:
template <class T,class compare = less<T>>
compare t;
cout << t(1, 2) << endl;
从外表来看,就像一个函数调用,但实际是,一个类的成员函数的调用
t.operator(1,2)
这样我们就可以直接改变仿函数的类,来控制大小堆了。
sort函数中的仿函数使用理解
函数模板中:
sort函数作为行参中使用:
sort(s.begin, s.end, greater<int> () ); // 函数中是要做参数的,我们加个括号就是一个匿名对象了
四. 反向迭代器(以list为例)
如果有小伙伴还学习普通迭代器,请参考这篇文章中的普通迭代器实现。
【STL】list用法&试做_底层实现_花果山~~程序猿的博客-CSDN博客
参考list源码,这里直接说结果,发现源码通过借用普通迭代器来构造反向迭代器。
直接上代码:
namespace my_list
{
template <class T>
struct list_node
{
list_node(const T& data = T())
: _data(data)
, _next(nullptr)
, _prv(nullptr)
{}
T _data;
list_node* _next;
list_node* _prv;
};
template <class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator< T, Ref, Ptr> iterator;
Node* _node;
list_iterator(Node* node)
: _node(node)
{}
bool operator!= (const iterator& it)
{
return _node != it._node;
}
bool operator==(const iterator& it)
{
return _node == it._node;
}
iterator& operator++()
{
_node = _node->_next;
return *this;
}
iterator& operator--()
{
_node = _node->_prv;
return *this;
}
iterator operator++(int)
{
iterator tmp(*this);
_node = _node->_next;
return *tmp;
}
Ptr operator*()
{
return _node->_data;
}
Ref operator->()
{
return &(operator*());
}
};
template <class Iterator, class Ref, class Ptr>
struct _reverse_iterator
{
typedef _reverse_iterator<Iterator, Ref, Ptr> reverse_iterator;
Iterator _cur;
_reverse_iterator(const Iterator& cur)
: _cur(cur)
{}
reverse_iterator& operator++()
{
--_cur;
return *this;
}
reverse_iterator operator++(int)
{
reverse_iterator temp(*this);
--_cur;
return temp;
}
reverse_iterator& operator--()
{
++_cur;
return _cur;
}
reverse_iterator operator--(int)
{
reverse_iterator temp(*this);
++_cur;
return temp;
}
// !=
bool operator!=(const reverse_iterator& end)
{
return _cur != end._cur;
}
bool operator==(const reverse_iterator& end)
{
return _cur == end._cur;
}
// *
Ptr operator*()
{
auto tmp = _cur;
--tmp;
return *tmp;
}
// ->
Ref operator->()
{
return &(operator*());
}
};
template <class T>
class list
{
typedef list_node<T> Node;
public:
typedef list_iterator<T, T*, T&> iterator;
typedef list_iterator<T, const T*, const T&> const_iterator;
typedef _reverse_iterator<iterator, T*, T&> reverse_iterator;
typedef _reverse_iterator<const_iterator, const T*, const T&> const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
const_reverse_iterator rbegin() const
{
return const_reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(begin());
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
..... //list其他成员函数这里就不再赘述了
设计思路比较简单,本质上是复用普通迭代器的函数,其他重载函数思想跟普通函数差不多。但这里也有一个比较艺术性的设计:
那这里我们来讨论一下,这个反向迭代器是否能给vector使用?? 答案是肯定的
看图:
结论:反向迭代器:迭代器的适配器。
2. 关于迭代器文档一个小细节
那是不是所有的容器都合适呢?
不一定,因为容器的普通迭代器最起码要支持++,--接口(比如:foward_list就不支持--,所以其没有反向迭代器)
这里补充一些关于[STL]文档的使用,从迭代器功能角度分为三类:
1. forward_iterator (单向迭代器) 支持——> ++ 比如: foward_list等等
2. bidirectional_iterator(双向迭代器) ——> ++ -- 比如: list等
3. radom_access_iterator (随机迭起器) ——> ++ -- + - 比如:vector, deque等, 第三中迭代器继承1,2种
那意义又是什么??
意义:就是提示在使用迭代器时,接口会提示你合适的的迭代器类型。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。