队列1:队列的基本结构和基本操作

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了队列1:队列的基本结构和基本操作相关的知识,希望对你有一定的参考价值。

​1.队列的主要内容

从今天开始我们一起学习队列的内容。队列专题相对其他部分来说比较简单一点,不那么烧脑。

队列的特点是节点的排队次序和出队次序按入队时间先后确定,即先入队者先出队,后入队者后出队,即我们常说的FIFO(first in first out)先进先出。

作为java程序员去面试的时候,你知道在队列方面主要考察什么吗?我告诉你,主要两个方面:一个是Java的并发编程中阻塞队列的原理。具体就是JUC家族中AQS、condition关键字,优先级锁、CountDownLantch等的实现原理。可以说整个并发框架JUC就是基于队列构建起来的。没想到吧,队列竟然这么牛(A+C)>>1!

不过我们现在专注算法,所以暂时不讨论。第二个方面是树的层次遍历和广度优先等方面的问题,纯粹的队列的题目不多。

另外,LRU的设计在很多地方中都有介绍,最典型的设计是Hash+双向链表。双向链表作为队列在JUC等中都是大量使用的,所以我们一起介绍。

这个专题题目比较少,我们这么计划的:
第一篇:队列的构造,基于数组构建队列和环形队列。只有基本问题搞清楚,我们才能看透本质。所以我们就亲自创建一下。

第二篇:队列的构造,基于链表构造队列和环形队列

相关LeetCode题。

第三篇:栈实现队列和队列实现栈

第四篇:LRU设计与实现

2.基于数组的队列定义和相关操作

一般队列的基本结构如下所示:

他只能从左边进,右边出,队列实现方式一般有两种,一种是基于数组的,还一种是基于链表的, 如果基于链表的倒还好说,因为链表的长度是随时都可以变的,这个实现起来比较简单。如果是基于数组的,就会稍微有点不同,因为数组的大小在初始化的时候就已经固定了,我们来看一下基于 数组的实现,假如我们初始化一个长度是10的队列:

 

顺序存储结构存储的队列称为顺序队列,内部使用一个一维数组存储,用一个队头指针front指向队列头部节点(即使用int类型front来表示队头元素的下标),用一个队尾指针rear,指向队列尾部元素(int类型rear来表示队尾节点的下标)。

  初始化队列时: front = rear = -1 (非必须,也可设置初始值为0,在实现方法时具体修改)

  队列满时:rear = maxSize-1 (其中maxSize为初始化队列时,设置的队列最大元素个数)

  队列为空时:front = rear

