数据结构学习笔记(二):栈(stack)

目录

1 栈的结构形式与操作原则

2 两种类型栈的增删查操作以及代码实现(Java)

2.1 顺序栈增删查操作及其代码实现

2.2 链式栈增删查操作及其代码实现


1 栈的结构形式与操作原则

栈是仅允许在一端进行增删操作的线性表,由于进出都在同一端,栈一般被形象化为一个纵向的容器,第一个进栈的元素位于栈底(bottom),最后进入的元素位于栈顶(top),只有栈顶能进行数据增删的操作。至于查找操作,和链表一样,只能遍历,别无他法。我们用一个存储了八大行星的栈来展示栈的结构形式。

向栈中存入数据的操作被称为压栈(push,也叫进栈),从栈中取出元素的操作被称为弹栈(pop,也叫出栈)。由于栈只有一个数据出入口,因此遵循后进先出(last in first out, 简写为LIFO)的操作原则,因为一旦有新数据进入,原来的栈顶元素就出不来了,如同只有一个门的公共汽车要求先下后上,否则想要下车的人就会被已经上车的人堵住去路。下图展示了压栈的过程,向栈中依次存入八大行星。

准备存入地球时出现了错误,按照八大行星的顺序,地球(Earth)应该在金星(Venus)后面,然后才是火星(Mars),即地球在金星和火星之间。然而,由于栈的LIFO原则,不允许在金星和火星之间插入地球,为了保持顺序,唯一的办法是先弹栈取出火星(先出),再压栈存入地球(后进),然后重复压栈过程,依次存入火星、木星、土星、天王星和海王星,即可得到上述八大行星栈。

从栈的结构形式和增删操作原则来看,栈本质上是一种功能受限的线性表,不如结构形式差不多、但支持从任意位置操作元素的数组和链表灵活。然而,任何事物的存在都有其理由,正由于链表或数组的灵活性,暴露了太多可操作性的接口,对于仅涉及端点数据处理的场景,这是没有必要的,同时也是不安全的。

 

2 两种类型栈的增删查操作以及代码实现(Java)

栈根据存储方式分为两种类型,一是基于顺序存储的顺序栈,二是基于链式存储的链式栈。下面用java模拟八大行星栈的操作。

2.1 顺序栈增删查操作及其代码实现

顺序栈可以用数组来模拟,数组的第0个元素为栈底元素,最后一个元素为栈顶元素,有一个top指针始终指向栈顶元素。由于顺序栈是用数组模拟的,因此top指针对应于数组最后一个非空元素的索引,栈为空时top=-1。数组需要指定容量,因此需要根据栈内数据量扩充或收缩数组的最大容量。

我们在顺序栈中定义了一系列用于增删查的公共方法,包括:push()压栈,pop()弹栈并取回元素,remove()弹栈不取回元素,peek()获取栈顶元素、get(int distance)根据与栈顶距离获取任意位置的元素、printAll()从栈顶到栈底遍历并打印所有的元素。

MyArrayStack.java:准备工作1,创建一个顺序栈的类

package com.notes.data_structure2;

import java.util.EmptyStackException;

public class MyArrayStack<T> {
    private T[] array = (T[]) new Object[8]; // 数组的最大容量初始值设定为8
    private int top = -1; // 栈顶指针,对应数组索引,初始值为-1,此时栈为空

    /**
     * 获取当前 栈 中元素的个数
     * @return top+1
     */
    public int size() {
        return top + 1;
    }

    /**
     * 模拟 压栈 操作
     */
    public void push(T data) {
        if(size() == array.length) {
            expend(); // 如果 栈 的数据量达到数组最大容量,扩容数组
        }
        array[++top] = data; // 栈顶指针上移一位,然后新增元素
    }

    /**
     * 弹栈 并 取回 元素
     * @return 弹出的元素
     */
    public T pop() {
        if(top==-1){ // 栈为空,抛出异常
            throw new EmptyStackException();
        }
        T ele = array[top]; // 将元素取回
        array[top--] = null; // 将元素弹出,栈顶指针下移一位
        if(array.length-size()>8) {
            shrink(); // 数组的空余容量超过8,缩容数组
        }
        return ele;
    }

