Java数据结构——栈和队列
1. 栈(Stack)
1.1 概念
- 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的
一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
1.2实现
(相对来说,顺序表的实现上要比链表更为简单一些)
- 利用顺序表实现,即使用尾插 + 尾删的方式实现
package java41_0308;
//使用顺序表实现栈
public class MainStack {
private int[] data = new int[100];
private int size = 0;
//基本操作
//入栈
public void push(int val) {
if(size >= data.length) {
//在这里可以扩容
return;
}
data[size] = val;
size++;
}
//出栈。返回值就是出栈的那个元素
public Integer pop() {
if (size == 0) {
return null;
}
//站定元素就是最后一个元素
int ret = data [size-1];
size--;
return ret;
}
//3.取栈顶元素
public Integer Peek() {
if (size == 0) {
return null;
}
return data[size-1];
}
}
- 利用链表实现,则头尾皆可
package java41_0308;
//使用链表来实现栈
class Node {
int val;
Node next;
public Node(int val){
this.val = val;
}
}
public class MainStack2 {
//此处使用不带傀儡节点的链表表示
//如果使用带傀儡节点的链表的话,就更简单了
private Node head = null;
//核心操作
//1. 入栈
public void push(int val) {
Node newNode = new Node(val);
//把新结点进行头头插
//由于当前是不带傀儡节点的,所以就需要判定当前链表是空还是非空
if (head == null) {
head = newNode;
return;
}
newNode.next = head;
head = newNode;
}
//2.出栈
public Integer pop() {
//进行头删。
if (head == null) {
return null;
}
if (head.next == null) {
int ret = head.val;
head = null;
return ret;
}
int ret = head.val;
head = head.next;
return ret;
}
//3. 取栈顶元素
public Integer peek() {
if (head ==null) {
return null;
}
return head.val;
}
}
2. 队列(Queue)
2.1 概念
- 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
2.2 实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列
在数组头上出数据,效率比较低
- 使用链表实现队列
package java41_0308;
//使用链表实现队列
public class MyQueue {
static class Node {
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
//创建一个链表的头结点
//为了方便进行尾插,也记录尾节点
private Node head = null;
private Node tail = null;
//队列的核心操作
// 1. 入队列。 返回值表示插入成功/失败(也是为了和标准队列的 offer 接口看齐)
public boolean offer(int val) {
Node newNode = new Node(val);
//插入到链表尾部。需要考虑当前链表是否为空
if (head == null) {
//直接让 head 和 tail 指向新节点即可
head = newNode;
tail = newNode;
return true;
}
tail.next = newNode;
tail = tail.next;
return true;
}
//2. 出队列
public Integer poll() {
if (head == null) {
return null;
}
int ret = head.val;
if (head.next == null) {
head = null;
return ret;
}
head = head.next;
return ret;
}
//3.取栈顶元素
public static void main(String[] args) {
//测试一下入队出队操作
MyQueue myQueue = new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
myQueue.offer(4);
Integer ret = null;
ret = myQueue.poll();
System.out.println("ret=" + ret);
ret = myQueue.poll();
System.out.println("ret=" + ret);
ret = myQueue.poll();
System.out.println("ret=" + ret);
ret = myQueue.poll();
System.out.println("ret=" + ret);
ret = myQueue.poll();
System.out.println("ret=" + ret);
}
}
结果
- 使用顺序表实现队列
package java41_0308;
//使用数组来实现环形队列
public class MyQueue2 {
private int[] data = new int[100];
private int head = 0;
private int tail = 0;
private int size = 0;
//核心操作
//1.入队列
public boolean offer(int val) {
if (size == data.length) {
//队列满了,此处也可以实现扩容逻辑。
return false;
}
//把新元素放到 tail 对应的下标上,
data[tail] = val;
//自增 tail
tail++;
//一旦 tail 达到了数组的末尾,就需要让 tail 从头开始
if (tail ==data.length) {
tail = 0;
}
//这个代码还可以写作
//tail = tail % data.length;
//更新 size 的值
size++;
return true;
}
//2. 出队列
public Integer poll() {
if (size == 0) {
return null;
}
int ret = data[head];
//更新 head 的位置
head++;
if (head == data.length) {
head = 0;
}
size--;
return ret;
}
//3. 取栈顶元素
public Integer peek() {
if (size == 0) {
return null;
}
return data[head];
}
}
2.3 循环队列
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。循环队列通常使用数组实现。
数组下标循环的小技巧
- 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
- 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length
如何区分空与满
- 通过添加 size 属性记录
- 保留一个位置
3. 面试题
- 括号匹配问题。
- 用队列实现栈。
- 用栈实现队列。
- 实现一个最小栈。
- 设计循环队列。