1. 栈

1.1. 栈的简介

是一种 特殊的线性表,具有数据 先进后出 特点。

注意:

  1. stack本身 不支持迭代器操作
    主要原因是因为stack不支持数据的随机访问,必须保证数据先进后出的特点。
  2. stack在CPP库中实现为一种 容器适配器
    所谓容器适配器,是为了适应不同的数据存储而修改底层的数据结构从而达到优化效率的目的。
    参考:C++ STL容器适配器(详解)

1.2. 栈为什么没有提供迭代器???

栈为什么没有提供迭代器???

我们看CPP::stack的文档,发现stack并没有提供迭代器,这是因为stack要保证其自身特性是先进后出,后进先出的特性,如果提供了迭代器,就能够打破这一规则,栈也不再是栈。

1.3. 栈简化模拟实现

栈简化模拟实现

C版简化模拟栈:【数据结构】栈
CPP版简化模拟栈:

#pragma once
#include<vector>
#include<iostream>using namespace std;
namespace szg
{template<class T, class Container = vector<T>>class stack{private:Container _st;public:void push_back(const T& num){_st.push_back(num);}void pop_back(){_st.pop_back();}bool empty(){return _st.empty();}size_t size(){return _st.size();}const T& top(){return _st.back();}};
}

实际上,stack在库中给的容器缺省值是deque,是一个 顺序表与链表的结合体
deque参考文档:stl_deque
deque底层逻辑简介:【CPP】双端队列简介(deque)

1.4. 适配器

适配器

在上面我们模拟实现的栈中,用到了Container,这个适配器。

什么是适配器?所谓适配器就是服务于我们所写的类能够支持其快速底层转换的一种方式。

适配器模式主要应用于,当接口里定义的方法无法满足客户的需求,或者说接口里定义的方法的名称或者方法界面与客户需求有冲突的情况。

两类模式:

对象适配器模式 - 在这种适配器模式中,适配器容纳一个它我包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。

类适配器模式 - 这种适配器模式下,适配器继承自已实现的类(一般多重继承)。

适配器不具备数据速率转换功能。

说到这里,我来简单介绍一种新的容器,专门用来做适配器的容器——deque

2. deque双端队列

deque是一个融合了listvector相关特性的“混合容器”,既有list的快速插入删除特性,也有vector[]随机访问功能,算是“文武双全”的容器。

应用:作为容器适配器使用。

2.1. deque的特性

容器

vector

list

deque

优点

支持下标随机访问,[]效率高

任意位置插入删除效率高

头插、尾插效率高

缺点

头部/中间插入删除效率低,扩容消耗大

不支持[]随机访问

中间插入删除效率一般且[]效率不够极致

2.2. deque的内部构造

deque的内部控制是依靠迭代器实现的。

  • cur是指向当前的访问元素
  • first是指向当前buff的开始元素
  • end是指向当前buff的末尾元素的下一个地址
  • node是指向当前buff在中控数组中存放的位置

2.3. deque的插入删除遍历逻辑

deque的插入和删除:

deque的头插尾插效率是挺高的。这是因为尾插一个元素后,迭代器会看看中控数组最后一个buff是否还有空间,如果有则尾插到最后一个buff,如果没有就新开一个buff插入。头插一个元素,他会现在中控数组的头部开一个buff,因为默认是从中控数组中间开始新增的,所以可以支持常数时间开空间,之后同尾插同理。

中间插入插入元素处理比较麻烦

  • 如果中间插入元素后面所有元素都往后挪动一位,效率比较低

  • 如果中间插入元素改变buff的大小,那么上面[]访问规则就不适用,会很麻烦。

deque的元素[]访问计算规则,且[]访问效率一般

一般情况下,buff每个都是相同大小并且没有头插新元素时候,下标访问可以采用

  • 先找是第几个buff,n/buff.size()
  • 在确定是这个buff中的第几个元素,n%buff.size()

但是如果有头插元素,首先应该减去第一个buff元素的个数,然后在进行上面步骤。

  • n-=buff1.size()
