前言
上一篇博客没有对LinkedList进行太多的讲解,本博客会在队列的内容进行详细的说明,此外本系列博客不会进行刷题网站的题目讲解,主要目的是介绍说明基本的数据结构的概念。
栈
栈的特性
栈(stack)是一种新的数据结构,它遵循先进后出的原则。什么意思呢?拿生活中的事物来举例
图片来源互联网侵删
我们在打开羽毛球桶的时候,假设里面没有羽毛球,我们要往里面放球,比如放了三个球,第一个球即为球1,第二个为球2,第三个为球3。在装完之后,我们想要取到球1是不是要先把,压在球1上面的球2球3拿出来才能拿到球1。
我们要说的栈就是像这样的结构,一种先进后出的结构。
栈的模拟实现
栈的模拟可以有多种形式,可以由数组进行实现,也可以由我们上一篇说过的链表来实现,甚至可以用队列来实现。这里我们使用数组来实现。
栈主要涉及三个方法,push(加入数据),pop(弹出数据),peek(查看栈顶数据)
也都很好理解根据翻译意思,push是推的意思自然要把元素推栈的底部,pop有爆开脱落弹走的意思所以就是弹出数据,peek如果有玩过CounterStrike(反恐精英)的朋友应该很熟悉,意思就是偷瞄一眼再回来的意思,在这里的意思就是看一下栈顶数据是什么,但是不做出改动。
public class MyStack {
private int[] elem;
private int usedSize;
private static final int DEFAULT_SIZE = 10;
public MyStack(){
elem = new int[DEFAULT_SIZE];
}
public void push(int val){
if(isFull()){
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
}
private boolean isFull(){
return usedSize==elem.length;
}
public int pop(){
if(isEmpty()){
return -1;
}
int data = elem[usedSize-1];
usedSize--;
return data;
}
private boolean isEmpty(){
return usedSize == 0;
}
public int peek(){
if(isEmpty()){
return -1;
}
int data = elem[usedSize-1];
return data;
}
public int size(){
return usedSize;
}
}
需要注意的是usedSize和数组的容量大小,确保没有越界
栈的使用
实例化一个栈的格式为:Stack<T> stack = new Stack<>(); T是任意包装类型。
public class UseStack {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.peek());
System.out.println(stack.peek());
}
}
这里我们打个断点进行debug
结果和用法都和前面模拟的一样,有个小细节,Stack自带的pop和peek方法都是带有返回值的,所以可以直接用数据进行接收并直接打印输出。
队列
队列和LinkedList的关系,集合框架图(部分)
在具体说队列的特性之前,我先解决上一篇博客留下的问题。
直线之间没有交点
我们都知道,不可以直接实例化一个接口,只能实例化一个他派生出来的类,然后发生向上转型。这张图里的关系就解释了为什么我们更推荐以List-ArrayList 和 List-LinkedList的方式去书写。
当然也不防Queue-ArrayList或者List-Stack这样的奇葩写法,这类写法虽然语法上没有出错误,但是不推荐这样写,具体为什么不推荐这样写,现阶段的知识还不能解释其中的原因,后面深入学习之后会知道为什么,这里点到为止。
队列的特性
队列(queue) 是一种先进先出的数据结构,什么叫先进先出。就像现实中的排队一样,先来后到,先排队的人肯定先进。
队列模拟实现
前面了解了队列的特性,我们可以发现队列是一种容器两头都发生变化的结构,所以我们不考虑使用数组,因为数组比较适合静态单向的输入输出数据,如果要使用数组的话,要更加的麻烦。这里我们使用链表来进行模拟,链表并不是固定大小的容器,而且头尾分明,更加符合队列的结构。
链表方面我们采用双向链表
队列主要围绕offer,poll,peek几个方法,offer提供数据,入队。poll放出数据,出队。peek和前面栈一样,查看出队口的数据。
public class MyQueue {
//头进尾出
static class ListNode {
private int val;
private ListNode next;
private ListNode prev;
public ListNode(int val) {
this.val = val;
}
}
private ListNode front;
private ListNode rear;
private int usedSize;
public void offer(int val) {
ListNode node = new ListNode(val);
if(front==null) {
front=rear=node;
}else {
node.next = front;
front.prev = node;
front = node;
}
usedSize++;
}
public int poll() {
if(rear==null) {
return -1;
}
int data = rear.val;
if(rear==front) {
rear = null;
front = null;
}
rear = rear.prev;
rear.next = null;
usedSize--;
return data;
}
public int peek() {
int data = rear.val;
return data;
}
public int size() {
return usedSize;
}
public boolean isEmpty() {
return usedSize==0;
}
}
push和poll方法需要注意队列为空的情况即可,其余难度不高,这里一笔带过。
队列的使用
队列实例化的格式为Queue<> queue = new LinkedList<>();
这里需要注意是new一个LinkedList而不是Queue,因为Queue是一个接口,不是一个类,前面集合框架图也有说明。
public class UseQueue {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.peek());
System.out.println(queue.peek());
}
}
双端队列
双端队列Deque是一种两头都可以进行入队出队的结构,现阶段只做浅了解。
public class UseDeque {
public static void main(String[] args) {
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现
}
}