栈和队列使用的范围很广,本篇用来深度解析Java数据结构中的栈和队列的深度解析,栈和队列都能用单向或双向链表来实现哦,希望可以帮助各位!
文章目录
-
目录
一、栈
1.1栈的概念
1.2栈的使用
1.3栈的自定义顺序栈实现
1.4栈的练习题
二、队列
2.1队列的概念
2.2队列的使用
1.3队列的链式模拟实现
1.4双向链表实现队列
三、循环队列
一、栈
1.1栈的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守先进后出LIFO(Last In First Out)的原则。
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据在栈顶。
- 下面用图片来了解一下吧
1.2栈的使用
方法 | 功能 |
Stack() | 构造一个空的栈 |
E push(E e) | 将e入栈,并返回e |
E pop() | 将栈顶元素出栈并返回 |
E peek() | 获取栈顶元素 |
int size() | 获取栈中有效元素个数 |
boolean empty() | 检测栈是否为空 |
下面我来演示一下:
MyStack stack = new MyStack();
stack.push(1);
stack.push(2);
stack.push(3);
Integer a = stack.pop();//3
System.out.println(a);
Integer b = stack.peek();
System.out.println(b);//2
Integer b2 = stack.peek();
System.out.println(b2);//2
System.out.println(stack.isEmpty());
1.3栈的自定义顺序栈实现
public class EmptyException extends RuntimeException {
public EmptyException(String s) {
}
public EmptyException(){
}
}
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()) {
throw new EmptyException("栈是空的!");
}
return elem[--usedSize];
}
public boolean isEmpty() {
return usedSize == 0;
}
public int peek() {
if(isEmpty()) {
throw new EmptyException("栈是空的!");
}
return elem[usedSize-1];
}
}
1.4栈的练习题
1.逆序打印链表
void printList(Node head){
if(null == head){
return;
}
Stack<Node> s = new Stack<>();
// 将链表中的结点保存在栈中
Node cur = head;
while(null != cur){
s.push(cur);
cur = cur.next;
}
// 将栈中的元素出栈
while(!s.empty()){
System.out.print(s.pop().val + " ");
}
}
2.给你一个字符串数组
tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。150. 逆波兰表达式求值 - 力扣(LeetCode)
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 num2 = stack.pop();
int num1 = stack.pop();
switch (x) {
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.1队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(FirstIn First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)
2.2队列的使用
在Java中,Queue是个接口,底层是通过链表实现的。
方法 | 功能 |
boolean offer(E e) | 入队列 |
E poll() | 出队列 |
peek() | 获取队头元素 |
int size() | 获取队列中有效元素个数 |
boolean isEmpty() | 检测队列是否为空 |
下面来实现一下这些方法
public class Test {
public static void main(String[] args) {
MyQueue myQueue = new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
System.out.println(myQueue.peek());//1
System.out.println(myQueue.poll());//1
System.out.println(myQueue.poll());//2
System.out.println(myQueue.poll());//3
System.out.println();
}
}
1.3队列的链式模拟实现
单链表实现队列要尾插头删
class MyQueue {
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()) {
throw new EmptyException("队列为空");
}
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()) {
throw new EmptyException("队列为空");
}
return head.val;
}
public int getUsedSize() {
return usedSize;
}
}
1.4双向链表实现队列
public class Queue {
// 双向链表节点
public static class ListNode{
ListNode next;
ListNode prev;
int value;
ListNode(int value){
this.value = value;
}
}
ListNode first; // 队头
ListNode last; // 队尾
int size = 0;
// 入队列---向双向链表位置插入新节点
public void offer(int e){
ListNode newNode = new ListNode(e);
if(first == null){
first = newNode;
// last = newNode;
}else{
last.next = newNode;
newNode.prev = last;
// last = newNode;
}
last = newNode;
size++;
}
// 出队列---将双向链表第一个节点删除掉
public int poll(){
// 1. 队列为空
// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
int value = 0;
if(first == null){
return null;
}else if(first == last){
last = null;
first = null;
}else{
value = first.value;
first = first.next;
first.prev.next = null;
first.prev = null;
}
--size;
return value;
}
// 获取队头元素---获取链表中第一个节点的值域
public int peek(){
if(first == null){
return null;
}
return first.value;
}
public int size() {
return size;
}
public boolean isEmpty(){
return first == null;
}
}
三、循环队列
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。
如何去区分空和满呢?
下面我用牺牲一个空间的方式来演示:
用一道题来演示622. 设计循环队列 - 力扣(LeetCode)
class MyCircularQueue {
private int[] elem;
private int front;
private int rear;
public MyCircularQueue(int k) {
//如果是浪费空间 这里必须处理多加一个1
this.elem = new int[k+1];
}
/**
* 入队列
* @param value
* @return
*/
public boolean enQueue(int value) {
//1、检查是否队列是满的
if (isFull()){
return false;
}
elem[rear] = value;
rear = (rear+1) % elem.length;
return true;
}
/**
* 出队列
* @return
*/
public boolean deQueue() {
if(isEmpty()) {
return false;
}
//front++;
front = (front+1) % elem.length;
return true;
}
/**
* 得到队头元素
* @return
*/
public int Front() {
if (isEmpty()) {
return -1;
}
return elem[front];
}
/**
* 得到队尾元素
* @return
*/
public int Rear() {
if(isEmpty()) {
return -1;
}
int index = (rear == 0) ? elem.length-1 : rear-1;
return elem[index];
}
public boolean isEmpty() {
return front == rear;
}
public boolean isFull() {
return (rear+1) % elem.length == front;
}
}
总结:栈和队列在使用中还是很广泛的,要熟练掌握它们的方法是如何实现的,单链表和双链表实现栈:头插头删,单链表实现队列:尾插头删,双链表实现栈:头和尾都可以插入和删除。