栈的概念
栈(Stack)是一种后进先出(LIFO)的数据结构,只允许在栈顶进行插入、删除和读取操作。栈的实现可以通过多种数据结构,最常见的两种是链表和数组。以下将分别介绍这两种实现方式的特点及其优缺点。
基于链表的栈实现
接口定义
public interface Stack<E> {
boolean push(E e);
E pop();
E peek();
boolean isEmpty();
boolean isFull();
}
1. 数据结构设计
在链表实现中,每个节点包含数据和指向下一个节点的指针。栈的顶端通过一个指针(通常称为 top
)来表示,指向链表的头部。每次入栈时,新节点被添加到头部,出栈时则从头部删除节点。
2. 操作步骤
- 入栈(Push):
- 创建一个新节点,将数据存入该节点。
- 将新节点的
next
指针指向当前的top
节点。 - 更新
top
指向新节点。
- 出栈(Pop):
- 检查栈是否为空。如果为空,则返回错误。
- 保存当前
top
节点的数据。 - 更新
top
指向下一个节点(即top.next
)。 - 返回保存的数据并释放原
top
节点的内存。
3. 优缺点
- 优点:
- 动态大小:可以根据需要动态增长,不需要预先定义大小。
- 插入和删除操作时间复杂度为 O(1)。
- 缺点:
- 每个节点需要额外存储指针,内存开销较大。
- 随机访问性能较差,因为必须从头开始遍历链表。
代码示例
import javax.xml.soap.Node;
import java.util.Iterator;
import java.util.Queue;
/**
*基于链表实现的栈
* @param <E>
*/
public class LinkedListStack<E> implements Stack<E> , Iterable<E> {
private int size;
private int capacity;
private Node<E> head = new Node<>(null, null);
class Node<E> {
E value;
Node<E> next;
public Node(E item, Node<E> next) {
this.value = item;
this.next = next;
}
}
public LinkedListStack(int capacity) {
this.capacity = capacity;
}
@Override
//入栈
public boolean push(E t) {
if (isEmpty()) return false;
head.next = new Node<>(t, head.next);
size++;
return true;
}
@Override
//出栈
public E pop() {
if (isEmpty()) return null;
Node<E> temp = head.next;
head.next = temp.next;
size--;
return temp.value;
}
@Override
public E peek() {
if (isEmpty()) return null;
Node<E> temp = head.next;
return temp.value;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean isFull() {
return size == capacity;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
Node<E> current = head.next;
@Override
public boolean hasNext() {
return current!= null;
}
@Override
public E next() {
E value = current.value;
current = current.next;
return value;
}
};
}
}
基于数组的栈实现
1. 数据结构设计
在数组实现中,使用一个固定大小的一维数组来存储栈中的元素。通过一个变量(通常称为 top
)来指示当前栈顶的位置。
2. 操作步骤
- 入栈(Push):
- 检查数组是否已满。如果满,则返回错误(溢出)。
- 将
top
增加1,并将新数据存入数组的top
索引位置。
- 出栈(Pop):
- 检查栈是否为空。如果为空,则返回错误(下溢)。
- 保存当前
top
的数据。 - 将
top
减少1,并返回保存的数据。
3. 优缺点
- 优点:
- 内存使用效率高,不需要额外存储指针。
- 随机访问性能较好,因为可以直接通过索引访问元素。
- 缺点:
- 固定大小:一旦定义大小,无法动态调整,可能导致空间浪费或溢出。
- 插入和删除操作在数组末尾执行时效率较高,但在中间或开头位置执行时性能较差。
代码示例
import java.util.Iterator;
/**
* 基于数组的栈
*/
public class ArraysStack <E> implements Stack<E> ,Iterable<E> {
private E[] arrary;
private int capacity;
private int size;
private int top;//栈顶指针
@SuppressWarnings("all")
public ArraysStack(int capacity) {
this.arrary = (E[]) new Object[capacity];
}
@Override
public boolean push(E e) {
if (isEmpty()) return false;
arrary[top++] = e;
return true;
}
@Override
public E pop() {
if (isEmpty()) return null;
return arrary[--top];
}
@Override
public E peek() {
if (isEmpty()) return null;
return arrary[top - 1];
}
@Override
public boolean isEmpty() {
return top == 0;
}
@Override
public boolean isFull() {
return top == arrary.length;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int p = top;
@Override
public boolean hasNext() {
return p > 0;
}
@Override
public E next() {
return arrary[--p];
}
};
}
}
总结
栈可以通过链表或数组两种方式实现,各有其优缺点。在选择具体实现时,应根据应用场景、内存限制及性能需求来决定使用哪种方式。对于需要动态调整大小且频繁进行插入和删除操作的场合,可以选择链表实现;而对于固定大小且对性能要求较高的场合,则可选择数组实现。