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

(一)定义

是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
与栈相反,队列是先进先出(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\n");
    for (i = 1; i <= 5; i++)
        EnQueue(&lq, i);
    printf("3.DeQueue number for three times\n");
    for (i = 1; i <= 3;i++)
    {
        DeQueue(&lq, &e);
        printf("DeQueue %d: %d\n",i, e);
    }
    GetHead(lq, &e);
    printf("4.Get Head:%d\n",e);
    printf("5.EnQueue 6-10\n");
    for (i = 6; i <= 10; i++)
        EnQueue(&lq, i);
    printf("6.Get queue length:%d\n", QueueLength(lq));
    printf("7.DeQueue number for six times\n");
    for (i = 1; i <= 6; i++)
    {
        DeQueue(&lq, &e);
        printf("DeQueue %d: %d\n",i, e);
    }
    if (!QueueEmpty(lq))
    {
        printf("8.Queue is not Empty\n");
        ClearQueue(&lq);
        printf("9.Queue is Clear\n");
    }
    printf("10.Queue Empty:%d\n", 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\n");
    for (i = 1; i <= 5; i++)
        EnQueue(&lq, i);
    printf("3.DeQueue number for three times\n");
    for (i = 1; i <= 3; i++)
    {
        DeQueue(&lq, &e);
        printf("DeQueue %d: %d\n", i, e);
    }
    GetHead(lq, &e);
    printf("4.Get Head:%d\n", e);
    printf("5.EnQueue 6-10\n");
    for (i = 6; i <= 10; i++)
        EnQueue(&lq, i);
    printf("6.Get queue length:%d\n", QueueLength(lq));
    printf("7.DeQueue number for six times\n");
    for (i = 1; i <= 6; i++)
    {
        DeQueue(&lq, &e);
        printf("DeQueue %d: %d\n", i, e);
    }
    if (!QueueEmpty(lq))
    {
        printf("8.Queue is not Empty\n");
        ClearQueue(&lq);
        printf("9.Queue is Clear\n");
    }
    printf("10.Queue Empty:%d\n", 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.即便我们使用了循环队列,也会面临数组溢出的问题。
总结:
所有我们使用不需要担心队列长度的链式存储结构即可
posted @ 2018-08-08 13:48  山上有风景  阅读(511)  评论(0编辑  收藏  举报