[从今天开始修炼数据结构]基本概念

[从今天开始修炼数据结构]线性表及其实现以及实现有Itertor的ArrayList和LinkedList

[从今天开始修炼数据结构]栈、斐波那契数列、逆波兰四则运算的实现

[从今天开始修炼数据结构]队列、循环队列、PriorityQueue的原理及实现

一、什么是队列

  队列queue是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

  队列是先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。如下图所示

  

  例如聊天室消息发送就是队列形式,先发出去的消息会先显示,后发的会后显示。

二、队列的抽象数据类型

  队列的抽象数据类型与线性表的基本相同,只不过插入删除的位置有限制。

ADT Queue
Data
同线性表,相邻的元素具有前驱后继关系
Operation
InitQueue(*Q):初始化操作
DestroyQueue(*Q):若队列Q存在,销毁他
ClearQueue(*Q):将队列Q清空
QueueEmpty(Q):若队列Q为空,返回true,否则返回false
GetHead(Q,*e):若Q存在且非空,用e返回队首元素
EnQueue(*Q,e):若Q存在,插入新元素e到队尾
DeQueue(*Q,*e):删除Q中的队首元素,并用e返回其值
QueueLength(Q):返回队列Q的元素个数
endADT

三、循环队列

  1,在看循环队列之前,我们首先看一下普通的顺序存储队列。

  顺序存储队列由数组实现,下标0一端作为初始队首,另一端作为队尾,初始化数组大小应该比要存储的数据要大,以方便插入。我们声明一个队首指针front指向第一个元素,一个队尾指针rear指向队尾元素的下一个位置。

  每删除一个元素,队首指针就向后移动一个格子,每插入一个元素,队尾指针也向后移动一个格子。

  这样实现的队列有什么弊端? 由于数组的大小是固定的,尽管设计者可能考虑到声明一个足够大的数组,在经年累月的插入删除后,数组空间总有用完的一天,这时候只能想办法来扩容。但是此时数组的前端还剩下许多由于删除而没有使用的空间,这种现象叫做“假溢出”。为了解决这种现象,我们考虑使用循环队列。

  2,循环队列

  解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。这就是循环队列。当数组后端位置用到最后一个时,让rear指向0位置,这样不会造成指针指向不明的问题。

  现在解决了空间浪费和假溢出的问题,但是现在如何判断队列是满呢?当front=rear时,队列是空还是满呢?

  我们设队列的最大长度为QueueSize,且让队列满时,保留一个元素空间。也就是说array.size - 1 = QueueSize。这时队列满的条件为(rear + 1) % QueueSize == front。

  另外,通用的队列当前长度计算方法为(rear - front + QueueSize) % QueueSize。

  3,循环队列的实现

  

package Queue;

public class ArrayQueueDemo<T> {
/*
仿照JDK1.8 中的 ArrayQueue 实现一个简单的循环队列,有些许不同
JDK 13中为什么没找到ArrayQueue?
*/ private Object[] queue;
private int capacity; //实际要装的数据个数
private int front;
private int rear; public ArrayQueueDemo(int capacity){
this.capacity = capacity + 1;
this.queue = new Object[capacity + 1]; //+1之后是数组长度,因为要预留一个空位置给rear。 文中提到的QueueSize是没有 + 1 的capacity
this.front = 0;
this.rear = 0;
} public void add(T data){
queue[rear] = data;
int newRear = (rear + 1) % capacity;
if (newRear == front){
throw new IndexOutOfBoundsException();
}
rear = newRear;
} public T remove(){
if (isFull()){
throw new IndexOutOfBoundsException();
}
Object removed = queue[front];
queue[front] = null;
front = (front + 1) % capacity;
return (T)removed;
} /**
* 返回最大队列长度
* @return 最大
*/
public int capacity(){
return capacity;
} /**
* 返回当前队列大小
* @return
*/
public int size(){
return (rear - front + capacity - 1) % (capacity - 1);
}
public boolean isFull(){
return (rear + 1) % (capacity - 1) == front;
}
}

四、队列的链式存储结构及实现。

  队列的脸书存储结构,就是线性表的单链表,只不过限制了只能尾进头出。我们把它简称为链队列。为了操作方便,我们把队头指针指向链表的表头,队尾指针指向链表的表尾。空队列时,front和rear都指向头结点。

  由于JDK中没有单纯的链式队列,且链式队列的实现很简单,所以我选择在这里实现一下PriorityQueue

