当前位置: 首页 > news >正文

C++栈的模拟实现

今天,我们来讲解一下Stack和queue的模拟实现,由于有了我们C语言数据结构的Stack和queue的模拟实现的铺垫,现在我们写出C++的Stack和queue的模拟实现就变得非常容易了。

数据结构——栈的实现-CSDN博客

数据结构——队列_队列可以插入字符吗-CSDN博客

在此之前,我们先简单认识一下C++ Stack中库的接口有哪些:

预知道:

什么是 容器适配器?

它是基于现有的容器类型(比如vector,list,deque),提供了不同的接口和特定的行为(对底层容器的接口进行封装和调整)。

栈的函数接口 

认识

1.栈的操作是后进先出,而且只能从栈顶那里出入或者删除的。

2.stack是作为容器适配器被实现的容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

3.栈的底层容器的容器类应支持

empty:判空操作

back:获取尾部元素操作

push_back:尾部插入元素操作

pop_back:尾部删除元素操作

因此,标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque(我们在后面讲解)。

 

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

有了之前的基础,相信看到这些函数名字都基本能看懂并可以使用的。

 队列的函数接口:

 认识:

1.queue是一种先进先出的模式,一端进,一端出(即队头出,队尾进)。

2.队列的底层容器的容器类应支持

empty:检测队列是否为空

size:返回队列中有效元素的个数

front:返回队头元素的引用

back:返回队尾元素的引用

push_back:在队列尾部入队列

pop_front:在队列头部出队列

因此,标准容器deque、list均符合这些需求,默认情况下,如果没有为queue指定特定的底层容器,默认情况下使用deque(我们在后面讲解)。

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

 从上面的接口我们知道,Stack实质上就是特殊的vector,为了让大家更好理解,所以我们先以vector标准容器来实现。

使用vector标准容器来实现stack

成员函数的框架建立

