想清楚映射规则,栈、队列、双端队列的实现都差不多

今天开始,啃读算法导论第10章。既然是啃就要有啃的样子,我决定将例题和习题全部用C++实现一遍,总结同一类问题的共性。
10.1节介绍了栈,队列,双端队列及一些组合形式,为了突出体现思路,让代码更加简洁明了,暂且另元素的类型是int,存储结构都采用定长数组吧。

  • 例题1 :栈
    下面的代码就实现了一个简单的栈,栈内可容纳元素个数有上限。在实现每个类型的时候都应该问问自己,为什么需要维护这些数据成员,多一个会不会更好?少一个可行吗?

    因为栈是一种逻辑数据结构,而每种逻辑数据结构的实现都需要依赖一种物理存储结构的支持,这里我选择了数组,所以我需要维护一个数组及其总长度(m_array和m_totalLength)。

    由于Push、Pop、Top方法中待处理元素的位置信息,不是由参数给定的,而是由栈自身维护的,所以我还需要记录当前待处理元素(这里指的是栈顶元素)的下标(m_top),其取值范围是[-1, m_totalLength-1]。

    m_top的作用之一是用来计算栈顶元素所对应的数组元素的下标f(m_top),从而将逻辑层操作转化成存储层的操作,这里我将映射法则定义为f(m_top) = m_top。

    m_top的作用之二是计算:当前栈内元素个数 = m_top + 1,从而判断栈是否为空或已满。

    这已经是数据成员最精简的版本,一个也不能少。

class Stack
{
public:Stack(int len);         //len表示栈的最大长度~Stack();void Push(int val);     //压栈,若栈已满,报错”Overflow“void Pop();             //出栈,若栈为空,报错”Underflow“int  Top() const;           //读取栈顶,若栈为空,报错”Empty“bool IsEmpty() const;   //栈是否为空bool IsFull()  const;       //栈是否已满private:int* m_array;   //数组const int m_totalLength;    //栈的最大长度,也是数组的总长度int m_top;  //栈顶元素下标 
};Stack::Stack(int len):m_totalLength(len),m_array(new int[len]),m_top(-1)
{}Stack::~Stack()
{delete[] m_array;
}void Stack::Push(int val)
{if(IsFull())cerr << "Overflow" << endl;elsem_array[++m_top] = val;
}void Stack::Pop()
{if(IsEmpty())cerr << "Underflow" << endl;else--m_top;
}int Stack::Top() const
{if(IsEmpty()){cerr << "Empty" << endl;return -1;}elsereturn m_array[m_top];
}bool Stack::IsEmpty() const
{return m_top == -1;
}bool Stack::IsFull()  const
{return m_top == m_totalLength - 1;
}
  • 例题2:队列
    同样的,队列可同时容纳元素个数有上限。我都需要保存哪些数据成员呢?

    数组及其总长度(m_array, m_totalLength)
    入队,出队方法中待处理元素位置信息,这里指的是队头和队尾元素的下标,同样需要由方法内部维护(m_begin, m_end)。

    m_begin, m_end的作用之一是计算队头、队尾所对应数组元素下标,依据逻辑含义应是只增不减的,而依据环形队列的映射法则计算出的数组元素下标是在[0, m_totalLength)区间内循环取值的。
    m_begin, m_end的作用之二是计算当前容纳元素个数,作为判断操作合法性的边界条件。
    具体实现中,在不影响上述两作用的前提下,必须对m_begin,m_end的值加以限制。(见方法Dequeue)

    由于入队,出队方法的被调用频率不同且无关,必须被记录至两个变量中,所以数据成员不能更少了。

