《大话数据结构》--队列

1,701 阅读4分钟

队列的定义

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

队列的结构

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

循环队列

队列顺序存储的不足

在队尾追加元素,不需要移动任何元素,时间复杂度为O(1)

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

循环队列

把队列头尾相接的顺序存储结构称之为循环队列。

队列满的条件

(rear + 1) %QueueSize = front

左图,当队列空时,条件就是front = rear, 当队列满时,我们修改其条件,保留一个元素空间。

右图。空队列时,front 等于tear, 现在当队列满时,也是front等于rear。

为了区分空队列和满队列,我们采用左图的方案。

队列长度的计算公式

(rear - front + QueueSize)%QueueSize

结构

static class SqQueue {    
    int rear;    
    int front;    
    int[] data;
}

入队

若队列未满,则插入元素elementData为queue新的队尾元素

static void EnQueue(SqQueue queue, Integer elementData) throws Exception {    
    if((queue.rear + 1) % MAXSIZE == queue.front) {        
        throw new Exception("队列已满");    
    }    
    queue.data[queue.rear] = elementData; // 将元素赋值给队尾    
    queue.rear = (queue.rear + 1) % MAXSIZE; // rear后移一位置,若到最后则转到数组头部
}

出队

若队列不空,则删除queue中队头元素,返回其值

static Integer DeQueue(SqQueue queue) throws Exception {    
    if(queue.rear == queue.front) {        
        throw new Exception("队列是空的");    
    }    
    int result = queue.data[queue.front]; // 将对头元素赋值result    
    queue.front = (queue.front + 1) % MAXSIZE;  // front后移一位置,若到最后则转到数组头部    
    return result;
}

不足

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

链队列

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。将队头指针指向链队列的头结点,而队尾指针指向终端结点。空队列时,front和rear都指向头结点

结构

static class Node{    
    int data;    
    Node next;    
    public Node() {    }
}
static class LinkQueue {    
    Node front;    
    Node rear;    
    public LinkQueue() {    }
}

入队

入队操作,就是在链表尾部插入结点

static void EnQueue(LinkQueue queue, int elementData) {    
    Node newNode = new Node();    
    newNode.data = elementData;    
    queue.rear.next = newNode; // 把新结点node赋值给原队尾结点的后继,上图①    
    queue.rear = newNode; // 将新结点设置为队尾结点。上图②
}

出队

出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。

static Integer DeQueue(LinkQueue queue) throws Exception {    
    if(queue.rear == queue.front) {        
        throw new Exception("队列是空的");    
    }    
    Node p = queue.front.next; // 将预删除的结点赋值给p  1    
    int result = p.data;    
    queue.front.next = p.next; // 将原对头结点的后继p.next赋值给头结点后继  2    
    if(queue.rear == p) { // 若队头是队尾,则删除后将rear指向头结点  3        
        queue.rear = queue.front;    
    }    
    return result;
}

总结

循环队列与链队列的比较

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

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

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