队列(从数据结构的三要素出发)

发布于:2024-05-21 ⋅ 阅读:(103) ⋅ 点赞:(0)

存储结构

队列是只允许在一端进行插入,在另一端进行删除操作的线性表
在这里插入图片描述

物理结构

顺序队列

利用静态数组实现,并需要记录队头和队尾指针,且队列的大小不可改变。

typedef struct {
	int data;			// 数据域
	int front, rear;	// 队头队尾指针域
} SqQueue;

在这里插入图片描述
在图d队列中,仅有一个元素。这时入队出现“上溢出”,但这种溢出并不是真正的溢出,在data 数组中依然存在可以存放元素的空位置,所以是一种 “假溢出”,极大的浪费了存储空间以及大小不可扩充的问题 。

循环队列

为了解决 “假溢出” 的问题,我们引入循环队列。将顺序队列想象成一个环状的空间,将存储队列从逻辑上视为一个环,称之为循环队列。

在这里插入图片描述

定义与顺序队列一样,区别在于判满和判空的逻辑不同。解决了“假溢出”的问题,但是没有解决队列不可扩充的问题。

链式队列

利用单链表实现,需要头指针和队尾指针,解决了顺序队列中队列的大小不可扩充的问题。

在这里插入图片描述

定义结点

typedef struct LinkNode {
	int data;					// 数据域
	struct LinkNode *next;		// 指针域
} LinkNode;

定义队列

typedef struct {
	LinkNode *front, *rear;		// 定义队头队尾指针
} LinkQueue;

双端队列

双端队列是指允许两端都可以进行插入和删除操作线性表,双端队列两端的地位是平等的,为了方便理解,将左端也视为前端,右端也视为后端。

在这里插入图片描述

链式存储

typedef struct DequeNode {			// 定义双端队列结点
	int data;						// 数据域
	struct DequeNode *pre, *next;	// 前后指针域
} DequeNode;

typedef struct {					// 定义双端队列
	DequeNode *front, *rear;		// 头尾指针域
} Deque;

数据的操作

顺序队列的基本操作

初始化

void InitQueue(SqQueue &q)
{
	q.front = q.rear = 0;
}

判断队列为空

bool isEmpty(SqQueue q)
{
	if (q.front == q.rear) return true;
	return false;
}

判断队列为满

bool isFull(SqQueue q) 
{
	if (q.rear == Maxsize) return true;
	return false;
}

入队列

bool EnQueue(SqQueue &q, int &x)
{
	if (!isFull(q))
	{
		q.data[q.rear ++ ] = x;
		return true;
	}
	return false;
}

出队列

bool DeQueue(SqQueue &q, int &x)
{
	if (!isEmpty(q))
	{
		x = q.data[q.front ++ ];
		return true;
	}
	return false;
}

循环队列的基本操作

初始化

void InitQueue(SqQueue &q)
{
	q.rear = q.front = 0;
}

判空/判满/入队/出队(三种处理方式)

若用循环队列的话,判断队空的条件是Q.front==Q.rear,判断队满的条件也是Q.front==Q.rear这就会区分不出来到底是队空还是队满。

为了区分是队空还是队满的情况,有三种处理方式:

  • 牺牲一个单元来区分队空和队满,入队时少用一个队列单元,约定以“队头指针在队尾指针的下一位置作为队满的标志”。
bool isEmpty(SqQueue q)
{
	if (q.rear == q.front) return true;
	return false;
} 

bool isFull(SqQueue q)
{
	if ((q.rear + 1) % Maxsize == q.front) return true;
	return false;
}

bool EnQueue(SqQueue &q, int x)
{
	if (!isFull(q))
	{
		q.data[q.rear] = x;
		q.rear = (q.rear + 1) % Maxsize;
		return true;
	}
	return false;
}

bool DeQueue(SqQueue &q, int &x)
{
	if (!isEmpty(q))
	{
		x = q.data[q.front];
		q.front = (q.front + 1) % Maxsize;
		return true;
	}
	return false;
}
  • 类型中增设size数据成员,表示元素个数。
typedef struct {
	int data[Maxsize];			// 数据域
	int front, rear;			// 队头队尾指针域
	int size;					// 队中元素个数
} SqQueue;

void InitQueue(SqQueue &q)
{
	q.front = q.rear = 0;
	q.size = 0;
}

bool isEmpty(SqQueue q)
{
	if (q.size == Maxsize) return true;
	return false;
}

bool isFull(SqQueue q)
{
	if (q.size == 0) return true;
	return false;
}

bool EnQueue(SqQueue &q, int x)
{
	if (!isFull(q))
	{
		q.data[q.rear] = x;
		q.rear = (q.rear + 1) % Maxsize;
		q.size ++ ;
		return true;
	}
	return false;
}

bool DeQueue(SqQueue &q, int &x)
{
	if (!isFull(q))
	{
		x = q.data[q.front];
		q.front = (q.front + 1) % Maxsize;
		q.size -- ;
		return true;
	}
	return false;
}
  • 类型中增设 tag 数据成员,以区分是队满还是队空。删除成功置 tag=0,插入成功置 tag=1只有删除操作才能使得队空,只有插入操作才能使得队满。
typedef struct {
	int data[Maxsize];			// 数据域
	int front, rear;			// 队头队尾指针
	int tag;					// 标志域
} SqQueue;

