1.栈(Stack)
1.1 概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
1.2 方法
方法 | 解释 |
---|---|
E push(E item) | 压栈 |
E pop() | 弹栈 |
E peek() | 查看栈顶元素 |
boolean empty() | 判断栈是否为空 |
代码示例
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(12);
stack.push(13);
stack.push(14);
stack.push(15);
System.out.println(stack);
System.out.println("size = " + stack.size());
int ret = stack.pop();
System.out.println(stack.peek());
System.out.println(ret);
System.out.println(stack);
System.out.println("size = " + stack.size());
}
运行结果
[12, 13, 14, 15]
size = 4
14
15
[12, 13, 14]
size = 3
1.3 实现
- 利用顺序表实现,即使用尾插 + 尾删的方式实现
- 利用链表实现,则头尾皆可
相对来说,顺序表的实现上要更为简单一些,所以我们优先用顺序表实现栈。
代码示例
//顺序栈
class MyStack {
public int[] elem;
public int top;
public MyStack() {
this.elem = new int[10];
}
public void push(int item) {
//这里不考虑扩容
if (isFull()) {
System.out.println("溢出");
return;
}
this.elem[top++] = item;
}
public int pop() {
if (empty()) {
throw new RuntimeException("栈为空");
}
int ret = this.elem[top-1];
this.top--;
return ret;
}
public int peek() {
if (empty()) {
return -1;
}
return this.elem[top-1];
}
public boolean empty() {
if (top == 0) {
return true;
}else {
return false;
}
}
public boolean isFull() {
if (top == elem.length) {
return true;
}else {
return false;
}
}
}
public static void main(String[] args) {
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.push(3);
myStack.push(4);
//System.out.println(myStack.pop());
System.out.println(myStack.peek());
System.out.println(myStack.pop());
System.out.println(myStack.peek());
}
运行结果
4
4
3
如果要使用泛型,我们只需要添加上<>,把相应的类型变成泛型即可
代码示例
//顺序栈
class MyStack<T> {
public T[] elem;
public int top;
public MyStack() {
this.elem = (T[])new Object[10];
}
public void push(T item) {
if (isFull()) {
System.out.println("溢出");
return;
}
this.elem[top++] = item;
}
public T pop() {
if (empty()) {
throw new RuntimeException("栈为空");
}
T ret = this.elem[top-1];
this.top--;
//这里置空防止T是个引用类型
this.elem[top] = null;
return ret;
}
public T peek() {
if (empty()) {
throw new RuntimeException("栈为空");
}
return this.elem[top-1];
}
public boolean empty() {
if (top == 0) {
return true;
}else {
return false;
}
}
public boolean isFull() {
if (top == elem.length) {
return true;
}else {
return false;
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyStack<Integer> myStack = new MyStack<>();
myStack.push(1);
myStack.push(2);
myStack.push(3);
myStack.push(4);
//System.out.println(myStack.pop());
System.out.println(myStack.peek());
System.out.println(myStack.pop());
System.out.println(myStack.peek());
}
}
运行结果
4
4
3
2.队列(Queue)
2.1 概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(FirstIn First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)
2.2 方法
解释 | 方法1 | 方法2 |
---|---|---|
入队列 | add(e) | offer(e) |
出队列 | remove() | poll() |
队首元素 | element() | peek() |
一般情况下方法2用的比较多
示例代码
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.add(2);
queue.add(3);
queue.offer(5);
queue.offer(6);
queue.offer(7);
System.out.println(queue);
System.out.println(queue.poll());
System.out.println(queue.peek());
System.out.println(queue);
System.out.println(queue.isEmpty());
}
运行结果
[1, 2, 3, 5, 6, 7]
1
2
[2, 3, 5, 6, 7]
false
2.3 实现
队列也可以数组和链表结构实现,使用链表的结构实现更优一点,因为如果使用数组的结构,出队列在数组头上出,效率会很低
代码示例
class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
class MyQueue {
public Node head;
public Node last;
public void offer(int val) {
Node node = new Node(val);
if (head == null) {
this.head = node;
this.last = node;
}else {
this.last.next = node;
this.last = this.last.next;
}
}
public int poll() {
int ret = this.head.val;
if (head.next == null) {
this.head = null;
this.last = null;
return ret;
}else {
this.head = this.head.next;
return ret;
}
}
public int peek() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return this.head.val;
}
public boolean isEmpty() {
return this.head == null;
}
}
public static void main(String[] args) {
MyQueue myQueue = new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
myQueue.offer(4);
System.out.println(myQueue.poll());
System.out.println(myQueue.peek());
System.out.println(myQueue.isEmpty());
}
运行结果
1
2
false
转成泛型与上面同理
2.4 循环队列
假设我们现在想用数组实现一个队列,那么我们会发现如果每次假设数组头部出,尾部进,那么就会存在有空余的情况,那么这种情况到底是属于空还是满?我们就不好评判了,因此,要用数组实现,我们可以把数组看成是一个循环的数组,即就形成了循环队列
我们让front和rear分别代表队列的头和尾
- 刚开始的时候两个指针重合,此时队列为空
- 此时我们开始添加数据,每次添加一个,rear往后走一步
- 当rear和front相遇的时候我们可以判定为满,但是此时会出现一个问题,当我们rear和front只差一个位置的时候,此时再添加元素,rear会和front重合,按照我们的判断条件此时为空了,但是实际上整个队列里都放满数据,这里就有矛盾了
- 因此为了解决上述问题,我们可以通过放弃一个位置,即rear的下一位是front,此时我们就可以判定为满,如图所示便是满的,有了这样一个改变,我们给定的数组长度和实际可用会少一位,因此在实际使用的时候要达到预期长度,可以给长度加1
- 我们现在已经解决了判定是否未满的情况,但是通过看图,发现有很多小伙伴肯定会发现,此时当rear在数组最后位置上的时候,例如在图中7号位置上的时候,我们该怎样让rear移动呢?答案很简单,我们让此时rear每次移动它的下标加上1然后和整个数组的长度取模,即(rear+1)%数组.length,对于front来说也是一样的,即(front+1)%数组.length
示例代码
class MyCircularQueue {
public int[] elem;
public int front;
public int rear;
//构造方法
public MyCircularQueue(int k) {
this.elem = new int[k+1];
}
//入队
public boolean enQueue(int value) {
if (isFull()) {
return false;
}else {
elem[rear] = value;
rear = (rear+1) % elem.length;
return true;
}
}
//出队
public boolean deQueue() {
if (isEmpty()) {
return false;
}else {
front = (front+1) % elem.length;
return true;
}
}
//显示对头
public int Front() {
if (isEmpty()) {
return -1;
}
return this.elem[front];
}
//显示队尾
public int Rear() {
if (isEmpty()) {
return -1;
}
int index = this.rear == 0 ? this.elem.length-1 : this.rear-1;
return this.elem[index];
}
//判断是否为空
public boolean isEmpty() {
return front == rear;
}
//判断是否为满
public boolean isFull() {
return (this.rear+1) % this.elem.length == front;
}
}
public class TestDemo3 {
public static void main(String[] args) {
MyCircularQueue myCircularQueue = new MyCircularQueue(8);
myCircularQueue.enQueue(1);
myCircularQueue.enQueue(2);
myCircularQueue.enQueue(3);
myCircularQueue.enQueue(4);
myCircularQueue.enQueue(5);
System.out.println(myCircularQueue.Front());
System.out.println(myCircularQueue.Rear());
}
}
运行结果
1
5
3.双端队列(Deque)
3.1 概念
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
3.2 方法
错误处理 | 抛出异常 | 返回特殊值 | 抛出异常 | 返回特殊值 |
入队列 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
出队列 | removeFirst() | pollFirst() | removeLast() | pollLast() |
获取元素 | getFirst() | peekFirst() | getLast() | peekList() |