《大话数据结构》队列的顺序存储和链式存储
1. 简介
成都的火车南站早上真的恐怖,地铁站人山人海,从地铁里面一直排队到门口,虽然人很多但是不得不说我国人民素质还是蛮高的,都是来了之后排在队伍的最后面,没有一个人去插队。这样不仅避免了人员拥挤的混乱,也让需要乘坐地铁的人可以尽快乘上地铁。
其实我们的队列就像排队等地铁一样,遵从先来后到,先来的人先上车后来的人后上车,队列则是先插入的数据,先取出去,后来的数据后取出去,先进先出的原则。而且总是从一端进入,另一端出去。
忽略那些排了队然后不想排的和插队的人。
顺序队列结构如下。
队列也是一种线性表,满足前驱后继,同样可以有顺序队列和链式队列,而顺序队列一般可以使用数组进行实现,那么队头就是下标为0,而队尾则是数组的最后一位(length-1),而链式列表可以使用链表,队头就是第一个结点,而队尾则是最后一个结点。
了解了队列的基本知识,下面看一下顺序队列基本实现思路,首先我们要定义两个标识一个是队尾,一个是队首,这两个标识就像两个小旗子,队列最前面和最后面的人都拿着一个旗子,好让别人知道现在的队首和队尾究竟是那一个人,如果新增数据首先知道数组的长度是否足够,如果不够则需要代码实现扩容,如果够则在队尾加入数据,同时将指向队尾的标识(小旗子)现在指向新增数据上,而获取数据时首先拿出数据,同时将旗子交给他的下一个数据。
讲到这儿有同学开始质疑,难道你第一个人上车了,下一个不向前走一走吗?确实如此,但是如果每次取数据都需要移动,因为采用的是顺序存储结构(数组)那么取数据的时间复杂度将会是O(n),因为你需要改变数组的结构,每一个人都要向前移动,实际上我们不需要这样做只需要把队首的取出来,然后把队首的旗子交给下一个,我们每次去拿数据只是去找队首的旗子在谁的手上就拿谁。但是这样又会存在一个问题,如果你前面走了两个人(取出了两个数据),然后后面不断的来了新的人(数据),然后数组因为初始化了容量发现已经满了,此时实际上我们并没有满,因为前面还有两个位置,而这就是我们常说的假溢出,就像你去坐公交车上车后看到后面全坐满了,而前面还有两个位置,你告诉师傅我要下车,因为没有位置了。
通过上面出现的问题,诞生出了一种循环队列,听名字就知道收尾相连接,实际上就是你上公交车后发现后面没有位置了,前面还有,那么肯定坐在前面撒。同样如果我们在插入数据时发现队尾已经超出数组长度了,但是队首确不是为0,也就是已经有人离开了,那么新增的就到前面去,同时队尾的旗子他也要拿上,直到队首的旗子和队尾的旗子相遇时也就是相等时,此时才满了,才需要进行扩容。而取数据时如果队尾小于队首那么每次取出数据后,旗子是交给前一个人,而不是后一个人。
2. 实现循环队列
package netty;
/** * 队列顺序存储-循环存储 * @author damao * @date 2019-11-28 10:39 */public class CircularQueue<T> {
private DataArray<T>[] dataArrays = new DataArray[DEFAULT_CAPACITY];
private static int DEFAULT_CAPACITY = 3;
/** * 前端指针,默认指向数组的第一位 */ private int front = 0;
/** * 后端指针,默认指向数组的第一位 */ private int rear = 0;
public boolean inQueue(T t){ DataArray<T> dataArray = new DataArray(); dataArray.setData (t); dataArrays[rear] = dataArray; rear++; capacityExpansion(); return true; }
public T outQueue(){ if(rear < front && front == DEFAULT_CAPACITY){ front = 0; } if(dataArrays[front] == null){ throw new RuntimeException ("Queue is already empty"); } T data = dataArrays[front].getData (); dataArrays[front].setData (null); front++; return data; }
public void capacityExpansion(){ if(rear == DEFAULT_CAPACITY && front != 0){ rear = 0; }else if(front == rear || (front<rear && rear == DEFAULT_CAPACITY)){ //容量扩大2倍 int newCapacity = DEFAULT_CAPACITY*2; DataArray<T>[] newDataArrays = new DataArray[DEFAULT_CAPACITY*2]; if(front < rear){ System.arraycopy (dataArrays,0,newDataArrays,0,DEFAULT_CAPACITY); }else { System.arraycopy (dataArrays,front,newDataArrays,0,DEFAULT_CAPACITY-front); System.arraycopy (dataArrays,0,newDataArrays,DEFAULT_CAPACITY-front,rear); } front = 0; rear = DEFAULT_CAPACITY; DEFAULT_CAPACITY = newCapacity; dataArrays = newDataArrays; } }
public static class DataArray<T>{
T data;
public T getData() { return data; }
public void setData(T data) { this.data=data; } }}
测试结果如下。
3. 使用链式存储结构实现栈
此处使用的是单向链表,非双向链表,由于链表不存在溢出的状况,所以不需要扩容,只需要新增数据时将旗子交给新来的,而取数据时将旗子交给他的下一个。
package netty;
/** * @author damao * @date 2019-11-28 10:40 */public class LinkedQueue<T>{
Node<T> front;
Node<T> rear;
public boolean inQueue(T t){ if(rear == null){ front = rear = new Node(t,null); }else { Node<T> node = new Node(t,null); rear = rear.next = node; } return true; }
public T outQueue(){ if(front == null){ throw new RuntimeException ("Queue is already empty"); } Node<T> result = front; front = result.next; result.next = null; return result.data; }
public static class Node<T>{
T data;
Node<T> next;
public Node(T data, Node<T> next) { this.data=data; this.next=next; } }}
测试结果如下
ps:两者的优缺点,顺序存储由于需要扩容,才能实现不会被溢出,而扩容之后需要将原数据进行拷贝,所以插入数据时相对而言会比链式队列慢一点,而取数据都是O(1),且实现代码来看,链式队列相比循环队列要简单很多,所以个人推荐使用链式队列。
- 对一道if-else相关的程序题的简单分析(r5笔记第45天)
- 持续近7个小时的索引扫描的查询优化分析 (r5笔记第44天)
- 04.Java对象和类
- 关于Oracle数据恢复的两个临界点(r5笔记第42天)
- 关于提问的一些建议(r5笔记第41天)
- shell中echo的显示格式 (r5笔记第58天)
- springboot 入门教程(5) 基于ssm框架的crud操作(前端部分-附源码)
- springboot入门(4)_web开发
- springboot入门教程(2)_Thymeleaf集成
- VList data structures in C#
- 编程思想 之「语言导论」
- 编程思想 之「对象漫谈」
- Github 项目推荐 | TensorFlow 概率推理工具集 —— probability
- Github 项目推荐 | 用于 C/C++、Java、Matlab/Octave 的特征选择工具箱
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 筋头云案例
- 线上问题排查思路、工具小结
- 返回顶部案例
- 机器视觉 | 光源照明综述(详细版)
- 网页轮播图案例
- Angular单元测试ComponentFixture的实例化过程明细
- 如何在Chrome调试器里检查嵌套Observable对象
- Angular 指令ngTemplateOutlet的运行原理单步调试
- 跳出源码地狱,Spring巧用三级缓存解决循环依赖-原理篇
- Angular NgTemplateOutlet的一个例子
- 手把手教你自制基于TencentOS Tiny的智能甲醛监测仪
- ASP.NET Core 使用 AutoFac 注入 DbContext
- Python爬虫练习:爬取800多所大学学校排名、星级等
- Python爬取股票信息,并实现可视化数据
- Python爬虫练习:爬取素材网站数据