数据结构之队列分析与实现

队列是一种十分常见的数据结构,它的应用范围非常广泛,例如在操作系统中调度算法、人工客服排队序列等。队列要求删除元素的操作必须在队列头部,这个过程称为出队(pop),而队列插入元素的操作必须发生在队列尾部,这个过程称为进队(push)。队列不允许随意访问其中元素,能够直接访问的元素只有队头元素。

顺序队列。顺序队列需要或动态或静态分配一块连续的内存,设置队头指针front指向队头元素和队尾指针rear指向队尾指针的下一个元素。如下图时代表内存地址为1,2,3时是队列元素,而4并不是。

这种结构的队列由于入队和出队操作中,头尾指针只增加不减小,致使出队元素的空间永远无法重新利用。虽然队列中实际的元素个数远远小于向量空间的规模时,rear指针也可能已超越向量空间的上界。这种情况下或者当队列已满时进行进队操作会导致溢出,进行进队操作之前应当检测队尾指针是否溢出。这样的队列判空条件 front == rear。

链式队列。因为队列内部元素不允许随意访问,只能在对头或队尾操作,所以如果使用链表实现队列,那么链表元素访问不便的缺点也无关紧要了,更重要的是,使用链表实现队列的话,理论上队列大小只取决于内存大小,并且出队元素的空间可以被释放重新利用,进队元素即时申请内存。相较于顺序结构队列,容量以及空间利用率都大幅提高。链式队列设置的队头指针front指向队头节点,队尾指针rear指向队尾节点。判空条件front或rear为空(与顺序结构队列不同,当front==rear时队列有一个元素)。

循环队列。循环队列是在顺序结构队列的基础之上优化,充分利用存储空间,并且减少了维护队列结构的开销。循环队列在结构上与顺序队列没有太大区别。不过当队头指针front或队尾指针rear到达存储边界时,下一次移动并非继续向前移动,而是移动至存储空间的第一个位置。如下图,若现在有一个元素进队之后,rear将移动至1处。

在逻辑上可以把这块区域看作是一块环状空间,使得队列可以循环使用,队列满时会覆盖队头元素,front向前移动一个单位。但是这样会导致一个问题,当队列为空时有front==rear,当队列满时,也有front==rear。为了避免这种情况的发生,我们通常的做法是在最大存储空间(maxSize)的基础之上添加一个空的存储单元,比如上图有九个存储空间但是真正作为队列使用的maxSize只有八个,rear指向的单元永远不会有队列元素。这样当front==rear时队列为空,当front==(rear + 1)%maxSize时队列已满,实时队列大小为(rear - front + maxSize) % maxSize。

 

基于链表的链式队列

设计链式节点的结构

typedef struct Node {
    dataType data;  //数据域
    struct Node *next;  //指针域
} *list;

设计队列结构

typedef struct {
    list front;  //队头节点
    list rear;  //队尾节点
    int size;   //队列大小
} queue;

相关操作

queue createQueue() {   //创建一个新的队列
    queue res;
    res.front = res.rear = NULL;
    res.size = 0;
    return res;
}

bool isEmpty(queue Q) { //判断一个队列是否为空
    return Q.size == 0;
}

dataType getFront(queue Q) {    //获取队头元素
    return Q.front->data;
}

void pop(queue &Q) { //队头元素出队
    list p = Q.front;
    Q.front = Q.front->next;
    Q.size--;
    free(p);
}

void push(queue &Q, dataType value) {    //元素进入队尾
    list p = (list)malloc(sizeof(list));
    p->next = NULL;
    p->data = value;
    if(isEmpty(Q)) {    //若队列为空,前后队头队尾均为新添加元素
        Q.front = Q.rear = p;
    }
    else {  //队列不为空,移动队尾
        Q.rear->next = p;
        Q.rear = p;
    }
    Q.size++;
}

int getSize(queue Q) {  //获取队列大小
    return Q.size;
}

 

动态分配的循环链表

#define dataType int    //队列数据类型

typedef struct {
    dataType *start;
    int front;  //队头指针
    int rear;  //队尾指针
    int maxSize;    //队列最大存储量
} circularQueue;

circularQueue createCircularQueue(int maxSize) {   //创建一个新的循环队列,参数为队列最大存储数量
    circularQueue res;
    res.start = (dataType *)malloc(sizeof(dataType) * (maxSize + 1));
    res.maxSize = maxSize;
    res.front = res.rear = 0;
    return res;
}

bool isEmpty(circularQueue Q) { //判断一个循环队列是否为空
    return Q.front == Q.rear;
}

bool isFull(circularQueue Q) {  //判断队列是否已满
    return ((Q.rear + 1) % (Q.maxSize + 1)) == Q.front;
}

dataType getFront(circularQueue Q) {    //获取队头元素
    return Q.start[Q.front];
}

void pop(circularQueue &Q) { //队头元素出队
    Q.front = (Q.front + 1) % (Q.maxSize + 1);
}

void push(circularQueue &Q, dataType value) {    //元素进入队尾
    if(isFull(Q)) { //如果队列已满,队头元素将被覆盖,队头指针需要移动
        pop(Q);
    }
    Q.start[Q.rear] = value;
    Q.rear = (Q.rear + 1) % (Q.maxSize + 1);
}

int getSize(circularQueue Q) {  //获取队列大小
    return (Q.rear - Q.front + Q.maxSize + 1) % (Q.maxSize + 1);
}

文件下载

基于链表的队列.cpp

循环队列.cpp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章