在逻辑结构中,我们已经学习了一个非常经典的结构类型:栈。今天,我们就来学习另外一个也是非常经典的逻辑结构类型:队列。相信不少同学已经使用过 redis 、 rabbitmq 之类的缓存队列工具。其实,数据库、程序代码,这些都可以实现队列的操作,就和栈一样,队列也是有其特定的规则,只要符合这个规则,它就叫做队列。

什么是队列?

相对于栈来说,队列是一种先进先出(FIFO)的顺序逻辑结构。什么叫先进先出呢?就和我们的排队一样,当我们去银行或者医院的时候,总是要在门口取一个号,这个号是按顺序叫的。先来的人就可以先办业务或者看病,这就是一个典型的队列。同理,日常的排队就是一个标准的队列模式。如果有插队的,在有正当理由的情况下,我们可以认为它的优先级更高,这是队列中元素的一种特殊形式。就像我们会在等地铁或者公交的时候让孕妇优先,在排队买火车票的时候也有军人的优先窗口。不过,这个并不在我们这次的讨论范围之内。

在公交站排队时,排第一个的当然可以第一个上车,然后依次。这时,你来到了公交站,那么你只能排到最后一位。这个就是队列的具体表现形式。

同样,和栈一样,也有一些名词我们需要了解。当你来到公交站并排到最后一位时,这个操作叫作“入队”。当公交车进站后,第一位乘客上车,这个操作叫做“出队”。第一位乘客所处的位置叫做“队头”,你做为当前队列的最后一位乘客,你的位置就叫做“队尾”。回到代码逻辑上面来看,也就是说队列是从“队尾”“入队”,从“队头”“出队”。

顺序队列

OK,我们还是直接从来代码来看,首先看到的依然是顺序队的实现。

class SqQueue{
public $data;
public $front;
public $rear;
}

既然是顺序队,我们依然还是用一个数组 data 来表示队内的元素。然后定义两个指针 front 和 rear 来表示队头和队尾。因为是顺序队,所以这里的指针其实也就是保存的是数组的下标。接下来的操作其实就非常的简单了,“入队”时 rear++ ,“出队”时 front++ 。

function InitSqQueue(){
$queue = new SqQueue();
$queue->data = [];
$queue->front = 0;
$queue->rear = 0;
return $queue;
} function EnSqQueue(SqQueue &$queue, $e){
$queue->data[$queue->rear] = $e;
$queue->rear ++;
} function DeSqQueue(SqQueue &$queue){
// 队列为空
if($queue->front == $queue->rear){
return false;
}
$e = $queue->data[$queue->front];
$queue->front++;
return $e;
} $q = InitSqQueue();
EnSqQueue($q, 'A');
EnSqQueue($q, 'B');
print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => A
// [1] => B
// ) // [front] => 0
// [rear] => 2
// )

是不是感觉学过了栈之后,队列也很好理解了。初始化队列时,就是让队头和队尾指针都是 0 下标的记录就可以了。入队的时候让队尾增加,在这段代码中,我们入队了两个元素,打印出来的顺序队列内容就如注释所示。

EnSqQueue($q, 'C');
EnSqQueue($q, 'D');
EnSqQueue($q, 'E');
print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => A
// [1] => B
// [2] => C
// [3] => D
// [4] => E
// ) // [front] => 0
// [rear] => 5
// ) echo DeSqQueue($q), PHP_EOL; // A
echo DeSqQueue($q), PHP_EOL; // B
echo DeSqQueue($q), PHP_EOL; // C
echo DeSqQueue($q), PHP_EOL; // D
echo DeSqQueue($q), PHP_EOL; // E echo DeSqQueue($q), PHP_EOL; // print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => A
// [1] => B
// [2] => C
// [3] => D
// [4] => E
// ) // [front] => 5
// [rear] => 5
// )

出队的时候,就让 front 进行加 1 操作。不过,在出队的时候还需要判断数组中的元素是否全部出队了,在这里,我们只用了一个非常简单的判断条件,那就是 front 和 rear 是否相等来判断队列是否空了。大家可以通过一个图示来辅助对代码的理解。

循环队列

相信已经有不少同学看出来了。队列操作只是修改队头和队尾的指针记录,但是数组会一直增加,这样如果一直增加的话,就会导致这一个数组占满内存,这肯定不是一个好的队列实现。其实,在 C 语言中,数组就是要给一个固定的长度的。而 PHP 中的数组更像是一个 Hash 结构,所以它是可以无限增长的,并不需要我们在一开始定义一个具体的数组长度。这也是 PHP 的方便之处,不过如果我们不想浪费内存空间的话,应该怎么办呢?就像在 C 语言中一样,我们在 PHP 中也为数组指定一个长度,并且使用非常经典的“循环队列”来解决队列数组的存储问题。就像下图所示:

