栈是一种先进后出的数据结构,我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何元素的栈称为空栈。
1、栈的操作端通常被称为栈顶,另一端被称为栈底。
2、栈的插入操作称为进栈(压栈|push);栈删除操作称为出栈(弹栈|pop)。
根据栈的存储方式,栈可以分为静态栈(数组实现)和动态栈(链表实现)。
对于静态栈,我们一般通过数组来实现的。实现一个栈,里面主要涉及到 2 种状态和 2 种操作。
Java代码实现静态栈
首先我们需要使用到一个数组来存储数据,其次还需要一个变量用于栈的容量大小,还有一个变量来指示栈顶的数据。因此栈的内部数据结构如下所示:
private T data[];// 用数组表示栈元素
private int maxSize;// 栈空间大小(常量)
private int top;// 栈顶指针(指向栈顶元素)
对于栈的构造函数,我们可以如下表示:
public LJStack(int maxSize) {
this.maxSize = maxSize;
this.top = -1;
this.data = (T[]) new Object[maxSize];
}
对于栈空的判断很简单,只需要判断 top 是否等于-1即可:
/**
* 判断栈是否为空
*
* @return
*/
public boolean isEmpty() {
return this.top == (-1) ? true : false;
}
对于栈满的状态判断也很简单,通过 top 的值是否等于栈的容量大小 -1 即:
/**
* 判断栈是否为满了
*
* @return
*/
public boolean isFull() {
return this.top == this.maxSize - 1 ? true : false;
}
两种状态已经实现了,接下来我们来看下两种操作,对于入栈操作,核心就是数组的增加而已:
/**
* 压栈操作,将数据存入到栈中
*
* @param value
*/
public boolean push(T value) {
if (isFull()) {
return false;
} else {
data[++top] = value;
return true;
}
}
对于出栈来说就是将取出数组最后一个值:
/**
* 出栈操作
* @return
*/
public T pop() {
if (isEmpty()) {
return null;
} else {
return data[top--];
}
}
以上便是用数组来实现的静态栈,我们可以进行简单的测试一下:
LJStack stack = new LJStack(10);
System.out.println("是否栈空:" + stack.isEmpty());
stack.push("AAAA");
stack.push("BBBB");
stack.push("CCCC");
stack.push("DDDD");
stack.push("1111");
stack.push("2222");
stack.push("3333");
stack.push("4444");
stack.push("5555");
stack.push("6666");
stack.push("7777");
stack.push("8888");
System.out.println("是否栈满:" + stack.isFull());
打印出结果是:
我们观察结果会发现,打印栈里面的结果发现,竟然没有“7777”,“8888”,那是因为栈满了,所以无法入栈了。因此可以判断,上面我们写的代码是正确的。
接着我们来讨论一下链式栈的实现。其实链式栈是通过链表实现栈,我们可以想象有个栈顶节点,当入栈的时候,原先的栈顶节点成为新的节点后继节点,新的节点成为新的栈顶节点。同理,出栈的时候,将栈顶节点弹出,第二个节点成为新的栈顶节点。
Java代码实现链式栈
既然链式栈是通过链表来实现的,首先我们需要构造一个链表的节点LJLinkNode,
public class LJLinkNode {
private T data;//数据域
private LJLinkNode next;//指针域
public LJLinkNode() {
this.data=null;
this.next=null;
}
public LJLinkNode(T data) {
this.data=data;
this.next=null;
}
public void setData(T data) {
this.data=data;
}
public T getData() {
return this.data;
}
public void setNext(LJLinkNode next) {
this.next=next;
}
public LJLinkNode getNext(){
return this.next;
}
}
具体实现如下:
private LJLinkNode topLinkNode;// 栈顶节点
/**
* 初始化
*/
public LJLinkStack() {
this.topLinkNode = new LJLinkNode();
}
/**
* 初始化
*/
public void initLinkStack() {
this.topLinkNode.setData(null);
this.topLinkNode.setNext(null);
}
判断栈空状态:
/**
* 判断是否栈空
*
* @return
*/
public boolean isEmpty() {
return this.topLinkNode.getNext() == null;
}
压栈操作:
/**
* 压栈
* 当入栈的时候,原先的栈顶节点成为新的节点后继节点,新的节点成为新的栈顶节点。
*
* @param node
*/
public void push(LJLinkNode node) {
if (isEmpty()) {
this.topLinkNode.setNext(node);
} else {
node.setNext(this.topLinkNode.getNext());
this.topLinkNode.setNext(node);
}
}
出栈操作:
/**
* 出栈
* 出栈的时候,将栈顶节点弹出,第二个节点成为新的栈顶节点。
* @return
*/
public LJLinkNode pop() {
if (isEmpty()) {
// 栈空无法弹栈
return null;
} else {
LJLinkNode delNode = this.topLinkNode.getNext();// 取出删除节点
this.topLinkNode.setNext(this.topLinkNode.getNext().getNext());// 删除节点
return delNode;
}
}
我们来测试一下写的代码:
LJLinkStack linkStack = new LJLinkStack();
System.out.println("栈是否为空:" + linkStack.isEmpty());
linkStack.push(new LJLinkNode("AAAAA"));
linkStack.push(new LJLinkNode("BBBBB"));
linkStack.push(new LJLinkNode("CCCCC"));
// 依次弹栈
System.out.println("弹栈顺序:");
System.out.println(linkStack.pop().getData());
System.out.println(linkStack.pop().getData());
System.out.println(linkStack.pop().getData());
打印结果如下:
以上便是栈的静态及动态实现方式。那么这两种方式有什么具体运用呢?接着我们来通过具体的面试题目来运用栈。
题目:匹配有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses