目录
0.目录
1.线性表 – 数组
2.线性表 – 单向链表
3.栈
栈
栈是一种比较常见的数据结构,是一种较为特殊的线性表。对于栈来说,插入、删除、访问元素只能在栈顶进行。
对栈的基本操作有push(进栈)、pop(出栈)和peek(查询栈顶)。基于这种特性,栈又叫做LIFO(Last In First Out)表,即后进先出。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6bd3deb5494fca8f83fd3669952cede7.png)
因为栈的本质是一个线性表,所以任何实现线性表的数据结构都能实现栈。JDK中的类Stack是继承类Vector。现在我们使用前两节的Array类和Linked类来分别实现栈的常用方法。
定义栈的接口类Stack
我们先对栈的常用方法定义成接口,接口类如下:
public interface Stack<E> {
boolean isEmpty();
int getSize();
/**
* 将元素压入栈顶
* @param e
*/
void push(E e);
/**
* 返回并删除栈顶的值
* @return
*/
E pop();
/**
* 返回栈顶的值
* @return
*/
E peek();
/**
* 返回元素距离栈顶的偏移量(栈顶为0)
* 若元素不在栈中则返回-1
* @param e
* @return
*/
int search(E e);
}
基于Array类的实现
由于动态数组Array对数组尾端的增删改查的时间复杂度为O(1),因此使用Array类实现栈,应当将数组尾端作为栈顶,则push、pop、peek方法的时间复杂度也是O(1)。
实现代码如下:
public class ArrayStack<E> implements Stack<E> {
Array<E> elements;
public ArrayStack() {
elements = new Array<>();
}
public ArrayStack(E e) {
this();
elements.add(e);
}
@Override
public boolean isEmpty() {
return elements.getSize() == 0;
}
@Override
public int getSize() {
return elements.getSize();
}
@Override
public void push(E e) {
elements.add(e);
}
@Override
public E pop() {
E result = elements.getLast();
elements.removeLast();
return result;
}
@Override
public E peek() {
return elements.getLast();
}
@Override
public int search(E e) {
return elements.contains(e) == -1 ? elements.contains(e) : elements.getSize() - 1 -elements.contains(e);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ArrayStack base[");
for (int i = 0; i < elements.getSize(); i++) {
sb.append(elements.get(i));
if (i != elements.getSize() - 1) {
sb.append(", ");
}
}
sb.append("]top");
return sb.toString();
}
}
基于Linked类的实现
由于单链表Linked对链表首端的增删改查的时间复杂度为O(1),因此使用Linked类实现栈,应当将单链表首端作为栈顶,则push、pop、peek方法的时间复杂度也是O(1)。
实现代码如下:
public class LinkedStack<E> implements Stack<E> {
Linked<E> elements;
public LinkedStack() {
elements = new Linked<>();
}
public LinkedStack(E e) {
elements = new Linked<>(e);
}
@Override
public boolean isEmpty() {
return elements.getSize() == 0;
}
@Override
public int getSize() {
return elements.getSize();
}
@Override
public void push(E e) {
elements.addFirst(e);
}
@Override
public E pop() {
E result = elements.getFirst();
elements.removeFirst();
return result;
}
@Override
public E peek() {
return elements.getFirst();
}
@Override
public int search(E e) {
return elements.contains(e);
}
@Override
public String toString() {
String before = "LinkedStack base";
String after = "top";
return elements.toStringDesc(before, after);
}
}
复杂度分析
对于Array实现的栈和Linked实现的栈来说,push、pop、peek操作时间复杂度都是O(1)级别,因此性能上不会有量级的差别。但在实际使用中,还是会有一些区别。原因在于Array类会有resize操作(扩容、缩容),每一次resize都会消耗O(n)的时间复杂度来重新开辟一整块内存空间以及复制元素(均摊下来则是O(1));而Linked类虽然不需要resize,但是每一次新加入元素都需要开辟一小块内存空间存放节点。具体哪种方式效率高,取决于多种因素(操作系统、CPU等),因此不在本节讨论范围之内。