考研复习之数据结构(六)栈和队列(下)(包含队列的相关内容、栈和队列的对比以及总结)

目录

一、队列的定义

1.1 相关概念和特点

(1)基本概念

(2)队列特点

(3)案例引入

2.1 队列分类

二、队列的表示和实现

2.1 顺序队列的表示和实现

(1)结构体定义

(2)初始化操作

(3)入队操作

(4)出队操作

(5)队列大小与判断队列是否为空

2.2 链式队列的表示和实现

(1)结构体定义

(2)初始化操作

(3)入队操作

(4)出队操作

(5)输出队列全部字符与判断队列是否为空

2.3 双向队列的表示和实现

2.4 循环队列的表示和实现

(1)结构体定义

(2)初始化操作

(3)入队操作

(4)出队操作

(5)全部源码

三、经典实例

3.1 层次遍历

3.2 队列在计算机系统中的应用

3.3 其他实例

四、小结:栈与队列


一、队列的定义

1.1 相关概念和特点

(1)基本概念

        队列(queue)是一种先进先出(First In First Out, FIFO )线性表。它只允许在表的一端进行插入,而在另一端删除元素

        这和日常生活中的排队是一致的,最早进入队列的元素最早 离开。在队列中,允许插入的一端称为队尾(rear),允许删除的一端则称为队头(front)

(2)队列特点

        先入先出(FIFO)

(3)案例引入

        队列在程序设计中也经常出现。一个最典型的例子就是操作系统中的作业排队。在允许多道程序运行的计算机系统中,同时有几个作业运行。如果运行的结果都需要通过通道输出,那就要按请求输入的先后次序排队。每当通道传输完毕可以接受新的输出任务时,队头的作业先从队列中退出做输出操作。凡是申请输出的作业都从队尾进入队列。

 图1.1-1 队列示意图

2.1 队列分类

 

 图1.2-1 不同队列结构示意图

二、队列的表示和实现

2.1 顺序队列的表示和实现

(1)结构体定义

    #define MAX 1000
    typedef struct Queue//队列构造
    {
        void *p[MAX];//数据域,数组存储指针(地址)
        int size;//队列大小
    } Queue;

(2)初始化操作

 图2.1-1 顺序队列初始化

Queue *initQueue()//队列初始化
{
    Queue *queue = (Queue *)malloc(sizeof(Queue));//在堆区申请内存
    if (queue == NULL)
        return NULL; //内存分配失败
    memset(queue->p, 0, sizeof(void *) * MAX);//为数组p初始化
    queue->size = 0;
    return queue;
}

(3)入队操作

int push(void *data, Queue *queue) //入队列,本质是尾插
{
    if (data == NULL || queue == NULL)
        return 0;                 //空指针判断,入队列失败
    queue->p[queue->size] = data; //把data插入在队列末位
    queue->size++;                //队列大小+1
    return 1;
}

(4)出队操作

void *pop(Queue *queue) //出队列,即pop出首个元素
{
    if (queue == NULL || queue->size == 0)
        return NULL;
    void *data = queue->p[0]; //需要出队列的元素
    for (int i = 0; i < queue->size - 1; i++)
        queue->p[i] = queue->p[i + 1]; //更新队列,剩余的数据地址前移一位
    free(queue->p[queue->size-1]);
    queue->p[queue->size-1] = NULL;//将原队列末位数据置空
    queue->size--;                     //队列大小减1
    return data;

}

(5)队列大小与判断队列是否为空

int isEmptyQueue(Queue *queue)//判断队列是否为空
{
    if (queue == NULL)
        return -1;
    return queue->size == 0; //空时返回1
}
int sizeOfQueue(Queue *queue)//返回队列的大小
{
    if (queue == NULL)
        return -1;
    return queue->size;
}

2.2 链式队列的表示和实现

(1)结构体定义

图2.2-1 链式队列结构

//定义队列
typedef struct qNode{
	int data;//整型数据(可以根据需要修改数据类型) 
	struct qNode *next;
}qNode,*queue;
typedef struct{
	queue front;
	queue rear;
}Queue;

(2)初始化操作

图 2.2-2 链式队列初始化

//初始化队列
void initQueue(Queue &Q){
	Q.front=Q.rear=(queue)malloc(sizeof(qNode));//队头指针、队尾指针指向同一空结点 
	Q.front->next=NULL;
}

(3)入队操作

 图2.2-3 入队操作

//入队列
void inQueue(Queue &Q,int data){
	queue p;	
	p=(queue)malloc(sizeof(qNode));//分配一个结点空间
	p->data=data;
	p->next=NULL;
	Q.rear->next=p;//队尾指针指向p
	Q.rear=p;//队尾指针后移一位
	printf("数据 %d 已经入队列\n\n",data);
}

(4)出队操作

  图2.2-4 出队操作

