网站建设考试苏州市现代建设咨询管理公司
目录
- 栈(Stack)
- 什么是栈
- 栈的使用
- 栈的模拟实现
- 队列(Queue)
- 什么是队列
- 队列的使用
- 队列的模拟实现
- 循环队列
- 双端队列 (Deque)
栈(Stack)
什么是栈
栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后入先出的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据是在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
出栈时,是C先出,然后是B,再是A .
注意:这个栈是一种数据结构,与JVM内存模型中的栈不同,JVM中的栈是指JVM中一段内存区域。
栈的使用
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()); // 获取栈中有效元素个数---> 4System.out.println(s.peek()); // 获取栈顶元素---> 4s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为2if(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 = 0; //size用于记录当前元素个数public MyStack() { this(12); //默认开辟大小为12的空间}public MyStack(int m) {array = new int[m];}//入栈public int push(int value) {if(size == array.length) {//扩容int[] copy;//复制array,并将其扩容一倍copy = Arrays.copyOf(array,2* array.length);array = copy; //将array指向扩容后的数组}array[size] = value;size++;return value;}//出栈public int pop() {if(size == 0) {throw new RuntimeException("栈中没有元素");}int n = array[size-1];size--;return n;}//获取栈顶元素public int peek() {if(size == 0) {throw new RuntimeException("栈中没有元素");}return array[size-1];}//获取元素个数public int getSize() {return size;}//判断栈是否为空public boolean isEmpty() {return size == 0;}
}
队列(Queue)
什么是队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出特性。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
队列的使用
在Java中,Queue是个接口,底层是通过链表LinkedList实现的,LinkedList实现了Queue接口,我们可以通过LinkedList实例化对象,如:
Queue<Integer> queue = new LinkedList<>();
队列里的方法:
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.size()); //获取元素个数System.out.println(queue.peek()); // 获取队头元素queue.poll();System.out.println(queue.poll()); // 从队头出队列,并将删除的元素返回if(queue.isEmpty()){System.out.println("队列空");}else{System.out.println(queue.size());}
}
输出:
队列的模拟实现
实现一个队列,我们可以用数组存储数据,也可以用链表存储数据,那究竟用哪种存储方式更好呢?
我们认为链表存储更好,因为链表的存储空间不连续,可以更好的利用存储空间(可以利用零碎的空间),而数组它的空间是连续的,万一空间不足就会出问题(不能利用零碎空间)。
public class MyQueue {//双向链表节点public static class ListNode {ListNode next; //记录下一个节点ListNode prev; //记录上一个节点int val; //记录该节点存储的值ListNode(int val){this.val = val;}}ListNode head; // 队头ListNode last; // 队尾int size = 0;//入队列public void offer(int val) {ListNode node = new ListNode(val); //先实例化这个节点出来if(last == null) { //链表中未存放元素时last = node;head = last;}else {last.next = node;node.prev = last;last = node;}size++; //链表元素个数加一}//出队列public int poll() {if(isEmpty()) { //判断队列是否为空throw new RuntimeException("队列为空,无法出元素");}int m = head.val;head = head.next;head.prev = null;size--;return m;}//判断队列是否为空public boolean isEmpty() {return size == 0;}//获取对头元素public int peek() {if(isEmpty()) { //判断队列是否为空throw new RuntimeException("队列为空,无法获取元素");}return head.val;}//获取元素个数public int getSize() {return size;}
}
循环队列
我们有时还会使用一种队列叫循环队列。环形队列通常使用数组实现。
队头添加元素,队尾删除元素,每块空间都能反复利用,有效节省空间。
front 指向队列的第一个元素,即array[front]为队列的第一个元素,初始值为0;
rear 指向最后一个元素的后一个元素,初始值为0;
从上图可以看出队列为空的条件:front == rear
当队列存放一个元素时,rear向前走一步,如下图:
那队列啥时候满了呢?当情况为下图时吗?
如果这就是满的话,但如何判断呢?是 front == rear 吗?
显然,这与判断队列为空相矛盾,我们不应该这样判断,那应如何判断呢?
我们有两种方法:
- 通过添加 size 属性记录(用size记录元素个数)
- 牺牲一个位置来表示满
我们就以牺牲一个位置为例吧,如果我们牺牲一个位置来表示满,当情况为下图时:
此情况就表示满,条件即是:(rear + 1) % array.length == front
class MyCircularQueue {int front = 0;int rear = 0;int[] array;public MyCircularQueue(int k) { //设计一个可以存放k个数据的数组array = new int[k+1]; //为什么要设计k+1个大小呢?} //因为在队尾后面需要牺牲一个空间,方便判满//向循环队列插入一个元素。如果成功插入则返回真。public boolean enQueue(int value) {if(isFull()) {return false;}array[rear] = value;rear = (rear + 1) % array.length;return true;}//从循环队列中删除一个元素。如果成功删除则返回真。public boolean deQueue() {if(isEmpty()) {return false;}front = (front + 1) % array.length;return true;}//从队首获取元素。如果队列为空,返回 -1 。public int Front() {if(isEmpty()) {return -1;}return array[front];}//获取队尾元素。如果队列为空,返回 -1 。public int Rear() {if(isEmpty()) {return -1;}return array[(rear+array.length-1)%array.length];}//检查循环队列是否为空。public boolean isEmpty() {if(rear == front) {return true;}return false;}//检查循环队列是否已满。public boolean isFull() {if((rear+1) % array.length == front) {return true;}return false;}
}
双端队列 (Deque)
双端队列:指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
Deque是一个接口,使用时必须创建LinkedList或ArrayDeque的对象。
在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现