一、栈(Stack)
栈是一种先进后出的数据结构。其只允许在固定的一端进行插入和删除元素操作。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
1.栈的使用
栈的模拟实现:
使用数组实现
public class MyStack implements IStack{
private int[] elem;//定义一个数组
private int usedSize;//数组的有效数据个数
private static final int DEFAULT_SIZE = 10;//数组的默认大小
public MyStack(){
elem = new int[DEFAULT_SIZE];
}
}
1.入栈
public void push(int x) {
if(full()){
//满了就扩容
elem = Arrays.copyOf(elem,elem.length*2);
}
elem[usedSize]=x;
usedSize++;
}
2.出栈
public int pop() {
if(isEmpty()){
//栈若为空,这里抛出异常!!!
throw new ElemNullException("栈为空!");
}
int e = elem[usedSize-1];
usedSize--;
//若是引用类型,应该置空
//elem[usedSize]=null;
return e;
}
3.查看栈顶元素
public int peek() {
if(isEmpty()){
throw new ElemNullException("栈为空!");
}
return elem[usedSize-1];
}
4.栈里有多少元素
public int size() {
return usedSize;
}
5.栈是否为空
public boolean isEmpty() {
if(usedSize==0){
return true;
}
return false;
}
栈的应用场景:
逆波兰表达式求值
逆波兰式是一种将待计算量写在前, 把运算符写在后(通常是两数之后)的计算式。
代码实现:
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String x : tokens){
if(!isOperate(x)){
//不是操作符,入栈,要转成int类型
stack.push(Integer.parseInt(x));
}else{
int nums2 = stack.pop();//右操作数
int nums1 = stack.pop();//左操作数
switch(x){
case "+":
stack.push(nums1+nums2);
break;
case "-":
stack.push(nums1-nums2);
break;
case "*":
stack.push(nums1*nums2);
break;
case "/":
stack.push(nums1/nums2);
break;
}
}
}
return stack.pop();
}
private boolean isOperate(String s){
if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")){
return true;
}
return false;
}
二、队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进
先出FIFO(FirstIn First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头。
双向链表实现队列
static class ListNode{
public int val;
public ListNode next;
public ListNode prev;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
public ListNode last;
public int usedSize;
入队
public boolean offer(int val){
ListNode node = new ListNode(val);
if(head==null){
head=node;
last=node;
}else{
last.next = node;
node.prev = last;
last = node;
}
usedSize++;
return true;
}
出队
public int poll(){
if(head == null){
return -1;
}
int val = head.val;
if(head.next==null){
head=null;
last=null;
}else{
head=head.next;
head.prev=null;
}
usedSize--;
return val;
}
查看队头元素
public int peek(){
if(head==null){
return -1;
}
return head.val;
}
队列是否为空
public boolean empty(){
return head == null;
}
队列长度
public int size(){
return usedSize;
}
循环队列
设计循环队列主要就是注意 front 和 rear 的位置,它们不能超过数组长度。
如何区分空与满???
这里浪费一个空间来演示:
先创建循环队列:
class MyCircularQueue {
public int[] elem;
public MyCircularQueue(int k) {
elem = new int[k+1];
}
public int front;//队头
public int rear;//队尾
}
入队
防止 rear 越界,往后移的同时应该 % 数组长度!!!
不能直接写 rear++ !!
public boolean enQueue(int value) {
if(isFull()){
return false;
}
elem[rear]=value;
rear = (rear+1) % elem.length;//rear不能++
return true;
}
出队
队头位置要后移,同样要 % 数组长度!!
public boolean deQueue() {
if(isEmpty()){
return false;
}
front = (front+1)%elem.length;
return true;
}
查看队头元素
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
获取队尾元素
rear 不能直接减,如果恰好在0位置,rear-1 变成负数了,所以这里应该判断:
如果在0位置,直接返回数组长度 -1,否则就是 rear-1;
public int Rear() {
if(isEmpty()){
return -1;
}
int index = (rear==0) ? elem.length-1 : rear-1;//当rear在0位置处时不能-1
return elem[index];
}
队空
public boolean isEmpty() {
return front==rear;
}
队满
public boolean isFull() {
return (rear+1) % elem.length == front;
}