数据结构-队列
数据结构-队列
应用
日常生活中,电脑里面的操作系统以及像移动联通中的客服系统,都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。
定义
队列(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(删除最小者),它的作用是找出、删除优先队列中的最小的元素(出队)。
其中每个元素被赋予优先级,其中优先级最高的最先被删除。其中优先级若以值来代表,值越大优先级越高,这种被称为最大优先队列。同理可得最小优先队列,而堆则正是这种优先队列的具体实现。
优先队列的实现常选用二叉堆,在数据结构中,优先队列一般也是指堆。堆是一种常用的树形结构,是一种特殊的完全二叉树。这里先提一下,后面学到树的相关内容再仔细讲述记录。
参考资料: 程杰《大话数据结构》