数据结构-队列

数据结构之队列

    • 队列的概念
      • 顺序队列
      • 循环队列
    • 顺序循环队列的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。允许插入的一 端称为队尾,允许删除的一端称为队头。
数据结构-队列_第1张图片
线性表有顺序存储和链式存储两种形式,队列作为一种特殊的线性表,也存在着这两种存储方式。

顺序队列

顺序存储形式的队列,是利用一组地址连续的存储单元,每个存储单元依次存放队列中的元素。为了避免当只有一个元素时,队头和队尾重合使得处理变得麻烦,所以引入两个指针(头指针和尾指针):头指针front指针指向队头元素,尾指针rear指针指向队尾元素的下一个位置。初始化时的头尾指针,初始值均为0。
入队时尾指针rear加1,出队时头指针front加1,头尾指针相等时队列即为空。

当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素入队,就会发生“溢出”。
为了解决溢出问题,可以采用循环队列。

循环队列

使用循坏队列,将新元素再插入到第一个位置上,入队和出队仍按先进先出的原则进行,操作效率高,空间利用率高,同时解决了顺序队列的假溢出问题。
数据结构-队列_第2张图片
但是,同时仅凭 front = rear 不能判定循环队列是空还是满
判断队空或者队满,有三种方式:

数据结构-队列_第3张图片
数据结构-队列_第4张图片

顺序循环队列的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;
}

顺序队列和链式队列的比较

顺序队列是以数组的形式实现的,首指针在出队的时候向后移动,尾指针在入队的时候向后移动,需要考虑队列为空、队列为满的两种情况。

链式队列是以链表的形式实现的,首指针不移动始终指向头节点,尾指针在入队的时候移动指向插入的元素,只考虑队列为空的情况
(只要存储空间够,就能申请内存空间来存放节点,所以不用考虑满,因为链表长度在程序运行过程中可以不断增加)

参考资料:数据结构与算法基础-王卓老师

你可能感兴趣的:(数据结构与算法,数据结构,java,算法)