[JAVA] 数据结构-栈与队列及其基础操作
一.概念
1.什么是栈?
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则
入栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
2.什么是队列?
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表.
队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
二.实现
1.栈的实现
(1)顺序表-栈的实现
a.使用尾插操作表示“入栈”
b.使用尾删操作表示”出栈”
c.使用根据下标获取元素的操作表示”取栈顶元素”
代码如下
//使用顺序表来实现
public class MyStack {
private int[] data = new int[100];
private int size = 0;
//核心操作
//1.入栈
public void push(int val) {
if (size >= data.length) {
return;
}
data[size] = val;
size++;
}
//2.出栈 (返回值:出栈的元素)
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];
}
}
(2)链表-栈的实现
a.使用头插操作表示“入栈”
b.使用头删操作表示”出栈”
c.直接取到头节点,表示”取栈顶元素”
代码如下
//使用链表来实现
class Node{
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
public class MyStack2 {
//此处使用不带傀儡节点的链表来表示
//如果使用带傀儡节点的链表的话,就更简单了
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.出栈(返回值:出栈元素)
//ret的存在是为了有返回值
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.队列的实现
(1)链表-队列的实现
使用尾插表示入队列
使用头删表示出队列
直接去到头结点,就是取队首元素
代码如下
//使用链表实现队列
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.入队列(返回值:插入成功/失败)
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 Integer peek() {
if (head == null) {
return null;
}
//一般情况
return head.val;
}
//使用测试用例测试一下
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);
}
}
(2)顺序表-队列的实现
单独使用顺序表,势必会涉及到头插头删,效率比较低 |
解决办法:
a.创建两个下标,分别表示 队列的头部 和 队列的尾部
private int head = 0;
private int tail = 0;
队列的有效元素区间 [ head , tail ]
b.入队列: 就是把新的元素给放到tail对应的下标上,同时tail++
c.出队列: 就是把head++ ,就意味着把原来head指向的元素就排除到有效区间外了
代码如下
//使用数组实现队列
public class MyQueue2 {
private int[] data = new int[100];
//队列的有效区间 [head , tail]
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到达数组末尾,则重新开始
if (tail == data.length) {
tail = 0;
}
// //还可以写成
// tail = tail % data.length;
size++;
return true;
}
//2.出队列
public Integer poll() {
if(size == 0) {
return null;
}
int ret = data[head];
//更新head的位置
head++;
//head到达终点
if (head == data.length) {
head = 0;
}
size--;
return ret;
}
//3.取队首元素
public Integer peek() {
if (size == 0) {
return null;
}
return data[head];
}
}
使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。 |
3.循环队列
实际中我们有时还会使用一种队列叫循环队列。
如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。
数组下标循环的小技巧
(1)下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
index = 7
array.length = 9
offset = 4
结果 = 2
(2)下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length
index = 2
array.length = 9
offset = 4
结果 = 7
这个版本的队列相比于链表版本来说,更快,
局限性是 空间是固定大小,扩容成本高
当环形队列为空时, 此时head和tail是重合的
当环形队列为满时, 此时head和tail是重合的
解决方案
A方案:
不要把这个环形队列压榨的这么干净,故意浪费一个空间
用head 和 tail重合时表示空队列
用head == tail - 1 来表示满队列
B方案:
不浪费空间了,专门搞一个size变量记录队列的元素个数
Size == 0 就是空队列
Size == 数组长度 就是满队列
具体环形队列的代码,下次有机会了写出来和大家探讨探讨
大家要一起努力成为自己想成为的那个人! |