前言:
线性表和栈都是我们常用的数据结构,栈可以看成一种特殊状态的线性表,栈的实现,一般都是使用线性表来实现,线性表分为顺序表和链表,使用线性表中的顺序表来实现栈时这种栈被称为顺序栈,相应的使用线性表中的链表来实现栈时这种栈被称为链栈,但是需要说明的是,虽然栈是一种特殊的线性表,但是栈和线性表并不是一种数据结构。这篇文章总结如何使用顺序表实现栈,也就是顺序栈的实现。
一、实现过程
这部分总结顺序栈的实现过程,以及对应方法实现思路,这里提供一个栈的顶层接口IStack,用以声明栈中所应实现的方法,提供该接口不仅可供顺序栈使用,链栈也是可以使用的。下面顺序栈的实现通过实现ISstack接口来完成,详细步骤如下。
1.提供栈接口:IStack
该接口定义了栈必须实现的接口,有如下方法:
/** * 该接口是:栈的顶层接口 * 他的实现类会有:顺序栈、链栈 * * 栈:先入后出 */ public interface IStack { void clear();//清空方法 boolean isEmpty();//判空方法 int length();//栈深度方法 Object peek();//取栈顶元素并返回值,若栈为空,返回null void push(Object object) throws Exception;//入栈操作,元素进入栈顶 Object pop();//将栈顶元素出站 }
2.提供顺序栈的实现:ShunxuStack
提供一个顺序栈的实现类ShunxuStack,顺序栈我们需要使用数组来实现数据的存储,因此提供数组类型的实例变量来存储栈元素:Object[] object,此外栈是一种先入后出的数据结构,因此我们只需要将数组的插入进行倒叙输出就是正常的出栈顺序了,但是我们每次想要插入或者出栈都去遍历一遍数组然后判断插入和删除的位置,无疑是一种很耗费时间的操作,因此我们提供一个指针,用以指向当前栈顶元素所在的位置,当空栈时,我们将该指针指向-1,然后每增加一个元素该指针就加1,每减一个元素该指针就减1,这样我们就可以准确知道栈顶的元素了。
/** * * @author pcc * @version 1.0.0 * @className ShunxuStack:这是一种顺序栈,使用顺序存储结构来实现的栈 * @date 2021-04-20 16:08 */ public class ShunxuStack implements IStack { Object[] objArray; int top = -1;//指向栈顶元素所在下标 public ShunxuStack(int i){ objArray = new Object[i]; } }
3.提供判空(isEmpty)、栈深度(length)等计算方法.
这些方法的实现都比较简单,因此都一起写出来了,判空方法,只需要判断top指针是否是-1即可,因为只有空栈时,top指针的值才会指向-1,计算栈深度,使用top+1即可实现,因为top指针指向的是栈顶的数据元素的下标,所以+1就是栈的数据元素的多少。
@Override public boolean isEmpty() { return top == -1?true:false; } @Override public int length() { return top+1; }
4.提供清空栈的方法:clear()
这里的清空方法的实现是通过lamdba表达式拿到数组里面的所有元素,然后将所有元素都置null来完成的,值得说的是ArrayList的clear方法也是使用的这种方式来作清空操作的。这种清空操作更有利于jvm对垃圾的回收。若是只将数组作置null操作,其实数组中的对象还是有引用链和数组的内存相连,这样会增加垃圾回收时的判断,所以最正确的操作还是将每个元素都置null,代码如下:
@Override public void clear() { Arrays.stream(objArray).forEach(obj ->obj=null); top = -1; }
5.提供获取栈顶元素方法:peek()
该方法用以获取栈顶元素,但并不会对元素有其他任何操作。获取栈顶元素的思路就是空栈返回null,不是空栈就返回top为下标的数据元素即可(top指向栈顶数据元素)。
@Override public Object peek() { if(isEmpty()) return null; return objArray[top]; }
6.提供数据入栈方法:push(Object object)
数据入栈是顺序栈的核心方法,该方法的实现思路:top初始状态为-1,因此我们需要先将top+1得到栈顶元素需要存放的下标,第二步直接将元素根据下标放入 数组即可。
@Override public void push(Object object) throws Exception{ if(top>=objArray.length-1) throw new Exception("StackOverFlowError"); top++; objArray[top] = object; }
7.提供数据元素出栈方法:pop()
栈是一种先入后出的数据结构,最先出栈的只能是栈顶的数据元素,因此我们只需要将指向栈顶数据元素的top,减1即可实现栈的数据元素的出栈,不过这种实现其实并没有真正删除数组中的数据元素,只是通过top指针去将其隐藏了。当然了也可以真正的去实现删除栈顶的数据元素,直接置null即可。
@Override public Object pop() { if(isEmpty()) return null; top--; return objArray[top+1]; }
8.提供顺序栈实现的完整代码
到这里顺序栈的所有方法都全部实现了,可以看到其实无论是思路还是代码栈都是一种很简单的数据结构,也是很容易掌握的一种数据结构。下面展示下完整的栈方法实现的完整代码。
/** * * @author pcc * @version 1.0.0 * @className ShunxuStack:这是一种顺序栈,使用顺序存储结构来实现的栈 * @date 2021-04-20 16:08 */ public class ShunxuStack implements IStack { Object[] objArray; int top = -1;//指向栈顶元素所在下标 public ShunxuStack(int i){ objArray = new Object[i]; } @Override public void clear() { Arrays.stream(objArray).forEach(obj ->obj=null); top = -1; } @Override public boolean isEmpty() { return top == -1?true:false; } @Override public int length() { return top+1; } @Override public Object peek() { if(isEmpty()) return null; return objArray[top]; } @Override public void push(Object object) throws Exception{ if(top>=objArray.length-1) throw new Exception("StackOverFlowError"); top++; objArray[top] = object; } @Override public Object pop() { if(isEmpty()) return null; top--; return objArray[top+1]; } }
二、测试顺序栈的相应方法
第一部分已经详细描述了顺序栈的实现过程,下面就来测试下这些方法是否可以正常使用吧。
1.测试入栈和出栈
创建一个测试类,然后往栈中插入五个数据元素,并依次出栈,若是出栈顺序和入栈顺序相反则说明是正确的了,测试结果如下图。
从结果可以看出,出栈顺序是正常的了,也达到了先进后出的要求了,同时也验证了isEmpty方法也是正常的。
2.验证获取栈顶元素方法peek、栈深度方法length、清空方法clear
还是往栈里面放入原先的五个元素,然后栈顶元素应该是“李四5”,长度应该是5,第二次长度应该是0,如果输出内容是这些说明栈的实现就没有问题了,结果见下图:
从上面的输出结果可以看到,顺序栈的各个方法实现均没有问题。
三、总结
栈这种数据结构有很多的应用场景,比如虚拟机栈等,当然可能各种栈的实现语言不同,但是思想都是一样的,他们的数据结构并没有区别,这篇文章是使用顺序存储结构实现的顺序栈,这种栈我们可以将他看成一种特殊的顺序表(或者叫线性表也可以)—只能在一端进行插入和删除的顺序表。我们知道顺序表的结构特点是查询快、插入(任意位置插入)和删除的时间复杂度是O(n),是很慢的,那么顺序表实现的顺序栈呢?因为顺序栈都是在头部插入删除,且没有遍历的场景,所以顺序表实现的顺序栈的插入和删除的时间复杂度都是O(1),所以顺序栈无论是插入和删除都很快。