文章目录
- 前言
- 1. 栈的基础概要
- 1.1 栈的特征
- 1.2 栈的操作
- 1.3 Java中的栈
- 2. 栈的实现(手写栈)
- 2.1 基于数组实现
- 2.2 基于链表实现
- 2.3 基于LinkedList实现
- 总结
前言
提示:我自己一个人的感觉很好 我并不想要拥有你 除非你比我的独处更加宜人 --瓦尔桑·希雷
1. 栈的基础概要
1.1 栈的特征
栈和队列是比较特殊的线性表,为什么特殊呢?(又称为访问受限的线性表)。栈常用于表达式、符号等运算的基础,也是递归的底层实现。理论上递归可以做的题目栈都是可以做的,只是有些问题用栈相对复杂一些。
栈的底层实现是我们常见的链表或者顺序表,栈与线性表的最大区别在于数据的存取操作被限制了,其插入和删除操作只允许在线性表的一端进行。一般而言,我们把允许操作的一端称为栈顶(top),不可以操作的一端称为栈底(bottom),同时插入元素的操作称为入栈(push)删除元素的操作称为出栈(pop)。若栈中没有任何元素,则称为空栈,栈的结构如图所示:
1.2 栈的操作
栈的常见操作主要有:
- push(E):增加一个元素E
- pop():弹出元素E
- peek():显示栈顶元素,但是不出栈
- empty():判断栈是否为空
我们在设计自己的栈的时候,不管是使用数组还是链表,都需要实现以上的几个方法。
一道经典的题目,入栈顺序为1234,所有可能的出栈序列是什么?
这个题是什么意思呢?举个例子,我们可以先让1,2入栈,然后2,1出栈,再让3,4入栈,然后一次出栈,就可以得到2143的序列。
4个元素的全排列有4!= 24,栈要求符合先进后出,根据这个条件我们可以排除:
1234 √ 1243 √ 1324 √ 1342 √ 1423 × 1432 √
2134 √ 2143 √ 2314 √ 2341 √ 2413 × 2431 √
3124 × 3142 × 3214 √ 3241 √ 3412 × 3421 √
4123 × 4132 × 4213 × 4231 × 4312× 4321 √
14中可能,10中不可能。
1.3 Java中的栈
Java中的栈,uitl中就提供了栈Stack类,使用起来也不复杂,我们看一下例子:
import java.util.Stack;
public class MainTest {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
// 出栈
stack.pop();
while (!stack.isEmpty()) {
// 展示但是不删除
System.out.println(stack.peek());
// 删除
System.out.println(stack.pop());
}
}
}
2. 栈的实现(手写栈)
我们在学习栈的过程中,需要了解一些问题,top栈顶指针的指向,有的地方指向栈顶再往上一个空位,有的地方指向栈顶元素。我们确定好设计就行,(根据题目调整,有时候也可以直接问面试官 top指向哪里)这里采用指向栈顶空位置。
如果我们自己要实现栈,可以使用数组,链表Java中提供了LinkedList三种基本实现方式,我们都可以看一下。
2.1 基于数组实现
采用顺序表实现的栈,内部以数组为基础,实现对元素的存取操作。在应用中还要之一每次入栈之前要确保栈的容量是否足够,不够需要考虑扩容的问题。
我们画一下入栈的过程:
出栈的过程:
出栈先将栈顶元素取出,然后top–
展示代码:
import java.util.Arrays;
class Mystack<T> {
private Object[] stack;
// 栈顶指针
private int top;
public Mystack() {
// 初始长度为10
stack = new Object[10];
}
/**
* 判断是否为空
*
* @return
*/
public boolean isEmpty() {
return top == 0;
}
/**
* 返回栈顶元素但是不删除
*
* @return
*/
public T peek() {
T t = null;
if (top > 0) {
t = (T) stack[top - 1];
}
return t;
}
/**
* 入栈操作
*
* @param t
*/
public void push(T t) {
expandCapacity(top + 1);
stack[top] = t;
top++;
}
public T pop() {
T t = peek();
if (!isEmpty()) {
// 清除元素
stack[top - 1] = null;
top--;
}
return t;
}
/**
* 确保容量
*
* @param size
*/
public void expandCapacity(int size) {
int len = stack.length;
if (size > len) {
size = size * 3 / 2 + 1;
stack = Arrays.copyOf(stack, size);
}
}
public static void main(String[] args) {
Mystack<String> stack = new Mystack<>();
System.out.println(stack.peek());// null
System.out.println(stack.isEmpty());// true
stack.push("java");
stack.push("is");
stack.push("beautiful");
stack.push("language");
System.out.println(stack.pop());// language
System.out.println(stack.isEmpty());// false
System.out.println(stack.peek()); // beautiful
}
}
2.2 基于链表实现
链表用来实现栈也很简单,头插法(在头部操作链表就可以了)。
我们先画个图:
在链表的那一章我们介绍过没有虚拟节点时对链表头部元素进行插入和删除的操作,不记得的可以回顾一下算法通过村第二关-链表青铜笔记_师晓峰的博客-CSDN博客,这里基于链表实现栈的操作时完全一样的。
代码实现:
class ListStack<T> {
// 构造节点
class Node<T> {
public T t;
private Node next;
}
public Node<T> head;
ListStack() {
head = null;
}
/**
* 入栈操作
* @param t
*/
public void push(T t) {
if (t == null) {
throw new IllegalStateException("参数不能为空");
}
// 头节点为空
if (head == null) {
head = new Node<T>();
head.t = t;
head.next = null;
}else {
Node<T> temp = head;
head = new Node<T>();
head.t = t;
head.next = temp;
}
}
/**
* 出栈操作
* @return
*/
public T pop(){
if (head == null) {
return null;
}
T t = head.t;
head = head.next;
return t;
}
public T peek(){
if (head == null) {
return null;
}
return head.t;
}
/**
* 判断是否为空
*
* @return
*/
public boolean isEmpty() {
return head == null;
}
public static void main(String[] args) {
ListStack stack = new ListStack();
System.out.println(stack.isEmpty());// true
stack.push("Java");
stack.push("is");
stack.push("beautiful");
System.out.println(stack.peek());// beautiful
System.out.println(stack.pop());// beautiful
System.out.println(stack.isEmpty());// false
}
}
2.3 基于LinkedList实现
这里就很简单了,直接上代码就行;
import java.util.LinkedList;
/**
* 基于Java的LinkedList来实现栈
* @param <T>
*/
public class LinkedListStack<T> {
private LinkedList<T> ll;
LinkedListStack(){
ll = new LinkedList<T>();
}
/**
* 入栈操作
* @param t
*/
public void push( T t){
ll.addFirst(t);
}
/**
* 出栈但是不删除
* @return
*/
public T peek(){
T t = null;
if (!ll.isEmpty()){
t = ll.peek();
}
return t;
}
public T pop(){
return ll.removeFirst();
}
public boolean isEmpty(){
return ll.isEmpty();
}
public static void main(String[] args) {
LinkedListStack<String> stack = new LinkedListStack();
System.out.println(stack.isEmpty());//true
System.out.println(stack.peek());//null
stack.push("java");
stack.push("is");
stack.push("beautiful");
System.out.println(stack.peek());//beautiful
System.out.println(stack.pop());//beautiful
System.out.println(stack.isEmpty());//false
}
}
总结
提示:记住栈的特性,先进后出