//出队列
void outQueue(Queue &Q,int &data){
	queue p;
	p=(queue)malloc(sizeof(qNode));//分配一个结点空间
	p=Q.front->next;//指定一个新的结点指向队头指针指向的结点
	data=p->data;//返回结点数据值
	Q.front->next=p->next;//将p指向的下一数据给队头指针,令下一数据前移到队头 
	printf("数据 %d 已经出队列\n\n",data);
	if(Q.rear==p){
		Q.rear=Q.front;//使队尾指针回到初始位置
		printf("请注意:队列中已经没有数据!\n\n");//提示用户队列无数据 
	}
	delete(p);//释放p所指结点空间 
	
}

(5)输出队列全部字符与判断队列是否为空

//判断队列是否为空
bool emptyQueue(Queue &Q){
	if(Q.front==Q.rear){
		return false;
	}else{
		return true;
	}
}

//输出链队列中储存的全部字符 
void outQ(Queue Q){
	queue q;//辅助变量
	q=Q.front->next;
	//如果链队列不为空,输出其全部字符 
	while(q){
		printf("%d ",q->data);
		q=q->next;
	}
	printf("\n");//换行 
}

2.3 双向队列的表示和实现

这里可以采用双向链表实现,本人并没有实现。因此就暂时不放源码了

如果后续有补充会回来更新。

2.4 循环队列的表示和实现

(1)结构体定义

 图2.4-1 循环队列结构

代码实现如下:

