数据结构 -- 栈的实现

    xiaoxiao2023-10-06  142

    一、栈的概念及结构 --  先进后出!

    栈就是一种特殊线性表,只允许再固定的一端进行元素的插入和删除操作。进行数据的插入和删除操作的一端称为栈顶,另一端为栈底。栈中的数据元素遵守后进先出LIFO(Last in First Out)的原则。压栈:栈的插入操作叫做进栈/压栈/入栈,进去的数据在栈顶。出栈:栈的删除操作。出数据也在栈顶。结构如下:

    二、栈的特点分析

    从栈的操作特性来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。他的操作再数组和链表中都可以实现。但是特定的数据结构是对特定场景的抽象,数组和链表有太多的操作接口,操作上可能会更灵活,但使用的时候不可控情况会出现较多,自然出错也就比较多了。当某个数据集合只涉及在一端插入和删除数据,并且满足先进后出,后出先进的特性,我们就应该首先选“栈”这种数据结构。

    三、栈的实现

    栈接口声明: public interface MyStack<T> { /** * 向栈中添加元素/压栈 * @param t */ void push(T t); /** * 取出栈顶元素 */ T pop(); /** * 查看栈顶元素 * @return 返回栈顶元素 */ T peek(); /** * 返回当前栈的元素个数 * @return */ int size(); /** * 判断当前栈是否为空 * @return 为空返回true,否则返回false */ boolean isEmpty(); }

    2.数组的动态扩容

    以前基于数组实现的栈,是一个固定大小的栈,在初始化时就要指定栈的大小。当栈满之后,就无法再继续添加数据了。想要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满以后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。代码如下:

     

    import java.util.Arrays; public class ArrayStack { private String[] items; //数组 private int maxSize; //存放的最大数量 private int currentSize; //当前元素的个数 //初始化数组 public ArrayStack(int maxSize){ this.items = new String[maxSize]; this.maxSize = maxSize; this.currentSize = 0; } //入栈 public boolean push(String item){ if(currentSize == maxSize) { //判断 int oldCurrent = maxSize; int newCurrent = oldCurrent << 1; //栈大小已经超过int的最大值 if (((newCurrent + 8) - Integer.MAX_VALUE) > 0) { return false; } //数组扩容 maxSize = newCurrent; items = Arrays.copyOf(items, newCurrent); } items[currentSize] = item; ++currentSize; return true; } //出栈 public String pop(){ if(currentSize == 0){ System.out.println("栈为空"); return null; }else{ String temp = items[currentSize-1]; --currentSize; return temp; } } public int getSize(){ return this.currentSize; } //测试 public static void main(String[] args) { ArrayStack arrayStack = new ArrayStack(4); arrayStack.push("1"); arrayStack.push("2"); arrayStack.push("3"); arrayStack.push("4"); arrayStack.push("5"); System.out.println(arrayStack.getSize()); System.out.println(arrayStack.pop()); System.out.println(arrayStack.getSize()); System.out.println(arrayStack.pop()); } }

    3.链表的实现:

     

    /** * 基于链表实现的栈 * @param <T> */ class LinkedStack<T> implements MyStack<T>{ private class ListNode{ T data; ListNode next; public ListNode(T data, ListNode next) { this.data = data; this.next = next; } } //栈顶元素 private ListNode top; //栈中元素个数 private static int currentSize; @Override public void push(T t) { ListNode newNode = new ListNode(t,null); if (top == null){ top = newNode; }else{ newNode.next = top; top = newNode; } currentSize++; } @Override public T pop() { if (currentSize == 0){ System.out.println("栈为空。"); return null; } T value = top.data; top = top.next; currentSize--; return value; } @Override public T peek() { if (currentSize == 0){ System.out.println("栈为空"); return null; } return top.data; } @Override public int size() { return currentSize; } @Override public boolean isEmpty() { return currentSize == 0; } }

     

    四、栈的应用

     

    栈在函数调用中的应用

    操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量。每进入一个函数,就会将其中的临时作为栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

          2.栈在表达式求值中的应用

    利用两个栈,其中一个用来保存操作数,另一个用来保存运算符。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈,若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入操作数栈,继续比较。

         3.栈在括号匹配中的应用

    用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。 当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明未匹配的左括号为非法格式。(括号匹配的具体代码在上一篇博客中)。

    最新回复(0)