    /**
     * 出栈不取回
     */
    public void remove() {
        if(top==-1){ // 栈为空,抛出异常
            throw new EmptyStackException();
        }
        array[top--] = null; // 将元素弹出,栈顶指针下移一位
        if(array.length-size()>8) {
            shrink(); // 数组的空余容量超过8,缩容数组
        }
    }

    /**
     * 获取栈顶元素
     * @return 栈顶元素
     */
    public T peek() {
        if(top==-1){ // 栈为空,抛出异常
            throw new EmptyStackException();
        }
       return array[top];
    }

    /**
     * 获取任意位置的元素,从栈顶向下查找
     * @param distance 与栈顶的距离,从栈顶开始数第几个元素,0即栈顶本身
     * @return
     */
    public T get(int distance) throws DistanceOutOfBoundsException {
        // 这里故意写成循环,模拟指针移动的过程
        int temp = top; // 移动的指针,起始位置为栈顶
        if(top-distance>=0) { // 距离未超出栈容量限制
            for(int i=0;i<distance;i++) {
                temp--; // 指针不断下移,直到指定的位置
            }
            return array[temp]; // 取出指定位置的元素
        }else { // 距离超出栈容量限制,抛出自定义异常
            throw new DistanceOutOfBoundsException("栈顶与栈低的距离为"+top);
        }
    }

    public void printAll() {
        if(top==-1){ // 栈为空,抛出异常
            throw new EmptyStackException();
        }else{
            int temp = top; // 移动的指针,起始位置为栈顶
            while (temp>-1) { // 不断下移,直到栈底
                System.out.println(array[temp--]);
            }
        }
    }

    /**
     * 数组的动态扩容
     */
    private void expend() {
        T[] newArray = (T[]) new Object[array.length + 8];
        System.arraycopy(array,0,newArray,0,size());
        array = newArray;
    }

    /**
     * 数组的动态缩容
     */
    private void shrink() {
        int newSize = array.length - 8;
        T[] newArray = (T[]) new Object[newSize];
        System.arraycopy(array,0,newArray,0,newSize);
        array = newArray;
    }
}

DistanceOutOfBoundsException.java:准备工作2,针对get()方法自定义一个异常类

package com.notes.data_structure2;

public class DistanceOutOfBoundsException extends Exception {
    public DistanceOutOfBoundsException(String message) {
        super(message);
    }
}

ArrayStackDemo1.java:模拟后进先出的操作(结合上一节的图示)

package com.notes.data_structure2;

public class ArrayStackDemo1 {
    public static void main(String[] args) throws DistanceOutOfBoundsException {
        // 实例化 MyArrayStack 类
        MyArrayStack<String> myStack = new MyArrayStack<>();

        /**
         * 模拟 后进先出 的过程
         */
        // 压栈:新增元素
        myStack.push("Mercury"); // 水星
        myStack.push("Venus"); // 金星
        myStack.push("Mars"); // 火星
        // myStack.printAll(); // 打印验证

        // 弹栈并取回:将 火星 从栈中弹出并取回(先出,红色箭头)
        String mars = myStack.pop();
        // 压栈:将 地球 放在金星的上面(后进:绿色箭头)
        myStack.push("Earth");
        // 重新压栈:将 火星 重新增加到栈里面
        myStack.push(mars);
        myStack.printAll(); // 打印验证
    }
}

ArrayStackDemo2.java:验证两个查找方法:peek() 和 get(int distance)

package com.notes.data_structure2;

