数据结构——栈

简介: 数据结构——栈

介绍

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

数据的存取的操作,我们可以这样认为栈(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));

   }

}

目录
相关文章
|
17天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
91 9
|
7天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
16 1
|
10天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
13天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
15天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
43 4
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
19天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
初步认识栈和队列
初步认识栈和队列
59 10
|
1月前
数据结构(栈与列队)
数据结构(栈与列队)
17 1
|
1月前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
46 3