队列
今天我们一起学习队列。
今天 一起来学习印度老哥Harsha Suryanarayana的数据结构,并对学习过程中有问题的点进行描述。
原创点:第1节,第2节,第3节涉及的代码均由笔者亲自“操刀”,第4节的代码引用Harsha的代码。
1. 队列的介绍
定义:
和栈大差不差,但也具有一定的限制——插入元素只能从队尾进行,而删除从另一端进行,即队头。
操作:
(1)Push/EnQueue:从队尾插入一个元素。
(2)PoP/DeQueue :从队头删除一个元素。
(3)Peek/front:简单地查看(返回)队列的头部元素。
(4)IsEmpty:判断队列是否为空。
(5)IsFull:如果队列有大小,检查队列是否已满。
以上操作的时间复杂度都 为O(1)。
2. 用数组实现队列
队列也有两种方法进行实现:数组和链;
我们首先拿数组实现队列;
队列的定义和初始化:
#define MAXSIZE 100
struct Queue
{
int data[MAXSIZE];
int head;
int rear;
};
struct Queue* q;
void Init()
{
q->head = q->rear = 0;
}
代码解释:
首先是 struct Queue 的解释:我为什么要定义 head 和 rear 这两个整型变量,data用来存储数据,head 用来确定队头,rear 用来确定队尾;
其次是 Init 函数,对其 head 和 rear 进行初始化,确保目前这个队列为空。
IsEmpty函数:
int IsEmpty()
{
if(q->head == q->rear)
return 1;
return 0;
}
IsFull函数:
int IsFull()
{
if(q->rear == MAXSIZE && q->rear != q->head)
return 1;
return 0;
}
两个代码,我一起解释:
首先明确这两个函数的return值
如果函数return 1 那么我们认为这个队列是空(满);相反如果函数 return 0,那么我们认为这个队列是有元素的;
其次是我对于这两个函数的判断:
如果q->head 等于 q->rear,即头尾指向同一个元素,那么这个队列是空的;如果 q->rear 处于数组的最后且 q->head 不等于 q->rear,那么我们认为这个数组是满的。
EnQueue函数:
void EnQueue(int x)
{
if(q->rear == MAXSIZE)
{
printf("队列已满");
return;
}
q->data[q->rear] = x;
q->rear++;
}
代码解释:
首先判断队列是否已满,满了就跳出去;
没有满,就在这个数组的最后一位进行插入,并让rear往后挪一位。
DeQueue函数:
void DeQueue(int* x)
{
if(q->rear == q->head)
{
printf("队列已空");
return;
}
* x = q->data[q->head];
q->data[q->head] = NULL;
q->head++;
}
代码解释:
首先判断队列是否为空,空队列就跳出去;
不是空队列,就在这个数组的第一位一位进行删除,并让head往后挪一位。
完整代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAXSIZE 100
struct Queue
{
int data[MAXSIZE];
int head;
int rear;
};
struct Queue* q;
void Init()
{
struct Queue* temp = (struct Queue*)malloc(sizeof(struct Queue));
q = temp;
free(temp);
q->head = 0;
q->rear = 0;
}
int IsEmpty()
{
if (q->head == q->rear)
return 1;
return 0;
}
int IsFull()
{
if (q->rear == MAXSIZE && q->rear != q->head)
return 1;
return 0;
}
void EnQueue(int x)
{
if (q->rear == MAXSIZE)
{
printf("队列已满");
return;
}
q->data[q->rear] = x;
q->rear++;
}
void DeQueue()
{
int * x = (int*)malloc(sizeof(int));
if (q->rear == q->head)
{
printf("队列已空");
return;
}
*x = q->data[q->head];
q->data[q->head] = NULL;
q->head++;
free(x);
}
int main()
{
int i, a;
Init();
a = IsEmpty();
if (a == 1)
printf_s("队列为空\n");
else
printf_s("队列可插入");
EnQueue(2);
EnQueue(4);
EnQueue(6);
EnQueue(8);
for(i=q->head ;i<q->rear;i++)
printf_s("%d ", q->data[i]);
printf_s("\n");
DeQueue();
for (i = q->head; i < q->rear; i++)
printf_s("%d ", q->data[i]);
}
3.循环队列
可能很多人意识到了上面这个队列有逻辑上的小瑕疵:那就是会有假溢出现象——指数组空间其实未用完,但是我们的 head 和 rear 导致不能在进行输入元素;
下面我说一下解决办法:
求模运算(%):求a/b的余数。
新EnQueue函数:
void EnQueue(int x)
{
if (q->rear == MAXSIZE)
{
printf("队列已满");
return;
}
q->data[q->rear] = x;
q->rear = (q->rear+1)%MAXSIZE;
}
新DeQueue函数:
void DeQueue()
{
int * x = (int*)malloc(sizeof(int));
if (q->rear == q->head)
{
printf("队列已空");
return;
}
*x = q->data[q->head];
q->data[q->head] = NULL;
q->head = (q->head+1)%MAXSIZE;
free(x);
}
4. 用链表实现队列
学习完数组实现队列,接下来学习链表实现队列;
队列的定义和初始化:
struct Node
{
int data;
struct Node* next;
};
struct Node* front = NULL;
struct Node* rear = NULL;
Enqueue函数:
void Enqueue(int x)
{
struct Node*temp = (struct Node*)malloc(sizeof(struct Node*));
temp->data = x;
temp->next = NULL;
if(front == NULL && rear == NULL)
{
front = rear =temp;
return;
}
rear->next =temp;
rear = temp;
}
代码解释:
首先在堆为 temp 开辟内存,为 temp->data 赋值,让 temp->next 指向NULL;
接下来进行判断,如果队列是空队列,那么让 front 和 rear 都指向 temp;
如果队列不为空,那么让 rear->next 指向 temp 指向,这样就把前一个元素和现在这个元素连接上了;
再让 rear = temp ,这样就能为下一次连接做准备。
Dequeue函数()
void Dequeue
{
struct Node* temp = front;
if (front == NULL)
return;
else if (front == rear)
front = rear = NULL;
else
{
front = front->next;
}
free(temp);
}
代码解释:
这个就很好理解,头删除,我只需要删除队列的第一个元素就ok,然后再让 front 指向下一个,最后释放 temp 指针。
Peek函数:
void Peek()
{
if (front == rear)
printf_s("空队列");
printf_s("%d\n",front->data);
}
Peek函数很容易理解,这里我就不多赘述了。
-
最后以上内容就是这么多,希望读者多加练习,会很容易了解数据结构的。
Harsha Suryanarayana的熟肉
同时也感谢up主fengmuzi2003的翻译。 -
后续秋秋文章发布的频率不会这么快了,完结数据结构之前,秋秋可能一周发布一到两篇,其中数据结构内容包括:链,栈,队列,二叉树,图,看情况发布一些额外的排序和查找功能。