栈
栈是一种以先进后出的形式 组织数据的数据结构.
比如: 子弹相当于数据, 弹匣相当于栈, 最先放入弹匣的子弹最后才打出来, 最后放入弹匣的子弹最先打出类
Java是用动态数组实现栈的.因为Java中的 Stack 继承了 Vector , 而 Vector 与 ArrayList 类似, 都是动态的顺序表.
栈主要的方法
栈的应用场景: 逆波兰表达式求值 (计算器计算原理)
leetcode 题目链接:点击这里
逆波兰表达式也称后缀表达式.
平常在数学书上见到的表达式是中缀表达式,例如 (4 + (13 / 5))
那么上面中缀表达式转换为后缀表达式就是 4 13 5 / +
可以发现 : 中缀表达式是把操作符放在两个操作数之间,而后缀表达式是把操作符放在两个操作数的后面.
我们可以利用栈先进后出的特性计算出后缀表达式的结果 .
计算过程:
我们可以创建一个栈用于存储数字, 然后遍历后缀表达式, 如果是数字就入栈, 如果遍历到操作符, 就出栈两个数字与这个操作符进行计算并且把计算出来的结果入栈 (注意第一个出栈的数字作为第二个操作数,第二个出栈的数字作为第一个操作数).
我的代码实现:
class Solution {
public int evalRPN(String[] tokens) {
//创建一个栈,把数字存到栈中
Stack<Integer> stack = new Stack<>();
//遍历字符串数组
for(String x : tokens) {
//如果是数字,就把该数字入栈. 否则出栈两个数进行运算
if(!isOperation(x)) {
stack.push(Integer.parseInt(x));
}else {
//第一个出栈的数字当做右操作数
//第二个出栈的数字当做左操作数
int num1 = stack.pop();
int num2 = stack.pop();
switch(x) {
case("+"):
stack.push(num2 + num1);
break;
case("-"):
stack.push(num2 - num1);
break;
case("*"):
stack.push(num2 * num1);
break;
case("/"):
stack.push(num2 / num1);
break;
}
}
}
//最后栈中唯一的数字就是表达式的计算结果
return stack.pop();
}
//isOperation 方法用来判断该字符串是否是操作符
public boolean isOperation(String s) {
if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
return true;
}
return false;
}
}
队列
队列是一种以先进先出的形式 组织数据的数据结构.
队头称做 Head / Front , 队尾称做 Tail / Rear
比如: 学生相当于数据, 食堂就是队列, 最先排队的同学最先打到饭出来. 后面排队的同学后出来.
Java中的队列 Queue 是个接口, 底层是通过链表实现的.
如果要实例化一个队列 Queue, 就必须实例化一个 LinkedList 的对象,因为 LinkedList 实现了 Queue 接口 (准确的来说是 LinkedList 实现了 Deque 接口, Deque 接口继承了 Queue 接口).
实例化一个队列的代码例子:
Queue<Integer> queue = new LinkedList<>();
队列主要的方法
双端队列
双端队列也就是 Deque, 是 double ended queue 的简称.
Deque 是指在两端都可以进行入队和出队操作的队列.
而 Queue 是只能在一端入队, 另一端出队的队列.
Deque 也是一个接口, 实现双端队列必须创建 LinkedList 对象
Deque 既可以当做队列, 也可以当做栈.
Deque<Inetger> stack = new LinkedList<>();
Deque<Inetger> queue = new LinkedList<>();
//ArrayDeque 也实现了 Deque接口, ArrayDeque 的底层是数组
Deque<Inetger> stack = new ArrayDeque<>();
设计循环队列
leetcode 题目链接: 点击这里
循环队列用数组实现.
创建一个数组,再设置头指针和尾指针用于标记队头和队尾, 再创建一个整型变量用于计数存储循环队列的元素个数.
设计循环队列需要解决最重要的问题: 如果循环队列当中的最后一个下标已经存储元素了,而第一个下标(下标 0)是 空的 , 那么如果此时我想在循环队列的尾指针怎么从指向最后一个下标到指向第一个下标 (下标 0) ? 单纯的下标加一肯定是不可以的. 而 (尾指针指向的位置 + 1) % 循环队列的长度 这个公式可以解决这个问题, 在任何位置元素入队的情况下 rear 指针都能指向正确的位置.
我的代码实现:
class MyCircularQueue {
private int[] circularQueue;
private int front = -1;//用于指向循环队列的第一个元素的位置
private int rear = -1;//用于指向循环队列的最后一个元素的位置
private int size;//用于记录存储的元素个数
public MyCircularQueue(int k) {
circularQueue = new int[k];
}
public boolean enQueue(int value) {
if(isFull()) {
return false;
}else if(isEmpty()){
front = 0;
rear = 0;
circularQueue[rear] = value;
}else {
//定位到rear的后面一个位置
rear = (rear + 1) % circularQueue.length;
circularQueue[rear] = value;
}
size++;
return true;
}
public boolean deQueue() {
if(isEmpty()) {
return false;
}else if(size == 1) {//如果只有一个元素,front指向的位置不变
size--;
}else {
//把front的位置往后移一位即删除
front = (front + 1) % circularQueue.length;
size--;
}
return true;
}
public int Front() {
return isEmpty() ? -1 : circularQueue[front];
}
public int Rear() {
return isEmpty() ? -1 : circularQueue[rear];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == circularQueue.length;
}
}
栈和队列的模拟实现(代码练习)
我的代码模拟实现在 gitee 里: 点击 这里 参考