namespace bai  //避免与库里面的stack命名冲突
{template<class T>  class stack{public:private:std::vector<T> _st;   //利用vector标准容器};

构造函数(初始化)

因为它是自定义类型,不写初始化列表,自己默认去找编译器的构造函数去初始化

stack()
{ }

插入push

void push(const T& x)
{_st.push_back(x);
}

 删除pop

void pop()
{_st.pop_back();
}

 栈顶元素top

T& top()
{return _st.back();
}

 返回size个数

int size()const
{return _st.size();
}

 判断空empty

bool empty()const
{return _st.empty();
}

 

 从上面的接口我们知道,queue需要大量的头删,尾插。如果使用vector的话,效率太低了,所以实质上就是特殊的list,为了让大家更好理解,所以我们先以list标准容器来实现。

使用list标准容器来实现queue

成员函数的框架建立

#include <list>
namespace bai
{template<class T>class queue{public:private:std::list<T> _Q;};
}

 构造函数

 queue(){}

插入push 

  void push(const T& x) {_Q.push_back(x);}

 删除pop

 void pop() {_Q.pop_front();}

 返回队尾元素

T& back() 
{return _Q.back();
}//不可修改,只读const T& back()const {return _Q.back();}

 返回队头元素front

 T& front() {    return _Q.front();}const T& front()const {return _Q.front();}

返回size个数 

size_t size()const 
{return _Q.size();
}   

判断是否空empty

bool empty()const  
{return _Q.empty();
}

经过上面两个的实现,我们发现它们都大差不差。 

好了,有了上面的实现,现在我们再来认识一下关于deque的知识。

适配器:

概念:适配器是一种设计模式,它允许将一个类的接口转换成客户端所期望的另一种接口,从而使原本不兼容的类能够一起工作。

 

我们可以看到,无论是stack,queue还是priority_queue,它这里使用了类模板,都使用了容器适配器,并且stack和queue里面的deque是一个容器。

那么duque是什么呢?

deque的介绍(简单了解):

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高 

 

注意:deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组


 

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上。

借助其迭代器维护其假想连续的结构

我们知道,vector可以支持下标随机访问,但是它存在扩容问题,中间和头部的插入删除效率问题。list可以任意位置的插入或者删除,按需申请释放空间,不存在扩容问题,但是它不支持随机访问。而deque可以认为是结合这两个的优点进行结合的。

那么,有人会问了,既然deque都结合了它们的优点了,解决了它们之间的缺点,能不能就完全取代了它呢?显然这是不能的,deque还是在一些情况下有非常大的缺点的。

相比vector:

deque确实极大缓解了扩容问题和中间,头部的插入删除,但是它的[]效率不够vect快,因为它还要计算在哪个buffer,buffer的第几个。

下面我们通过比较在deque的不同方式的排序来证明它们的差异:

一个是通过拷贝到vector再排序。

另一个是之间利用deque的函数接口下排序。

#include<iostream>
using namespace std;
#include<deque>
#include<vector>
#include<algorithm>
void test_op()
{srand(time(0));const int N = 1000000;vector<int> v1;vector<int> v2;v1.reserve(N);v2.reserve(N);deque<int> dq1;deque<int> dq2;for (int i = 0; i < N; ++i){auto e = rand();//v1.push_back(e);//v2.push_back(e);dq1.push_back(e);dq2.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();// 先拷贝到vectorfor (auto e : dq1){v1.push_back(e);}// 排序sort(v1.begin(), v1.end());// 拷贝回去size_t i = 0;for (auto& e : dq1){e = v1[i++];}int end1 = clock();int begin2 = clock();//sort(v2.begin(), v2.end());sort(dq2.begin(), dq2.end());int end2 = clock();// 11:46继续printf("deque copy vector sort:%d\n", end1 - begin1);printf("deque sort:%d\n", end2 - begin2);
}

我们可以发现,deque的效率确实比vector低。

同样,与list相比,deque可以支持随机访问,cpu高速缓存效率高,头插头删,尾插尾删效率高。但是中间插入删除效率是非常低的,原因也是要找在哪个buffer,buffer的第几个。

那么,为什么它适用于stack于queue?

你想想stack与queue的性质,它们是不是只要都是在头和尾进行操作的?这样它们是不是就对于deque就非常适用了。

所以,STL中,我们的stack实现方式

namespace bai
{template<class T,class Container=deque<T>>class stack{public:stack(){ }void push(const T& x){_st.push_back(x);}void pop(){_st.pop_back();}T& top(){return _st.back();}int size()const{return _st.size();}bool empty()const{return _st.empty();}private:Container _st;};

queue

namespace bai
{template<class T,class Container=deque<T>>class Queue{public:void push(const T&x){_q.push_back(x);}void pop(){_q.pop_front();}T& front(){return _q.front();}T& back(){return _q.back();}int size()const{return _q.size();}bool empty()const{return _q.empty();}private:Container _q;};

好了,本次分享到处结束了,希望你我共进步!

相关文章:

  • Tableau 基础表制作
  • Qt Charts 绘制曲线图示例
  • Trae 宝藏功能实测:从 Mcp 搭建天气系统,到 AI 重塑 Excel 数据处理
  • C语言 函数递归
  • Eclipse 插件开发 4 工具栏
  • JAVA JVM面试题
  • 【TypeScript】速通篇
  • 比象AI创作系统,多模态大模型:问答分析+AI绘画+管理后台系统
  • ip-prefix前缀列表
  • 基于PyTorch的图像识别主要依赖于深度学习模型(尤其是卷积神经网络,CNN)对图像特征进行自动学习和分类
  • dubbo 异步化实践
  • Python类和对象四(十三)
  • 【springboot知识】配置方式实现SpringCloudGateway相关功能
  • 通过Golang实现快速实现MCP Server
  • Go 语言中的实时交互式编程环境
  • 量子跃迁:Vue组件安全工程的基因重组与生态免疫(完全体终局篇)
  • 正则表达式 工作案例
  • docker 常用配置
  • python 画折线统计图
  • Linux下的I/O复用技术之epoll
  • 从地下金库到地上IP,看海昏汉文化“最美变装”
  • 拖车10公里收1900元?货车司机质疑收费过高,潮州饶平县市监局已介入
  • 商务部新闻发言人:截至目前,中美之间未进行任何经贸谈判
  • 国家发改委:更大力度、更实举措促进民营经济高质量发展
  • “仅退款”将成历史?电商平台集中调整售后规则
  • “低头捡星光”,艺术创作直面三江源生态保护