Java栈,队列和循环队列的实现
1. 栈
1.1 概念
一种特殊的线性表,说明了具有前驱和后继关系。只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵循后进先出 LIFO 原则【Last In First Out】
压栈:栈的插入/进栈/压栈,入数据在栈顶
出栈:栈的删除操作叫做出栈,出数据也在栈顶
后进先出【类似于浏览器的回退,只会返回上一个浏览后的页面而不是返回第一个打开的页面;弹夹中的子弹】
邪恶的小技巧:合理的出栈顺序(画栈图)
已知一个栈的入栈序列是m, n, x, y, z.则不可能出现的出栈顺序是: C
A mnxyz
B xnyzm
C nymxz
D nmyzx
A
B
C
m要想出栈必须让x出栈才行,所以是不可能的出栈顺序
D
邪恶的小技巧:中缀表达式转后缀表达式
有一个中缀表达式为a*(b-(c+d)),它的后缀表达式可以是什么: A
A abcd+-*
B abc+d-*
C ab-cd+*
D abd+c-*
前缀表达式: 把运算符放前边
中缀表达式:a*(b-(c+d)
后缀表达式: 把运算符放后边
按照运算优先级给操作符号放在括号外边
a*(b-(c+d))确定优先级
(a*(b-(c+d))) 打括号
+ 先算
(a*(b- ( cd )+ ))移动符号
(a* ( b(cd)+ )- )
( a(b(cd)+)- )*
后缀表达式: abcd+-*
这样就不用像第一题那样每次都画操作栈
方法总结:从左往右,根据运算符的优先级(注意括号问题),额外加括号,把每个对应的运算符移到对应的括号外面。去除所有的括号,结果就是对应的前后缀表达式【后缀放后边,前缀放前边即可】
2*3(4+5)-6
(2*3(4+5)-6)
(2*3(45)+-6)
(2*(345)+*-6)
(2((345)+*)-6)
((2((345)+*))-6)
((2((345)+*))*6)-
2345 +**6-
应用
int func(后缀表达式)
1.2 实现
public static void main(String[] args) {Stack<Integer> stack = new Stack<>();stack.push(1);// 增加元素stack.push(2);stack.push(3);System.out.println(stack.peek());// 获取栈顶元素但不删除 3System.out.println(stack.pop());// 弹出栈顶元素 删除3System.out.println(stack.peek());// 获取栈顶元素但不删除2System.out.println(stack.empty());// 栈自带的 empty 方法System.out.println(stack.isEmpty());// 继承 vector 的方法System.out.println(stack.search(2));// 查找下标System.out.println(stack.search(3));// 没有就返回 -1}3
3
2
false
false
1
-1
Stack
源码
发现只有
push
,peek
,pop
,empty
,search
方法
为何可以使用isEmpty
呢?
为何可以使用 isEmpty
方法呢。
因为
Stack
继承Vector
,所以可以使用父类的isEmpty
方法
分析一下push
压栈操作,来查看它的底层是如何实现的
查看addElementr
方法
如果不清楚源代码内容的可以查看我的博客——List的使用中add
方法一步步源码解析,了解栈的底层数组如何扩容如何增加元素
我们查看一下 Stack
继承和实现
发现这个体系是:Stack
类继承 Veator
类, Vector
类继承 AbstractList
类, AbstractList
类继承自 AbstractCollection
类, AbstractCollection
类实现 Collection
接口, Collection
接口继承 Iterable
接口
1.2.1 数组实现Stack
import java.util.Arrays;public class MyStackList<E> {// public static void main1(String[] args) {// Stack<Integer> stack = new Stack<>();
// stack.push(1);// 增加元素
// stack.push(2);
// stack.push(3);
// System.out.println(stack.peek());// 获取栈顶元素但不删除 3
// System.out.println(stack.pop());// 弹出栈顶元素 删除3
// System.out.println(stack.peek());// 获取栈顶元素但不删除2
// System.out.println(stack.empty());// 栈自带的 empty 方法
// System.out.println(stack.isEmpty());// 继承 vector 的方法
// System.out.println(stack.search(2));// 查找下标
// System.out.println(stack.search(3));// 没有就返回 -1
// }private E[] elem;private int usedSize = 0;private int capacity = 4;MyStackList() {this.elem = (E[]) new Object[capacity];}// 扩容private void checkCapacity() {if (this.usedSize == this.capacity) {this.capacity *= 2;this.elem = Arrays.copyOf(this.elem, this.capacity);}}E push(E e) {checkCapacity();this.elem[this.usedSize++] = e;return this.elem[this.usedSize - 1];}E pop() {if (this.usedSize == 0) {throw new ArrayIndexOutOfBoundsException("Stack is empty !!!");} else {return this.elem[--this.usedSize];}}E peek() {if (this.usedSize == 0) {return null;} else {return this.elem[this.usedSize - 1];}}boolean empty() {return this.usedSize == 0;}void display() {for (int i = this.usedSize-1; i >= 0; i--) {System.out.print(this.elem[i] + " ");}System.out.println();}private static void testPush() {MyStackList stack = new MyStackList();for (int i = 0; i < 10; i++) {System.out.print(stack.push(i) + " ");}}private static void testPop() {System.out.println("Push:");MyStackList stack = new MyStackList();for (int i = 0; i < 10; i++) {stack.push(i);}stack.display();System.out.println("Pop:");for (int i = 0; i < 15; i++) {System.out.print(stack.pop() + " ");}System.out.println("Residue: ");stack.display();}private static void testPeek() {MyStackList stack = new MyStackList();System.out.println("Push:");for (int i = 0; i < 10; i++) {stack.push(i);}stack.display();System.out.println("Peek:");for (int i = 5; i < 10; i++) {System.out.print(stack.peek() + " ");}System.out.println("Residue:");stack.display();}public static void main(String[] args) {// 放置测试用例testPop();}
}
数组实现栈, 为了提高效率该如何插入删除数据呢?
- 尾插尾删: 避免了数组元素的移动, 时间复杂度O(1)
- 头插头删: 还要移动数据, 时间复杂度O(N)
1.2.2 单链表实现Stack
class Node<E> {private E val;private Node next;public Node(E val) {this.val = val;}Node head;E push(E val) {Node node = new Node(val);if (head == null) {head = node;return (E) head.val;} else {node.next = head;head = node;return (E) head.val;}}E pop() {if (head == null) {throw new NullPointerException("NULL !!!");} else {E ret = (E) head.val;head = head.next;return ret;}}E peek() {if (head == null) {throw new NullPointerException("NULL !!!");} else {return (E) head.val;}}void display() {Node cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}}
}public class MyStackLinked<E> {private static void testPush() {Node node = new Node(null);for (int i = 0; i < 8; i++) {System.out.print(node.push(i) + " ");}}private static void testPop() {Node node = new Node(null);for (int i = 0; i < 8; i++) {node.push(i);}for (int i = 0; i < 5; i++) {System.out.print(node.pop() + " ");}}private static void testPeek() {Node node = new Node(null);for (int i = 0; i < 8; i++) {node.push(i);}for (int i = 0; i < 5; i++) {System.out.print(node.peek() + " ");}}public static void main(String[] args) {testPeek();}
}
单链表实现栈, 为了提高效率该如何插入删除数据呢?
- 头插头删: 时间复杂度O(1)
- 尾插尾删: 不带尾节点单链表时间是复杂度O(N);带尾节点尾结点
tail
可以把入栈push操作的时间复杂度降到O(1)但是无法把出栈pop时间复杂度降到O(1)【需要找尾节点的前一个节点,所以是O(N)】
2. 队列
2.1 概念
只允许一端进行数据插入操作,另一端进行数据删除操作的特殊线性表.队列具有先进先出功能FIFO(First In First Out)
入队列: 进行插入操作的一端称为队尾
出队列: 进行删除操作的一端称为对头
先进先出【操作系统的程序点开执行顺序,客服电话,键盘的输入,记事本的输出】
2.2 实现
发现 Queue
只有方框中的方法
Queue
接口继承了 Collection
接口, Collection
接口继承 Iterable
接口【实现过程比 Stack
简单许多】
数组实现Queue
数组实现队列有没有什么问题呢?
因为是先进先出的, 所以会导致先出空间的内容造成浪费。而不是像栈那样一直处于一端操作,不会造成空间浪费。如果元素移动的话来决解空间问题的话,时间复杂度会变为O(N)效率太低
因此数组的话可以通过“循环”的方式实现队列
什么是“循环队列”呢?
分析解释环形图
- 0~7: 数组的引用
- 11, 12, 13, 14, 15, 16, 17存储的元素值
- rear:指向插入元素的引用【带插入元素的下一个索引】,图中代表的是刚插入元素17还未移动rear下标,此时插入元素操作完毕后的rear应该移动到下一个索引0处
- front:指向对头元素的索引,每次出队列一个元素就移动一次front
思考: 何时代表队列满了, 何时代表队列空?
- 当
front==rear
的时候,有两种可能: 要么队列满要么队列空- 如果 rear 的下一个是 front 就代表满了【现在可以区
front==rear
】的两种情况了
思考: rear走到数组末尾,该如何跳转到数组0下标开头呢?
最简单的方法就是 if 判断
if (re a r== elem.length-1){// 走到了队尾rear = 0;
}
这时候如果利用取余运算呢? 会发现有不同的运算效果
假设一边有插入一边有删除,那么这个时候即可实现循环操作对立达到堆空间的利用的同时复杂度还是O(1)
队列长度为8, rear+1 | 取余结果 |
---|---|
1%8 | 1 |
2%8 | 2 |
3%8 | 3 |
4%8 | 4 |
5%8 | 5 |
6%8 | 6 |
7%8 | 7 |
8%8 | 0 |
9%8: 不会出现这种情况,rear+1==front的话就已经是满队列 | 1 |
2.2.1 数组实现循环队列
这是一个 OJ 链接
方案1会浪费一个位置的队列空间,代码如下:
class MyCircularQueue {private int[] elem;private int front, rear;public MyCircularQueue(int k) {this.elem = new int[k+1];}/*** 入队列** @param value 值* @return*/public boolean enQueue(int value) {if ((this.rear + 1) % this.elem.length == this.front) {return false;//满了} else {this.elem[this.rear] = value;this.rear = (this.rear + 1) % this.elem.length;return true;}}/*** 出队列** @return*/public boolean deQueue() {if (this.front == this.rear) {return false;} else {int val = this.elem[this.front];
// this.front = (this.front + 1) % this.elem.length;// 因为是循环,所以不能直接 front+1return true;}}/*** 取队头元素【相当于 peek 】** @return*/public int Front() {if (this.front == this.rear) {return -1;// leetcode 抛异常会认为是你的代码错误,所以不能抛异常} else {int val = this.elem[this.front];return val;}}/*** 取队尾元素* rear处于0位置则返回this.elem.length-1* 所以不能直接进行 -1 操作** @return*/public int Rear() {if (this.rear == this.front) {return -1;} else {if (this.rear == 0) {return this.elem[this.elem.length - 1];} else {return this.elem[this.rear - 1];}}}public boolean isEmpty() {return this.front == this.rear;}public boolean isFull() {return this.rear + 1 == this.front;}
}
方案2,通过一个额外的变量来记录队列的状态【并非 OJ 题目代码,只是提供一个思路,把最后的队列空间利用完】
public class my_Queue_list<E> {E[] elem = (E[]) new Object[5];// 队列有效区间[head, tail)private int head = 0;private int tail = 0;private int size = 0;// 1.入队列boolean offer(E val) {if (size == elem.length) {return false;} else {elem[tail++] = val;tail = tail % elem.length;++size;return true;}}// 2.出队列E poll() {if (size == 0) {return null;} else {E ret = (E) elem[head++];head = head % elem.length;--size;return ret;}}// 3.取队首元素E peek() {if (size == 0) {return null;} else {return (E) elem[head];}}// 4.打印void display() {for (int i = 0; i < size; i++) {System.out.print(elem[i] + " ");}}private static void test_offer() {my_Queue_list my_queue_list = new my_Queue_list();for (int i = 0; i < 5; i++) {my_queue_list.offer(Integer.valueOf(i));}my_queue_list.display();}private static void test_poll() {my_Queue_list my_queue_list = new my_Queue_list();for (int i = 0; i < 5; i++) {my_queue_list.offer(Integer.valueOf(i));}for (int i = 0; i < 5; i++) {System.out.print(my_queue_list.poll() + " ");}}private static void test_peek() {my_Queue_list my_queue_list = new my_Queue_list();for (int i = 0; i < 5; i++) {my_queue_list.offer(Integer.valueOf(i));}System.out.println(my_queue_list.peek());}public static void main(String[] args) {// 放置测试用例test_peek();}
}
2.2.2 单链表实现Queue
class Node<E>{private E val;private Node next;private int usedSize;public Node(E val) {this.val = val;this.usedSize = 0;}Node front;Node rear;/*** 入队列* @param val 值* @return*/E offer(E val){Node node = new Node(val);if (this.front == null){this.front = node;this.rear = node;}else{this.rear.next = node;this.rear = node;}++this.usedSize;return (E) this.rear.val;}/*** 出队列* @return*/E poll(){if (this.front == null){throw new NullPointerException("Queue is empty !!!");}else{E ret = (E) this.front.val;if (this.front.next == null){this.front = null;this.rear = null;}else{this.front = this.front.next;}--this.usedSize;return ret;}}/*** 出对头元素但不删除* @return*/E peek(){if (this.front == null){throw new NullPointerException("Queue is empty !!!");}else{E ret = (E) this.front.val;return ret;}}/*** 为空* @return*/boolean isEmpty(){return this.usedSize == 0;}/*** 队列长度* @return*/int size(){return this.usedSize;}
}
public class MyQueueLinked {private static void tstOffer(){Node node = new Node(null);for (int i = 0; i < 8; i++) {System.out.print(node.offer(i) + " ");}}private static void testPoll(){Node node = new Node(null);for (int i = 0; i < 8; i++) {node.offer(i);}for (int i = 0; i < 9; i++) {System.out.print(node.poll()+" ");}}private static void testPeek(){Node node = new Node(null);for (int i = 0; i < 8; i++) {node.offer(i);}for (int i = 0; i < 9; i++) {System.out.print(node.peek()+" ");}}public static void main(String[] args) {// 放置测试用例testPeek();}
}
3. Stack 和 Queue 常用 API 总结
Stack
方法 | 解释 |
---|---|
E push(E item) | 压栈并返回压入的元素 |
E pop() | 弹出栈顶元素并返回弹出的元素 |
E peek() | 查看顶元素且不弹出,保留在栈中 |
boolean empty() | 栈是否为空 |
Queue
抛出异常 | 返回值 | |
---|---|---|
入队列 | boolean add(e) | boolean offer(e) |
出队列 | E remove() | E poll() |
队首元素 | E element() | E peek() |
4. 结尾语
两栈共享空间这种操作我还不会,以后会学会了会继续更新栈内容
栈和队列说简单也简单,说难也有一丝的难度。
下面是我的感悟:
人生,就像是一个很大的栈演变。
自下而上的增长,不仅仅是自然万物,也是我们的成长经历过程,
从出生时我们赤裸裸的来到这个世间,渐渐地长大,知道我们变老,经历的周围人的生老病死,最终我们还的赤裸裸地衣锦还“乡”。
人生,又放佛是一天一天小小的栈重现。
呀呀学语的时候,父母每天为抱着我们不断的进出家门,青少年的时候有叛逆,青年的时候高中和大学的接轨,是否和父母一样保持着童年时期的联系,壮年时候将会奔波于家和事业之间,老年了,我想我会小园香径独徘徊于养老院门里屋前。
人生,更需要有栈的精神体现。
在哪里跌倒就在哪里爬起,无论是否见到栈底,我们始终应该保持栈定的仰望天空。只要有希望,不断进取,出头之日将会重现。困难不会永远存在,强者才会勇往直前。
人生,其实就是一个大大的队列演变。
无知童年,快乐少年,稚嫩青年,成熟中年,安逸晚年。
人生,也是一个又一个小小的队列重现。
春夏秋冬轮回年年,早中晚夜循环天天。变化的是时间,不变的是对未来执着的信念。
人生,更需要有队列精神的体现。
南极到北极,不过时南纬 90 度到北纬 90 度的队列,如果中途犹豫,临时转向,也许只能与企鹅或北极熊相伴。可事实是无论坚持哪个方向,你都会到达终点。
大部分人都是普通人,学习数据结构,我很笨,想着宏伟的计划,却发现别人学一遍的我需要两遍才能弄明白。队列精神对于普通人来说,已经很宝贵,聪明人可以转向,绕过许多沟壑以最快的速度到达终点,但是普通人只能脚踏实地的一步一个脚印走完该走的路。
Java栈,队列和循环队列的实现相关推荐
- java循环队列_Java版-数据结构-队列(循环队列)
前情回顾 在上一篇,笔者给大家介绍了数组队列,并且在文末提出了数组队列实现上的劣势,以及带来的性能问题(因为数组队列,在出队的时候,我们往往要将数组中的元素往前挪动一个位置,这个动作的时间复杂度O(n ...
- 计算机二级循环队列知识点,考点!计算机二级考试公共基础知识冲刺复习笔记:栈、队列和循环队列...
小编所收集到的相关计算机二级考试公共基础知识冲刺复习笔记:栈.队列和循环队列的资料 大家要认真阅读哦! 1.栈(Stack)又称堆栈. (1)栈是一种运算受限的线性表,其限制是仅允许在表的一端进行插入 ...
- JAVA:实现一个CircularQueue循环队列算法(附完整源码)
JAVA:实现一个CircularQueue循环队列算法 package com.thealgorithms.datastructures.queues; public class CircularQ ...
- 数据结构(8)----栈与队列之循环队列
循环队列的使用 循环队列顺序存储结构 #define MAXSIZE 100 typedef int QElemType; typedef struct SqQueue{QElemType data[ ...
- 数据结构之——队列与循环队列
数据结构学习之--队列与循环队列 什么是队列(Queue) 队列基于动态数组的实现及时间复杂度分析 优化队列 循环队列(LoopQueue) 什么是队列(Queue) 队列(Queue)同栈(stac ...
- 《恋上数据结构第1季》队列、双端队列、循环队列、循环双端队列
队列(Queue) 队列 Queue 队列的接口设计 队列源码 双端队列 Deque 双端队列接口设计 双端队列源码 循环队列 Circle Queue 循环队列实现 索引映射封装 循环队列 – %运 ...
- 队列及循环队列为什么用空一个元素的位置
队列,循环队列为什么用空一个元素的位置 本篇笔记是我在B站上跟着尚硅谷教程进行学习,记录的学习笔记,在学习中产生了疑问,并通过查找资料解决了相关问题. 队列介绍 1)队列是一个有序列表,可以用数组或 ...
- 数据结构-队列之循环队列
将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上看成一个环,称为循环队列. 当队首指针q.front=MaxSize-1后,再前进一个位置就自动归0,可以通过除法取余运算(%)来实现. 初 ...
- 顺序队列,循环队列,链队列
队列 先看队列接口和结点类 1. 顺序队列 2. 循环队列 3. 链队列 先看队列接口和结点类 package com.lovely.queue;/** * 2020年4月26日下午2:42:44* ...
- C/C++队列与循环队列
C/C++数据结构 - 队列 循环队列 快速入门 介绍 1. 队列的定义 队列是一种线性存储结构,每次对队列的增删操作如下 增:在队列尾部添加元素 删(取出):在队列头部删除元素 这种数据存储方式遵循 ...
最新文章
- 实模式和保护模式区别及寻址方式
- MapReduce on Hbase
- Q79:怎么用三角形网格(Triangle Mesh)细分曲面
- 为SQL Server 增加链接到SQL Server 的链接服务器
- Eucalyptus简介
- Word中把竖排文字里的数字变横向
- Tableau自定义调色板
- 电脑出现无法访问您试图使用的功能所在的网络位置问题的解决方式
- 同路光芒 一亿光年同路光芒战略合作签约仪式圆满成功
- MySQL轻快入门2021.3.18(事务)
- 【Java基础】2020如何查看Java官方文档
- Photo Album: 2008年5月-三亚爱琴海岸康年度假村-day2
- 我的ElasticSearch认证工程师之路
- 《自拍教程40》Python adb一键导出Log
- 【评测】MP DSS肠炎模型专用
- Google Earth Engine(GEE)提取水体
- BTIM提升移动运维管理效率
- logistic回归的一些直观理解(1.连接函数 logit probit)
- 随笔0006 个人作业——软件产品评测
- Kali--渗透攻击Mysql数据库服务