线性表:栈
栈的定义
栈(Stack)是一种特殊的线性表,它只允许在一端进行插入(称为入栈,Push)和删除(称为出栈,Pop)操作。这一端被称为栈顶(Top),另一端则被称为栈底(Bottom)。
基本概念
-
先进后出(FILO - First In Last Out)原则:就像一个堆满物品的桶,先放入的物品被压在底部,后放入的物品在顶部。只有先取出顶部的物品,才能逐步取出底部先放入的物品。比如往一个桶里依次放入篮球 1、篮球 2、篮球 3,要取出时,只能先取出篮球 3,然后是篮球 2,最后是篮球 1。如下图所示:
-
栈顶和栈底:栈顶是进行操作的一端,栈底则是相对固定的一端。栈顶元素是当前可以操作的元素。
-
空栈:当栈中没有任何元素时,称为空栈。
-
栈的长度:栈中元素的个数。
-
顺序栈和链式栈:顺序栈是使用数组来实现的栈,链式栈则是使用链表来实现的栈。今天讲解的是链式栈。
在实际应用中,栈的这种特殊结构使得它在很多场景中发挥着重要作用,比如函数调用、表达式求值、回溯算法等。
栈的操作
1.入栈(Push)操作
入栈操作是将一个元素添加到栈顶的过程。
在顺序栈中,首先需要检查栈是否已满。如果未满,将新元素放在栈顶指针所指的位置,然后将栈顶指针向上移动一位。
例如,假设我们有一个容量为 5 的顺序栈,当前栈中的元素为 10、20、30,栈顶指针指向位置 3。现在要将元素 40 入栈,操作如下:
- 检查栈未满,当前还有空间。
- 将 40 存储在位置 3 。
- 栈顶指针加 1,指向位置 4 。
在链式栈中,入栈操作相对简单。创建一个新的节点,将新元素存储在该节点中,然后让新节点的指针指向当前的栈顶节点,更新栈顶指针使其指向新节点。
以下是用 Java 语言实现链式栈入栈操作的示例代码:
import org.w3c.dom.Node;
import java.util.NoSuchElementException;
public class Linked_Stack<E> { // 链表实现
private final Node<E> head = new Node<E>(null);
private static class Node<E>{
private E e;
private Node<E> next;
public Node(E e) {
this.e = e;
}
}
public void push(E e) {
Node <E> node = new Node <> (e);
node.next = head.next;
head.next = node;
}}
入栈操作是栈的基本操作之一,它为后续的出栈、栈的遍历和其他相关操作提供了基础。
2.出栈(Pop)操作
出栈操作是从栈顶删除一个元素的过程。
在顺序栈中,首先需要检查栈是否为空。如果不为空,取出栈顶元素,然后将栈顶指针向下移动一位。
例如,有一个顺序栈,其中的元素为 50、60、70,栈顶指针指向位置 2 。进行出栈操作时:
- 检查栈不为空。
- 取出位置 2 的元素 70 。
- 栈顶指针减 1,指向位置 1 。
在链式栈中,出栈操作是先检查栈是否为空。若不为空,取出栈顶节点的数据,更新栈顶指针使其指向下一个节点,然后释放原栈顶节点的内存。
以下是用 Java 语言实现链式栈出栈操作的示例代码:
public E pop() {
if (isEmpty()) throw new NoSuchElementException("栈为空");
E e = head.next.e;
head.next = head.next.next;
return e;
}
出栈操作与入栈操作相辅相成,共同构成了栈的基本功能,常用于解决各种需要后进先出逻辑的问题,比如撤销操作、表达式计算等。
3.判空(is_Empty)操作
判空操作用于判断栈是否为空。
在顺序栈中,通常通过检查栈顶指针的值来判断。如果栈顶指针为 -1(假设初始时栈顶指针为 -1 ),则栈为空。
例如,一个顺序栈,其栈顶指针为 -1 ,表示此时栈中没有任何元素,即栈为空。
在链式栈中,判空操作则是检查栈顶指针是否为 NULL
。如果为 NULL
,则栈为空。
比如,一个链式栈的栈顶指针为 NULL
,意味着没有任何节点存在于栈中,栈处于空的状态。
以下是用 Java 语言实现链式栈判空操作的示例代码:
public boolean isEmpty(){
return head.next == null;
}
4.读取栈顶元素(Peek)
- 此操作用于获取栈顶元素的值,但不会将其从栈中删除。
- 例如,在一个栈中存储着 10、20、30 ,通过 Peek 操作可以获取到 30 ,但栈中的元素不变。
以下是用 Java 实现读取栈顶元素(Peek)操作的示例代码:
public E peek() {
if (head.next == null) {
throw new NoSuchElementException("empty stack");
}
return head.next.e;
}
5.清空栈(Clear)操作
- 将栈中的所有元素删除,使栈回到初始的空状态。
public void clear() {
Node current = head.next;
while (current!= null) {
Node temp = current;
current = current.next;
temp.next = null;
temp = null;
}
head.next = null;
}
通过遍历链表,依次释放每个节点的内存,这样可以确保链表栈中的所有节点都被正确释放,实现了真正的清空操作。
6.获取栈的长度(GetLength)操作
- 返回栈中当前元素的个数。
以下是一个使用链表实现的栈的 GetLength
方法的 Java 示例代码:
public int getLength() {
int length = 0;
Node<E> current = head.next;
while (current!= null) {
length++;
current = current.next;
}
return length;
}
完整链式栈代码:
import org.w3c.dom.Node;
import java.util.NoSuchElementException;
public class Linked_Stack<E> { // 链表实现
private Node<E> head = new Node<E>(null);
public void push(E e) {
Node <E> node = new Node <> (e);
node.next = head.next;
head.next = node;
}
public E pop() {
if (isEmpty()) throw new NoSuchElementException("栈为空");
E e = head.next.e;
head.next = head.next.next;
return e;
}
public boolean isEmpty(){
return head.next == null;
}
private static class Node<E>{
private E e;
private Node<E> next;
public Node(E e) {
this.e = e;
}
}
public void clear() {
Node current = head.next;
while (current!= null) {
Node temp = current;
current = current.next;
temp.next = null;
temp = null;
}
head.next = null;
}
public E peek() {
if (head.next == null) {
throw new NoSuchElementException("empty stack");
}
return head.next.e;
}
public int getLength() {
int length = 0;
Node<E> current = head.next;
while (current!= null) {
length++;
current = current.next;
}
return length;
}
}
代码测试:
public static void main(String[] args) {
Linked_Stack<String> st = new Linked_Stack<>();
st.push("A");
st.push("B");
st.push("C");
st.push("D");
st.clear();
st.push("Q");
System.out.println(st.peek());
System.out.println(st.getLength());
while(!st.isEmpty()){
System.out.println(st.pop());
}
System.out.println(st.getLength());
}
/*输出:
Q
1
Q
0
进程已结束,退出代码为 0
*/