void test_op1()
{srand(time(0));const int N = 1000000;deque<int> dq;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;v.push_back(e);dq.push_back(e);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();sort(dq.begin(), dq.end());int end2 = clock();printf("vector:%d\n", end1 - begin1);printf("deque:%d\n", end2 - begin2);
}
//vector:259
//deque:1263void test_op2()
{srand(time(0));const int N = 1000000;deque<int> dq1;deque<int> dq2;for (int i = 0; i < N; ++i){auto e = rand() + i;dq1.push_back(e);dq2.push_back(e);}int begin1 = clock();sort(dq1.begin(), dq1.end());int end1 = clock();int begin2 = clock();// 拷贝到vectorvector<int> v(dq2.begin(), dq2.end());sort(v.begin(), v.end());dq2.assign(v.begin(), v.end());int end2 = clock();printf("deque sort:%d\n", end1 - begin1);printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}
//deque sort : 1345
//deque copy vector sort, copy back deque : 358

2.4. deque作为stack/queue适配器的优先性?

为什么CPP库中选择deque作为stack/queue的适配器呢?

因为stackqueue都只会用到头插尾插头删尾删,恰好deque头尾插删效率都很好。也可以说,deque就是专门为stack/queue适配专门设计的一个容器。

3. 队列queue

queue是队列的含义,其特点是保证了数据先进先出,后进后出的特点,底层可以用vector、list或deque进行适配。

3.1. 队列的简单模拟实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<deque>namespace szg
{template<class T, class Container = std::deque<T>>class queue{private:Container _con;public:size_t size(){return _con.size();}bool empty(){return _con.empty();}T& front(){return _con.front();}T& back(){return _con.back();}void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}};
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
#include<iostream>int main()
{szg::queue<int> q;for (int i = 0; i < 100; i++){q.push(i);}while (!q.empty()){std::cout << q.front() << " ";q.pop();}std::cout << std::endl;//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99return 0;
}

3.2. 队列与栈的异同?

队列与栈的差异重点在于相同序列进入容器,出数据会有所不同。

容器

数据变化

栈的出数据序列会变化,这取决于栈的push,pop顺序

队列

队列的出数据序列不会变化,是恒定的

4. priority_queue优先级队列 -> 堆

4.1. 简单介绍

优先级队列不是队列,是一种容器适配器,底层是大堆。

在优先级队列提供了一些接口允许公开调用:

4.2. 简单使用

// 优先级队列,默认底层是大堆。
priority_queue<int> ps;
ps.push(2);
ps.push(3);
ps.push(4);
ps.push(1);while (!ps.empty())
{cout << ps.top() << " ";ps.pop();
}//4 3 2 1

我们发现,优先级队列默认是大堆

大堆小堆 升序降序

  1. 优先级队列默认是大堆
  • 小于是大堆
  • 大于是小堆
  1. CPP中STL::sort()
  • 小于是升序
  • 大于是降序

这里区分两个概念:取出结果是有序 真正排序 的区别。

在上面优先级队列中,我们发现while+top取出的数据是有序的,这是因为我们每次取得都是堆顶元素。至于怎么弄的,可以参考C数据结构钟堆删除数据时候得操作,首尾交换,然后size--,我觉得应该是相同的操作。

显然上面不是真正的有序,因为优先级队列中是堆。我们可以用sort这个算法函数来进行区分。

我们发现两个greater一个带括号一个不带,这是什么情况呢?

priority_queue<int, vector<int>, greater<int>> pq;
sort(v.begin(), v.end(), greater<int>());

模板参数与函数参数的需要

要注意优先级队列是一种模板,需要的是类型进行实例化,而sort是函数模板实例化出来的一种函数,需要迭代器区间和具体的比较仿函数对象,而不是仅仅一个仿函数类型就行。

4.3. 仿函数

既然上面用到了仿函数,这里来介绍一下。

仿函数:也称函数对象,仿函数是一种特殊的对象!他的对象可以像函数一样去使用。

下面进行举例:

//仿函数类
struct Less
{
public:bool operator()(const int& x, const int& y){return x < y;}
};void Test()
{Less lessfunc;cout << lessfunc(5,6) << endl; // 结果:1
}

这里需要注意哈,上面的数字5和6作为参数传递给operator(),如果要用引用来接收,必须前面加上const,因为这是引用常量值

//仿函数类
struct Less
{
public:bool operator()(const int& x, const int& y){return x < y;}
};void Test()
{Less lessfunc;cout << lessfunc(5,6) << endl;//1vector<int> v = { 1,3,4,2 };vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}//1 3 4 2cout << endl;sort(v.begin(), v.end(), lessfunc);it = v.begin();while (it != v.end()){cout << *it << " ";it++;}//1 2 3 4
}

然后上面的仿函数类可以加上模板的语法,就跟我们用的greater<int>差不多了。

//仿函数类
template<typename T>
struct Less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};void Test()
{vector<int> v = { 1,3,4,2 };vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;sort(v.begin(), v.end(), Less<int>());it = v.begin();while (it != v.end()){cout << *it << " ";it++;}
}

