数据结构-队列

队列

队列概述

  • 概述:队列是线性结构的一种,数组组成的队列是顺序队列,链表组成的队列是链队,还有一种特殊的队列概念上首位相邻叫做循环队列。
  • 队列的特点
    • 先进先出、后进后出
    • 队列是在一端进行插入元素,在另一端进行删除元素,插入元素的一端叫做队尾,删除元素的一端叫做队头。尾进头出
  • 队列的应用场景
    • 网络数据传输:在网络传输中,队列可以用来存储待发送的数据包,保证数据的顺序和完整性。
    • 任务调度:在操作系统中,队列可以用来存储待执行的任务,确保任务按照先后顺序执行。
    • 消息队列:在分布式系统中,队列可以用来实现消息的异步传输和处理,提高系统的可伸缩性和可靠性。
    • 事件处理:在事件驱动的系统中,队列可以用来存储待处理的事件,确保事件按照顺序被处理。
    • 缓存管理:在计算机系统中,队列可以用来实现缓存管理,缓解系统压力,提高性能。
    • 批处理:在数据处理系统中,队列可以用来存储待处理的数据,实现批量处理和优化资源利用。
    • 系统监控:在监控系统中,队列可以用来存储待处理的监控数据,确保数据的实时性和完整性。
    • 任务排队:在服务行业中,队列可以用来排队等待服务,确保服务的公平性和顺序性。

顺序队

  • 概述:顺序队,是由数组实现的
结构体
typedef struct SqQueue{
    int data[maxSize];
    int front;                               // 队头指针
    int rear;                                // 队尾指针
}SqQueue;
顺序队基本操作
初始化队列
void initQueue(SqQueue &qu){
    qu.front = qu.rear = 0;            // 队首和队尾指针重合
}
判断队空
int isEmpty(SqQueue qu){
    if(qu.front == qu.rear){
        return 1;						// 头指针等于尾指针,队空
    }else{
        return 0;						// 否则队不空
    }
}

注意:队空返回 1 ,队列不空返回 0。

入队操作
int enQueue(SqQueue &qu , int x){
    if(qu.rear == maxSize){			// 尾指针等于最大空间,队满
        return 0;                                        
    }
   	// 队尾进元素
    qu.rear += 1;
    qu.data[qu.rear] = x;           // 存入元素
    return 1;
}

注意:入队需要判断队满,队满的条件是队尾指针等于数组最大空间数,但是这样,队头出元素会浪费很多时间

出队操作
int deQueue(SqQueue &qu , int &x){
    if(qu.front == qu.rear){			// 队空,出队失败 
        return 0;                                
    }       
    x = qu.data[qu.front];              // 元素赋值
    qu.front -= 1;						// 队头指针移动
    return 1;
}

循环队列

  • 概述:循环队列是由普通队列的演变而来,是因为普通队列在出队操作后,队列的头部空间就无法利用,导致空间的浪费,为了解决这一问题,循环队列产生了,循环队列就是将队列的头尾相连,形成一个环状结构,这样就可以循环利用头部空间避免浪费

  • 循环队列的特点

    // 队空
    front == rear;                              // 头指针和尾指针 相等
    
    // 队满
    front = (rear + 1)%maxSize;
    rear = (front -1 + maxSize)%maxSize;
    
    // 入队、出队,均先移动指针
    rear = (rear + 1)%maxSize;
    front = (front + 1)%maxSize;
    
结构体
typedef struct Queue{
    int data[maxSize];
    int front;                             // 队头指针
    int rear;                              // 队尾指针
}Queue;
循环队列基本操作
初始化队列
void initQueue(SqQueue &qu){
    qu.front = qu.rear = 0;            // 队首和队尾指针重合
}
判断队空
int isEmpty(SqQueue qu){
    if(qu.front == qu.rear){			// 队空
        return 1;
    }else{								// 队满
        return 0;
    }
}
入队操作
int enQueue(SqQueue &qu , int x){
    if(qu.front == (qu.rear + 1)%maxSize){
        return 0;                            // 队满,进队失败
    }
    /*队尾进元素,队头出元素*/
    qu.rear = (qu.rear + 1)%maxSize;         // 移动尾指针,指向空位置
    qu.data[qu.rear] = x;                    // 存入元素
    return 1;
}
出队操作
int deQueue(SqQueue &qu , int &x){
    if(qu.front == qu.rear){
        return 0;                           // 队空,出队失败 
    }
    qu.front = (qu.front + 1)%maxSize;      // 移动头指针,指向出队元素
    x = qu.data[qu.front];                  // 元素赋值
    return 1;
}

链队

  • 概述:链队,是由链表实现的
结构体
// 队列结点
typedef struct QNode{
    int data;
    struct QNode *next;
}QNode;
// 链队
typedef struct LiQueue{ 
    QNode *front;                            // 队头指针
    QNode *rear;                             // 队尾指针
}LiQueue;
链队的基本操作
初始化队列
void initQueue(LiQueue *&lqu){
    lqu = (LiQueue *)malloc(sizeof(LiQueue));
    lqu->front = lqu->rear = NULL;
}
判断队空
int isEmpty(LiQueue *lqu){
    if(lqu->front == NULL || lqu->rear == NULL){
        return 1;
    }else{
        return 0;
    }
}
入队操作
void enQueue(LiQueue *lqu , int x){
    QNode *p;
    p = (QNode *)malloc(sizeof(QNode));                   // 创建一个新结点
    p->data = x;
    p->next = NULL;
    
    /*如果队列为空,则插入结点是首元结点,头、尾指针都指向*/
    if(lqu->front == NULL || lqu->rear == NULL){
        lqu->front = p;
        lqu->rear = p;
    }else{                            // 新结点链接到队尾即可
        lqu->rear->next = p;
    	lqu->rear = p;
    } 
}

