队列的结构特点及操作
队列是限定只能在表的一端进行插入,在表的另一端进行删除的线性表。表中允许插入的一端称为队尾,允许删除的一端称为队头。总结下来就是只能在队尾插入,在队头删除。也就是在队尾进去之后从队头出去。在队尾进行插入的操作称之为入队列,在队头进行删除的操作叫做出队列。队列也有两种存储方式,分别为链队列和循环队列。
链队列
用链表表示的队列简称为链队列。一个链队列需要两个分别指向队头和队尾的指针(分别叫头指针和尾指针)。为了操作方便,为链队列添加一个头结点,并约定头指针始终指向这个头结点,尾指针指向真正的队尾元素结点。一个空的链队列只含有一个头结点并且队列的头指针和尾指针都指向这个头结点。在出队列(即删除队头数据元素时),如果此时只有一个数据结点,则直接删除会丢失尾指针。所以要提前将尾指针指向头指针。
#include<iostream>
#include<string>
using namespace std;
//定义链队列的结点
typedef struct LNode {
char data;
struct LNode *next;
}LNode;
//定义链队列
typedef struct
{
LNode *front; //定义头指针
LNode *rear; //定义尾指针
}LinkQueue;
//构建链队列 链队列的初始化
void Init_LinkQueue(LinkQueue &Q)
{
Q.front = Q.rear = new LNode; //为头指针和尾指针分配同一个新结点
Q.rear->next = NULL; //尾结点的next指针为空
}
//销毁链队列
void Destroy_LinkQueue(LinkQueue &Q)
{
while (Q.front)
{
Q.rear = Q.front; //此时尾指针不再指向队尾 而是单纯作为一个辅助指针
Q.front = Q.front->next; //头指针后移
delete Q.rear; //删去原先的第一个结点
}
}
//插入操作 队尾元素入队列
void Enter_Queue(LinkQueue &Q,char e)
{
LNode *p;
p = new LNode; //创建一个新结点
p->data = e; //将新元素的值赋给新结点
p->next = NULL;
Q.rear->next = p; //将新结点指向队尾指针
Q.rear = p; //队尾指针重新回到队尾
}
//删除操作 队头元素出队列
void Delete_LinkQueue(LinkQueue &Q, char &e)
{
LNode *p; //辅助姐弟啊指针
p = Q.front->next; //p指向第一个数据结点
e = p->data; //用e接收队头结点的数据值
Q.front->next = Q.front->next->next; //由于Q.front头结点一直指向头结点(空的) 所以应该是头结点的next指针后移
if (Q.rear == p) //说明此时只有一个数据结点 直接删除的化会使尾指针丢失
Q.rear = Q.front;
delete p;
}
//判断链队列是否为空
int Empty_LinkQueue(LinkQueue Q)
{
if (Q.front == Q.rear)
return 1;
else
return 0;
}
//访问队头元素
void GetTop_LinkQueue(LinkQueue Q, char &e)
{
e = Q.front->next->data;
}
//从队头到队尾依次输出各个数据元素
void QueueTraverse(LinkQueue Q)
{
LNode *p; //辅助结点指针
p = Q.front->next; //指向队头的第一个数据结点
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
int main()
{
LinkQueue Q;
char e;
Init_LinkQueue(Q);
string a = "LXYILOVEU!";
int i;
for (i = 0; i < a.length(); i++)
Enter_Queue(Q, a[i]);
cout << "从队头到队尾输出队内元素为:";
QueueTraverse(Q);
cout << "访问队头元素为:";
GetTop_LinkQueue(Q, e);
cout << e << endl;
cout << "删除队头元素为:";
Delete_LinkQueue(Q, e);
cout << e << endl;
cout << "删除后对内元素为:";
QueueTraverse(Q);
cout << "判断链队列是否为空的结果为:";
if (!Empty_LinkQueue(Q))
cout << "非空!" << endl;
else
cout << "队列为空!" << endl;
}
循环队列
循环队列本质上也是利用一维数组进行数据的存储,与顺序栈不同的是循环队列需要另设两个指针 front 和 rear 用以指示队头和队尾的位置。初始化建空队列时,令front = rear = 0;每当插入一个新的队尾元素后,尾指针rear增1,每当删除一个队头元素之后,头指针增1,因此,在非空队列中,头指针始终指向队头元素,而尾指针指向队尾元素的下一个位置。 对于循环队列,重要的思想就是对数组下标进行求模运算,使得下标由0,1,2一直增到最大值后能返回来继续从0开始增加。由于这种近似的循环,判断是一个空队的条件是 front==rear;为了使得满队和空队能够区分出来,必须牺牲一个数组单元,提前定义为满队状态;此时,一个满队的条件是rear下标加一取模运算结构后等于front,说明是满队即 (rear+1)%Q.queuesize;对于这种下标加一的操作,都要注意进行%Q.queuesize求模操作。
#include<iostream>
#include<string>
using namespace std;
const int QUEUE_INIT_SIZE = 100; //初始的最大容量
const int QUEUEINCREMENT = 10; //默认的增补空间量
typedef struct {
char *elem; //存储数据的数组
char front; //头指针
char rear; //尾指针(本质是数组的下标,只是起到了指针的作用)
int queuesize; //当前的队列能存储的最大容量
int incrementsize; //队列空间增不是能分配的空间
}SqQueue;
//构建队列 初始化一个队列
void INIT_SqQueue(SqQueue &Q, int maxsize = QUEUE_INIT_SIZE, int incresize = QUEUEINCREMENT)
{
Q.elem = new char[maxsize + 1]; //对数组分配内存空间 满队列的状态是要空出来一个单位
Q.front = Q.rear = 0; //初始化头指针和尾指针
Q.queuesize = maxsize; //当前队列的最大容量
Q.incrementsize = incresize; //队列扩容时一次时的增补空间量
}
//求队列长度
int Length_Queue(SqQueue Q)
{
return (Q.rear - Q.front + Q.queuesize) % Q.queuesize;
}
//删除操作 队头元素出队列
void Delete_SqQueue(SqQueue &Q, char &e)
{
e = Q.elem[Q.front]; //将队头元素赋值给e
Q.front = (Q.front + 1) % Q.queuesize; //头指针加一
}
//扩容操作
void increment_SqQueue(SqQueue &Q)
{
char *a = new char[Q.queuesize + Q.incrementsize + 1]; //构建辅助数组空间
int i;
for (i = 0; i < Q.queuesize; i++) //要把旧数组的全部复制到新数组中 要循环Q.queuesize+1次
a[i] = Q.elem[(Q.front + i) % Q.queuesize]; //将原来队列中的数据元素复制到新数组中 新数组中 头指针是从下标0开始的
delete[] Q.elem; //删除原来的数组空间
Q.elem = a; //设置新的内存空间
Q.front = 0;
Q.rear = Q.queuesize; //调整头尾指针的位置
Q.queuesize = Q.queuesize + Q.incrementsize; //调整扩容后队列的最大容量
}
//插入操作 队尾元素入队列
void Enter_SqQueue(SqQueue &Q, char e)
{
if ((Q.rear + 1) % Q.queuesize == Q.front) //容量不足 先扩容
increment_SqQueue(Q);
Q.elem[Q.rear] = e;
Q.rear = (Q.rear + 1) % Q.queuesize;
}
//判断循环队列是否为空
int Empty_SqQueue(SqQueue Q)
{
if (Q.front == Q.rear) //表示循环队列为空
return 1;
else
return 0;
}
//访问队列的队头元素
void GetTop_SqQueue(SqQueue Q, char &e)
{
e = Q.elem[Q.front];
}
//从队头到队尾依次输出队列中的数据元素
void Traverse_SqQueue(SqQueue &Q)
{
int i;
for (i = Q.front; i < Q.rear; i = (i + 1) % Q.queuesize)
cout << Q.elem[i] << " ";
cout << endl;
}
int main()
{
char e;
SqQueue Q;
INIT_SqQueue(Q);
string a = "LXYILOVEU!";
int i;
for (i = 0; i < a.length(); i++)
Enter_SqQueue(Q, a[i]);
cout << "从队头到队尾依次遍历输出数据元素为:";
Traverse_SqQueue(Q);
cout << "访问队头元素为:";
GetTop_SqQueue(Q, e);
cout << e << endl;
cout << "删除队头元素为:";
Delete_SqQueue(Q, e);
cout << e << endl;
cout << "删除后再次从队头到队尾输出为:";
Traverse_SqQueue(Q);
cout << "输出队列长度为:" << Length_Queue(Q) << endl;
cout << "判断队列是否非空的结果为:";
if (!Empty_SqQueue(Q))
cout << "非空!" << endl;
else
cout << "队列为空!" << endl;
return 0;
}
本笔记所依据的教材为严薇敏版的《数据结构及应用算法教程》
所有代码在Visual Studio 2017上均可正常运行
如有错误欢迎指出