简介

队列Queue是一个非常常见的数据结构,所谓队列就是先进先出的序列结构。

想象一下我们日常的排队买票,只能向队尾插入数据,然后从队头取数据。在大型项目中常用的消息中间件就是一个队列的非常好的实现。

队列的实现

一个队列需要一个enQueue入队列操作和一个DeQueue操作,当然还可以有一些辅助操作,比如isEmpty判断队列是否为空,isFull判断队列是否满员等等。

为了实现在队列头和队列尾进行方便的操作,我们需要保存队首和队尾的标记。

先看一下动画,直观的感受一下队列是怎么入队和出队的。

先看入队:

再看出队:

可以看到入队是从队尾入,而出队是从队首出。

队列的数组实现

和栈一样,队列也有很多种实现方式,最基本的可以使用数组或者链表来实现。

先考虑一下使用数组来存储数据的情况。

我们用head表示队首的index,使用rear表示队尾的index。

当队尾不断插入,队首不断取数据的情况下,很有可能出现下面的情况:

上面图中,head的index已经是2了,rear已经到了数组的最后面,再往数组里面插数据应该怎么插入呢?

如果再往rear后面插入数据,head前面的两个空间就浪费了。这时候需要我们使用循环数组。

循环数组怎么实现呢?只需要把数组的最后一个节点和数组的最前面的一个节点连接即可。

有同学又要问了。数组怎么变成循环数组呢?数组又不能像链表那样前后连接。

不急,我们先考虑一个余数的概念,假如我们知道了数组的capacity,当要想数组插入数据的时候,我们还是照常的将rear+1,但是最后除以数组的capacity, 队尾变到了队首,也就间接的实现了循环数组。

看下java代码是怎么实现的:

public class ArrayQueue {

    //存储数据的数组
private int[] array;
//head索引
private int head;
//real索引
private int rear;
//数组容量
private int capacity; public ArrayQueue (int capacity){
this.capacity=capacity;
this.head=-1;
this.rear =-1;
this.array= new int[capacity];
} public boolean isEmpty(){
return head == -1;
} public boolean isFull(){
return (rear +1)%capacity==head;
} public int getQueueSize(){
if(head == -1){
return 0;
}
return (rear +1-head+capacity)%capacity;
} //从尾部入队列
public void enQueue(int data){
if(isFull()){
System.out.println("Queue is full");
}else{
//从尾部插入
rear = (rear +1)%capacity;
array[rear]= data;
//如果插入之前队列为空,将head指向real
if(head == -1 ){
head = rear;
}
}
} //从头部取数据
public int deQueue(){
int data;
if(isEmpty()){
System.out.println("Queue is empty");
return -1;
}else{
data= array[head];
//如果只有一个元素,则重置head和real
if(head == rear){
head= -1;
rear = -1;
}else{
head = (head+1)%capacity;
}
return data;
}
}
}

大家注意我们的enQueue和deQueue中使用的方法:

rear = (rear +1)%capacity
head = (head+1)%capacity

这两个就是循环数组的实现。

队列的动态数组实现

上面的实现其实有一个问题,数组的大小是写死的,不能够动态扩容。我们再实现一个能够动态扩容的动态数组实现。

    //因为是循环数组,这里不能做简单的数组拷贝
private void extendQueue(){
int newCapacity= capacity*2;
int[] newArray= new int[newCapacity];
//先全部拷贝
System.arraycopy(array,0,newArray,0,array.length);
//如果real<head,表示已经进行循环了,需要将0-head之间的数据置空,并将数据拷贝到新数组的相应位置
if(rear< head){
for(int i=0; i< head; i++){
//重置0-head的数据
newArray[i]= -1;
//拷贝到新的位置
newArray[i+capacity]=array[i];
}
//重置real的位置
rear= rear+capacity;
//重置capacity和array
capacity=newCapacity;
array=newArray;
}
}

需要注意的是,在进行数组扩展的时候,我们不能简单的进行拷贝,因为是循环数组,可能出现rear在head后面的情况。这个时候我们需要对数组进行特殊处理。

其他部分是和普通数组实现基本一样的。

队列的链表实现

除了使用数组,我们还可以使用链表来实现队列,只需要在头部删除和尾部添加即可。

看下java代码实现:

public class LinkedListQueue {
//head节点
private Node headNode;
//rear节点
private Node rearNode; class Node {
int data;
Node next;
//Node的构造函数
Node(int d) {
data = d;
}
} public boolean isEmpty(){
return headNode==null;
} public void enQueue(int data){
Node newNode= new Node(data);
//将rearNode的next指向新插入的节点
if(rearNode !=null){
rearNode.next=newNode;
}
rearNode=newNode;
if(headNode == null){
headNode=newNode;
}
} public int deQueue(){
int data;
if(isEmpty()){
System.out.println("Queue is empty");
return -1;
}else{
data=headNode.data;
headNode=headNode.next;
}
return data;
}
}

队列的时间复杂度

上面的3种实现的enQueue和deQueue方法,基本上都可以立马定位到要入队列或者出队列的位置,所以他们的时间复杂度是O(1)。

本文的代码地址:

learn-algorithm

本文已收录于 http://www.flydean.com/12-algorithm-queue/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