class Queue
{
public:Queue(int len);~Queue();void Enqueue(int val);void Dequeue();int Front() const;int Back() const;bool IsEmpty() const;bool IsFull() const;
private:int IndexInArray(indexInQueue) const;
private:int* m_array;const int  m_totalLength;int  m_begin;   //index of front elementint  m_end;     //index of the one next to back element
};Queue::Queue(int len): m_totalLength(len),m_array(new int[len]),m_begin(0),m_end(0)
{
}
Queue::~Queue()
{delete[] m_array;
}void Queue::Enqueue(int val)
{if(IsFull())cerr << "Overflow" << endl;else{m_array[IndexInArray(m_end)] = val;++m_end;}
}void Queue::Dequeue()
{if(IsEmpty())cerr << "Underflow" << endl;else{++m_begin;//限制m_begin,m_end取值范围if(m_begin >= m_totalLength){m_begin -= m_totalLength;m_end -= m_totalLength;}}
}int Queue::Front() const
{if(IsEmpty()){cerr << "Empty" << endl;return -1;}return m_array[IndexInArray(m_begin)];
}int Queue::Back() const
{if(IsEmpty()){cerr << "Empty" << endl;return -1;}return m_array[IndexInArray(m_end - 1)];
}bool Queue::IsEmpty() const
{return m_begin == m_end;
}bool Queue::IsFull() const
{return m_end - m_begin == m_totalLength;
}int Queue::IndexInArray(indexInQueue) const
{assert(indexInQueue >= 0);return indexInQueue % m_totalLength;
}
  • 习题10.1-2 用一个数组存储两个栈,只有当两个栈总长度达到数组总长度时,才算作栈已满。

    我们把数组视为环形数组。

    因为需要把一个数组分给两个栈使用,所以我们需要设置一个分界线,两个栈分别以分界线处两个相邻元素为起点,分别向左右两个方向生长,直到二者总长度达到数组总长度为止。

    如此说来,除了数组和总长度外,还需要保存一个常量(分界线的位置m_divide)和两个变量(两个栈顶元素下标m_top[A],m_top[B])。

    m_top[A]和m_top[B]保存的是逻辑层的栈内下标,它的作用和Stack::m_top以及Queue::m_begin,Queue::m_end都是一样的,一是计算对应的数组元素下标,二是计算边界条件,判断调用合法性。

class DoubleStack
{
public:enum StackID    //用A,B标识两个栈{A = 0,B = 1};DoubleStack(int len);~DoubleStack();void Push(StackID id, int val);void Pop(StackID id);int  Top(StackID id) const;bool IsEmpty(StackID id) const;bool IsFull() const;    //两个栈会同时到达满栈条件,所以这里无需传入StackID
private:int TopIndexInArray(StackID id) const;
private:int* m_array;const int m_totalLength;int  m_top[2];const int m_divide;
};DoubleStack::DoubleStack(int len): m_array(new int[len]),m_totalLength(len),m_divide(len/2) //any value within [0, m_totalLength) is ok
{m_top[A] = -1;m_top[B] = -1;
}
DoubleStack::~DoubleStack()
{delete[] m_array;
}
void DoubleStack::Push(StackID id, int val)
{if(IsFull()){cout << "Overflow" << endl;return;}++ m_top[id];m_array[TopIndexInArray(id)] = val;
}void DoubleStack::Pop(StackID id)
{if(IsEmpty(id)){cerr << "Underflow" << endl;return;}-- m_top[id];
}int  DoubleStack::Top(StackID id) const
{if(IsEmpty(id)){cerr << "Empty" << endl;return -1;}return m_array[TopIndexInArray(id)];
}bool DoubleStack::IsEmpty(StackID id) const
{return m_top[id] == -1;
}bool DoubleStack::IsFull() const
{return m_top[A] + m_top[B] + 2 == m_totalLength;
}int DoubleStack::TopIndexInArray(DoubleStack::StackID id) const
{//let m_array[m_divide] belongs to stack Bif(id == A)return (m_divide - (m_top[A] + 1) + m_totalLength) % m_totalLength;elsereturn (m_divide + m_top[B]) % m_totalLength;
}
  • 以上三个类型的实现有一些共同的逻辑,是时候来总结一下了。
  1. 需要保存哪些信息?和普通线性表不同,调用栈的Push,Pop方法时,被操作的元素所处的位置是调用方和被调用方之间的一种约定,这里约定为栈顶的元素。类似的,双方约定调用队列的Enqueue、Dequeue所操作的元素分别为队尾和队头。既然这一信息不是由参数传入,就需要类型自行维护。总结一下,需要保存的是待操作元素的位置信息。每个栈有一个,每个队列有两个。

  2. 逻辑层信息和存储层信息,选择保存哪一个?假设你有一个菜谱,如果你想照着它炒出一盘菜,你还需要存放和操作食材的厨房。每个逻辑数据结构就好像一个菜谱,如果你想用程序实现它并运行起来,你还需要一个存储和操作它的介质,那就是物理存储结构,比如数组。数组是存储层的结构,而栈是逻辑层的结构,随之而产生的是每个元素都有两个位置信息,我叫它们逻辑层下标和存储层下标。两者构成一对映射,通常是满射。通过映射法则,两者可以互相求得。所以我们只需要在数据成员中保存一方,就可以在需要时计算出另一方。我在上面的实现中,都选择了保存逻辑下标,并将计算存储下标的工作封装在一个函数中,这样做的好处是:1. 几乎每个接口都有逻辑层的处理,但不是每个都需要动用存储层逻辑,所以保存逻辑层信息可以使得接口在逻辑层的处理更直接高效。例如,IsEmpty接口就无需计算存储层下标;2. 当你想改换一种物理存储结构时,例如从数组改为链表,你只需要修改从逻辑层下标到物理层下标的映射过程,灵活性较好。

  3. 映射法则可以很灵活。通常,考虑效率和易读性,栈下标x到数组下标f(x)的映射法则往往定义为:f(x) = x。如果你很任性,就想玩些花样,其实你完全可以把数组当做环形数组,将数组的任意位置作为起始点,比如f(x) = x + 2,就是用数组的第三个元素存储第一个入栈的元素,满栈前最后两个入栈的元素放在数组第一个,第二个元素上。或者你还可以倒过来存储,f(x) = 数组总长度 - 1 - x;甚至你可以毫无规律地将逻辑下标{0, 1, 2, 3}映射成存储下标{3, 1, 2, 0},只要它是满射,只要你开心。为什么我要这样折腾这个映射法则呢?因为有时候,它可以帮助我们灵活地解决问题。例如下面的习题10.1-2,如何将两个栈的逻辑下标映射到一个数组的存储下标上去,既要彼此不干扰,又可以最大限度利用数组,这便是映射法则的用武之地了。具体实现见函数DoubleStack::TopIndexInArray。

  • 10.1-4 同例题2

  • 10.1-5 双端队列

    按照前面总结的规律,需要保存的是待操作元素的位置信息,这里有两个待操作元素的位置会移动,所以保存它们在逻辑层的下标,m_begin, m_end,分别表示队头和队尾元素的下一个元素的下标。

class Deque
{
public:Deque(int len);~Deque();void Push_back(int );void Push_front(int );void Pop_back();void Pop_front();int  Front() const;int  Back()  const;bool IsEmpty() const;bool IsFull() const;
private:int IndexInArray(int indexInDeque) const;
private:int*        m_array;const int   m_totalLength;int         m_begin; //the index of front elementint         m_end;  //the index of one after the back element
};Deque::Deque(int len): m_totalLength(len),m_array(new int[len]),m_begin(len/2),//as long as m_begin = m_end,any value > 0 is okm_end(len/2)
{}
Deque::~Deque()
{delete[] m_array;
}void Deque::Push_back(int val)
{if(IsFull()){cerr << "Overflow" << endl;return;}m_array[IndexInArray(m_end)] = val;//set an upper limit to m_endif(++m_end > 2 * m_totalLength){m_end -= m_totalLength;m_begin -= m_totalLength;}
}void Deque::Push_front(int val)
{if(IsFull()){cerr << "Overflow" << endl;return;}//set a lower limit to m_beginif(--m_begin < 0){m_begin += m_totalLength;m_end += m_totalLength;}m_array[IndexInArray(m_begin)] = val;
}void Deque::Pop_back()
{if(IsEmpty()){cerr << "Underflow" << endl;return;}--m_end;
}void Deque::Pop_front()
{if(IsEmpty()){cerr << "Underflow" << endl;return;}++m_begin;
}int  Deque::Front() const
{if(IsEmpty()){cerr << "Empty" << endl;return -1;}return m_array[IndexInArray(m_begin)];
}int  Deque::Back()  const
{if(IsEmpty()){cerr << "Empty" << endl;return -1;}return m_array[IndexInArray(m_end-1)];
}bool Deque::IsEmpty() const
{return m_begin == m_end;
}
bool Deque::IsFull() const
{return m_end - m_begin == m_totalLength;
}int Deque::IndexInArray(int indexInDeque) const
{return indexInDeque % m_totalLength;
}

转载于:https://www.cnblogs.com/meixiaogua/p/9670744.html

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

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

相关文章

日期加减加1天_2小时整理了13个时间日期函数,动图演示简单易学,收藏备用吧...

Hello&#xff0c;大家好&#xff0c;今天跟大家整理汇总了13个工作中经常用到的日期与时间函数的使用方法&#xff0c;学会它们几乎可以解决所有工作中遇到的&#xff0c;关于日期与时间提取与转换的问题。话不多说&#xff0c;让我们直接开始吧一、了解时间与日期的本质工作中…

T-1-java语言基础

一、Linux的由来和发展Linux是开源的操作系统Linux是服务器端的操作系统java主要用于服务器端二、Linux目录结构&#xff08;与Windows不同&#xff09;文件系统不同&#xff1a;Windows是盘符&#xff0c;Linux是目录。外部设备映射不同&#xff1a;Windows是盘符&#xff0c;…

点云数据显示_vispy 显示 kitti 点云数据

国内博客找了一圈&#xff0c;居然没有发现有用 vispy 做可视化的代码&#xff0c;这里做一个简单的示例&#xff0c;代码大部分来自官方。import numpy as np import vispy.scene from vispy.scene import visuals import sys# Make a canvas and add simple view canvas vis…

python函数定义关键字_Python(2)深入Python函数定义

在Python中&#xff0c;可以定义包含若干参数的函数&#xff0c;这里有几种可用的形式&#xff0c;也可以混合使用&#xff1a;1. 默认参数最常用的一种形式是为一个或多个参数指定默认值。>>> def ask_ok(prompt,retries4,complaintYes or no Please!):while True:ok…

稳定婚姻(tarjan)

传送门 这道题一开始可能以为是二分图匹配……&#xff1f;不过后来发现和二分图没啥大关系。 简单分析之后发现&#xff0c;把夫妻之间连边&#xff08;男性向女性连边&#xff09;&#xff0c;之后再将每对曾经是情侣的人连边&#xff08;女性向男性连边&#xff09;&#xf…

Webpack 常用命令总结以及常用打包压缩方法

前言&#xff1a;Webpack是一款基于node的前端打包工具&#xff0c;它可以将很多静态文件打包起来&#xff0c;自动处理依赖关系后&#xff0c;生成一个.js文件&#xff0c;然后让html来引用&#xff0c;不仅可以做到按需加载&#xff0c;而且可以减少HTTP请求&#xff0c;节约…

在Spring MVC Web应用程序中添加社交登录:单元测试

Spring Social 1.0具有spring-social-test模块&#xff0c;该模块为测试Connect实现和API绑定提供支持。 该模块已从Spring Social 1.1.0中删除&#xff0c;并由 Spring MVC Test框架替换。 问题在于&#xff0c;实际上没有有关为使用Spring Social 1.1.0的应用程序编写单元测…

CSS3 Filter详解(改变模糊度 亮度 透明度等方法)

文章目录 1.模糊2.灰度3.亮度4.对比度5.饱和度6.色相旋转7.反色8.阴影9.透明度10.褐色CSS3 Filter&#xff08;滤镜&#xff09;属性提供了提供模糊和改变元素颜色的功能。CSS3 Fitler 常用于调整图像的渲染、背景或边框显示效果。 -webkit-filter是css3的一个属性&#xff0c;…

laravel中使用offsetSet

首先不用offsetSet方法&#xff0c;使用laravel的硬添加属性如下&#xff1a; 下边使用offsetSet 转载于:https://www.cnblogs.com/qaing123/p/9672241.html

python做自动化控制postman_使用postman+newman+python做接口自动化测试

postman是一款API调试工具&#xff0c;可用于测试接口&#xff0c;相类似的工具还有jmeter、soupUI。通过postmannewmanpython可以批量运行调试接口&#xff0c;达到自动化测试的效果。1、PostMan安装共有两种方式&#xff0c;一种是chrome浏览器上的插件&#xff0c;一种是pos…

转3d视图快捷键_最全Solidworks快捷键,值得收藏!

SOLIDWORKS软件提供了很多实用的快捷键&#xff0c;如果我们熟练掌握这些快捷键无疑可以减轻工作强度和提高工作效率。当然我们也可以点击【工具】-【自定义】-【键盘】&#xff0c;自己定义一些快捷键。本文为大家整理一些常用的快捷键&#xff0c;多使用快捷键可以帮助我们节…

构建前端自动化工作流环境

目标&#xff1a;建一个自动化工作流环境 自动编译 自动合并 自动刷新 自动部署 工作流&#xff1a; 1 全局安装webpack 执行命令&#xff1a; npm install webpack webpack-cli -g 其中webpack-cil 是命令接口工具 2 初始化当前项目&#xff1a;npm init 然后一路回车…

大型布线:Java云应用程序缺少的技术

您是否曾经想过&#xff0c;为什么大多数Java框架中的依赖项注入仅用于本地进程内服务而不是分布式服务&#xff1f; 我最近在2013年EMC世界大会上遇到了Paul Maritz的主题演讲 &#xff08;跳至第32分钟&#xff09;&#xff0c;这使我在云平台的背景下思考了这个问题。 主题…

【单镜头反光相机】CCD、CMOS

CCD、CMOS&#xff1a;图像传感器、电荷耦合器 CCD简介CMOS简介&#xff1a;①被动式 &#xff0c;被动式像素结构&#xff08;Passive Pixel Sensor.简称PPS&#xff09;&#xff0c;又叫无源式。 ②主动式&#xff0c;主动式像素结构&#xff08;Active Pixel Sensor.简称APS…

CSS-定位

CSS定位&#xff1a;相对定位 position: relative 参照物&#xff1a;相对元素原来的位置进行偏移。绝对定位 position:absolute 参照物&#xff1a;往上级元素逐层查找&#xff0c;直到找有position属性的元素&#xff0c;以有position属性的元素为参照物来偏移&#xff0c…

java面向对象中的抽象,类与对象

一、抽象 什么是抽象&#xff1f;将鸽子和麻雀看做是鸟&#xff0c;这是抽象&#xff1b;将整个空调的行为用遥控代替&#xff0c;遥控就可以看做是空调的抽象&#xff1b;将外貌&#xff0c;种族&#xff0c;语言等等形形色色不同的个体看做是人这样一个概念&#xff0c;这也是…

剑灵系统推荐加点_剑灵重制修炼系统 无定式加点打造自我风格

这将是《剑灵(和“标准答案”说再见我们经常可以在论坛中看到类似“求刺客副本修炼”这样的帖子&#xff0c;也有很多热心玩家分享他们的点法。而在这些“标准答案”中&#xff0c;我们又可以经常看到类似“A、B、C必点&#xff0c;剩下的3点修炼随意”这样的论调。但其实这些并…

使用Guava对并发应用程序进行基于对象的微锁定

编写并发Java应用程序时最令人讨厌的问题之一是对线程之间共享的资源的处理&#xff0c;例如Web应用程序的会话和应用程序数据。 结果&#xff0c;如果应用程序的并发级别很低&#xff0c;许多开发人员选择根本不同步这些资源。 例如&#xff0c;不太可能同时访问会话资源&…

java cpu过高排查_涨薪秘籍:JAVA项目排查cpu负载过高

背景我负责的其中一个项目在空负载的情况下&#xff0c;CPU占用率依然保持着100%左右&#xff0c;线上、测试、开发的服务都一样&#xff1b;是什么导致的呢&#xff1f;在开发环境我查看了请求流量&#xff0c;这个流量可以忽略但CPU占用率一直在60%-100%之间浮动。分析问题流…

理财app说明

cordova库和魔蝎库使用的比较多 另外一个使用比较多的就是腾讯的youtu的第三方库&#xff0c;友盟的app统计的功能也有用 推送上使用的有激光推送、魅族推送、小米推送 另外多任务处理方面&#xff0c;有使用blots的库转载于:https://www.cnblogs.com/istar/p/9673249.html