C++容器适配与栈的实现及dequeque和优先级详解
作者:头发没有代码多 发布时间:2023-11-02 12:57:52
容器适配器
我们可以看出,栈中没有空间配置器(内存池),而是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口
栈的实现
#include<vector>
#include<iostream>
using namespace std;
namespace myspace
{
template<class T>
class Stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();//back接口访问尾部的数据
}
T& top()const
{
return _con.back();//back接口访问尾部的数据
}
bool empty()
{
return _con.empty();
}
size_t size()const
{
return _con.size();
}
private:
vector<T> _con;
};
}
此时这个栈并不是适配器,因为底层被写死了,底层是用vector实现的,如果想让它适配,加上适配器即可
此时就是适配器
list
注意队列不能用vector,编译会报错,因为不支持头删,没有pop_front
queque实现
namespace myspace
{
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& back()
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& back() const
{
return _con.back();
}
const T& front() const
{
return _con.front();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
dequeque
我们发现栈和队列都有一个dequeque
dequeque不是队列,是vector和list的结合体
1.支持任意位置的插入删除
2.支持随机访问
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
dequeque的缺陷
vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。 但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下(中间的插入删除效率很低),
而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构
测试之后,dequeque显然效率低
void test_op()
{
srand(time(0));
const int N = 100000;
vector<int> v;
v.reserve(N);
deque<int> dp;
for (int i = 0; i < N; ++i)
{
auto e = rand();
v.push_back(e);
dp.push_back(e);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
sort(dp.begin(), dp.end());
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("deque sort:%d\n", end2 - begin2);
}
优先级队列
priority_queque
优先级队列的底层是堆(二叉树的堆)
第二个参数容器适配器,第三个参数仿函数,less是大的优先级高
后面俩个参数给缺省值,测试优先级队列,默认大的优先级高
也可以用一个区间去初始化
把第三个参数改为greater,就是小的优先级高
习题
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> pq(nums.begin(),nums.end());
while(--k)
{
pq.pop();
}
return pq.top();
}
};
215. 数组中的第K个最大元素 - 力扣(LeetCode)
优先级队列模拟实现
namespace myspace
{
//大堆
template<class T,class Container=vector<T>>
class priority_queque
{
public:
template<class InputerIterator>
priority_queque(InputerIterator first, InputerIterator last)//迭代器区间
{
while (first < last)
{
_con.push_back(*first);
++first;
}
//建堆
for (int i = (_con.size() - 1 - 1)/2;i>=0;--i)
{
adjust_down(i);
}
}
priority_queque()//默认构造,不然会报错,因为上面的迭代器区间这个函数跟构造函数同名
{}
void adjust_up(size_t child)
{
size_t parent = (child - 1) / 2;
while (child>0)
{
if (_con[parent] < _con[child])
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child + 1] > _con[child])
{
++child;
} //选出最大的孩子
if (_con[child] > _con[parent])
{
std::swap(_con[child],_con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)//(大堆)堆的插入
{
_con.push_back(x);
adjust_up(_con.size()-1);//尾插后向上跳转
}
void pop()//删除堆顶数据
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}//对顶数据和最后一个数据交换,之后删除最后一个数据,然后向下调整堆
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()const
{
return _con.size();
}
private:
Container _con;
};
}
int main()
{
int a[]= { 156,132,156,156,31,5,15,31,364,15 };
myspace::priority_queque<int> pq(a,a+sizeof(a)/sizeof(int));
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
return 0;
}
优先级队列要控制比较大小的逻辑,上面的写法我们以大堆为例但是这样把优先级队列给写死了,如果把里面的>改为<则会变成小堆,但是这样比较麻烦。上面我们只传了俩个参数,还有一个参数没传,第三个参数是仿函数
仿函数
仿函数/函数对象——是个类,重载的是operator(),类对象可以像函数一样去使用,本质就是重载
()也是一个运算符
跟sort不同,sort传的是函数模板,传的是对象,而这里传的是类模板,传的是类型
这里的lsFunc不是函数名,而是一个类对象
这俩个等价
不仅有less,还有greater
namespace myspace
{
template<class T>
class less
{
public:
bool operator()(const T& l, const T& r)const
{
return l < r;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& l, const T& r)const
{
return l > r;
}
};
}
我们将这里全部改成小于号
传入仿函数
这样就可以去替换小于号
小堆
大堆
完整代码
namespace myspace
{
//大堆
template<class T,class Container=vector<T>,class Compare=less<T>>
class priority_queque
{
public:
template<class InputerIterator>
priority_queque(InputerIterator first, InputerIterator last)//迭代器区间
{
while (first < last)
{
_con.push_back(*first);
++first;
}
//建堆
for (int i = (_con.size() - 1 - 1)/2;i>=0;--i)
{
adjust_down(i);
}
}
priority_queque()//默认构造,不然会报错,因为上面的迭代器区间这个函数跟构造函数同名
{}
Compare com;
void adjust_up(size_t child)
{
size_t parent = (child - 1) / 2;
while (child>0)
{
if (com(_con[parent] , _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
size_t 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]))
{
std::swap(_con[child],_con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)//(大堆)堆的插入
{
_con.push_back(x);
adjust_up(_con.size()-1);//尾插后向上跳转
}
void pop()//删除堆顶数据
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}//对顶数据和最后一个数据交换,之后删除最后一个数据,然后向下调整堆
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()const
{
return _con.size();
}
private:
Container _con;
};
}
namespace myspace
{
template<class T>
class less
{
public:
bool operator()(const T& l, const T& r)const
{
return l < r;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& l, const T& r)const
{
return l > r;
}
};
}
int main()
{
int a[]= { 156,132,156,156,31,5,15,31,364,15 };
myspace::priority_queque<int,vector<int>,less<int>> pq(a,a+sizeof(a)/sizeof(int));
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
return 0;
}
来源:https://blog.csdn.net/weixin_49449676/article/details/127202451


猜你喜欢
- java中对集合对象list的几种循环访问的总结如下 1 经典的for循环 public static voi
- 线程概念进程:启动一个应用程序就叫一个进程。 接着又启动一个应用程序,这叫两个进程。每个进程都有一个独立的内存空间;进程也是程序的一次执行过
- Mybatis @Select、foreachforeach属性属性描述item循环体中的具体对象。支持属性的点路径访问,如item.age
- 本文介绍了C# 用什么方法将BitConverter.ToString产生字符串再转换回去,分享给大家,具体如下:byte[]
- 本文实例讲述了Android动画之补间动画。分享给大家供大家参考,具体如下:前面讲了《Android动画之逐帧动画(Frame Animat
- Spring p和c标签注入方式1.编写实体类package com.ming04.pojo;import lombok.AllArgsCo
- 本文实例讲述了Android编程开发中ListView的常见用法。分享给大家供大家参考,具体如下:一、ListView的使用步骤ListVi
- 这篇文章主要介绍了SpringBoot 使用Mybatis分页插件实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 本文实例讲述了C#进程监控方法。分享给大家供大家参考。具体如下:using System;using System.Collections.
- 冒泡排序:就是按索引逐次比较相邻的两个元素,如果大于/小于(取决于需要升序排还是降序排),则置换,否则不做改变这样一轮下来,比较了n-1次,
- 本文实例讲述了java实现基于SMTP发送邮件的方法。分享给大家供大家参考。具体实现方法如下:import java.util.Date;i
- 最近在做关于PDF文档添加水印的功能,折腾了好久,终于好了。以下做个记录:首先会用到iTextSharp组件,大家可以去官网下载,同时我也会
- 记住我功能原理分析还记得前面咱们分析认证流程时,提到的记住我功能吗?现在继续跟踪找到AbstractRememberMeServices对象
- 本文想阐述一下当你开发Android应用并采用RxJava作为你的架构,尤其是有关网络请求时最常见的三种场景。我使用Retrofit来作为网
- 事务介绍一个事务要么同时成功,要么同时失败特性Atomic原子性 事务是由一个或多个活动组成的一个工作单元。原子性确保事务中的所有操作全部发
- 首先是main.xml文件代码如下:<LinearLayout xmlns:android="http://schemas.
- 一、使用注解实现自定义映射关系当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用 @
- 目前知道的情况被调用的C/C++函数只能是全局函数 不能调用类中的成员方法被调用的C函数必须使用extern “C“包含,保证采用的导出函数
- DAO层测试难点可重复性,每次运行单元测试,得到的数据是重复的独立性,测试数据与实际数据相互独立数据库中脏数据预处理不能给数据库中数据带来变
- 折半搜索,也称二分查找算法、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。A 搜素过程从数组的中间元素开始,如果中间元素正好是要查