看动画学算法之:队列queue的更多相关文章

  1. 看动画学算法之:双向队列dequeue

    目录 简介 双向队列的实现 双向队列的数组实现 双向队列的动态数组实现 双向队列的链表实现 双向链表的时间复杂度 简介 dequeue指的是双向队列,可以分别从队列的头部插入和获取数据,也可以从队列的 ...

  2. 看动画学算法之:排序-count排序

    目录 简介 count排序的例子 count排序的java实现 count排序的第二种方法 count排序的时间复杂度 简介 今天我们介绍一种不需要作比较就能排序的算法:count排序. count排 ...

  3. 看动画学算法之:linkedList

    目录 简介 linkedList的构建 linkedList的操作 头部插入 尾部插入 中间插入 删除节点 简介 linkedList应该是一种非常非常简单的数据结构了.节点一个一个的连接起来,就成了 ...

  4. 看动画学算法之:栈stack

    目录 简介 栈的构成 栈的实现 使用数组来实现栈 使用动态数组来实现栈 使用链表来实现 简介 栈应该是一种非常简单并且非常有用的数据结构了.栈的特点就是先进后出FILO或者后进先出LIFO. 实际上很 ...

  5. 看动画学算法之:平衡二叉搜索树AVL Tree

    目录 简介 AVL的特性 AVL的构建 AVL的搜索 AVL的插入 AVL的删除 简介 平衡二叉搜索树是一种特殊的二叉搜索树.为什么会有平衡二叉搜索树呢? 考虑一下二叉搜索树的特殊情况,如果一个二叉搜 ...

  6. 看动画学算法之:hashtable

    目录 简介 散列表的关键概念 数组和散列表 数组的问题 hash的问题 线性探测 二次探测 双倍散列 分离链接 rehash 简介 java中和hash相关并且常用的有两个类hashTable和has ...

  7. 看动画学算法之:二叉搜索树BST

    目录 简介 BST的基本性质 BST的构建 BST的搜索 BST的插入 BST的删除 简介 树是类似于链表的数据结构,和链表的线性结构不同的是,树是具有层次结构的非线性的数据结构. 树是由很多个节点组 ...

  8. 看动画学算法之:doublyLinkedList

    目录 简介 doublyLinkedList的构建 doublyLinkedList的操作 头部插入 尾部插入 插入给定的位置 删除指定位置的节点 简介 今天我们来学习一下复杂一点的LinkedLis ...

  9. 【Java数据结构学习笔记之二】Java数据结构与算法之队列(Queue)实现

      本篇是数据结构与算法的第三篇,本篇我们将来了解一下知识点: 队列的抽象数据类型 顺序队列的设计与实现 链式队列的设计与实现 队列应用的简单举例 优先队列的设置与实现双链表实现 队列的抽象数据类型 ...

随机推荐

  1. Git 系列教程(6)- 查看 commit 提交历史

    查看提交历史 在提交了若干更新,又或者克隆了某个项目之后,如何查看提交历史 git log 官方栗子 运行下面的命令获取该项目: git clone https://github.com/scha 运 ...

  2. Intel® QAT加速卡之加密、哈希操作流程和示例

    Intel QAT 加密API介绍 文章主要讲述了Intel QAT 加密API接口的说明,以及多种应用场景下的使用方法. 文章目录 Intel QAT 加密API介绍 1. 概述 1.1 会话(se ...

  3. LinkedList 添加元素源码解析

    jdk版本:1.8 LinkedList添加元素有两个方法:add(E e)和add(int index,E e). add(E e) /** * Appends the specified elem ...

  4. servlet请求转发于重定向

    请求的转发与重定向是Servlet控制页面跳转的主要方法,在Web应用中使用非常广泛. 一. 请求的转发 Servlet接收到浏览器端请求后,进行一定的处理,先不进行响应,而是在服务器端内部" ...

  5. 利用GetInvalidFileNameChars()得到有效的文件名

    public static string GetValidName(string fileName) {     foreach (char c in System.IO.Path.GetInvali ...

  6. 在PHP中灵活使用foreach+list处理多维数组

    先抛出问题,有时候我们接收到的参数是多维数组,我们需要将他们转成普通的数组,比如: $arr = [ [1, 2, [3, 4]], [5, 6, [7, 8]], ]; 我们需要的结果是元素1变成1 ...

  7. 如何使用SQL的备份文件(.bak)恢复数据库

    出于很多情况,数据库只剩下.bak文件,想要恢复数据库,找了很多资料才知道可以这样!!!!! 个人觉得图片教程更有意义,请看步骤: 1.选中"数据库" 右击 选择"还原数 ...

  8. Linux系列(7) - 链接命令

    硬链接 拥有相同的i节点和存储block块,可以看做事同一个文件 可通过i节点识别 不能跨分区 不能针对目录使用,只能针对文件 软链接 类似Windows快捷方式 软链接拥有自己的i节点和block块 ...

  9. Dapr + .NET Core实战(四)发布和订阅

    什么是发布-订阅 发布订阅是一种众所周知并被广泛使用的消息传送模式,常用在微服务架构的服务间通信,高并发削峰等情况.但是不同的消息中间件之间存在细微的差异,项目使用不同的产品需要实现不同的实现类,虽然 ...

  10. WireShark基础用法

    特点 免费 开源 跨平台 抓包原理 内部原理 抓取网卡 抓包环境 抓取本地数据 抓取外部数据 利用hub 流量镜像span.rspan.erspan 界面介绍.首选项.抓包选项 界面介绍 菜单栏 帮助 ...