public class ArrayStackDemo2 {
    public static void main(String[] args) throws DistanceOutOfBoundsException {
        MyArrayStack<String> myStack2 = new MyArrayStack<>();
        myStack2.push("Mercury"); // 水星
        myStack2.push("Venus"); // 金星
        myStack2.push("Earth"); // 地球
        myStack2.push("Mars"); // 火星
        myStack2.push("Jupiter"); // 木星
        myStack2.push("Saturn"); // 土星
        myStack2.push("Uranus"); // 天王星
        myStack2.push("Neptune"); // 海王星

        /**
         * 验证查找元素的方法:peek() 和 get(int distance)
         */
        // 验证 peek() 方法
        String topElement = myStack2.peek();
        System.out.println(topElement); // 输出栈顶元素:Neptune

        // 验证 get(int distance) 方法
        String ele1 = myStack2.get(0);
        System.out.println(ele1); // 输出栈顶元素:Neptune
        String ele2 = myStack2.get(3);
        System.out.println(ele2); // 输出从与栈顶距离为3的元素:Jupiter
        String ele3 = myStack2.get(10); // 报出异常:DistanceOutOfBoundsException: 栈顶与栈低的距离为7
        System.out.println(ele3);
    }
}

2.2 链式栈增删查操作及其代码实现

链式栈就是用链表的结构形式组织数据。为方便理解,将其形象化为一个自上而下的链条,从栈顶结点开始,每一个结点都有一个指针指向它的下一个结点,栈底结点的指针为空。所有结点的指针都不允许修改,无法通过改变指针指向实现增删操作。有一个top指针始终指向栈顶结点,用以支持压栈与弹栈的操作。

 

压栈时,先让新结点的指针指向原栈顶,再让栈顶指针指向新结点。弹栈时,让栈顶指针指向原栈顶的下一个结点,使下一个结点成为栈顶。

我们在链式栈中定义了一系列用于增删查的公共方法,包括:push()压栈,pop()弹栈并取回元素,remove()弹栈不取回元素,peek()获取栈顶元素、get(int distance)根据与栈顶距离获取任意位置的元素、printAll()从栈顶到栈底遍历并打印所有的元素。

MyLinkStack.java:准备工作,创建一个链式栈的类

package com.notes.data_structure2;

public class MyLinkedStack<T> {

    private Node top; // 栈顶指针
    private Node topNode; // 栈顶结点

    // 定义一个结点类
    public class Node {
        // 结点的两个要素:数据和指针
        private T data;
        private Node next;

        // 构造方法,初始化data属性
        public Node(T data) {
            this.data = data;
        }

        @Override
        public String toString() { // 用于打印结点中的data属性
            return "Node{" +
                    "data=" + data +
                    '}';
        }
    }

    /**
     * 压栈
     * @param data
     */
    public void push(T data) {
        Node newNode = new Node(data);
        if (top == null) { // 链栈为空,新结点为栈底结点
            newNode.next = null; // 栈底结点的指针为空
        }else { // 新结点指向原来的栈顶结点
            newNode.next = topNode;
        }
        topNode = newNode; // 新结点成为顶结点
        top = topNode; // 栈顶指针指向栈顶结点
    }

    /**
     * 弹栈 并 取回 结点的数据
     * @return
     */
    public T pop() {
        Node node = topNode; // 将原栈顶结点取回
        top = topNode.next; // 栈顶指针指向原栈顶的下一个结点
        topNode = top; // 原栈顶的下一个结点成为栈顶
        return node.data; // 为方便使用,返回结点的数据
    }

    /**
     * 弹栈不取回
     */
    public void remove() {
        top = topNode.next; // 栈顶指针指向原栈顶的下一个结点
        topNode = top; // 原栈顶的下一个结点成为栈顶
    }

    /**
     * 获取栈顶结点的数据
     * @return
     */
    public T peek() {
        return topNode.data; // 为方便使用,返回结点的数据
    }

    /**
     * 获取任意位置的结点,从栈顶向下查找
     * 如果距离超出限制,系统会报空指针异常
     * @param distance 与栈顶的距离,从栈顶开始数第几个元素,0即栈顶本身
     * @return Node
     */
    public T get(int distance) {
        Node temp = top; // temp是一个移动的指针
        for(int i=0;i<distance;i++){
            temp = temp.next; // 不断下移,直到指定位置
        }
        return temp.data; // 为方便使用,返回结点的数据
    }

    /**
     * 遍历打印所的元素
     */
    public void printAll() {
        Node temp = top; // temp是一个移动的指针
        while (temp!=null) { // 不断下移,直到null
            System.out.println(temp); // 打印时自动调用toString()方法
            temp = temp.next;
        }
    }

}