五、PriorityQueue  这部分参考和转载自https://blog.csdn.net/u010623927/article/details/87179364

  优先队列是在入队时自动按照自然顺序或者给定的比较器排序的队列。

  优先队列是通过数组表示的小顶堆实现的。小顶堆可以理解为,父结点的权值总是不大于子节点的完全二叉树。

上图中我们给每个元素按照层序遍历的方式进行了编号,如果你足够细心,会发现父节点和子节点的编号是有联系的,更确切的说父子节点的编号之间有如下关系:

leftNo = parentNo*2+1

rightNo = parentNo*2+2

parentNo = (nodeNo-1)/2

通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。

PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)。

方法剖析
add()和offer()
add(E e)和offer(E e)的语义相同,都是向优先队列中插入元素,只是Queue接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回false。对于PriorityQueue这两个方法其实没什么差别。

新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整。

Poll()方法的实现原理如下

首先记录0下标处的元素,并用最后一个元素替换0下标位置的元素,之后调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)。重点是siftDown(int k, E x)方法,该方法的作用是从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。

PriorityQueue的简易代码实现:

package Queue;

import java.util.Arrays;
import java.util.Comparator; /*
优先队列的作用是能保证每次取出的元素都是队列中权值最小的
(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)。
这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序(natural ordering),
也可以通过构造时传入的比较器。
*/
public class PriorityQueueDemo<T> {
private Object[] queue;
private int size;
private Comparator<? super T> comparator; public PriorityQueueDemo(int capacity){
queue = new Object[capacity];
size = 0;
} public PriorityQueueDemo(int capacity,Comparator<? super T> comparator){
this.comparator = comparator;
queue = new Object[capacity];
size = 0;
} /**
* 这里给出一个堆排序初始化一个小顶堆队列的方法
* @param data 要被排序的数组
* @param comparator 定义比较方式的比较器
*/
public PriorityQueueDemo(T[] data, Comparator<T> comparator) throws Exception {
int capacity = data.length;
queue = new Object[capacity];
size = 0;
this.comparator = comparator;
for (int i = 0; i < data.length; i++) {
add(data[i]);
}
} public void add(T data) throws Exception {
int i = size;
if (data == null){
throw new Exception();
}else if (i >= queue.length){
grow(i);
}
if (size == 0){
queue[0] = data;
size++;
}
else {
queue[i] = data;
shiftUp(i, data);
size++;
}
} public T poll(){
if (size == 0){
throw new NullPointerException();
}
T polled = (T)queue[0];
int s = --size;
T x = (T)queue[s];
queue[0] = queue[s];
queue[s] = null;
if (s != 0){
shiftDown(0,x);
} return polled;
} private void shiftDown(int i, T x) {
//父节点是否大于子节点的标志
//若为true,则需要进入循环。
boolean flag = true;
while(flag) {
int child = (i << 1) + 1;
T smallerChild = (T) queue[child];
int right = child + 1;
if (right >= size || child >= size){
flag = false;
break;
}
if (comparator.compare(smallerChild, (T) queue[right]) > 0) {
child = right;
smallerChild = (T) queue[child];
}
if (comparator.compare(x, smallerChild) <= 0) {
flag = false;
break;
}
queue[i] = smallerChild;
i = child;
}
queue[i] = x;
} /**
* 新结点上浮
* @param i i的初始值是size的引用,即尾指针的位置。
* @param data 新结点的数据
*/
//理解。
private void shiftUp(int i,T data) {
while(i > 0) {
int parent = (i - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(data, (T)e) >= 0) {
break;
}
queue[i] = e;
i = parent;
}
queue[i] = data;
} private void grow(int i) {
Object[] newQueue = Arrays.copyOf(queue, i + 1);
this.size++;
}
}

