数据结构之队列
- 队列的概念
 - 顺序队列
 - 循环队列
 
- 顺序循环队列的ADT定义
 - 1、简单结构体定义
 - 2、初始化
 - 3、队列的清空
 - 4、计算队列的长度
 - 5、判断队列是否为空
 - 6、插入新的元素
 - 7、元素的删除
 - 8、遍历输出队列内的所有元素
 
- 链队列的ADT定义
 - 1、链队列简单结构体定义
 - 2、初始化链队列
 - 3、判断链队列是否为空
 - 4、清空链队列
 - 5、销毁链队列
 - 6、获取链队列的长度
 - 7、获取链队列的头元素
 - 8、在链队列尾插入新元素
 - 9、删除链队列的头元素
 - 10、遍历链队列中的元素
 
- 顺序队列和链式队列的比较
 
队列的概念
队列(queue)和栈类似,队列中的数据也呈线性排列,但是队列中添加和删除数据的操作分别是在两端进行。就和“ 队列” 这个名字一样,把它想象成排成一队的人更容易理解。在队列中,处理总是从第一个位置开始往后进行,而新来的人只能排在最后的位置。
 队列是只允许在一端进行插入操作,在另一端进行删除操作,是一种先进先出的线性表, First In First Out,简称FIFO。允许插入的一 端称为队尾,允许删除的一端称为队头。
 
 线性表有顺序存储和链式存储两种形式,队列作为一种特殊的线性表,也存在着这两种存储方式。
顺序队列
顺序存储形式的队列,是利用一组地址连续的存储单元,每个存储单元依次存放队列中的元素。为了避免当只有一个元素时,队头和队尾重合使得处理变得麻烦,所以引入两个指针(头指针和尾指针):头指针front指针指向队头元素,尾指针rear指针指向队尾元素的下一个位置。初始化时的头尾指针,初始值均为0。
 入队时尾指针rear加1,出队时头指针front加1,头尾指针相等时队列即为空。
当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素入队,就会发生“溢出”。
 为了解决溢出问题,可以采用循环队列。
循环队列
使用循坏队列,将新元素再插入到第一个位置上,入队和出队仍按先进先出的原则进行,操作效率高,空间利用率高,同时解决了顺序队列的假溢出问题。
 
 但是,同时仅凭 front = rear 不能判定循环队列是空还是满
 判断队空或者队满,有三种方式:

 
顺序循环队列的ADT定义
1、简单结构体定义
typedef int Status;
typedef int QElemType; // QElemType类型根据实际情况而定,这里假设为int 
// 循环队列的顺序存储结构
typedef struct
{
    QElemType data[MAXSIZE];
    int front;      // 头指针
    int rear;       // 尾指针,若队列不空,指向队列尾元素的下一个位置 
}SqQueue;
 
2、初始化
Status InitQueue(SqQueue *Q)
{
    Q->front = 0;
    Q->rear = 0;
    return  TRUE;
}
 
3、队列的清空
Status ClearQueue(SqQueue *Q)
{
    Q->front = Q->rear = 0;
    return TRUE;
}
 