其实意思就是,在有限的数组空间范围内,当我们达到数组的最大值时,将新的数据保存回之前的下标位置。比如图中我们有 6 个元素,当前队头在 2 下标,队尾在 5 下标。如果我们入队一个元素,队尾移动到 6 下标。再添加一个元素的话,队尾移动回 0 下标,如果继续添加的话,当队尾下标等于队头下标减 1 的时候,我们就认为这个队列已经满了,不能再增加元素了。

同理,出队操作的时候我们也是循环地操作队头元素,当队头元素到 6 的下标后,继续出队的话,也会回到 0 下标的位置继续出队。当队头和队尾相等时,当前的队列也可以判定为空队列了。

由此,我们可以看出,循环队列相比普通的线性队列来说,多了一个队满的状态。我们还是直接从代码中来看看这个队满的条件是如何判断的。

define('MAX_QUEUE_LENGTH', 6);

function EnSqQueueLoop(SqQueue &$queue, $e){
// 判断队列是否满了
if(($queue->rear + 1) % MAX_QUEUE_LENGTH == $queue->front){
return false;
}
$queue->data[$queue->rear] = $e;
$queue->rear = ($queue->rear + 1) % MAX_QUEUE_LENGTH; // 改成循环下标
} function DeSqQueueLoop(SqQueue &$queue){
// 队列为空
if($queue->front == $queue->rear){
return false;
}
$e = $queue->data[$queue->front];
$queue->front = ($queue->front + 1) % MAX_QUEUE_LENGTH; // 改成循环下标
return $e;
} $q = InitSqQueue();
EnSqQueueLoop($q, 'A');
EnSqQueueLoop($q, 'B');
EnSqQueueLoop($q, 'C');
EnSqQueueLoop($q, 'D');
EnSqQueueLoop($q, 'E'); EnSqQueueLoop($q, 'F'); print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => A
// [1] => B
// [2] => C
// [3] => D
// [4] => E
// [5] => // 尾
// ) // [front] => 0
// [rear] => 5
// ) echo DeSqQueueLoop($q), PHP_EOL;
echo DeSqQueueLoop($q), PHP_EOL;
print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => A
// [1] => B
// [2] => C // 头
// [3] => D
// [4] => E
// [5] => // 尾
// ) // [front] => 2
// [rear] => 5
// ) EnSqQueueLoop($q, 'F');
EnSqQueueLoop($q, 'G'); EnSqQueueLoop($q, 'H');
print_r($q);
// SqQueue Object
// (
// [data] => Array
// (
// [0] => G
// [1] => B // 尾
// [2] => C // 头
// [3] => D
// [4] => E
// [5] => F
// ) // [front] => 2
// [rear] => 1
// )

出、入队的下标移动以及队满的判断,都是以 (queue->rear + 1) % MAX_QUEUE_LENGTH 这个形式进行的。根据队列长度的取模来获取当前的循环下标,是不是非常地巧妙。不得不感慨先人的智慧呀!当然,这也是基本的数学原理哦,所以,学习数据结构还是要复习一下数学相关的知识哦!

链式队列

顺序队列有没有看懵?没关系,队列的链式结构其实相比顺序结构还要简单一些,因为它真的只需要操作队头和队尾的指针而已,别的真的就不太需要考虑了。而且这个指针就是真的指向具体对象的指针了。

class LinkQueueNode{
public $data;
public $next;
} class LinkQueue{
public $first; // 队头指针
public $rear; // 队尾指针
}

这里我们需要两个基本的物理结构。一个是节点 Node ,一个是队列对象,节点对象就是一个正常的链表结构,没啥特别的。而队列对象里面就更简单了,一个属性是队头指针,一个属性是队尾指针。

