1. 栈(Stack)
1.1 概念
栈 :一种特殊的线性表,其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO ( Last In First Out )的原则。压栈 :栈的插入操作叫做进栈 /压栈/ 入栈, 入数据在栈顶 。出栈 :栈的删除操作叫做出栈。 出数据在栈顶 。
有点类似于我们的羽毛球筒,只能在一边取,一边放
1.2 栈的使用
方法Stack() 构造一个空的栈E push(E e) 将e 入栈,并返回 eE 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出栈,栈顶元素变为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println(s.size());
}
}
Stack 继承了 Vector , Vector 和 ArrayList 类似,都是动态的顺序表,不同的是 Vector 是线程安全的。
import java.util.Arrays;
public class MyStack {
int[] array;
int size;
public MyStack() {
array = new int[10];
}
public int push(int e) {
ensureCapacity();
array[size++] = e;
return e;
}
public int pop() {
if(empty()) System.out.println("栈为空");
int e = peek();
size--;
return e;
}
public int peek() {
int e = array[size-1];
return e;
}
public int size() {
return size;
}
public boolean empty() {
return 0 == size;
}
private void ensureCapacity() {
if (size == array.length) {
array = Arrays.copyOf(array, size * 2);
}
}
}
public class Test233 {
public static void main(String[] args) {
MyStack myStack = new MyStack();
System.out.println(myStack.push(1));
System.out.println(myStack.push(2));
System.out.println(myStack.push(3));
System.out.println(myStack.pop());
System.out.println(myStack.pop());
System.out.println(myStack.pop());
System.out.println(myStack.empty());
}
}
1.3 栈的应用
1.3.1 用栈改变序列
若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1 C一个栈的初始状态为空。现将元素 1 、 2 、 3 、 4 、 5 、 A 、 B 、 C 、 D 、 E 依次入栈,然后再依次出栈,则元素出栈的顺序是( )。/// BA: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA
1.3.2 递归转换为循环
// 递归方式
void printList(Node head){
if(null != head){
printList(head.next);
System.out.print(head.val + " ");
}
}
// 循环方式
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. 栈、虚拟机栈、栈帧的区别
栈是一种数据结构,虚拟机栈是JVM中的一块内存,栈帧是调用方法时开辟的一块空间,栈帧也是存在与虚拟机栈中的
栈是一种通用的数据结构概念,虚拟机栈是 Java 虚拟机中的特定内存区域,而栈帧是在虚拟机栈中为方法调用开辟的一块空间,用于存储方法执行的具体状态信息。
- 关于栈
栈作为一种数据结构,具有后进先出(LIFO)的特性。它可以在不同的场景中被实现和应用,用于存储和管理数据。
- 虚拟机栈
在 Java 虚拟机中,虚拟机栈是一块用于支持 Java 方法执行的内存区域。每个线程都有自己独立的虚拟机栈。它主要用于存储方法调用的相关信息,如局部变量、操作数栈、动态链接、方法返回地址等。虚拟机栈的大小可以是固定的,也可以是动态扩展的。如果栈空间不足,可能会导致栈溢出错误。
- 栈帧
当一个方法被调用时,会在虚拟机栈中为该方法创建一个栈帧。栈帧是虚拟机栈中的一个逻辑单元,它包含了方法执行所需的各种信息。具体来说,栈帧中通常包括局部变量表、操作数栈、动态链接、方法返回地址等部分。局部变量表用于存储方法的局部变量,操作数栈用于方法执行过程中的运算操作,动态链接用于支持方法的动态绑定,方法返回地址则记录了方法执行完毕后应该返回的位置。当方法执行完毕时,对应的栈帧会被弹出虚拟机栈,释放其占用的空间。
综上所述,栈是一种通用的数据结构概念,虚拟机栈是 Java 虚拟机中的特定内存区域,而栈帧是在虚拟机栈中为方法调用开辟的一块空间,用于存储方法执行的具体状态信息。
3. 队列(Queue)
3.1 概念
队列 : 只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表 ,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾( Tail/Rear ) 出队列:进行删除操作的一端称为 队头 ( Head/Front )
Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
3.2 队列的方法及其使用与定义
在 Java 中, Queue 是个接口,底层是通过链表实现 的。
boolean offer(E e) 入队列E poll() 出队列peek() 获取队头元素int size() 获取队列中有效元素个数boolean isEmpty() 检测队列是否为空
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.3 队列模拟实现
队列的底层是双向链表,双向链表的实现队列更加便利
public class MyQueue {
// 双向链表节点
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;
}else{
last.next = newNode;
newNode.prev = last;
}
last = newNode;
size++;
}
//队头出列
public int poll(){
int value = 0;
if(first == null){
throw new NullPointerException("first is 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){
throw new NullPointerException("first is null");
}
return first.value;
}
public int size() {
return size;
}
public boolean isEmpty(){
return first == null;
}
}
import java.util.*;
public class Test233 {
public static void main(String[] args) {
MyQueue myQueue = new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
myQueue.offer(4);
myQueue.offer(5);
System.out.println(myQueue.size());
System.out.println(myQueue.poll());
System.out.println(myQueue.poll());
}
}
3.4 循环队列
如图,首尾相接的叫做循环队列
环形队列通常使用数组实现:
数组下标可以返回一个特定值,使其不会越界:
如何区分空与满1. 通过添加 size 属性:如果size等于队列长度,则队列已满2. 保留一个位置:3. 使用标记flg:初始frongt==rear时标记为未满,再一次frongt==rear时标记为满
3.5 双端队列 (Deque)
双端队列( deque )是指允许两端都可以进行入队和出队操作的队列, deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。Deque 是一个接口,使用时必须创建 LinkedList 的对象。
3.6 模拟实现:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
class Deque<T> {
private List<T> data;
public Deque() {
data = new ArrayList<>();
}
public void addFront(T item) {
data.add(0, item);
}
public void addRear(T item) {
data.add(item);
}
public T removeFront() {
if (isEmpty()) {
return null;
}
return data.remove(0);
}
public T removeRear() {
if (isEmpty()) {
return null;
}
return data.remove(data.size() - 1);
}
public boolean isEmpty() {
return data.isEmpty();
}
}
public class Main {
public static void main(String[] args) {
Deque<Integer> deque = new Deque<>();
deque.addFront(1);
deque.addRear(2);
System.out.println(deque.removeFront());
System.out.println(deque.removeRear());
}
}
import java.util.Deque;
import java.util.LinkedList;
public class DequeExample {
public static void main(String[] args) {
// 创建一个基于 LinkedList 的 Deque
Deque<String> deque = new LinkedList<>();
// 在双端队列的两端添加元素
deque.addFirst("First element");
deque.addLast("Last element");
// 从双端队列的两端取出元素
String first = deque.removeFirst();
String last = deque.removeLast();
System.out.println("First element removed: " + first);
System.out.println("Last element removed: " + last);
}
}
一、插入操作
addFirst(E e)
:在双端队列的头部插入指定元素。如果插入成功则返回true
,如果当前没有可用空间则抛出IllegalStateException
异常。addLast(E e)
:在双端队列的尾部插入指定元素。如果插入成功则返回true
,如果当前没有可用空间则抛出IllegalStateException
异常。offerFirst(E e)
:在双端队列的头部插入指定元素。如果插入成功则返回true
,如果当前没有可用空间则返回false
。offerLast(E e)
:在双端队列的尾部插入指定元素。如果插入成功则返回true
,如果当前没有可用空间则返回false
。二、移除操作
removeFirst()
:移除并返回双端队列的头部元素。如果双端队列为空,则抛出NoSuchElementException
异常。removeLast()
:移除并返回双端队列的尾部元素。如果双端队列为空,则抛出NoSuchElementException
异常。pollFirst()
:移除并返回双端队列的头部元素。如果双端队列为空,则返回null
。pollLast()
:移除并返回双端队列的尾部元素。如果双端队列为空,则返回null
。三、查看操作
getFirst()
:返回双端队列的头部元素,但不删除它。如果双端队列为空,则抛出NoSuchElementException
异常。getLast()
:返回双端队列的尾部元素,但不删除它。如果双端队列为空,则抛出NoSuchElementException
异常。peekFirst()
:返回双端队列的头部元素,但不删除它。如果双端队列为空,则返回null
。peekLast()
:返回双端队列的尾部元素,但不删除它。如果双端队列为空,则返回null
。四、其他操作
size()
:返回双端队列中的元素个数。isEmpty()
:判断双端队列是否为空。contains(Object o)
:判断双端队列中是否包含指定元素。removeFirstOccurrence(Object o)
:从双端队列中移除第一次出现的指定元素。removeLastOccurrence(Object o)
:从双端队列中移除最后一次出现的指定元素。