front指向的是队列的头,tail指向的是队列尾的下一个存储空间,最初始的时候front=0, tail=0,每添加一个元素tail就加1,每移除一个元素front就加1,但是这样会有一个问题,如果一 个元素不停的加入队列,然后再不停的从队列中移除,会导致tail和front越来越大,最后会导致队 列无法再加入数据了,但实际上队列前面全部都是空的,这导致空间的极大浪费。

  下面先使用java实现一个基于一维数组的顺序队列,代码如下:

 /**  * 定义一个queue  */ class ArrayQueue{     private int[] data ; //队列中存放的数据     private int maxSize ; //队列的大小     private int front ;//指向队列头部的指针      private int rear ; //指向队列尾部的指针     public ArrayQueue(int maxSize){         this.maxSize = maxSize;         data = new int[maxSize];         front = -1;         rear = -1;     }     /**      * 判断队列是否已满      * @return      */     public boolean isFull(){         return rear == maxSize -1 ;     }     /**      * 判断队列是否为空      * @return      */     public boolean isEmpty(){         return rear == front;    }     /**      * 添加数据到队列      * @param n      */     public void add(int n){         if(isFull()){             System.out.println("队列已满,不能添加");             return;         }         data[++rear] = n;     }     /**      * 显示头部数据      * @return      */     public void head(){         if(isEmpty()){                            throw new RuntimeException("队列为空");         }         System.out.println(data[front+1]);     }     /**      * 取出头部数据      * @return      */     public int pop(){         if(isEmpty()){                     throw new RuntimeException("队列为空");         }         int a = data[++front];         data[front] = 0;         return a;     }     /**      * 打印全部数据      */     public void print(){         if(isEmpty()){             System.out.println("队列为空");             return;         }         for(int i=0;i<data.length;i++){             System.out.printf("array["+i+"]=%d\\n",data[i]);         }     } }

简单描述顺序队列的入队(add方法):

public static void main(String []args) {        //1.声明一个可以存储6个元素的顺序队列,默认值为0,front 和rear指针为-1        ArrayQueue queue = new ArrayQueue(6);        //2.向顺序队列中添加元素        queue.add(1);        queue.add(2);        queue.add(3);        queue.add(4);        queue.add(5);        queue.add(6);        //2.1打印当前队列元素        queue.print();        //3.将顺序队列中元素取出        queue.pop();        queue.pop();        queue.pop();        queue.pop();        queue.pop();        queue.pop();        //4.队列中元素全部取出}

3.图解上述过程

在代码中初始化了一个大小为6的顺序队列,下图展示了第一步(即代码ArrayQueue queue = new ArrayQueue(6))中队列元素及指针情况:

其中front和rear指向的虚线框实际并不存在,仅用来表示初始化时的默认状态,因我们实现的队列元素使用int[]存储元素,所以初始值均为0(如用Object[]或范型则初始值为null)

      执行queue.add(1)方法后队列的状态如下图:

可以看到向队列中添加元素后,rear指针向后移动一个位置指向第一个元素位置,后面继续添加后面5个元素后队列如下图所示:

接下来看下队列的出队情况 :

当第一次执行queue.pop()方法后,队列元素如上图所示,此时队列剩下5个元素

当第六次执行queue.pop()方法后,队列元素如下图所示:

 此时队列中元素已全部出队,按正常逻辑应该可以添加元素到队列中,但此时添加元素却会报队列已满错误(rear=maxSize-1),当然即使前面元素未出队也会报相同错误。这就是我们常说的“假溢出”问题。为解决这个问题,就引出了我们的环形队列。

4.环形队列

环形队列,顾名思义即让普通队列首尾相连,形成一个环形。当rear指向尾元素后,当队列有元素出队时,可以继续向队列中添加元素。

  这里我使用的是rear指针指向最后一个节点的后一个元素,即会占用一个位置用来表示队列已满。

  初始化队列时: front = rear = 0

  队列满时:   ( rear +1 ) % maxSize == front (其中maxSize为初始化队列时,设置的队列最大元素个数)

  这里不能使用rear = maxSize-1作为判断队满的条件,因使用环形队列方式实现,当第一次队满时,rear = maxSize -1,执行出队操作后原队头位置空出,此时继续执行入队操作,则rear向后移动一个位置,则rear = 0,而此时队列也是已满状态。所以只要rear 向前移动一个位置就等于front时,就是队满的情况。

  队列为空时:front == rear

  先看下具体代码

public class CycleQueue {    /**     *     */    private int maxSize ;    private int data[] ;    private int front ;    /**     * 这里rear指向最后一个数据的后面一个位置,即队列中有一个为空占位     */    private int rear ;    public CycleQueue(int maxSize){        this.maxSize = maxSize;        data = new int[maxSize];        front = 0;        rear = 0;    }    /**     * 判断队列是否已满     * 因是循环队列,所以rear值可能小于front,所以不能使用rear == maxSize -1来判断     * @return     */    public boolean isFull(){        return (rear + 1) % maxSize == front;    }    public boolean isEmpty(){        return rear == front;    }    public void add(int n){        if(isFull()){            System.out.println("队列已满,不能添加");            return;        }        data[rear] = n;        rear = (rear + 1) % maxSize;    }    public void head(){        if(isEmpty()){            throw new RuntimeException("队列为空");        }        System.out.println("head="+data[front]);    }    public int pop(){        if(isEmpty()){            throw new RuntimeException("队列为空");        }        int value = data[front];        front = (front + 1) % maxSize;        return value;    }    public void print(){        if(isEmpty()){            System.out.println("队列为空");            return;        }        for(int i= front; i<front+size(); i++){            System.out.printf("array"+(i%maxSize)+"=%d",data[i%maxSize]);        }    }    /**     * 因是循环队列,所以会出现rear<front情况,这里需要+maxSize     * @return     */    public int size(){        return (rear - front + maxSize)%maxSize;    }}

5.图解环形队列

下面再以图解的方式讲解一下环形队列的入队出队以及队满情况。当执行初始化代码后

CycleQueue queue = new CycleQueue(6);  

此时front = rear = 0,队列为空。当第一次执行queue.add(1)后,环形队列元素如下图所示:

当依次执行queue.add(2);queue.add(3);queue.add(4);queue.add(5);后,达到(rear+1)%maxSize=front (即rear=5)条件,队列已满不能添加新元素。此时环形队列元素情况如下图:

所以这种情况会浪费一个空间来作为判满的条件。

  下面执行出队操作,当第一次执行出队操作queue.pop()方法后,环形队列元素情况如下图所示:

此时 (rear+1)%maxSize = 0  不等于 front=1 ,所以可以继续向队列中添加元素,也就不会出现假溢出的情况。当执行入队(例queue.add(6))操作后,rear = (rear+1)%maxSize 即rear=0,以此来生成环形队列。此时队列元素情况如下图所示:

另外,再说明下环形队列有效元素个数问题,如果不是环形队列,则有效元素个数size = rear - front。而使用环形实现后,会出现rear<front的情况,所以这里使用(rear-front+maxSize)%maxSize的方式计算有效元素个数。(或者在内部定义一个size属性,当元素入队时size++,当出队时size--)

  因此在打印队列中元素时,从front位置开始至 front+size位置结束来循环打印有效元素。

如果不实用环形队列方式实现队列,则会出现“假溢出”情况(即队列满后,将全部元素出队却不能继续添加元素的情况)。而环形队列会在队头元素出队后,将队尾指针rear重新分配为0,以达到循环使用队列空间的目的。

以上是关于队列1:队列的基本结构和基本操作的主要内容,如果未能解决你的问题,请参考以下文章

基本数据结构——队列

数据结构3. 栈和队列

数据结构学习笔记——队列的基本知识和顺序存储结构实现队列(顺序队列)

数据结构学习笔记——队列的基本知识和顺序存储结构实现队列

队列1:队列的基本结构和基本操作

数据结构之队列的基本操作以及栈和队列的OJ题画图详解