如何用C++实现栈

栈的定义
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 ——百度百科
简单定义:栈就是一种只允许在表尾进行插入和删除操作的线性表
如何理解栈的概念
① 举一个生活中的例子:我在一个储物箱中,堆了一堆衣服,我的一件球衣在最下面,而我要拿这件衣服,就意味着我必须将上面的衣服全部拿出来才可以,但是由于箱子只有一个口,我也只能从上面拿东西,心里还默默想着,当初就不该将球衣早早的放进去,导致结果就是先进后出!
你就不能举个计算机中的例子?这就安排!
② 计算机中很多操作都是使用栈的原理来实现的,我们就比如常见的浏览器中的 “前进键” “后退键” 就可以利用栈的原理来实现,我们来用图说明一下
我们想要实现前进后退,可以使用两个栈(暂时称作 M、N)来实现
我们分别浏览了页面A、页面B、页面C,所以我们将这些页面依次压入栈,即图中打开页面部分
当用户点击后退时,我们需要退回到页面B中去,但是由于页面C在B上方,我们就必须将页面C从栈M中先弹出,放到栈N中,即图中后退部分
但是如果用户突然又想回到页面C去,原理相似的,只需要把栈N中的页面C弹出,重新压入栈M即可
而如果用户在浏览B界面的时候,打开了新的界面D,那么C就无法通过前进后退访问了,所以栈M中压入页面D的同时还需要清空栈N

栈的术语说明
栈顶:允许进行插入和进行删除操作的一段成为栈顶
栈底:表的另一端称为栈底 (第一个元素进入的位置)
压栈:在栈顶位置插入元素的操作叫做压栈,或入栈、进栈
出栈:删除栈顶元素的操作叫做出栈,也叫作弹栈,或者退栈
空栈:不含元素的空表
栈溢出:当栈满的时候,如果再有元素压栈,则发生上溢,当栈空的时候,再出栈则发生下溢

栈的抽象数据类型
#ifndef _STACK_H_
#define _STACK_H_
#include <exception>
using namespace std;
template <class T>
class Stack {
public:
virtual bool empty() const = 0;
virtual int size() const = 0;
virtual void push(const T &x) = 0;
virtual T pop() = 0;
virtual T getTop() const = 0;
virtual void clear() =0;
virtual ~Stack() {}
};
/*
自定义异常类
*/
// 用于检查范围的有效性
class outOfRange:public exception {
public:
const char* what()const throw()
{ return "ERROR! OUT OF RANGE.\n"; }
};
// 用于检查长度的有效性
class badSize:public exception {
public:
const char* what()const throw()
{ return "ERROR! BAD SIZE.\n"; }
};
#endif
顺序栈——栈的顺序存储结构
开头我们就已经提过了,栈实际上就是一种线性表的特例,所以栈的实现和线性表一样,均使用数组实现,我们使用一个一维数组来存储元素,那么总得有个头阿,我们就需要确定栈底的位置,通常我们选择 0 的一端作为栈底,这样更加方便理解与操作,特别的是,我们设置了一个整型变量top 用来存放栈顶元素的位置(下标),也称作栈顶指针
(一) 顺序栈的类型描述
初始的时候,给top赋值-1,表示栈为空,元素进栈以后,top + 1,元素出栈后,top - 1
// array-based stack: definition and implementation for some methods
#ifndef _SEQSTACK_H_
#define _SEQSTACK_H_
#include "Stack.h"
template <class T>
class seqStack : public Stack<T> {
private:
T * data;
int top;
int maxSize;
void resize();
public:
seqStack(int initSize = 100) {
if(initSize<=0) throw badSize();
data = new T[initSize];
maxSize = initSize ;
top = -1;
}
~seqStack(){ delete [] data;}
bool empty() const{ return top == -1;}
int size() const{ return top + 1; }
void clear() { top = -1; } // 清空栈内容
void push(const T &value);
T pop();
T getTop() const;
};
#endif
(二) 进栈
template <class T>
void seqStack<T>::push(const T &value) {
if (top == maxSize - 1) resize();
data[++top] = value;
}
(三) 出栈
template <class T>
T seqStack<T>::pop() {
if(empty())throw outOfRange();
return data[top--];
}
(四) 取栈顶元素
template <class T>
T seqStack<T>::getTop() const{
if(empty())throw outOfRange();
return data[top];
}
(五) 扩容
template <class T>
void seqStack<T>::resize(){
T * tmp = data;
data = new T[2 * maxSize];
for (int i = 0; i < maxSize; ++i)
data[i] = tmp[i];
maxSize *= 2;
delete[] tmp;
}
(六) 两栈共享空间
栈这种数据结构相比较于线性表,没了有插入和删除的时候需要移动元素的情况,但是仍然有一个比较大的不足,那就是我们必须事先分配空间大小,如果一旦空间满了,再有元素近栈就必须使用编程手段对数组进行扩容,还是比较麻烦的
而有时候我们往往需要多个栈,我们之前的处理手段就是尽量的根据实际问题设计大小合适的数组,但是这显然是有一定难度的,而且常常是这样的,一个栈已经满了,而另一个栈可能还空着很多空间,如果能将那些空闲的位置利用起来就好了,而我们下面就要来提到一个这样的技巧的思路

