文章目录
栈(Stack)
1.认识栈
一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO的原则。
入栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
入栈出栈遵循后进先出原则,后来的元素不出去,先前进来的元素也不能出去。
在现实中,弹匣装填子弹就是入栈,射击就是出栈,最上面的子弹是最后装进来的,如果最上面的子弹没有射击出去,下面的子弹也不能射击。
2.栈的使用
方法 | 功能 |
---|---|
Stack() | 构造一个空的栈 |
E push(E e) | 将e入栈,并返回e |
E pop() | 将栈顶元素出栈并返回 |
E peek() | 获取栈顶元素 |
int size() | 获取栈中有效元素个数 |
boolean empty() | 栈是否为空 |
public static void main(String[] args) {
Stack<Integer> s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println(s.size()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println(s.size());
}
}
3.模拟栈
使用数组实现栈
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[10];
}
//压栈
public void push(int val) {
if(isFull()) {
//扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize++] = val;
}
public boolean isFull() {
return usedSize == elem.length;
}
//出栈
public int pop() {
if(isEmpty()) {
System.out.println("空栈");
}
return elem[--usedSize];
}
public boolean isEmpty() {
return usedSize == 0;
}
public int peek() {
if(isEmpty()) {
System.out.println("空栈");
}
return elem[usedSize-1];
}
}
【拓展】LinkedList可以作为栈使用
public static void main(String[] args) {
LinkedList<Integer> stack = new LinkedList<>();
//入栈
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
//出栈
System.out.println(stack.pop());
//栈顶元素
System.out.println(stack.peek());
}
观察底层源码,LinkedList的push是头插,pop是删除头结点,所以符合栈的原则
4.OJ题
1.逆波兰表达式求值
思路:后缀表达式转中缀表达式,数字依次压栈,遇到算术运算符,就pop()作为算数运算法右边和左边(一定是先右边,后左边),计算出值再压栈,依次这样操作,知道最后栈里面就一个数字,pop()出来就是最终的结果。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String s :tokens){
if(!isOperation(s)){
stack.push(Integer.valueOf(s));
}else{
//先取的数字放在右边
//后取得数字放在左边
int num2 = stack.pop();
int num1 = stack.pop();
switch(s){
case "+":stack.push(num1+num2);
break;
case "-":stack.push(num1-num2);
break;
case "*":stack.push(num1*num2);
break;
case "/":stack.push(num1/num2);
break;
}
}
}
return stack.pop();
}
//判断是不是算术运算符
private boolean isOperation(String x){
if (x.equals("+") || x.equals("-") || x.equals("/") || x.equals("*")) {
return true;
}
return false;
}
}
2.有效的括号
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
最后栈为空:返回true;有一个不匹配返回false
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i=0 ; i < s.length(); i++ ){
char c = s.charAt(i);
if(c=='{'||c=='['||c=='('){
stack.push(c);
}else{
//栈空,说明没有左括号,这个时候进来了有括号,肯定不对
if(stack.empty()){
return false;
}
//拿栈顶元素,如果匹配,就下一次循环
//不匹配,直接返回false
char c1 = stack.pop();
if(c=='}'&&c1=='{'||c==')'&&c1=='('||c==']'&&c1=='['){
continue;
}else{
return false;
}
}
}
//遍历完了,如果栈也空了,就返回true
if(stack.empty()){
return true;
}
return false;
}
}
3.栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
思路:设pushA是压栈顺序,popA是出栈顺序。i指向pushA的第一个元素,j指向popA的第一个元素。
通过i遍历pushA中的元素,先把pushA[i]压栈,再peek()与popA中的j指向的元素进行对比,不能直接用pushA[i]与popA[j]比较,如果不一样继续操作,一样就出栈
public class Solution {
public boolean IsPopOrder(int [] pushA, int [] popA) {
Stack<Integer> stack = new Stack<>();
int j = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
//不能使用if,存在连续操作
while (j < popA.length && !stack.isEmpty() && stack.peek() == popA[j]) {
stack.pop();
j++;
}
}
if (stack.isEmpty()) {
return true;
}
return false;
}
}
4.最小栈
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
思路:
class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int val) {
stack.push(val);
if (minStack.empty()) {
minStack.push(val);
} else {
if (val <= minStack.peek()) {
minStack.push(val);
}
}
}
public void pop() {
if (!stack.empty()) {
int val = stack.pop();
//维护最小栈
if (val == minStack.peek()) {
minStack.pop();
}
}
}
// peek
public int top() {
if (!stack.empty()) {
return stack.peek();
}
return -1;
}
public int getMin() {
return minStack.peek();
}
}
队列(Queue)
1.认识队列
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(ail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)
2.队列的使用
在Java中,Queue是个接口,底层是通过链表实现的。
方法 | 功能 |
---|---|
boolean offffer(E e) | 入队列 |
E poll() | 出队列 |
peek() | 获取队头元素 |
int size() | 获取队列中有效元素个数 |
boolean isEmpty() | 检测队列是否为空 |
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列
System.out.println(q.size());
System.out.println(q.peek()); // 获取队头元素
q.poll();
System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
if(q.isEmpty()){
System.out.println("队列空");
}else{
System.out.println(q.size());
}
}
3.模拟队列
使用单链表实现队列:只能尾插,头删
public class MyQueue {
static class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
public Node head;//头结点
public Node last;//尾结点
public int usedSize;
//入队
public void offer(int val) {
Node node = new Node(val);
if (head == null) {
head = node;
last = node;
} else {
last.next = node;
last = node;
}
usedSize++;
}
public int poll() {
if (empty()) {
System.out.println("队列为空");
}
int ret = head.val;
head = head.next;
if (head == null) {
last = null;//只有一个节点 那么last也要置空
}
usedSize--;
return ret;
}
public boolean empty() {
return usedSize == 0;
}
public int peek() {
if (empty()) {
System.out.println("队列为空");
}
return head.val;
}
public int getUsedSize() {
return usedSize;
}
}
4.循环队列
问题:rear假设从7下标变成了0下标,这个时候算是空队列还是满队列
实现方法一
添加一个usedSize,如果usedSize=8就是满队列,usedSize=0就是空队列
//循环队列
public class MyCircularQueue {
//队列数据
private int[] elems ;
//队头指针
private int front;
//队尾指针
private int rear;
//队列元素个数
private int size;
public MyCircularQueue(int k) {
elems = new int[k];
}
//出队
public boolean deQueue() {
if (isEmpty()) {
//队列为空
return false;
}
int ret = elems[front];
front =(front+1)%elems.length;
size--;
return true;
}
//入队
public boolean enQueue(int elem) {
if (isFull()) {
//队列满
return false;
}
elems[rear] = elem;
rear = (rear+1)%elems.length;
size++;
return true;
}
//获取队头元素
public int Front() {
if(isEmpty()) {
return -1;
}
return elems[front];
}
//获取对尾元素
public int Rear() {
if(isEmpty()) {
return -1;
}
int index = (rear == 0) ? elems.length-1 : rear-1;
return elems[index];
}
//是否为空队列
public boolean isEmpty() {
return size==0;
}
//是否满队列
public boolean isFull() {
if( size == elems.length) {
return true;
}
return false;
}
}
实现方法二
牺牲一个空间,当rear=7表示满队列
class MyCircularQueue {
private int[] elem;
private int front;//表示队列的头
private int rear;//表示队列的尾
public MyCircularQueue(int k) {
elem = new int[k+1];
}
//入队
public boolean enQueue(int value) {
if(isFull()){
return false;
}
elem[rear] = value;
rear = (rear+1)%elem.length;
return true;
}
//出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
int val = elem[front];
front = (front+1)%elem.length;
return true;
}
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()){
return -1;
}
if(rear == 0){
return elem[elem.length-1];
}
return elem[rear-1];
}
public boolean isEmpty() {
if(rear == front){
return true;
}
return false;
}
public boolean isFull() {
if((rear+1)%elem.length == front){
return true;
}
return false;
}
}
5.双端队列(Deque)
普通队列:一端进一端出
双端队列:每一端既可以进也可以出
Deque是一个接口,使用时必须创建LinkedList的对象。
在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现
6.OJ题
1.用队列实现栈
思路:
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
}
//入栈
public void push(int x) {
if(!qu1.isEmpty()){
qu1.offer(x);
}else if(!qu2.isEmpty()){
qu2.offer(x);
}else{
qu1.offer(x);
}
}
//出栈
public int pop() {
if(empty()){
return -1;
}
if(!qu1.isEmpty()){
int size = qu1.size() - 1 ;
for(int i = 0 ; i < size ; i++){
int val = qu1.poll();
qu2.offer(val);
}
return qu1.poll();
}
if(!qu2.isEmpty()){
int size = qu2.size() - 1 ;
for(int i = 0 ; i < size ; i++){
int val = qu2.poll();
qu1.offer(val);
}
return qu2.poll();
}
return -1;
}
//栈顶元素
public int top() {
if(empty()) {
return -1;//两个队列都为空,意味着当前的栈为空
}
if(!qu1.isEmpty()) {
int size = qu1.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu1.poll();
qu2.offer(val);
}
return val;
}else {
int size = qu2.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu2.poll();
qu1.offer(val);
}
return val;
}
}
//是否为空
public boolean empty() {
if(qu1.isEmpty() && qu2.isEmpty()){
return true;
}
return false;
}
}
2.用栈实现队列
思路:
class MyQueue {
private Stack<Integer> stack1;
private Stack<Integer> stack2;
public MyQueue2() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if(empty()) {
return -1;
}
if(stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if(empty()) {
return -1;
}
if(stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.empty() && stack2.empty();
}
}