4.4. 优先级队列模拟实现

#pragma once
#include<vector>
#include<iostream>
using namespace std;template<typename T>
struct Less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};template<typename T>
struct Greater
{
public:bool operator()(const T& x, const T& y){return x > y;}
};template<class T, class Container = vector<T>, class Compare = Greater<T>>
class periority_queue
{
private:Container _con;public:void adjust_up(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[child], _con[parent])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void adjust_down(int parent){Compare com;int child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child + 1], _con[child])){child = child + 1;}if (com(_con[child], _con[parent])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top(){return _con[0];}
};
// 指针模板
template<class T>
struct GreaterPDate
{bool operator()(const T& d1, const T& d2){return *d1 > *d2;}
};void test_priority_queue2()
{priority_queue<Date*, vector<Date*>, GreaterPDate<Date*>> pqptr;Date d1(2024, 4, 14);Date d2(2024, 4, 11);Date d3(2024, 5, 15);pqptr.push(&d1);pqptr.push(&d2);pqptr.push(&d3);while (!pqptr.empty()){cout << *(pqptr.top()) << " ";pqptr.pop();}cout << endl;
}

5. 反向迭代器

反向迭代器,顾名思义,反向迭代器的操作与正向迭代器恰好相反(方向)。

5.1. 简单介绍

下面我用库函数来进行一个简单演示。

std::list<int> l = { 1,2,3,4,5,6 };
std::list<int>::reverse_iterator rit = l.rbegin();
while (rit != l.rend())
{std::cout << *rit << " ";++rit;
}//6 5 4 3 2 1
std::cout << std::endl;

5.2. 反向迭代器的设计思路

  1. 思路1:我们可以像前面实现const迭代器一样写一个类,显然,这样我们即使写模板也需要针对不同的容器进行写不同的反向迭代器。
  2. 思路2:封装iterator,然后重载其部分运算符。

下面来重点介绍第二种思路的实现方式:

这样的好处是,我们只需要设计一个反向迭代器类,就可以根据不同的正向迭代器自由变换其反向迭代器。

5.3. 反向迭代器的模拟实现

