数据结构——栈

简介: 数据结构——栈

介绍

栈是一种用于存储数据的简单数据结构,有点类似链表或者顺序表(统称线性表),栈与线性表的最大区别是

数据的存取的操作,我们可以这样认为栈(Stack)是一种特殊的线性表,其插入和删除操作只允许在线性表的一端

进行,一般而言,把允许操作的一端称为栈顶(Top),不可操作的一端称为栈底(Bottom),同时把插入元素的操作

称为入栈(Push),删除元素的操作称为出栈(Pop)。若栈中没有任何元素,则称为空栈,栈的结构如下图:

由图我们可看成栈只能从栈顶存取元素,同时先进入的元素反而是后出,而栈顶永远指向栈内最顶部的元素。

到此可以给出栈的正式定义:栈(Stack)是一种有序特殊的线性表,只能在表的一端(称为栈顶,top,总是指向栈顶

元素)执行插入和删除操作,最后插入的元素将第一个被删除,因此栈也称为后进先出(Last In First Out,LIFO)或先

进后出(First In Last Out FILO)的线性表。栈的基本操作创建栈,判空,入栈,出栈,获取栈顶元素等,注意栈不

支持对指定位置进行删除,插入。

栈的设计与实现

顺序栈

接口Stack声明如下:

/**
* 栈接口抽象数据类型
*/
publicinterfaceStack<T> {

  /**
   * 栈是否为空
   * @return
   */
  booleanisEmpty();

  /**
   * data元素入栈
   * @param data
   */
  voidpush(Tdata);

  /**
   * 返回栈顶元素,未出栈
   * @return
   */
  Tpeek();

  /**
   * 出栈,返回栈顶元素,同时从栈中移除该元素
   * @return
   */
  Tpop();
}

顺序栈,顾名思义就是采用顺序表实现的的栈,顺序栈的内部以顺序表为基础,实现对元素的存取操作,当然我们

还可以采用内部数组实现顺序栈,在这里我们使用内部数据组来实现栈,至于以顺序表作为基础的栈实现,将以源

码提供。这里先声明一个顺序栈其代码如下,实现Stack和Serializable接口:

**
*顺序栈的实现
*/
publicclassSeqStack<T>implementsStack<T>, Serializable{

   // 栈顶指针, -1 为空栈
   privateinttop=-1;

   //存放元素的数组
   privateT[] array;

   //容量,默认为10
   privateintcapacity=10;

   // 当前存放的个数
   privateintsize;

   publicSeqStack(intcapacity) {
       array= (T[]) newObject[capacity];
   }

   publicSeqStack() {
       array= (T[]) newObject[capacity];
   }

   publicintgetSize() {
       returnthis.size;
   }

   @Override
   publicbooleanisEmpty() {

       returntop==-1;
   }

   @Override
   publicvoidpush(Tdata) {

       if (array.length==size){
           //扩容
           ensureCapacity(size*2+1);
       }
       //添加元素、记录存放个数
       array[++top] =data;
       size++;
   }

   @Override
   publicTpeek() {

       //判断是否为空栈
       if (isEmpty()){
           thrownewEmptyStackException();
       }

       returnarray[top];
   }

   @Override
   publicTpop() {

       //判断是否为空栈
       if (isEmpty()){
           thrownewEmptyStackException();
       }

       size--;
       returnarray[top--];
   }

   /**
    * 扩容方法
    * @param capacity
    */
   privatevoidensureCapacity(intcapacity) {

       // 如果需要拓展的容量比现在数组的容量还小,则无需扩容
       if (capacity<size){
           return;
       }

       //先将数据存起来
       T[] old=array;
       array= (T[]) newObject[capacity];
       for (inti=0; i<old.length ;i++){
           array[i] =old[i];
       }

   }

   //测试
   publicstaticvoidmain(String[] args) {

       SeqStack<Object>stack=newSeqStack<>();
       stack.push("A");
       stack.push("B");
       stack.push("C");

       // size在减少,必须记录
       intsize=stack.getSize();
       for (inti=0; i<size ;i++){
           System.out.println(stack.pop());
       }
       System.out.println(stack.getSize());
   }

}

链式栈

我们接着来看看链式栈,所谓的链式栈(Linked Stack),就是采用链式存储结构的栈,由于我们操作的是栈顶一端,因此这里采用单链表(不带头结点)作为基础,直接实现栈的添加,获取,删除等主要操作即可。其操作过程如下图:

无论是插入还是删除直接操作的是链表头部也就是栈顶元素,因此我们只需要使用不带头结点的单链表即可。