注意:单链表入队,无需判断队满

出队操作
int deQueue(LiQueue *lqu , int &x){
    QNode *p;
    if(lqu->front == NULL || lqu->rear == NULL){
        return 0;                                        // 队空不能出队
    }else{
        p = lqu->front;                                  // 减少冗余
        if(lqu->front == lqu->rear){                     // 只剩下首元结点
            // p = lqu->front;
            lqu->front = lqu->rear = NULL;               // 指针指向,删除结点
        }else{
            // p = lqu->front;
            lqu->front = p->next;
        }
    }
    x = p->data;
    free(p);                                              // 释放结点
    return 1;
}

注意:出队需要判断队空,链队入队和出队要注意 第一个元素的入队最后一个元素的出队

队列的应用

循环队列双端都可插入删除
  • 案例:如果允许在循环队列的两端都可以进行插入和删除操作,完成下述三个需求?
    • 写出循环队列的类型定义
    • 写出从队头删除和队尾插入的算法
    • 写出从队尾删除和队头插入的算法
/* 循环队列的类型定义 */
typedef struct cyQueue{
    int data[maxSize];                       // maxSize 为已定义的常量
    int front,rear;
}cyQueue;

/* 从队头删除和队尾插入的算法 */
/*队头删除元素*/
int deQueue(cyQueue &cqu , int &x){
    if(cqu.front == cqu.rear){                 		// 队空
        return 0;
    }
    cqu.front = (cqu.front + 1)%maxSize;       		// 指针移动,移到第一个删除元素
    x = cqu.data[cqu.front];                  
    return 1;
}

/*队尾进入元素*/
int enQueue(cyQueue &cqu , int x){
    if(cqu.front == (cqu.rear + 1)%maxSize){		// 队满
        return 0;
    }
    cqu.rear = (cqu.rear + 1)%maxSize;              // 指针移动,移动到空位置插入元素
    cqu.data[cqu.rear] = x;
    return 1;
}

/* 从队尾删除和队头插入的算法 */
/*队尾删除元素*/
int deQueue(cyQueue &cqu , int &x){
    if(cqu.front == cqu.rear){						// 队空
        return 0;
    }
    x = cqu.data[cqu.rear];                         // 先赋值
    cqu.rear = (cqu.rear - 1 + maxSize)%maxSize;	// 尾指针移动
    return 1;
}

/*队头插入元素*/
int enQueue(cyQueue &cqu , int x){
    if(cqu.rear == (cqu.front -1 + maxSize)%maxSize){
        return 0;									// 队满
    }
    cqu.data[cqu.front] = x;
    cqu.front = (cqu.front - 1 + maxSize)%maxSize;  // 头指针移动
    return 1;
}

注意:头插尾山,是逆时针从大到小的顺序

循环链表表示队列,只设队尾指针
  • 案例:假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾结点,但不设头指针,请写出相应的入队列和出队列的算法
  • 代码分析
    • 循环链表有一个尾指针 rear
    • 在循环链表尾部执行元素入队
    • 在循环链表头部执行元素出队
/*入队列*/
void enQueue(LNode *&rear , int x){                 // rear 指针发生改变
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->next = NULL;
    s->data = x;             						// x 给结点赋值
	
    /*头插法,将新结点插入循环链表尾部*/
    s->next = rear->next;
    rear->next = s;
    rear = rear->next;       						// 指针后移
}

/*出队列:方式一*/
int deQueue(LNode *&rear , int &x){
    LNode *p;
    /*出队列判断队空*/
    if(rear->next == rear){       					// 只有头结点
        return 0;
    }else{
        p = rear->next->next;
        if(rear->next->next == rear){				// 只有首元结点,特殊处理
            rear = rear->next;
        }else{  									// 不只有首元结点
            rear->next->next = p->next;
        }
        x = p->data;
    	free(p);
   		return 1;
    }
}

/*出队列:方式二*/
int deQueue(LNode *&rear , int &x){
    LNode *p;
    if(rear->next == rear){
        return 0;
    }else{
        p = rear->next->next;						// p 指向首元结点
        rear->next->next = p->next;					// 删除结点
        x = p->data;
        if(p == rear)
            rear = rear->next;
        free(p);
        return 1;
    }
}
二叉树层次遍历
  • 代码分析
    • 层次遍历要用到 队列,在这里我们使用循环队列,定义队列
    • 根结点入队列,然后根节点出队列(并输出访问),然后检查当前结点的左右子树,左非空,左子树先入队列,右非空,右子树再入队列
    • 然后当队列不空的时候一直循环,将队头元素弹出,重复 2)
void level(BTNode *bt){
    /*定义队列,并初始化*/
    BTNode *que[maxSize];
    int front , rear;
    front = rear = 0;
    BTNode *p;
    if(bt != NULL){
		rear = (rear + 1)%maxSize;
        que[rear] = bt;
        
        while(front != rear){							// 队列不空
            front = (front + 1)%maxSize;
            p = que[front];
            visit(p);									// 访问 p
            if(p->lChild != NULL){						// 左子书进入队列
                que[(rear + 1)%maxSize] = p->lChild;
            }
            if(p->rChild != NULL){						// 右子树进入队列
                que[(rear + 1)%maxSize] = p->rChild;
            }
        }

    }
}
  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值