function InitLinkQueue(){
$node = new LinkQueueNode();
$node->next = NULL;
$queue = new LinkQueue();
$queue->first = $node;
$queue->rear = $node;
return $queue;
} function EnLinkQueue(LinkQueue &$queue, $e){
$node = new LinkQueueNode();
$node->data = $e;
$node->next = NULL; $queue->rear->next = $node;
$queue->rear = $node;
} function DeLinkQueue(LinkQueue &$queue){
if($queue->front == $queue->rear){
return false;
} $node = $queue->first->next;
$v = $node->data; $queue->first->next = $node->next;
if($queue->rear == $node){
$queue->rear = $queue->first;
} return $v;
} $q = InitLinkQueue();
EnLinkQueue($q, 'A');
EnLinkQueue($q, 'B');
EnLinkQueue($q, 'C');
EnLinkQueue($q, 'D');
EnLinkQueue($q, 'E'); print_r($q);
// LinkQueue Object
// (
// [first] => LinkQueueNode Object
// (
// [data] =>
// [next] => LinkQueueNode Object
// (
// [data] => A
// [next] => LinkQueueNode Object
// (
// [data] => B
// [next] => LinkQueueNode Object
// (
// [data] => C
// [next] => LinkQueueNode Object
// (
// [data] => D
// [next] => LinkQueueNode Object
// (
// [data] => E
// [next] =>
// ) // ) // ) // ) // ) // ) // [rear] => LinkQueueNode Object
// (
// [data] => E
// [next] =>
// ) // ) echo DeLinkQueue($q), PHP_EOL; // A
echo DeLinkQueue($q), PHP_EOL; // B EnLinkQueue($q, 'F');
print_r($q);
// LinkQueue Object
// (
// [first] => LinkQueueNode Object
// (
// [data] =>
// [next] => LinkQueueNode Object
// (
// [data] => C
// [next] => LinkQueueNode Object
// (
// [data] => D
// [next] => LinkQueueNode Object
// (
// [data] => E
// [next] => LinkQueueNode Object
// (
// [data] => F
// [next] =>
// ) // ) // ) // ) // ) // [rear] => LinkQueueNode Object
// (
// [data] => F
// [next] =>
// ) // )

出、入队的代码函数和测试代码就一并给出了,是不是非常的简单。初始的队头元素依然是一个空节点做为起始节点。然后入队的时候,让 rear 等于新创建的这个节点,并在链表中建立链式关系。出队的时候也是同样的让 first 变成当前这个 first 的下一跳节点,也就是 first->next 就可以了。判断队空的条件也是简单的变成了队头和队尾指针是否相等就可以了。链队相比顺序队其实是简单了一些,不过同样的,next 这个东西容易让人头晕,硬记下来就可以了。大家还是可以结合图示来学习:

PHP 为我们提供的数组队列操作

最后,就和栈一样,PHP 代码中也为我们提供了一个可以用于队列操作的函数。

$sqQueueList = [];

array_push($sqQueueList, 'a');
array_push($sqQueueList, 'b');
array_push($sqQueueList, 'c'); print_r($sqQueueList);
// Array
// (
// [0] => a
// [1] => b
// [2] => c
// ) array_shift($sqQueueList);
print_r($sqQueueList);
// Array
// (
// [0] => b
// [1] => c
// )

array_shift() 函数就是弹出数组中最前面的那个元素。请注意,这里元素的下标也跟着变动了,如果我们是 unset() 掉数组的 0 下标元素的话,b 和 c 的下标依然还会是 1 和 2 。而 array_shift() 则会重新整理数组,让其下标依然有序。

unset($sqQueueList[0]);
print_r($sqQueueList);
// Array
// (
// [1] => c
// )

总结

关于栈的队列的内容我们就通过两篇文章介绍完了。不过光说不练假把式,接下来,我们来一点真实的干货,使用栈和队列来做做题呗,学算法就得刷题,一日不刷如隔三秋呀!!!

测试代码:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/3.栈和队列/source/3.2队列的相关逻辑操作.php

参考资料:

《数据结构》第二版,严蔚敏

《数据结构》第二版,陈越

《数据结构高分笔记》2020版,天勤考研

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

