目录
一.设计模式
二.stack的模拟实现
三.queue的模拟实现
四.deque的简单介绍(了解)
五.课后习题
在我们用C++模拟实现之前在C语言阶段的实现过的数据结构时,我们会想用更加舒服的方式写代码,这时我们就要用到设计模式
那么我们就要先了解一下什么是设计模式?
一.设计模式
设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路
比如适配器模式,迭代器模式
迭代器模式:迭代器封装后提供统一的访问方式,不暴露底层的细节,迭代器实际上是一种设计模式
适配器模式:适配器实际上是一种转换,通过已有的东西封装转换出你想要的东西
而我们今天要模拟实现的栈与队列就可以通过适配器模式进行实现
二.stack的模拟实现
关于栈的实现很简单,直接看代码,可以结合代码里标着的注释进行理解~
stack.h
#include<vector>
#include<list>
#include <deque>
namespace wyh
{
//适配器 //传一个容器 这里用list
template<class T, class Container = list<T>>
class stack
{
public:
void push(const T& x)//入栈
{
_con.push_back(x);
}
void pop()//出栈
{
_con.pop_back();
}
const T& top()
{
return _con.back();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
_con.size();
}
private:
Container _con;
};
}
然后我们可以在test.cpp中测试一下:
三.queue的模拟实现
queue.h
namespace wyh
{
//适配器 //传一个容器 这里用list
template<class T, class Container = list<T>>
class queue
{
public:
void push(const T& x)//入队
{
_con.push_back(x);
}
void pop()//出队
{
_con.pop_front();
}
const T& front()//取队头元素
{
return _con.front();
}
const T& back()//取队尾元素
{
return _con.back();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
_con.size();
}
private:
Container _con;
};
}
接下来我们对自己写的队列测试一下:
四.deque的简单介绍(了解)
上面我们用list实现了栈和队列,但是当我们查看文档时,发现Container用的并不是
vector或是list,而是deque
那么这个deque又是什么呢?
deque(双端队列):是一种双开口的"连续"空间的数据结构
双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
它与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,由buffer数组构成,确定中控指针数组:
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段
所以说,deque集成了vector和list的优点,它的出现本来是想取代vector和list,但是,正如大家所见,它并没有成功,否则我们学习的第一个容器也不会是vector或者list了
那么deque的缺陷又是什么呢?
deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多
而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构
最后一个问题:
为什么选择deque作为stack和queue的底层默认容器?
1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端操作
2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高
五.课后习题
155.最小栈
. - 力扣(LeetCode)
参考代码:
class MinStack //因为这个函数 所以采用2个栈的实现方式
{
public:
stack <int> s1;
stack <int> s2;
MinStack()
{}
void push(int val)
{
s1.push(val);
if (s2.empty() || s1.top() <= s2.top()) //s2为空或者val值比s2中更小
s2.push(s1.top());
}
void pop()
{
if (s1.top() == s2.top()) //s1出栈元素与s2顶部相同
s2.pop();
s1.pop();
}
int top()
{
return s1.top();
}
int getMin()
{
return s2.top();
}
};
栈的压入、弹出序列
栈的压入、弹出序列_牛客题霸_牛客网
参考代码
class Solution
{
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV)
{
stack <int> s;
int pushi = 0;//作为操控pushV这个压入栈的指针
int popi = 0; //作为操控popV这个弹出栈的指针
for(pushi = 0;pushi<pushV.size();pushi++)
{
//1.入一个pushV中的值
s.push(pushV[pushi]);
//2.s 跟popV 比较
while(!s.empty() && s.top() == popV[popi])
{
//相等 出栈序列popV往后走 s的栈顶元素出
++popi;
s.pop();
}
//不相等 出来 进行下一次循环
}
//s不为空,为false
if (!s.empty()) return false;
//s.empty()即s这个栈的所有数都出栈了,则为true
else return true;
}
};
232.用栈实现队列
. - 力扣(LeetCode)
参考代码:
class MyQueue
{
public:
stack <int> s1;//用来返回队列的值
stack <int> s2;
MyQueue() //构造 不用写了
{}
void push(int x) //push 是关键
{
while(!s1.empty())//x进来之前,先把s1的数全挪到s2去
{
s2.push(s1.top());
s1.pop();
}
s1.push(x);//x入栈
// 将s2中放着的s1的数全挪回来 如此可以保证用栈保持s1是队列的顺序
while(!s2.empty())
{
s1.push(s2.top());
s2.pop();
}
}
int pop() //出队列 并返回这个出队的元素
{
int x = s1.top();
s1.pop();
return x;
}
int peek() ///返回对列的顶部元素
{
return s1.top();
}
bool empty() //判断队列是否为空
{
return s1.empty();
}
};
225.用队列实现栈
. - 力扣(LeetCode)
参考代码:
class MyStack
{
public:
queue<int>q1;//返回值在q1实现
queue<int>q2;
MyStack()
{}
void push(int x)
{
q1.push(x);
}
int pop() //关键 使得我们用队列的结构模拟出栈的序列
{
//思路 : 出q1队列的元素到q2使得它只剩一个元素,再将这最后一个元素弹出
// 当然了 当q1的元素弹出后 我们还要将q2的元素还原回q1
int ans=q1.back();
int n=q1.size()-1;
while(n--)//出q1队列的元素到q2使得它只剩一个元素,再将这最后一个元素弹出就是我们要的
{
q2.push(q1.front());
q1.pop();
}
q1.pop();
//q1的元素弹出后 将q2的元素还原回q1
while(!q2.empty())
{
q1.push(q2.front());
q2.pop();
}
return ans;
}
int top()
{
return q1.back();
}
bool empty()
{
return q1.empty();
}
};