我们其实就是将两个栈的栈底全部放到了,数组的两端,然后两个栈处于相向位置,逐渐向中间靠拢,只要两个top指针不相遇,两个栈就可以一直用
链栈——栈的链式存储结构
链栈就是使用链式存储结构的栈,和我们在单链表中的链式存储的感觉相似,我们会设置一个指向栈顶的指针top,同时当top == NULL时为空栈

(一) 链栈的类型定义
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
#include <iostream>
#include "Stack.h"
template <class T>
class linkStack : public Stack<T>
{
private:
struct Node {
T data;
Node* next;
Node(){ next = NULL; }
Node(const T &value, Node *p = NULL){ data = value; next = p;}
};
Node* top;
public:
linkStack(){ top = NULL; }
~linkStack(){ clear(); }
void clear();
bool empty()const{ return top == NULL; }
int size()const;
void push(const T &value);
T pop();
T getTop()const;
};
#endif
(二) 清空栈
template <class T>
void linkStack<T>::clear() {
Node *p;
while (top != NULL) {
p = top;
top = top->next;
delete p;
}
}
(三) 求栈中元素个数
template <class T>
int linkStack<T>::size()const {
Node *p = top;
int count = 0;
while (p){
count++;
p = p->next;
}
return count;
}
(四) 进栈
template <class T>
void linkStack<T>::push(const T &value) {
Node *p = new Node(value, top);
top = p;
}
(五) 出栈
template <class T>
T linkStack<T>::pop() {
if (empty())throw outOfRange();
Node *p = top;
T value = p->data;
top = top->next;
delete p;
return value;
}
(六) 获取栈顶元素
template <class T>
T linkStack<T>::getTop() const {
if(empty())throw outOfRange();
return top->data;
}
结尾:
如果文章中有什么不足,或者错误的地方,欢迎大家留言分享想法,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
在这里的我们素不相识,却都在为了自己的梦而努力 ❤
一个坚持推送原创开发技术文章的公众号:理想二旬不止