【PHP数据结构】队列的相关逻辑操作的更多相关文章

  1. 【PHP数据结构】栈的相关逻辑操作

    对于逻辑结构来说,我们也是从最简单的开始.堆栈.队列,这两个词对于大部分人都不会陌生,但是,堆和栈其实是两个东西.在面试的时候千万不要被面试官绕晕了.堆是一种树结构,或者说是完全二叉树的结构.而今天, ...

  2. 【PHP数据结构】链表的相关逻辑操作

    链表的操作相对顺序表(数组)来说就复杂了许多.因为 PHP 确实已经为我们解决了很多数组操作上的问题,所以我们可以很方便的操作数组,也就不用为数组定义很多的逻辑操作.比如在 C 中,数组是有长度限制的 ...

  3. 【PHP数据结构】顺序表(数组)的相关逻辑操作

    在定义好了物理结构,也就是存储结构之后,我们就需要对这个存储结构进行一系列的逻辑操作.在这里,我们就从顺序表入手,因为这个结构非常简单,就是我们最常用的数组.那么针对数组,我们通常都会有哪些操作呢? ...

  4. java 数据结构 队列的实现

    java 数据结构队列的代码实现,可以简单的进行入队列和出队列的操作 /** * java数据结构之队列的实现 * 2016/4/27 **/ package cn.Link; import java ...

  5. C++ 泛型 编写的 数据结构 队列

    平时编程里经常需要用到数据结构,比如  栈和队列 等,  为了避免每次用到都需要重新编写的麻烦现将  C++ 编写的 数据结构 队列  记录下来,以备后用. 将 数据结构  队列  用头文件的形式写成 ...

  6. C语言数据结构-队列的实现-初始化、销毁、清空、长度、队列头元素、插入、删除、显示操作

    1.数据结构-队列的实现-C语言 //队列的存储结构 #define MAXSIZE 100 typedef struct { int* base; //基地址 int _front; //头指针 i ...

  7. java数据结构——队列、循环队列(Queue)

    每天进步一点点,坚持就是成功. 1.队列 /** * 人无完人,如有bug,还请斧正 * 继续学习Java数据结构————队列(列队) * 队列和栈一样,都是使用数组,但是队列多了一个队头,队头访问数 ...

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

    [从今天开始修炼数据结构]基本概念 [从今天开始修炼数据结构]线性表及其实现以及实现有Itertor的ArrayList和LinkedList [从今天开始修炼数据结构]栈.斐波那契数列.逆波兰四则运 ...

  9. (js描述的)数据结构[队列结构,优先级队列](3)

    (js描述的)数据结构[队列结构](3) 一.队列结构的特点: 1.基于数组来实现,的一种受限的线性结构. 2.只允许在表头进行删除操作,在表尾进行插入操作. 3.先进先出(FIFO) 二.队列的一些 ...

随机推荐

  1. 玩转Java8日期工具类-基础

    内容基于的是 Java8官方文档,以及Java时间类总结 的总结.BTW:其实具体方法的使用直接在IDEA中看源码更方便直接. 1.老一辈:Java.util.Date Java.sql.Date J ...

  2. idea注释

    * * $params$ * @author wangxiaolei * @date $date$ $time$ * @return $return$ */ groovyScript("de ...

  3. cobaltstrike 框架简述

    关于cobalt strike,火起来也有好几年了,首先感谢大佬们慷慨相助愿意在网上分享和翻译相关资料,让这么好的渗透测试框架工具被更多人知道 那就来整理一下在使用这个框架的过程中我认为需要了解的小知 ...

  4. 浅谈 asp.net core web api

    希望通过本文能够了解如下内容: ControllerBase Attributes Action的返回值类型 ControllerBase 当我们开始实际上项目, 真正实操 anc 时, 肯定会用到 ...

  5. 【GIS】点图层符号的方向和大小

    方向:根据属性字段设置点图层中每个要素的符号方向和大小, 1 所有要素使用同一种符号---简单渲染 在图层属性---符号系统---单一符号中进行设置,首先设置符号,在后面的[高级]选项按钮中分别设置[ ...

  6. Python打印图片

    准备好图片:(我女票) python代码: # by gubin 6.20 from dyy from PIL import Image import sys import os def _main( ...

  7. CSS定位(慕课网学习笔记)

    定位模型 static自然模型 relative相对定位模型 absolute绝对定位模型 fixed固定定位模型 sticky磁铁定位模型 possition之static(默认的设置)(静态定位. ...

  8. SpringCloud降级熔断 Hystrix

    1.分布式核心知识之熔断.降级讲解 简介:系统负载过高,突发流量或者网络等各种异常情况介绍,常用的解决方案 1.熔断:         保险丝,熔断服务,为了防止整个系统故障,包含子和下游服务 下单服 ...

  9. Spring中常用重要的接口

    Spring (ApplicationContext 初始化Bean的方法 refresh()) public void refresh() throws BeansException, Illega ...

  10. MySQL授权认证

    • MySQL-权限系统介绍 • 权限系统的作用是授予来自某个主机的某个用户可以查询.插入.修改.删除等数据库操作的权限 • 不能明确的指定拒绝某个用户的连接 • 权限控制(授权与回收)的执行语句包括 ...