/**
* 链式栈实现类
* @param <T>
*/
publicclassLinkedStack<T>  implementsStack<T>, Serializable{

   privateintsize;

   privateNode<T>top;

   publicintgetSize() {
       returnsize;
   }

   @Override
   publicbooleanisEmpty() {
       returntop==null||top.data==null;
   }

   @Override
   publicvoidpush(Tdata) {

       if (data==null){
           thrownewNullPointerException();
       }

       //调用pop()后top可能为null
       if (top==null){
           top=newNode<>(data, null);

       }elseif(top.data==null){
           //有头节点,但是内容为空
           top.data=data;

       }else{
           //加入子节点
           Node<T>p=newNode<T>(data, top);
           //更新栈顶
           top=p;
       }
       size++;

   }

   @Override
   publicTpeek() {

       if (isEmpty()){
           thrownewEmptyStackException();
       }

       returntop.data;
   }

   @Override
   publicTpop() {

       if (isEmpty()){
           thrownewEmptyStackException();
       }

       Tdata=top.data;
       //获取上一个节点
       top=top.top;
       size--;
       returndata;
   }

   publicstaticvoidmain(String[] args) {

       LinkedStack<Object>stack=newLinkedStack<>();
       stack.push("A");
       stack.push("B");
       stack.push("C");

       // size在减少,必须记录
       intsize=stack.getSize();
       for (inti=0; i<size ;i++){
           System.out.println(stack.peek());
           stack.pop();
       }
       System.out.println(stack.getSize());

   }

}

节点类

/**
* 节点类
* @param <T>
*/
publicclassNode<T> {

   publicTdata;

   publicNode<T>top;

   publicNode(Tdata, Node<T>top) {
       this.data=data;
       this.top=top;
   }
}

栈的应用

栈是一种很重要的数据结构,在计算机中有着很广泛的应用,如下一些操作都应用到了栈。

  • 符号匹配
  • 中缀表达式转换为后缀表达式
  • 计算后缀表达式
  • 实现函数的嵌套调用
  • HTML和XML文件中的标签匹配
  • 网页浏览器中已访问页面的历史记录

接下来我们分别对符合匹配进行简单的分析,以加深我们对栈的理解。

符号匹配

在编写程序的过程中,我们经常会遇到诸如圆括号“()”与花括号“{}”,这些符号都必须是左右匹配的,这就是我们所说的符合匹配类型,当然符合不仅需要个数相等,而且需要先左后右的依次出现,否则就不符合匹配规则,如“)(”,明显是错误的匹配,而“()”才是正确的匹配。有时候符合如括号还会嵌套出现,如“9-(5+(5+1))”,而嵌套的匹配原则是一个右括号与其前面最近的一个括号匹配,事实上编译器帮我检查语法错误是也是执行一样的匹配原理,而这一系列操作都需要借助栈来完成,接下来我们使用栈来实现括号”()”是否匹配的检测。

判断原则如下(str=”((5-3)*8-2)”):

  1. 设置str是一个表达式字符串,从左到右依次对字符串str中的每个字符char进行语法检测,如果char是,左括号则入栈,如果char是右括号则出栈(有一对匹配就可以去匹配一个左括号,因此可以出栈),若此时出栈的字符char为左括号,则说明这一对括号匹配正常,如果此时栈为空或者出栈字符不为左括号,则表示缺少与char匹配的左括号,即目前不完整。
  2. 重复执行a操作,直到str检测结束,如果此时栈为空,则全部括号匹配,如果栈中还有左括号,是说明缺少右括号。

整个检测算法的执行流程如下图:

接着我们用栈作为存储容器通过代码来实现这个过程

/**
* 表达式校验
*/
publicclassCheckExpression  {

   publicstaticStringisValid(Stringexpstr){

       LinkedStack<String>stack=newLinkedStack<String>();
       inti=0;
       
       while (i<expstr.length()){

           charc=expstr.charAt(i);
           i++;

           switch (c){
               case'(':
                   //入栈
                   stack.push(String.valueOf(c));
                   break;
               case')':
                   //出栈
                   stack.pop();
                   break;
           }
       }

       //最后检测是否为空,为空则检测通过
       if(stack.isEmpty()){
           return"check pass!";
       }
       else{
           return"check exception!";
       }

   }

   publicstaticvoidmain(String[] args) {

       Stringstr="(((5-3)*8-2)";
       System.out.println(isValid(str));

   }

}

目录
相关文章
|
3月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
332 9
|
3月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
55 1
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
4天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
44 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
46 9
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
38 7
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
116 21
|
3月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

热门文章

最新文章