[从今天开始修炼数据结构]队列、循环队列、PriorityQueue的原理及实现的更多相关文章

  1. 用数组实现队列(顺序队列&循环队列)

    用数组实现队列(顺序队列&循环队列) 顺序队列 ️ 队列(先进先出) 几个问题: 队列方法:入队.出队 队列的存储:即队首队尾两个指针, 扩容:如果队列容量不够了,应该扩容,如果队尾没有位置了 ...

  2. 【Java】 大话数据结构(7) 循环队列和链队列

    本文根据<大话数据结构>一书,实现了Java版的循环队列.链队列. 队列:只允许在一端进行插入操作,而在另一端进行删除操作的线性表. 1.循环队列 队列的顺序储存结构:用数组存储队列,引入 ...

  3. 数据结构:循环队列(C语言实现)

    生活中有非常多队列的影子,比方打饭排队,买火车票排队问题等,能够说与时间相关的问题,一般都会涉及到队列问题:从生活中,能够抽象出队列的概念,队列就是一个能够实现"先进先出"的存储结 ...

  4. TZOJ 数据结构实验--循环队列

    描述 创建一个循环队列,队列元素个数为4.能够实现队列的初始化.入队列.出队列.求队列长度等操作. 循环队列数据类型定义如下: typedef struct{ int data[Max];    in ...

  5. 数据结构之循环队列Demo

    循环队列 比较简单,循环队列主要是判断队满.队空.有效元素个数 画图说明: 假设:队的长度为5(0-4) 但是实际maxsize为6,需要一个预留空间(不存储元素)做计算 继续添加3个元素后: 出队一 ...

  6. java数据结构-10循环队列

    一.概念: 循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用 二.代码实现: @SuppressWarnings("unchecked" ...

  7. Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列

    Atitit.升级软件的稳定性---基于数据库实现持久化  循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...

  8. 【数据结构】循环队列 C语言实现

    "Queue.h" #include "Queue.h" #include <stdio.h> #include <stdlib.h> ...

  9. C_数据结构_循环队列

    # include <stdio.h> # include <malloc.h> typedef struct Queue { int * pBase; int front; ...

  10. TypeScript算法与数据结构-队列和循环队列

    本文涉及的源码,均在我的github.有两部分队列和循环队列.有问题的可以提个issue,看到后第一时间回复 1. 队列(Queue) 队列也是一种线性的数据结构, 队列是一种先进先出的数据结构.类似 ...

随机推荐

  1. [ch03-00] 损失函数

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力. 第3章 损失函数 3.0 损失函数概论 3.0.1 概念 ...

  2. Protues7.8仿真软件有中文路径无法正常运行怎么办?

    Protues7.8是一款功能强大的单片机仿真软件,在我们的学习生活中经常会用的到,在装软件时明明已经装好了,却不能报错跳出两行红字,让人心痛. 一般都是因为账户名字是中文的问题,这个软件对中文不兼容 ...

  3. linux [CTRL]+c与[CTRL]+d

    [CTRL]+c:中断目前程序.用于在linux中输入了错误的命令或者参数,有的时候会在系统不停的运行,如果想让程序需停下来,可以使用[CTRL]+C [CTRL]+d:这个组合键代表着键盘输入结束( ...

  4. python中将xml格式转json格式

    一.简介 在用python写脚本时,通常需要处理xml格式的文件或字符串.由于json格式处理的方便性, 我们可将其转为json格式进行处理. 二.步骤 1.安装工具包xmltodict 在命令行输入 ...

  5. Java基础面试题及答案(一)

    Java 基础部分 1. JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境. JRE:Java ...

  6. Logistic回归算法梯度公式的推导

    最近学习Logistic回归算法,在网上看了许多博文,笔者觉得这篇文章http://blog.kamidox.com/logistic-regression.html写得最好.但其中有个关键问题没有讲 ...

  7. 简单入门Kubernetes

    什么是Kubernetes 官网 https://kubernetes.io/ 中文版:https://kubernetes.io/zh/ 个人理解 基于容器技术 分布式架构 弹性伸缩 隔离物理机 和 ...

  8. PHP经典面试题目汇总

    1.双引号和单引号的区别 双引号解释变量,单引号不解释变量 双引号里插入单引号,其中单引号里如果有变量的话,变量解释 双引号的变量名后面必须要有一个非数字.字母.下划线的特殊字符,或者用{}讲变量括起 ...

  9. PHP fsockopen受服务器KeepAlive影响的解决

    在开发过程中常常遇到这样的需求,模拟浏览器访问某接口,并获取返回数据.我们比较常使用的方法是fsockopen与接口建立连接,然后发出指令,然后通过fgets接受返回值. 但是我们发现,通过PHP模拟 ...

  10. 微信小程序——页面栈

    刚开始用小程序的时候没怎么在意页面的跳转,也没仔细看文档中说的页面栈的内容.只要能跳转就行,wx.navigateTo,wx.redirectTo 这些方法一顿乱用.最后在做一个十层页面(以前页面栈是 ...