一、什么是栈
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈是一个元素先进后出的数据结构,我们可以把栈结构理解成手枪的弹夹。入栈就是就是将子弹压入弹夹中,后压的子弹总是在先压的子弹上面;而出栈就是开枪,先打出的子弹总是弹夹最上放的子弹,即后压入的子弹;所以先压进的子弹总是最后打出。
二、定义栈接口
栈是一种特殊的线性表,所以栈的接口也继承我们之前定义的 MyList
接口,IStack
接口包含一些栈的基本操作。
public interface IStack<T> extends MyList<T> {
/**
* 元素入栈
*/
void push(T e);
/**
* 弹出栈顶
*/
T pop();
/**
* 是否空栈
*/
boolean empty();
/**
* 栈内元素个数
*/
@Override
int getSize();
/**
* 查看栈顶元素,不弹出
*/
T peek();
}
三、栈的实现
数据的存储结构有两种:顺序存储结构和链式存储结构。堆栈只能对栈首进行操作(入栈/出栈),所以底层的存储结构用链表比数组更好些。为了方便获取元素的前驱,我们使用双链表来实现栈。我们只需要将栈的实现类继承 DoubleLinkedList
类就可以使用双链表进行存储栈的数据了。(DoubleLinkedList
类是我们上一篇双链表的实现类)
public class MyStack<T> extends DoubleLinkedList<T> implements IStack<T> {
}
1、入栈
入栈就是向栈的最上方添加元素,等同于向链表末尾添加元素,所以我们只需要调用双链表的 add
方法即可。
@Override
public void push(T e) {
super.add(e);
}
2、出栈
出栈包含两个操作:获取栈顶元素并将其删除;也就是获取链尾结点并将其删除。
@Override
public T pop() {
if (size <= 0) {
// 如果没有元素直接抛异常
throw new EmptyStackException();
}
// 获取链表尾部结点即栈顶元素
ListNode<T> the = last.pre;
T res = the.data;
// 将链表尾部结点的前面结点作为尾部结点
the.pre.next = last;
// 重新设置尾部哑元的指针
last.pre = the.pre;
the.next = null;
the.pre = null;
size--;
return res;
}
3、其他方法
MyStack
类完整的代码如下:
public class MyStack<T> extends DoubleLinkedList<T> implements IStack<T> {
@Override
public void push(T e) {
super.add(e);
}
@Override
public T pop() {
if (size <= 0) {
// 如果没有元素直接抛异常
throw new EmptyStackException();
}
// 获取链表尾部结点即栈顶元素
ListNode<T> the = last.pre;
T res = the.data;
// 将链表尾部结点的前面结点作为尾部结点
the.pre.next = last;
// 重新设置尾部哑元的指针
last.pre = the.pre;
the.next = null;
the.pre = null;
size--;
return res;
}
@Override
public boolean empty() {
return getSize() == 0;
}
@Override
public T peek() {
if (size <= 0) {
throw new EmptyStackException();
}
return last.pre.data;
}
}
四、关于栈的练习题
1、判断链表是不是回文链表
所谓回文链表就是顺序读和逆序读的结果一样,如果链表中的结点没有指向前驱,我们可以用栈结构来实现。
定义两个指针,慢指针一次移动一个节点,快指针一次移动两个节点。当快指针指向结尾时,慢指针指向链表的中间,循环也就结束了。在移动的过程中,将慢指针指向的元素全部入栈,然后继续移动慢指针和栈中的元素进行比较即可。注意:对链表长度的奇偶性进行处理。
public static boolean isPalindrome(ListNode<Integer> head) {
if (head == null) {
return false;
}
// 如果只有一个元素直接返回 true
if (head.next == null) {
return true;
}
// 定义两个指针,慢指针一次移动一个节点,快指针一次移动两个节点
// 当快指针指向结尾时,慢指针指向链表的中间
ListNode<Integer> slower = head;
ListNode<Integer> faster = head;
// 创建一个栈用于存储链表的前半部分
Stack<Integer> stack = new Stack<>();
// 判断链表的长度的奇偶数 true 为奇数
boolean isOdd = true;
while (faster != null && faster.next != null) {
// 每次将慢指针的元素压入栈底
stack.push(slower.data);
slower = slower.next;
faster = faster.next.next;
if (faster == null) {
isOdd = false;
}
}
// 奇数个节点,slower 还要 next 一下
if (isOdd) {
slower = slower.next;
}
while (!stack.empty() && slower!=null) {
// 每次将栈顶元素弹出与慢指针指向的后半链表的节点比较,不相等则不是回文
if (!stack.pop().equals(slower.data)) {
return false;
} else {
slower = slower.next;
}
}
return true;
}
2、O(1)获取栈的最小值
请设计一个栈,支持 min 方法,可返回栈元素中的最小值。时间复杂度必须为 O(1)。再用一个栈存储最小值。
/**
* 这个栈用于存储最小值
*/
private MyQueue<T> min=new MyQueue<T>();
public T min(){
// 每次获得栈顶元素
return min.peek();
// 每次添加元素时,和这个栈顶元素做比较,小于等于则存储
// 每次弹出元素时,和这个栈顶元素做比较,等于则也将该栈中的元素弹出
}
3、栈排序
请编写一个程序,按升序多栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。给定一个 int[] numbers,其中第一个元素为栈顶,请返回排序后的栈,请注意这是一个栈,意味着排序过程你只能访问第一个元素。
例子:
- 输入:
[1, 2, 3, 4, 5]
- 输出:
[5, 4, 3, 2, 1]
public static ArrayList<Integer> twoStackSort(int[] numbers) {
Stack<Integer> source = new Stack<>();
// 第一个元素作为栈顶进行初始化栈
for (int i = numbers.length - 1; i >= 0; i--) {
source.push(numbers[i]);
}
// 对栈进行排序
Stack<Integer> target = sortToTarget(source);
ArrayList<Integer> list = new ArrayList<>();
// 第一个元素作为栈顶
while (!target.empty()) {
list.add(target.pop());
}
return list;
}
private static Stack<Integer> sortToTarget(Stack<Integer> source) {
// 创建一个临时栈
Stack<Integer> target = new Stack<>();
// 遍历原栈进行排序
while (!source.empty()) {
// 揭开盖子 即弹出栈顶元素
int v1 = source.pop();
// 如果临时栈为空则直接压入
if (!target.empty()) {
// 取出临时栈的栈顶元素
int v2 = target.peek();
if (v1 < v2) {
// 如果盖子小,把大的先回收
source.push(target.pop());
// 直到有盖子的容身之所
while (!target.empty() && v1 < target.peek()) {
source.push(target.pop());
}
}
}
// 把盖子放入临时栈中
target.push(v1);
}
return target;
}
标题:栈(先进后出表)
作者:Yi-Xing
地址:http://47.94.239.232:10014/articles/2020/03/02/1583160485980.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!