void InitQueue(SqQueue &q)
{
	q.front = q.rear = 0;
	q.tag = 0;
}

bool isEmpty(SqQueue q)
{
	if (q.rear == q.front && tag == 0) return true;
	return false;
}

bool isFull(SqQueue q)
{
	if (q.rear == q.front && tag == 1) return true;
	return false;
}

bool EnQueue(SqQueue &q, int x)
{
	if (!isFull(q, x))
	{
		q.data[q.rear] = x;
		q.rear = (q.rear + 1) % Maxsize;
		q.tag = 1;
		return true;
	}
	return false;
}

bool DeQueue(SqQueue &q, int &x)
{
	if (!isEmpty(q))
	{
		x = q.data[q.front];
		q.front = (q.front + 1) % Maxsize;
		q.tag = 0;
		return true;
	}
	return false;
}

链式队列的基本操作

初始化

void InitQueue(LinkQueue &q)
{
	q.rear = q.front = (LinkNode *) malloc (sizeof(LinkNode));
	q.front->next = NULL;
}

判空

bool isEmpty(LinkQueue q)
{
	if (q.front == q.rear) return true;
	return false;
}

入队

bool EnQueue(LinkQueue &q, int x)
{
	LinkNode *t = (LinkNode *) malloc (sizeof(LinkNode));
	t->data = x;
	t->next = NULL;
	q.rear->next = t;
	q.rear = t;
	return true;
}

出队

bool DeQueue(LinkQueue &q, int &x)
{
	if (!isEmpty(q))
	{	
		LinkNode *t = q.front->next;
		x = t->data;
		q.front->next = t->next;
		if (q.rear == t) 				// 若只有一个结点,删除后使其变为空队列
			q.rear = q.front;		
		free(t);
		return true;
	}
	return false;
}

双端队列的基本操作

初始化

void InitQueue(Deque &q)
{
	q.front = q.rear = NULL;
}

判空

bool isEmpty(Deque q)
{
	if (q.front == NULL && q.rear == NULL) return true;
    return false;
}

队头插入

bool InsertFront(Deque &q, int x)
{
	DequeNode *newNode = (DequeNode *) malloc(sizeof(DequeNode));
	newNode->data = x;
    newNode->pre = NULL;
    newNode->next = q->front;

	if (isEmpty(q)) 
        q->rear = newNode;
    else
        q->front->pre = newNode;
  
    q->front = newNode;
    return true;
}

队尾插入

bool InsertRear(Deque &q, int x)
{
	DequeNode *newNode = (DequeNode *) malloc(sizeof(DequeNode));
	newNode->data = x;
    newNode->next = NULL;
    newNode->pre = q->rear;
	
	if (isEmpty(q)) 
        q->front = newNode;
    else 
        q->rear->next = newNode;
   
    q->rear = newNode;
    return true;
}

队头删除

bool DeleteFront(Deque &q, int &x)
{
	if (isEmpty(q)) return false;
	DequeNode *temp = q->front;
    x = temp->data;
    q->front = q->front->next;

	if (q->front)				// 如果队列不为空,更新新的队首节点的 prev 指针
        q->front->prev = NULL;
    else						// 如果删除后队列为空,更新队尾指针
        q->rear = NULL;
   
    free(temp);
    return true;
}

队尾删除

bool DeleteRear(Deque &q, int &x)
{
	if (isEmpty(q)) return false;
    DequeNode *temp = q->rear;
    x = temp->data;
    q->rear = q->rear->prev;

    if (q->rear)       				// 如果队列不为空,更新新的队尾节点的 next 指针
        q->rear->next = NULL;
    else 							// 如果删除后队列为空,更新队首指针
        q->front = NULL;
   
    free(temp);
    return true;
}

数据结构的应用

队列在层次遍历中的应用

队列适用于逐层或逐行处理,使用队列是为了保存下一步的处理顺序。下面用二叉树(见图 3.17)层次遍历的例子,说明队列的应用。
在这里插入图片描述
该过程的简单描述如下:
① 根结点入队,
② 若队空(所有结点都已处理完毕),则结束遍历;否则重复③操作。
③队列中第一个结点出队,并访问之。若其有左孩子,则将左孩子入队;若其有右孩子,则将右孩子入队,返回②。

void InverLevel(BiTree T)
{
	SqQueue q, InitQueue(q);
	BiTNode *p = T;				// 遍历指针
	EnQueue(q, p);

	while (!isEmpty(q))
	{	
		DeQueue(q, p);
		visit(p);

		if (p->lchild) Enqueue(q, p->lchild);
		if (p->rchild) Enqueue(q, p->rchild);
	}
}

队列在计算机系统中的应用(OS)

缓冲区的逻辑结构:

主机输出数据给打印机打印,输出数据的速度比打印数据的速度要快得多,因为速度不匹配,若直接把输出的数据送给打印机打印,则显然是不行的。解决的方法是设置一个打印数据缓冲区,主机把要打印输出的数据依次写入这个缓冲区,写满后就暂停输出,转去做其他的事情。打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求。主机接到请求后再向缓冲区写入打印数据。这样做既保证了打印数据的正确,又使主机提高了效率。由此可见,打印数据缓冲区中所存储的数据就是一个队列。

队列出队入队操作的应用:

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


网站公告

今日签到

点亮在社区的每一天
去签到