如何用C++实现栈的更多相关文章
- Python 如何用列表实现栈和队列?
1.栈结构,其实就是一个后进先出的一个线性表,只能在栈顶压入或弹出元素.用列表表示栈,则向栈中压入元素,可以用列表的append()方法来实现,弹出栈顶元素可以用列表的pop()方法实现. >& ...
- 4-29 c语言之栈,队列,双向链表
今天学习了数据结构中栈,队列的知识 相对于单链表来说,栈和队列就是添加的方式不同,队列就相当于排队,先排队的先出来(FIFO),而栈就相当于弹夹,先压进去的子弹后出来(FILO). 首先看一下栈(St ...
- 栈 队列 hash表 堆 算法模板和相关题目
什么是栈(Stack)? 栈(stack)是一种采用后进先出(LIFO,last in first out)策略的抽象数据结构.比如物流装车,后装的货物先卸,先转的货物后卸.栈在数据结构中的地位很重要 ...
- 前、中、后序遍历随意两种是否能确定一个二叉树?理由? && 栈和队列的特点和区别
前序和后序不能确定二叉树理由:前序和后序在本质上都是将父节点与子结点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树. 由二叉树的中序和前序遍历序列 ...
- MEAN stack 做网站【1】
做一个小project,学习如何用MEAN技术栈来搭建网站. JavaScript新手,不足之处,请指出.(系统为win10) 搭建环境: 安装Node.JS (略过) 安装MySQL,MongoDB ...
- Java数据结构和算法(七)——链表
前面博客我们在讲解数组中,知道数组作为数据存储结构有一定的缺陷.在无序数组中,搜索性能差,在有序数组中,插入效率又很低,而且这两种数组的删除效率都很低,并且数组在创建后,其大小是固定了,设置的过大会造 ...
- java0618
1. java的基本数据类型,各占多少字节? byte 8位 short 16位 int 32位 long 64位 float 32位 double 64位 boolean 1位 char 16位 2 ...
- linux安全机制学习【转】
转自:http://blog.csdn.net/qq_20307987/article/details/51307820 曾经一度想学来着,今天看到一个链接,讲的很好,算是写一下加深印象吧 1 栈溢出 ...
- chessy 提高篇系列 阅读笔记
java提高篇(一)—–理解java的三大特性之封装 封装的好处, 汇聚属性和方法 减少修改对 其他处的影响 控制get和set方法. java提高篇(二)—–理解java的三大特性之继承 继承的好处 ...
随机推荐
- dict 的 items() 方法与 iteritems() 方法的不同?
items方法将所有的字典以列表方式返回,其中项在返回时没有特殊的顺序: iteritems方法有相似的作用,但是返回一个迭代器对象
- CSS3 clip-path & clip-path 打破矩形设计的限制
CSS 形状模块标准1(CSS Shapes Module Level 1)这个规范打破了 WEB 中的矩形盒模型的限制,并且将网页设计提升到一个新的高度. 关于 Shapes 规范 shape-ou ...
- 微信小程序“一劳永逸”的接口封装
前言 最近都在研究小程序了,我可以的! 需求 之前都是用vue来开发项目的,接口模块我特意封装了一下.感觉也可以记录一下 小程序的接口虽说简单,但是重复调用那么多,显得不专业(一本正经的胡说八道) 还 ...
- RabbitMQ入门学习系列(四) 发布订阅模式
发布订阅模式 什么时发布订阅模式 把消息发送给多个订阅者.也就是有多个消费端都完整的接收生产者的消息 换句话说 把消息广播给多个消费者 消息模型的核心 RabbitMQ不发送消息给队列,生产者也不知道 ...
- ssm 项目记录用户操作日志和异常日志
借助网上参考的内容,写出自己记录操作日志的心得!! 我用的是ssm项目使用aop记录日志:这里用到了aop的切点 和 自定义注解方式: 1.建好数据表: 数据库记录的字段有: 日志id .操作人.操作 ...
- 表观 | Enhancer | ChIP-seq | 转录因子 | 数据库专题
需要长期更新! 参考:生信修炼手册 enhancer的基本概念: 长度几十到几千bp,作用是提高靶基因活性,属于顺式作用原件,DNA作用到DNA,转录因子就是反式,是结合到DNA的蛋白. 1981年, ...
- How to: Secure Connection Strings When Using Data Source Controls
https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-3.0/dx0f3cf2(v=vs.85) When wo ...
- MQTT(一)C#使用 MQTTnet 快速实现 MQTT 通信(文末有完整Demo下载)
https://blog.csdn.net/panwen1111/article/details/79245161 目录MQTT(一)C#使用 MQTTnet 快速实现 MQTT 通信(文末有完整De ...
- esxi上为基于LVM的centos7的根目录扩容
概念:据说默认centos都是基于LVM的 LVM:LVM是逻辑盘卷管理(Logical Volume Manager)的简称,它是Linux环境下对磁盘分区进行管理的一种机制. LVM 更加详细的说 ...
- Andorid SQLite数据库开发基础教程(3)
Andorid SQLite数据库开发基础教程(3) 数据库生成方式 数据库的生成有两种方式,一种是使用数据库管理工具生成的数据库,我们将此类数据库称为预设数据库,另一种是使用代码生成的数据库. 使用 ...