// 队列的顺序存储结构(循环队列)
#define MAX_QSIZE 5 // 最大队列长度+1
typedef struct {
    int *base; // 初始化的动态分配存储空间
    int front; // 头指针,若队列不空,指向队列头元素
    int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;

(2)初始化操作

// 构造一个空队列Q
SqQueue* Q_Init() {
    SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
    // 存储分配失败
    if (!Q){
        exit(OVERFLOW);
    }
    Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
    // 存储分配失败
    if (!Q->base){
        exit(OVERFLOW);
    }
    Q->front = Q->rear = 0;
    return Q;
}

(3)入队操作

 图2.4-2 入队操作

代码实现如下:

// 插入元素e为Q的新的队尾元素
int Q_Put(SqQueue *Q, int e) {
    if ((Q->rear + 1) % MAX_QSIZE == Q->front) // 队列满
        return -1;
    Q->base[Q->rear] = e;
    Q->rear = (Q->rear + 1) % MAX_QSIZE;
    return 1;
}

(4)出队操作

 图2.4-3 出队操作

// 若队列不空,则删除Q的队头元素,用e返回其值,并返回1;否则返回-1
int Q_Poll(SqQueue *Q, int &e) {
    if (Q->front == Q->rear) // 队列空
        return -1;
    e = Q->base[Q->front];
    Q->front = (Q->front + 1) % MAX_QSIZE;
    return 1;
}

(5)全部源码

注:这里我太懒了没有敲、源码来自于百度

// 队列的顺序存储结构(循环队列)
#define MAX_QSIZE 5 // 最大队列长度+1
typedef struct {
    int *base; // 初始化的动态分配存储空间
    int front; // 头指针,若队列不空,指向队列头元素
    int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;
 
 
// 构造一个空队列Q
SqQueue* Q_Init() {
    SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
    // 存储分配失败
    if (!Q){
        exit(OVERFLOW);
    }
    Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
    // 存储分配失败
    if (!Q->base){
        exit(OVERFLOW);
    }
    Q->front = Q->rear = 0;
    return Q;
}
 
// 销毁队列Q,Q不再存在
void Q_Destroy(SqQueue *Q) {
    if (Q->base)
        free(Q->base);
    Q->base = NULL;
    Q->front = Q->rear = 0;
    free(Q);
}
 
// 将Q清为空队列
void Q_Clear(SqQueue *Q) {
    Q->front = Q->rear = 0;
}
 
// 若队列Q为空队列,则返回1;否则返回-1
int Q_Empty(SqQueue Q) {
    if (Q.front == Q.rear) // 队列空的标志
        return 1;
    else
        return -1;
}
 
// 返回Q的元素个数,即队列的长度
int Q_Length(SqQueue Q) {
    return (Q.rear - Q.front + MAX_QSIZE) % MAX_QSIZE;
}
 
// 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
int Q_GetHead(SqQueue Q, int &e) {
    if (Q.front == Q.rear) // 队列空
        return -1;
    e = Q.base[Q.front];
    return 1;
}
 
// 打印队列中的内容
void Q_Print(SqQueue Q) {
    int p = Q.front;
    while (Q.rear != p) {
        cout << Q.base[p] << endl;
        p = (p + 1) % MAX_QSIZE;
    }
}
 
// 插入元素e为Q的新的队尾元素
int Q_Put(SqQueue *Q, int e) {
    if ((Q->rear + 1) % MAX_QSIZE == Q->front) // 队列满
        return -1;
    Q->base[Q->rear] = e;
    Q->rear = (Q->rear + 1) % MAX_QSIZE;
    return 1;
}
 
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回1;否则返回-1
int Q_Poll(SqQueue *Q, int &e) {
    if (Q->front == Q->rear) // 队列空
        return -1;
    e = Q->base[Q->front];
    Q->front = (Q->front + 1) % MAX_QSIZE;
    return 1;
}

三、经典实例

3.1 层次遍历

        在信息处理中有一大类问题需要逐层或逐行处理。这类问题的解决方法往往是在处理当前层 或当前行时就对下一层或下一行做预处理,把处理顺序安排好,等到当前层或当前行处理完毕,就可以处理下一层或下一行。

        使用队列是为了保存下一一步的处理顺序。下面举一个二叉树层次遍历的例子

图 3.1-1 二叉树

 图 3.1-2  二叉树遍历过程

该过程的简单描述如下:

1. 根节点入队

2. 若队空(所有节点均处理完毕),结束。否则重复3.

3. 队列中第一个结点出队,并访问之。若其有左孩子,若其有右孩子,则将右孩子入队,返回2.

3.2 队列在计算机系统中的应用

        队列在计算机系统中的应用非常广泛,以下仅从两个方面来简述队列在计算机系统中的作 用:

1. 第一个方面是解决主机与外部设备之间速度不匹配的问题,

2. 第二个方面是解决由多用户引起 的资源竞争问题。

        对于第一个方面,仅以主机和打印机之间速度不匹配的问题为例做简要说明。主机输出数据 给打印机打印,输出数据的速度比打印数据的速度要快得多,解决的方法是设置一个打印数据缓冲区,主机把要打印输出的数据依次写入这个缓冲区,写满后就暂停。打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求。主机接到请求后再向缓冲区写入打印数据。打印数据缓冲区中所存储的数据就是一个队列。

        对于第二个方面,CPU 资源的竞争就是一个典型的例子。在一个带有多终端的计算机系统上,有多个用户需要CPU各自运行自己的程序,它们分别通过各自的终端向操作系统提出占用CPU的请求。操作系统通常按照每个请求在时间上的先后 顺序,把它们排成一个队列,每次把CPU分配给队首请求的用户使用。当相应的程序运行结束或 用完规定的时间间隔后,令其出队,再把CPU分配给新的队首请 个用户的请求,又使CPU能够正常运行。

3.3 其他实例

其他的实例还包含:划分子集问题、农夫过河问题、离散事件模拟、作业调度、迷宫问题。

在这里不再列举,大家可以自行搜索。

四、小结:栈与队列

本章介绍了两种特殊的线性表:栈和队列,主要内容如下。

1 栈是限定仅在表尾进行插入或删除的线性表,又称为后进先出的线性表。栈有两种存储表示,顺序表示(顺序栈)和链式表示(链栈)。栈的主要操作是进栈和出栈,对于顺序栈的进栈 和出栈操作要注意判断栈满或栈空。

(2) 队列是一种先进先出的线性表。它只允许在表的一端进行插入,而在另一端删除元素。 队列也有两种存储表示,顺序表示(循环队列)和链式表示(链队)。队列的主要操作是进队和出 队,对于顺序的循环队列的进队和出队操作要注意判断队满或队空。凡是涉及队头或队尾指针的 修改都要将其对MAXQSIZE求模。

(3) 栈和队列是在程序设计中被广泛使用的两种数据结构,其具体的应用场景都是与其表示 方法和运算规则相互联系的。下表分别从逻辑结构、存储结构和运算规则方面对二者进行了比较。

队列

比较项目

队列

逻辑结构和线性表一样,元素之间存在一对一的关系和线性表一样,元素之间存在一对一的关系

存储结构

顺序存储:

存储空间预先分配,可能会导致空间闲置或 栈满溢出现象;数据元素个数不能自由扩充

顺序存储(常设计成循环队列形式):

存储空间预先分配,可能会导致空间闲置或 队满溢出现象;数据元素个数不能自由扩充

链式存储:

动态分配,不会出现闲置或栈满溢出现象; 数据元素个数可以自由扩充

链式存储:

动态分配,不会出现闲置或队满溢出现象; 数据元素个数可以自由扩充

运算规则

插入和删除在表的一端(栈顶)完成,后进先出

插入运算在表的一端(队尾)进行,删除运算在表的另一端(队头),先进先出

(4)栈有一个重要应用是在程序设计语言中实现递归。递归是程序设计中最为重要的方 法之一,递归程序结构清晰,形式简洁。但递归程序在执行时需要系统提供隐式的工作栈来 保存调用过程中的参数、局部变量和返回地址,因此递归程序占用内存空间较多,运行效率较低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D了一天bug忘了编译

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值