#define _CRT_SECURE_NO_WARNINGS 1namespace szg
{template<class Tterator, class Ref, class Ptr>class ReverseIterator{private:Tterator _it;public:typedef ReverseIterator<Tterator, Ref, Ptr> Self;//构造函数ReverseIterator(Tterator it):_it(it){}//解引用Ref operator*(){Tterator temp = _it;temp--;return *temp;}//->函数Ptr operator->(){return &(this->operator*());}//前置++Self& operator++(){--_it;return *this;}//前置--Self& operator--(){++_it;return *this;}//!=函数重载bool operator!=(const Self& s){return _it != s._it;}};
}
#include"List.h"
#include<list>void test()
{szg::list<int> l = { 1, 2, 3, 4, 5, 6 };/*l.push_back(1);l.push_back(2);l.push_back(3);l.push_back(4);l.push_back(5);l.push_back(6);*/szg::list<int>::reverse_iterator rit = l.rbegin();while (rit != l.rend()){std::cout << *rit << " ";++rit;}//6 5 4 3 2 1std::cout << std::endl;
}int main()
{//std::list<int> l = { 1,2,3,4,5,6 };//std::list<int>::reverse_iterator rit = l.rbegin();//while (rit != l.rend())//{//	std::cout << *rit << " ";//	++rit;//}//6 5 4 3 2 1//std::cout << std::endl;test();return 0;
}

5.4. rbegin、rend的解释

在库中,使用的是第一种方式设计的rbegin和rend,为什么呢?没啥意义,感觉单纯与begin,end对称一些。在解引用的时候,一直是解引用的下一个值而已。

5.5. 迭代器的分类

迭代器性质分类

单向

forward_list

++

双向

list

++/--

随机

vector/deque

++/--/+/-

迭代器功能分裂

正向

普通一般的迭代器

反向

反方向的正向迭代器

const

对指向内容只能读不能写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95402.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95402.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95402.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打造专属 React 脚手架:从 0 到 1 开发 CLI 工具

前言: 在前端开发中&#xff0c;重复搭建项目环境是个低效的事儿。要是团队技术栈固定&#xff08;比如 React AntD Zustand TS &#xff09;&#xff0c;每次从零开始配路由、状态管理、UI 组件&#xff0c;既耗时又容易出错。这时候&#xff0c;自定义 CLI 脚手架 就派上…

Python day43

浙大疏锦行 Python day43 import torch import numpy as np import pandas as pd import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torch.utils.data import Da…

python基于Hadoop的超市数据分析系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 摘要&…

如何用 COLMAP 制作 Blender 格式的数据集

如何用 COLMAP 制作 Blender 格式的数据集并划分出 transforms_train.json、transforms_val.json 和 transforms_test.json。 一、什么是 Blender 格式数据集? Blender 格式数据集是 Nerf 和 Nerfstudio 常用的输入格式,其核心是包含了相机内外参的 JSON 文件,一般命名为:…

[GESP202309 六级] 2023年9月GESP C++六级上机题题解,附带讲解视频!

本文为GESP 2023年9月 六级的上机题目详细题解和讲解视频&#xff0c;觉得有帮助或者写的不错可以点个赞。 题目一讲解视频 GESP2023年9月六级上机题一题目二讲解视频 题目一:小羊买饮料 B3873 [GESP202309 六级] 小杨买饮料 - 洛谷 题目大意: 现在超市一共有n种饮料&#…

linux 操作ppt

目录 方法1&#xff1a;用 libreoffice 打开PPT文件 播放脚本&#xff1a; 方法2&#xff1a;用 python-pptx 创建和编辑PPT 方法3&#xff1a;其他方法 在Linux中&#xff0c;可以使用Python通过python-pptx库来创建和编辑PPT文件&#xff0c;但直接播放PPT文件需要借助其…

元数据管理与数据治理平台:Apache Atlas 基本搜索 Basic Search

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 Apache Atlas 框架是一套可扩展的核心基础治理服务&#xff0c;使企业能够有效、高效地满足 Hadoop 中的合规性要求&#xff0c;并支持与整个…

LangChain4J-(1)-Hello World

一、LangChain4J是什么&#xff1f; LangChain4J 是一个专为 Java 生态系统设计的开源框架&#xff0c;用于简化与大语言模型&#xff08;LLM&#xff0c;如 OpenAI 的 GPT 系列、Google 的 Gemini、Anthropic 的 Claude 等&#xff09;的集成和交互。它借鉴了 Python 生态中 L…

HTTPS应用层协议-中间攻击人

HTTPS应用层协议-中间攻击人 • Man-in-the-MiddleAttack&#xff0c;简称“MITM 攻击” 确实&#xff0c;在方案 2/3/4 中&#xff0c;客户端获取到公钥 S 之后&#xff0c;对客户端形成的对称秘钥 X 用服务端给客户端的公钥 S 进行加密&#xff0c;中间人即使窃取到了数据&am…

利用 Makefile 高效启动 VIVADO 软件:深入解析与实践

利用 Makefile 高效启动 VIVADO 软件&#xff1a;深入解析与实践 系列文章目录 1、VMware Workstation Pro安装指南&#xff1a;详细步骤与配置选项说明 2、VMware 下 Ubuntu 操作系统下载与安装指南 3.基于 Ubuntu 的 Linux 系统中 Vivado 2020.1 下载安装教程 文章目录利用 …

[前端算法]排序算法

默认情况下&#xff0c;sort() 会将元素转换为字符串&#xff0c;然后按照 Unicode 编码的顺序进行排序&#xff1a; const fruits [apple, banana, cherry, date]; fruits.sort(); console.log(fruits); // 输出: ["apple", "banana", "cherry"…

C#标签批量打印程序开发

C#标签批量打印程序开发&#xff08;集成Bartender解决方案&#xff09;一、系统架构设计 1. 核心模块划分 public class LabelPrintingSystem {private IDataLoader _dataLoader; // 数据加载器private ITemplateEngine _templateEngine; // 模板引擎private IPrintControl…

ECC的原理、背景、工作机制和数学基础

ECC的原理、背景、工作机制和数学基础摘要&#xff1a;本文首先详细介绍ECC&#xff08;Error-Correcting Code&#xff0c;纠错码&#xff09;的原理&#xff0c;包括背景、工作机制和数学基础。然后&#xff0c;解释ECC在SRAM&#xff08;Static Random-Access Memory&#x…

计算机网络2-2:物理层下面的传输媒体

目录 导引型传输媒体 同轴电缆 双绞线 光纤 电力线 非导引型传输媒体 无线电波 微波 红外线 可见光 无线电频谱管理机构 导引型传输媒体 同轴电缆 双绞线 光纤 光在光纤中传播的基本原理 电力线 非导引型传输媒体 无线电波 微波 红外线 可见光 LiFi(可见光通信) …

Dify 从入门到精通(第 32/100 篇):Dify 的日志分析与监控

Dify 从入门到精通&#xff08;第 32/100 篇&#xff09;&#xff1a;Dify 的日志分析与监控 Dify 入门到精通系列文章目录 第一篇《Dify 究竟是什么&#xff1f;真能开启低代码 AI 应用开发的未来&#xff1f;》介绍了 Dify 的定位与优势第二篇《Dify 的核心组件&#xff1a…

【IntelliJ IDEA】修改堆内存

idea卡顿&#xff0c;鼠标漂移修改idea文件打开 idea 安装路径&#xff0c;【bin】目录下【idea64.exe.vmoptions】文件修改【-Xms】最小内存【-Xmx】最大内存-Xms2048m -Xmx9216midea更改内存设置工具栏帮助更改内存设置设置堆大小上限为 文件 设置的最大内存保存并重启Leslie…

Docker与Docker Compose:容器世界的“单兵作战”与“军团指挥官”

在容器化技术的浪潮中&#xff0c;Docker和Docker Compose如同“双子星”&#xff0c;一个专注于单兵作战&#xff0c;一个擅长军团指挥。它们看似相似&#xff0c;却各司其职。对于开发者来说&#xff0c;理解它们的区别不仅能让代码部署事半功倍&#xff0c;更能避免踩坑。本…

进阶向:Python编写自动化邮件发送程序

Python编写自动化邮件发送程序&#xff1a;从零开始详解在数字化时代&#xff0c;自动化邮件发送功能已成为企业和个人提升工作效率的重要工具。据统计&#xff0c;全球每天发送的商业邮件超过30亿封&#xff0c;其中约40%是通过自动化系统发送的。这种功能被广泛应用于多种场景…

ChatGpt 5系列文章1——编码与智能体

人工智能技术正在以惊人的速度发展&#xff0c;重新定义着开发人员的工作方式。2025年8月&#xff0c;OpenAI正式发布了面向开发人员的GPT-5 一、GPT-5的编码能力突破 GPT-5在关键编码基准测试中创造了行业新纪录(SOTA)&#xff0c;在SWE-bench Verified测试中得分74.9%&…

力扣top100(day02-05)--二叉树 02

102. 二叉树的层序遍历 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right)…