6.数据结构与算法---队列

366 阅读5分钟

一、顺序存储队列

队列(Queue):是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出(FIFO) 的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。

1.顺序存储队列假溢出

如上图,这个队列中的总个数为6个,但目前如果接着入队的话,会导致数组越界的错误,但是队列在下标为0,1,2,3的位置是没有元素的,造成了空间浪费。我们把这种现象叫做“假溢出”。

为了解决“假溢出”的问题,我们引入循环队列。

2.循环队列

就是队后面满了,再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

此时问题又来了,如上图第4个,队满时发现rear指针与front重合了,刚才说了,当rear=front时,表示是空队列,现在当队列满时,rear也等于front。那么如何判断队列到底是空的还是满的了?

解决办法为:当队列空时,判断条件就是rear=front, 当队列满时,我们修改其判断条件,保留一个元素空闲。也就是说,队列满时,数组中还有一个空闲单元。

由于rear可能比front大,也可能比front小,所以假设队列的最大尺寸为MaxSize, 队列满的判断条件改为(rear + 1)% MaxSize = front. 队列的长度为(rear - front + MaxSize)% MaxSize.

3.结构

#include "stdio.h"
#include "stdlib.h"

#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

循环队列的顺序存储结构

typedef struct {
    QElemType data[MAXSIZE];
    int front;        /* 头指针 */
    int rear;        /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;

4.初始化一个空队列Q

Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

5.清空队列

Status ClearQueue(SqQueue *Q){
    Q->front = Q->rear = 0;
    return OK;
}

6.判断是否为空

Status QueueEmpty(SqQueue Q){
    //队空标记
    if (Q.front == Q.rear)
        return TRUE;
    else
        return FALSE;
}

7.长度

int QueueLength(SqQueue Q){
    return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}


8.获取队头元素

Status GetHead(SqQueue Q,QElemType *e){
    //队列已空
    if (Q.front == Q.rear)
        return ERROR;
    
    *e = Q.data[Q.front];
    return OK; 
}

9.入队

Status EnQueue(SqQueue *Q,QElemType e){
    
    //队列已满
    if((Q->rear+1)%MAXSIZE == Q->front)
        return ERROR;
    
    //将元素e赋值给队尾
    Q->data[Q->rear] = e;
    
    //rear指针向后移动一位,若到最后则转到数组头部;
    Q->rear = (Q->rear+1)%MAXSIZE;
    
    或者
   // Q->data[(Q->rear)%MAXSIZE] = e;
   // Q->rear++ 
    
    return OK;
}

10.出队

//若队列不空,则删除Q中队头的元素,用e返回值
Status DeQueue(SqQueue *Q,QElemType *e){
   
    //判断队列是否为空
    if (Q->front == Q->rear) {
        return ERROR;
    }
    
    //将队头元素赋值给e
    *e = Q->data[Q->front];
    
    //front 指针向后移动一位,若到最后则转到数组头部
    Q->front = (Q->front+1)%MAXSIZE;
    
    return OK;
}


11.遍历

Status QueueTraverse(SqQueue Q){
    int i;
    i = Q.front;
    while (i != Q.rear) {
        printf("%d   ",Q.data[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
    return OK;
}

二、链式存储队列

链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 frontrear)分别指向链表中队列的头结点(没头结点的是首元节点)和队尾元素,现在的都是带头结点的

1.节点结构

#include <stdio.h>
#include "stdio.h"
#include "stdlib.h"

#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status;

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

结点结构

typedef struct QNode {    /* 结点结构 */
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;


typedef struct {           /* 队列的链表结构 */
    QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

2.初始化队列 带头结点的

Status InitQueue(LinkQueue *Q){
    //1. 头/尾指针都指向 头结点
    Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));

    //2.判断是否创建新结点成功与否
    if (!Q->front) {
        return ERROR;
    }

    //3.头结点的指针域置空
    Q->front->next = NULL;
    
    return OK;
}

3.销毁队列

Status DestoryQueue(LinkQueue *Q){
    //遍历整个队列,销毁队列的每个结点 借用Q->rear,省的用临时变量 
    //头节点也是个malloc开辟的,也需要释放
    while (Q->front) {
        Q->rear = Q->front->next;
        free(Q->front);
        Q->front = Q->rear;
    }
    return OK; 
}

4.将队列置空, 只保留头结点

Status ClearQueue(LinkQueue *Q){
    
    QueuePtr p,q;
    Q->rear = Q->front;
    p = Q->front->next;
    Q->front->next = NULL;
    
    //释放节点空间
    while (p) {
        q = p;
        p = p->next;
        free(q);
    }
    
    return OK;
}


5.判断队列是否为空

Status QueueEmpty(LinkQueue Q){
    if (Q.front == Q.rear)
        return TRUE;
    else
        return FALSE;
}


6.获取队列长度

int QueueLength(LinkQueue Q){
    int i= 0;
    QueuePtr p;
    p = Q.front;
    while (Q.rear != p) {
        i++;
        p = p->next;
    }
    return i;
}


7.入队

1.创建新节点s
2.rear-> = s
3.rear = s
Status EnQueue(LinkQueue *Q,QElemType e){
    
    //为入队元素分配结点空间,用指针s指向;
    QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
    
    //判断是否分配成功
    if (!s) {
         return ERROR;
    }
    
    //将新结点s指定数据域.
    s->data = e;
    s->next = NULL;
    
    //将新结点插入到队尾
    Q->rear->next = s;
    
    //修改队尾指针
    Q->rear = s;
    
    return OK;
}

8.出队

1.获取首元结点p,即front->next
2.头节点front的next指向p的next,front->next = p->next
3.释放首元结点空间free(p)
Status DeQueue(LinkQueue *Q,QElemType *e){
    
    QueuePtr p;
    
    //判断队列是否为空;
    if (Q->front == Q->rear) {
        return ERROR;
    }
    
    //将要删除的队头结点暂时存储在p
    p = Q->front->next;
    
    //将要删除的队头结点的值赋值给e
    *e = p->data;
    
    //将原队列头结点的后继p->next 赋值给头结点后继
    Q->front->next = p ->next;
    
    //若队头就是队尾,则删除后将rear指向头结点.
    if(Q->rear == p) Q->rear = Q->front;
    
    free(p);
    
    return OK;
}


9.获取队头元素

Status GetHead(LinkQueue Q,QElemType *e){
   
    //队列非空
    if (Q.front != Q.rear) {
        //返回队头元素的值,队头指针不变
        *e =  Q.front->next->data;
        return TRUE;
    }
    
    return  FALSE; 
}


10.遍历

Status QueueTraverse(LinkQueue Q){

    if (*Q->front->next == NULL) return ERROR;
    
    QueuePtr p;
    p = Q.front->next;
    while (p) {
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}