数据结构-队列

数据结构-队列

    应用

    日常生活中,电脑里面的操作系统以及像移动联通中的客服系统,都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。

    定义

    队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

 

    1、重要的概念:队头和队尾

   队头指向的是第一个元素,而队尾指向的是最后一个元素。

    2、重要的操作:入队和出队

   队列和栈一样也访问受限制的。

   1)入队:将一个元素添加到队尾

   2)出队:从队头取出一个元素

    队列的存储方式(底层实现)

    线性表有顺序存储和链式存储,栈是线性表,所以有这两种存储方式。队列作为一种特殊的线性表,也同样存在这两种存储方式。

    队列的底层实现可以用数组和链表,基于数组实现的队列叫做顺序队列基于链表实现的队列叫作链式队列

    一、队列的顺序存储结构及实现

    1.  队列顺序存储的不足

    假设一个队列有n个元素,则顺序存储的队列需建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。入队操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为O(1)。

 

   与栈不同的是,队列元素的出列是在队头,即下标为 0 的位置,这意味着,队列中的所有元素都得向前移动,以保证队列的队头,也就是下标为0的位置不为空,此时时间复杂度为O(n)。

   这里的实现和线性表的顺序存储结构完全相同,不再详述。

   现实确实如此,一群人排队买票,前面的人买好离开,后面的人就要全部向前一步,补上空位,似乎这也没什么不好。

   可是仔细想想,为什么出队列一定要全部移动呢,如果不去限制队列的元素必须存储在数组的前 n 个单元这一条件,出队的性能就会大大增加。也就是说,队头不需要一定在下标为0的位置

   进一步改进:

   为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入了两个指针front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时,此队列就是一个空队列。

   1)假设有一个长度为5的数组,初始状态是一个空队列,如下图左图所示。

front与rear指针均指向下标为0,然后入队a1,a2,a3,a4,front指针依然指向下标为0的位置,而rear指标指向下标为4 的位置,如下图右图所示。

 

   2)出队a1,a2,则front指针指向下标为2的位置,rear不变,如下图左图所示。再入队a5,此时front指针不变,rear指针移动到数组之外。数组之外将是到哪里呢?新的问题又产生了。

 

    问题还不止于此。假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的。我们把这种现象叫做“假溢出”。

 2.  循环队列的定义

    为了解决上面提出的“假溢出”的问题,就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

   循环队列需要保留一个存储空间,对于一个存储空间为n的循环队列来说只能存放n-1为数据,因为如果不保留一个存储空间的话,当front==rear时,就有可能存在队空或者队满的情况。

    1)队空: front==rear

    2)队满: (rear+1)%QueueSize == front

    3)说明:取模“%”的目的就是为了整合rear与front大小为一个问题

    4)通用的计算队列长度公式为:

  (rear - front + QueueSize)%QueueSize

    总结:单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,所以我们还需要研究一下不需要担心队列长度的链式存储结构。

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

    队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。

    为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点:

 

    空队列时,front和rear都指向头结点,如图:

 

    总结:对于循环队列和链队列的比较,可以从两方面来考虑

    1)时间上,它们的基本操作都是常数时间,即都是O(1),不过循环队列是事先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销,如果入队出队频繁,则两者还是有细微差异。

    2)空间上,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链队列更加灵活。

   总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果无法预估队列的长度时,则用链队列。

    三、优先队列

    优先队列是特殊的队列,从“优先”一词,可看出有“插队现象”。比如在火车站排队进站时,就会有些比较急的人来插队,他们就在前面先通过验票。优先队列至少含有两种操作的数据结构:insert(插入),即将元素插入到优先队列中(入队);以及deleteMin(删除最小者),它的作用是找出、删除优先队列中的最小的元素(出队)。

    其中每个元素被赋予优先级,其中优先级最高的最先被删除。其中优先级若以值来代表,值越大优先级越高,这种被称为最大优先队列。同理可得最小优先队列,而堆则正是这种优先队列的具体实现。

    优先队列的实现常选用二叉堆,在数据结构中,优先队列一般也是指堆堆是一种常用的树形结构,是一种特殊的完全二叉树。这里先提一下,后面学到树的相关内容再仔细讲述记录。

   

参考资料: 程杰《大话数据结构》

原文地址:https://www.cnblogs.com/hld123/p/14576886.html