• 数据结构(二)栈与队列---队列


    (一)定义

    是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
    与栈相反,队列是先进先出(First In First Out:FIFO)的线性表、
    与栈相同:队列也是一种重要的线性结构,实现一个队列同样需要顺序表或者链表来作为基础

    (二)结构

    例如键盘缓冲区就是队列形式接收输入输出的。

    (三)队列的抽象数据类型

    ADT 队列(Queue)
    
    Data
        同线性表。元素具有相同的类型,相邻元素具有前驱和后继的关系。
    
    Operation
        InitQueue( *Q): 初始化操作,建立一个空队列Q
        ClearQueue( *Q): 将队列清空
        QueueEmpty( Q): 若队列为空,返回true,否则返回false
        QueueLength( Q): 返回队列Q的元素个数
    
        GetHead( Q, *e): 若是队列存在且非空,用e返回Q的队头元素
        EnQueue( *Q, e):若是队列存在,则插入新的元素e入队为队尾
        DeQueue( *Q, *e):若是队列存在且非空,进行出队操作,用e接收数据
        DestroyQueue( *Q): 若是队列存在,则销毁他
    endADT

    (四)存储结构

    队列既可以使用链表实现,也可以使用顺序表来实现。跟栈相反的是,栈一般使用顺序表来实现,而队列常使用链表来实现,简称链队列
    //设置队列的数据结点
    typedef struct QNode
    {
        ElemType data;    //存放队列中的数据
        struct QNode* next;    //队列结点的指针域
    }QNode, *QNodePtr;
    
    //设置队列的结构体
    typedef struct
    {
        QNodePtr front,rear;    //队列头尾指针
    }LinkQueue;

    (五)队列的链式存储结构

    我们将个队头指针指向链队列的头结点,而队尾指针指向终端结点(注:头结点不是必须的,这里为了操作结点使用了)

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    
    typedef int ElemType;
    typedef int Status;
    
    //设置队列的数据结点
    typedef struct QNode
    {
        ElemType data;    //存放队列中的数据
        struct QNode* next;    //队列结点的指针域
    }QNode, *QNodePtr;
    
    //设置队列的结构体
    typedef struct
    {
        QNodePtr front,rear;    //队列头尾指针
    }LinkQueue;
    
    //四个基础操作
    Status InitQueue(LinkQueue *Q);    //初始化操作,建立一个空队列Q
    Status ClearQueue(LinkQueue *Q);//将队列清空
    Status QueueEmpty(LinkQueue Q);    //若队列为空,返回true,否则返回false
    int QueueLength(LinkQueue Q);    //返回队列Q的元素个数
    
    Status GetHead(LinkQueue Q, ElemType *e);    //若是队列存在且非空,用e返回Q的队头元素
    Status EnQueue(LinkQueue *Q, ElemType e);    //若是队列存在,则插入新的元素e入队为队尾
    Status DeQueue(LinkQueue *Q, ElemType *e);    //若是队列存在且非空,进行出队操作,用e接收数据
    Status DestroyQueue(LinkQueue *Q);            //若是队列存在,则销毁他
    
    int main()
    {
        LinkQueue lq;
        ElemType e;
        int i;
        //初始化一个空的队列
        InitQueue(&lq);
    
        printf("2.EnQueue 1-5
    ");
        for (i = 1; i <= 5; i++)
            EnQueue(&lq, i);
        printf("3.DeQueue number for three times
    ");
        for (i = 1; i <= 3;i++)
        {
            DeQueue(&lq, &e);
            printf("DeQueue %d: %d
    ",i, e);
        }
        GetHead(lq, &e);
        printf("4.Get Head:%d
    ",e);
        printf("5.EnQueue 6-10
    ");
        for (i = 6; i <= 10; i++)
            EnQueue(&lq, i);
        printf("6.Get queue length:%d
    ", QueueLength(lq));
        printf("7.DeQueue number for six times
    ");
        for (i = 1; i <= 6; i++)
        {
            DeQueue(&lq, &e);
            printf("DeQueue %d: %d
    ",i, e);
        }
        if (!QueueEmpty(lq))
        {
            printf("8.Queue is not Empty
    ");
            ClearQueue(&lq);
            printf("9.Queue is Clear
    ");
        }
        printf("10.Queue Empty:%d
    ", QueueEmpty(lq));
        printf("11.destroy Queue");
        DestroyQueue(&lq);
        system("pause");
        return 0;
    }
    
    //初始化操作,建立一个空队列Q
    Status InitQueue(LinkQueue *Q)
    {
        if (!Q)
            return ERROR;
        Q->front = Q->rear = (QNodePtr)malloc(sizeof(QNode));
        if (!Q->front)
            return ERROR;
        Q->front->next = Q->rear->next = NULL;
        return OK;
    }
    
    //将队列清空,保留头结点,注意队尾指针
    Status ClearQueue(LinkQueue *Q)
    {
        QNodePtr head = Q->front->next;    //获取开始结点
        QNodePtr cur;    //游标指针
        if (!Q)
            return ERROR;
        while (head)    //将数据全部释放
        {
            cur = head;
            head = head->next;
            free(cur);
        }
        Q->rear = Q->front;    //将队尾指向队头
        Q->rear->next = Q->front->next = NULL;    //记得:重点
        return OK;
    }
    
    //若队列为空,返回true,否则返回false
    Status QueueEmpty(LinkQueue Q)
    {
        if (!Q.front->next)
            return TRUE;
        return FALSE;
    }
    
    //返回队列Q的元素个数
    int QueueLength(LinkQueue Q)
    {
        QNodePtr head = Q.front;    //获取头结点
        QNodePtr end = Q.rear;    //获取终端结点
        int length = 0;
        while (head!=end)
        {
            length++;
            head = head->next;
        }
        return length;
    }
    
    //若是队列存在且非空,用e返回Q的队头元素
    Status GetHead(LinkQueue Q, ElemType *e)
    {
        QNodePtr head;
        if (!e||QueueEmpty(Q))
            return ERROR;
        head = Q.front->next;
        *e = head->data;
        return OK;
    }
    
    //注意:对于队列,我们更多关心队尾指针多余队头指针
    
    //若是队列存在,则插入新的元素e入队为队尾,注意还要考虑队尾指针
    Status EnQueue(LinkQueue *Q, ElemType e)
    {
        if (!Q)
            return ERROR;
        QNodePtr q = (QNodePtr)malloc(sizeof(QNode));
        if (!q)
            return ERROR;
        q->data = e;
        q->next = Q->rear->next;
        Q->rear->next = q;
        Q->rear = q;
        return OK;
    }
    
    //若是队列存在且非空,进行出队操作,用e接收数据,注意还要考虑队尾指针
    Status DeQueue(LinkQueue *Q, ElemType *e)
    {
        QNodePtr q;
        if (!Q || !e || QueueEmpty(*Q))
            return ERROR;
        q = Q->front->next;    //开始结点
        *e = q->data;
        Q->front->next = q->next;    //指针后移(这一步注意:重点,且易错)
        if (Q->rear == q)    //若是我们队列中只有一个结点,删除后需要修改队尾指针
            Q->rear = Q->front;
        free(q);    //释放结点
        return OK;
    }
    
    //若是队列存在,则销毁他,包含头结点
    Status DestroyQueue(LinkQueue *Q)
    {
        if (!Q)
            return OK;
        if (ClearQueue(Q))
        {
            free(Q->front);
            Q->front = Q->rear = NULL;
            return OK;
        }
        return ERROR;
    }

    (六)队列的顺序存储结构(了解思想即可)

     (1)顺序队列:队头指针不变

    上面的顺序队列:在出队时需要移动顺序队列中所有的元素,时间复杂度O(n),需要改进

     (2)顺序队列:队头指针移动

    缺点:队尾指针逐渐增加,而队头也增加,那么数组的可用空间会减少。前面的红色区域会造成空间浪费。

    (3) 顺序队列:循环队列

    成功解决上面两种情况的缺点,不足之处是对于数组溢出没有办法

    注意:和链队列相比

    链队列的队尾指针是执行最后的那个元素结点。
    而循环链表则是指向下一个空数组下标。(当然我们也可以执行最后一个有数据的下标) 

    (七)实现循环队列(可以使用堆,也可以使用数组,这里直接使用数组)

     注意:考虑对于队尾在队头后面和前面两种情况下的长度,如何使用一种方法来表示?还有判断队满是否也可以使用一种方法表示?还有判断队尾,队头下标方法是否一致?

    从上面的循环队列分析:
    长度可以使用-->(队尾下标-队头下标+数组长度)%数组长度
    判断是否队满-->(队尾下标+1)%数组长度==队头下标
    判断队尾,队头下标-->(队尾下标+1)%数组长度==新的队尾下标
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    
    #define MAXSIZE 10
    
    typedef int ElemType;
    typedef int Status;
    
    //设置队列的结构体
    typedef struct
    {
        ElemType data[MAXSIZE];
        int front;    //队头下标
        int rear;    //队尾下标,若队列不为空,指向队列元素的下一个位置
    }sqQueue;
    
    //四个基础操作
    Status InitQueue(sqQueue *Q);    //初始化操作,建立一个空队列Q
    Status ClearQueue(sqQueue *Q);//将队列清空
    Status QueueEmpty(sqQueue Q);    //若队列为空,返回true,否则返回false
    int QueueLength(sqQueue Q);    //返回队列Q的元素个数
    
    Status GetHead(sqQueue Q, ElemType *e);    //若是队列存在且非空,用e返回Q的队头元素
    Status EnQueue(sqQueue *Q, ElemType e);    //若是队列存在,则插入新的元素e入队为队尾
    Status DeQueue(sqQueue *Q, ElemType *e);    //若是队列存在且非空,进行出队操作,用e接收数据
    Status DestroyQueue(sqQueue *Q);            //若是队列存在,则销毁他
    
    int main()
    {
        sqQueue lq;
        ElemType e;
        int i;
        //初始化一个空的队列
        InitQueue(&lq);
    
        printf("2.EnQueue 1-5
    ");
        for (i = 1; i <= 5; i++)
            EnQueue(&lq, i);
        printf("3.DeQueue number for three times
    ");
        for (i = 1; i <= 3; i++)
        {
            DeQueue(&lq, &e);
            printf("DeQueue %d: %d
    ", i, e);
        }
        GetHead(lq, &e);
        printf("4.Get Head:%d
    ", e);
        printf("5.EnQueue 6-10
    ");
        for (i = 6; i <= 10; i++)
            EnQueue(&lq, i);
        printf("6.Get queue length:%d
    ", QueueLength(lq));
        printf("7.DeQueue number for six times
    ");
        for (i = 1; i <= 6; i++)
        {
            DeQueue(&lq, &e);
            printf("DeQueue %d: %d
    ", i, e);
        }
        if (!QueueEmpty(lq))
        {
            printf("8.Queue is not Empty
    ");
            ClearQueue(&lq);
            printf("9.Queue is Clear
    ");
        }
        printf("10.Queue Empty:%d
    ", QueueEmpty(lq));
        printf("11.destroy Queue");
        DestroyQueue(&lq);
        system("pause");
        return 0;
    }
    
    //初始化操作,建立一个空队列Q
    Status InitQueue(sqQueue *Q)
    {
        if (!Q)
            return ERROR;
        Q->front = Q->rear = 0;    //都指向下标0
        return OK;
    }
    
    //将队列清空,和初始化一样即可
    Status ClearQueue(sqQueue *Q)
    {
        if (!Q)
            return ERROR;
        Q->front = Q->rear = 0;    //都指向下标0
        return OK;
    }
    
    //若队列为空,返回true,否则返回false
    Status QueueEmpty(sqQueue Q)
    {
        if (Q.front == Q.rear)    //若是队头队尾指向一致,则为空
            return TRUE;
        return FALSE;
    }
    
    //返回队列Q的元素个数
    int QueueLength(sqQueue Q)
    {
        return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
    }
    
    //若是队列存在且非空,用e返回Q的队头元素
    Status GetHead(sqQueue Q, ElemType *e)
    {
        if (!e || QueueEmpty(Q))
            return ERROR;
        *e = Q.data[Q.front];
        return OK;
    }
    
    //若是队列存在,且未满,则插入新的元素e入队为队尾
    Status EnQueue(sqQueue *Q, ElemType e)
    {
        if (!Q || (Q->rear + 1) % MAXSIZE == Q->front)    //判断队满
            return ERROR;
        Q->data[Q->rear] = e;    //为队尾赋值
        Q->rear = (Q->rear + 1) % MAXSIZE;    //将队尾下标下移
        return OK;
    }
    
    //若是队列存在且非空,进行出队操作,用e接收数据
    Status DeQueue(sqQueue *Q, ElemType *e)
    {
        if (!Q || !e || QueueEmpty(*Q))
            return ERROR;
        *e = Q->data[Q->front];
        Q->front = (Q->front + 1) % MAXSIZE;
        return OK;
    }
    
    //若是队列存在,则销毁他(和初始化一致),要不再加一个将数据清空吧
    Status DestroyQueue(sqQueue *Q)
    {
        if (!Q)
            return ERROR;
        memset(Q, 0, MAXSIZE*sizeof(ElemType));
        Q->front = Q->rear = 0;    //都指向下标0
        return OK;
    }

    (八)我们为什么选用链队列

    1.若只是使用顺序存储,算法的时间性能不高,尤其是对于出队列时的相关操作。
    2.即便我们使用了循环队列,也会面临数组溢出的问题。
    总结:
    所有我们使用不需要担心队列长度的链式存储结构即可
  • 相关阅读:
    PHP实现无限极分类
    html2canvas生成并下载图片
    一次线上问题引发的过程回顾和思考,以更换两台服务器结束
    Intellij IDEA启动项目报Command line is too long. Shorten command line for XXXApplication or also for
    mq 消费消息 与发送消息传参问题
    idea 创建不了 java 文件
    Java switch 中如何使用枚举?
    Collections排序
    在idea 设置 git 的用户名
    mongodb添加字段和创建自增主键
  • 原文地址:https://www.cnblogs.com/ssyfj/p/9441765.html
Copyright © 2020-2023  润新知