LinkedStackDemo1.java:模拟后进先出的操作(结合上一节的图示)

package com.notes.data_structure2;

public class LinkedStackDemo1 {
    public static void main(String[] args) {
        // 实例化 MyLinkedStack 类
        MyLinkedStack myStack = new MyLinkedStack();

        /**
         * 模拟 后进先出 的过程
         */
        myStack.push("Mercury"); // 水星
        myStack.push("Venus"); // 金星
        myStack.push("Mars"); // 火星
        // myStack.printAll(); // 打印验证

        // 弹栈并取回:将 火星 结点的数据从栈中弹出并取回(先出,红色箭头)
        String marsData = (String) myStack.pop();
        // 压栈:将 地球 放在金星的上面(后进:绿色箭头)
        myStack.push("Earth");
        // 重新压栈:将 火星 重新增加到栈里面
        myStack.push(marsData);
        myStack.printAll(); // 打印验证
    }
}

LinkedStackDemo2.java:验证两个查找方法:peek() 和 get(int distance)

package com.notes.data_structure2;

public class LinkedStackDemo2 {
    public static void main(String[] args) {
        MyLinkedStack<String> myStack2 = new MyLinkedStack<>();
        myStack2.push("Mercury"); // 水星
        myStack2.push("Venus"); // 金星
        myStack2.push("Earth"); // 地球
        myStack2.push("Mars"); // 火星
        myStack2.push("Jupiter"); // 木星
        myStack2.push("Saturn"); // 土星
        myStack2.push("Uranus"); // 天王星
        myStack2.push("Neptune"); // 海王星

        /**
         * 验证查找元素的方法:peek() 和 get(int distance)
         */
        // 验证 peek() 方法
        String topElement = myStack2.peek();
        System.out.println(topElement); // 输出栈顶元素:Neptune

        // 验证 get(int distance) 方法
        String ele1 = myStack2.get(0);
        System.out.println(ele1); // 输出栈顶元素:Neptune
        String ele2 = myStack2.get(3);
        System.out.println(ele2); // 输出从与栈顶距离为3的元素:Jupiter
        String ele3 = myStack2.get(10); // 系统报出空指针异常:NullPointerException
        System.out.println(ele3); 
    }
}

 

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
算法与竞赛(第6章) - c 与stl基础:stack与前中后缀表达式的应用 在竞赛中,算法和数据结构是非常重要的。(stack)是一种常用的数据结构,具有先进后出(LIFO)的特性,它在解决一些问题时具有很大的帮助。 在数学表达式计算中有广泛的应用。一个常见的应用是将中缀表达式转换为后缀表达式,这样可以更方便地进行计算。中缀表达式是我们通常使用的表达式形式,例如 2 + 3 * 4。而后缀表达式是一种更加简洁的表达式形式,例如 2 3 4 * +。 转换过程中,我们使用到了。我们按照运算符的优先级依次扫描中缀表达式的每个元素,如果是操作数,就直接输出到后缀表达式中;如果是运算符,则将其与顶元素进行比较,如果优先级大于等于顶元素,则将其入,否则将顶元素出并输出到后缀表达式中,然后再将当前运算符入。最后,将中剩余的运算符依次出并输出到后缀表达式中。 转换完成后,我们可以使用来计算后缀表达式。遍历后缀表达式的每个元素,如果是操作数,则将其入;如果是运算符,则将顶的两个元素出并进行相应的运算,然后将结果入。最后,中的元素即为计算后的结果。 除了中缀转后缀表达式之外,还有其他的应用。例如,我们可以使用判断一个括号序列是否合法。当遇到左括号时,就将其入,当遇到右括号时,就将其与顶的元素进行匹配,如果匹配成功,则将顶元素出,否则,表示括号序列不合法。 总的来说,是一个非常常用的数据结构,有广泛的应用。在竞赛中,熟练掌握的方法和技巧,能够帮助我们更好地解决一些问题,提高编程的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文程公子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值