4、计算队列的长度
int QueueLength(SqQueue Q)
{
    return  (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
 
5、判断队列是否为空
若队列不空,则用e返回Q的队头元素,并返回TRUE,否则返回FALSE
Status GetHead(SqQueue Q, QElemType *e)
{
    if (Q.front == Q.rear) /* 队列空 */
        return FALSE;
    *e = Q.data[Q.front];
    return TRUE;
}
 
6、插入新的元素
若队列未满,则插入元素e为Q新的队尾元素
Status EnQueue(SqQueue *Q, QElemType e)
{
    if ((Q->rear + 1) % MAXSIZE == Q->front)    // 队列满的判断 
        return FALSE;
    Q->data[Q->rear] = e;           // 将元素e赋值给队尾
    Q->rear = (Q->rear + 1) % MAXSIZE;// rear指针向后移一位置,若到最后则转到数组头部 
    return  TRUE;
}
 
7、元素的删除
若队列不空,则删除Q中队头元素,用e返回其值
Status DeQueue(SqQueue *Q, QElemType *e)
{
    if (Q->front == Q->rear)  //*队列空的判断
        return FALSE;
    *e = Q->data[Q->front];  // 将队头元素赋值给e
    Q->front = (Q->front + 1) % MAXSIZE; //front指针向后移一位置,到了最后则转到数组头部
    return  TRUE;
}
 
8、遍历输出队列内的所有元素
队头到队尾依次对队列Q中每个元素输出
Status QueueTraverse(SqQueue Q)
{
    int i;
    i = Q.front;
    while ((i + Q.front) != Q.rear)
    {
        visit(Q.data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
    return TRUE;
}
 
链队列的ADT定义
队列的链式存储结构,就是一个单向链表。链式队列和单向链表比就多了两个指针,头指针和尾指针。
优点:
相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。
可动态分配空间,不需要预先分配大量存储空间。
适合处理用户排队等待的情况。
缺点:
需要为表中的逻辑关系增加额外的存储空间。
 
读取时的时间复杂度为O(1)。
 插入、删除时的时间复杂度为O(1)。
1、链队列简单结构体定义
typedef int Status; // 函数返回结果类型
typedef int ElemType; // 元素类型
// 队列节点
typedef struct QNode {
    ElemType data; // 元素值
    struct QNode *next; // 指向下一个节点的指针
} QNode, *QueuePtr;
// 链队列结构
typedef struct {
    QueuePtr front, rear; // 队头指针、队尾指针
} LinkQueue;
 
2、初始化链队列
Status InitQueue(LinkQueue *Q) {
    // 为队头和队尾指针分配内存
    Q->front = Q->rear = (QueuePtr) malloc(sizeof(QNode));
    // 内存分配失败,结束程序
    if (!Q->front || !Q->rear) {
        return FALSE;
    }
    Q->front->next = NULL; // 队头节点指向NULL
    return TRUE;
}
 
3、判断链队列是否为空
Status QueueEmpty(LinkQueue Q) {
    // 头指针和尾指针位置相等,队列为空
    if (Q.front == Q.rear) {
        return TRUE;
    } else {
        return FALSE;
    }
}
 
4、清空链队列
Status ClearQueue(LinkQueue *Q) {
    QueuePtr p, q; // p用来遍历队列节点,q用来指向被删除的节点
    Q->rear = Q->front; // 队尾指针指向队头指针
    p = Q->front->next; // p指向队头指针的下一个节点
    Q->front->next = NULL; // 队头指针的下一个节点指向NULL(表示删除之后的所有元素)
    // 当队列中还有元素,释放头节点之后的所有节点
    while (p) {
        q = p; // q节点指向被删除节点
        p = p->next; // p指向队列的下一个节点
        delete q; // 释放q节点
    }
    return TRUE;
}
 
5、销毁链队列
Status DestroyQueue(LinkQueue *Q) {
    // 当队列中还有元素
    while (Q->front) {
        Q->rear = Q->front->next;// 队尾指针指向队头指针的下一个元素
        delete Q->front; // 释放队头指针所在节点
        Q->front = Q->rear; // 队头指针指向队尾指针(即原来的下一个元素)
    }
    return TRUE;
}
 
6、获取链队列的长度
int QueueLength(LinkQueue Q) {
    int i = 0; // 用于统计队列长度的计数器
    QueuePtr p; // 用于遍历队列的元素
    p = Q.front; // p指向队头节点
    // 当p没有移动到队尾指针位置
    while (p != Q.rear) {
        i++; // 计数器加1
        p = p->next; // p移动到队列的下一个节点
    }
    return i; // 返回队列长度
}
 
7、获取链队列的头元素
Status GetHead(LinkQueue Q, ElemType *e) {
    QueuePtr p;
    // 队列为空,获取失败
    if (Q.front == Q.rear) {
        return FALSE;
    }
    p = Q.front->next; // p指向队列的第一个元素
    *e = p->data; // 将队列头元素的值赋值给e元素
    return TRUE;
}
 
8、在链队列尾插入新元素
Status EnQueue(LinkQueue *Q, ElemType e) {
    // 给新节点分配空间
    QueuePtr s = (QueuePtr) malloc(sizeof(QNode));
    // 分配空间失败,结束程序
    if (!s) {
        return FALSE;
    }
    s->data = e; // 将值赋值给新节点
    s->next = NULL; // 新节点指向NULL
    Q->rear->next = s; // 队尾指针的下一个元素指向新节点
    Q->rear = s; // 队尾指针指向新节点(新节点成为队尾指针的指向的节点)
    return TRUE;
}
 
9、删除链队列的头元素
Status DeQueue(LinkQueue *Q, ElemType *e) {
    QueuePtr p; // 用于指向被删除节点
    // 队列为空,出队失败
    if (Q->front == Q->rear) {
        return FALSE;
    }
    p = Q->front->next; // p指向队列的第一个元素
    *e = p->data; // 将队列头节点的值赋值给元素e
    Q->front->next = p->next; // 头指针的下一个节点指向下下个节点(跳过头节点)
    // 如果被删除节点是队尾指针指向的节点(删除后队列为空)
    if (Q->rear == p) {
        Q->rear = Q->front; // 队尾指针指向队头指针
    }
    delete p; // 释放队头节点
    return TRUE;
}
 
10、遍历链队列中的元素
Status QueueTravel(LinkQueue Q) {
    QueuePtr p; // 用于遍历队列中的节点
    p = Q.front->next; // p指向头节点
    printf("[ ");
    // 当队列中还有元素
    while (p) {
        printf("%d ", p->data); // 打印当前节点的值
        p = p->next; // p移动到队列下一个位置
    }
    printf("]\n");
    return TRUE;
}
 
顺序队列和链式队列的比较
顺序队列是以数组的形式实现的,首指针在出队的时候向后移动,尾指针在入队的时候向后移动,需要考虑队列为空、队列为满的两种情况。
链式队列是以链表的形式实现的,首指针不移动始终指向头节点,尾指针在入队的时候移动指向插入的元素,只考虑队列为空的情况
 (只要存储空间够,就能申请内存空间来存放节点,所以不用考虑满,因为链表长度在程序运行过程中可以不断增加)
参考资料:数据结构与算法基础-王卓老师















