目录
一.队列的介绍
1.1队列
队列是日常生活中最常见的,例如,到食堂买饭要排队,去银行要排队,到车站买票要排队,操作系统的进程需要排队等等。和栈相似,队列也是一种操作受限制的线性结构,我们限制在一端插入(称为:入队),在另一端删除(称为:出队),先进入队的成员总是先离开队列;因此,,队列也称做先进先出的线性表。
1.2队列的基本概念
(1).队列:是一种特殊的线性表,只允许在表的一端进行插人 ,而在另一端进行删除
(2).队首:允许进行删除的一端称为队首,用队首指针front表示。
(3).队尾:允许进行插人的-端称为队尾.用队尾指针rear表示。
说明:队列中没有元素时称为空队列。在空队列中依次加人元素a1,a2,…,an,之后.a1是队首元素,an是队尾元素。人队只能在队尾插人,出队的次序也只能是a1,a2,a3,…,an,即队列的修改是依次的先进先出的原则进行的。
二.顺序队列
2.1顺序队列
顺序队列,采用顺序存储结构存储的队列简称为顺序队列,顺序队列也采用数组来实现,要设定两个整数变量才指示队头和队尾的位置,称为队头指针front 和队尾指针 rear ,顺序队列的类型定义如下:
//创建队列顺序队列的自定义类型
#define maxsize 100
typedef int Elemtype;
typedef struct Snode
{
Elemtype data[maxsize];
int front, rear; //队头指针,队尾指针
}SqQueue;
说明:
(1).设置Q是指向定义好的顺序队列的指针,初始Q->rear = Q->front =0;
(2).入队操作时因为初始队首和队尾都是0号的位置,这时,将元素插入在队尾的位置后,Q->rear再加1,所以顺序队列中的队尾指针rear指向队尾元素的下一个位置。出队操作时,需先将队列首元素取出,然后Q->front加1;
(3).Q->data[Q->front]存放队首元素的值,Q->data[Q->rear-1],存放队尾元素的值。
(4).队列空的条件是Q->rear 等于 Q->front ,队列满的条件是Q->rear等于QUEUESIZE(队列最大值)
2.2循环队列
1.假溢出现象
初始化队列Q时,Q->rear==Q- > front=0;入队时,新元素存入队尾指针所指向的单元,队尾指针再往后移动一个;出队运算时 ,取出队首所指向指针所指向单元的元素的值。队首指针再往后移动一个。 也就是说,在非空顺序队列中 .队头指针始终指向当前的队头元素,而队尾指针始终指向队尾元素的下一个单元。
随着人队和出队的不断进行,整个队列不断后移,当Q ->rear== QUEUESIZE时,尾指针无法再移动则表示队满,但却不是真正的队满,因为经过出队操作后,队列前面出现了一些空单元。由于入队只能在队尾进行,队首之前的空存储单元无法使用,我们把这种现象称为“假溢出”,即存储空间未满,可是不能再做入队运算。
解决“假溢出”问题可以有两种方法:
(1)平移元素:把元素平移到队列的首部,但这种方法效率低。
(2)将新元素插人到第一个位置 上,构成循环队列,人队 和出队仍按“先进先出”的原则进行。但这种方法操作效率、空间利用率高。
因此,为了解决“假溢出”问题,我们一般采用循环队列的方法。即把顺序队列想象成一个首尾相接环状结构,即规定最后.个单元的后继为第一一个单元,并称这种环状数组表示的队列为循环队列。采用循环队列存储的队列,当尾指针指向队尾单元时,插入一个元素后 ,尾指针加1不再是指向队列存储空间之外,而是让尾指针指向第一个存储单元是空的,可以继续插入新元素。循环队列的结构体定义与顺序队列一致。将队列的存储空间构造成首尾相接的环状结构,存储在这样结构的队列称为循环队列(Cireular Queue)。在循环队列中进行出队、人队操作时,头尾指针仍要加1.向后移动。只不过当头尾指针指向上界(QUEUESIZE 1)时,其加1操作的结果是应当指向的下界0。
为了有效地区分队列满还是空,我们有多种方法可以采取,比较典型的有如下三种:
(1)多设一个逻辑变量存放队列满还是空。
(2)多使用一个整型变量做计数器来记录队列中元素的总数(即队列长度)。若相等则认为队满(注意:rear所指的单元始终为空)。
(3)少用一个元素的空间,约定人队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空)。
为了解决这一问题我们在本教材里采用第三种方法,即规定循环队列中始终留一个空的元素空间,即尾指针rear 所指向的空间永远是没有元素的。我们称这种方法为“队尾加1追上队首即为队列满”。
2.3循环队列的基本运算
基本运算包括:队列的初始化,判队空,判队满,入队,出队等操作;
下面,附代码参考:
在这里插入代码片:
#include <iostream>
#include <stdlib.h>
using namespace std;
//创建队列顺序队列的自定义类型
#define maxsize 100
typedef int Elemtype;
typedef struct Snode
{
Elemtype data[maxsize];
int front, rear; //队头指针,队尾指针
}SqQueue;
SqQueue* InitSqQueue() //初始化队列
{
SqQueue* Q;
Q = (SqQueue*)malloc(sizeof(SqQueue)); //给队列申请空间
if (!Q)
{
return 0;
}
else
{
Q->front = Q->rear = 0; //初始队首与队尾指针都指向0
return Q;
}
}
void AssignQueue(SqQueue* Q) //顺序栈的输入
{
int i, lenghth;
cout << "请输入顺序栈的长度" << endl; //输入栈的长度
cin >> lenghth;
cout << "请根据设定的长依次输入顺序栈的元素(用空格隔开)" << endl;
for (int i = 0; i < lenghth; i++)
{
cin >> Q->data[i]; //输入栈的元素
Q->rear = (Q->rear+1)%maxsize; //栈顶不断向后移动
}
}
//循环队列的入队操作
//算法思路
//1.判断队列是否已满,若已经满了,给出溢出提示,若没有,则进行下一步
//2.将新元素赋值给队尾元素位置
//3.队尾指针加1
int EnQueue(SqQueue* Q, Elemtype x)
{
if ((Q->rear + 1) % maxsize == Q->front) //判断队列是否已满
{
cout << "队列已经满了" << endl;
return 0;
}
else
{
Q->data[Q->rear] = x; //将新的值赋值给队列的队尾
Q->rear = (Q->rear + 1) % maxsize; //循环队列的队尾始终指向队尾的下一个空间
return 1;
}
}
//循环队列的出队运算
//删除循环队列的队首元素,并通过指针变量将队首元素的值带出,即出队运算
//1.判断是否队空,对空则无元素出队,给出提示返回信息,若是非空队列的值出队,接下来下一步
//2.将队首元素的值赋值给指针变量所指的存储空间
//3.队首值加1;
int DeleQueue(SqQueue* Q, Elemtype* x)
{
if (Q->rear == Q->front) //判断是否队空,对空则无元素出队
{
return 0;
}
else
{
*x = Q->data[Q->front]; //将队首释放
Q->front = (Q->front + 1) % maxsize; //队首位置向后移动
return 1;
}
}
//顺序栈的输出显示
void OputSqlist(SqQueue* Q)
{
int i;
int sum = Q->rear;
int toal = Q->front;
cout << "顺序表长度为:" << sum - toal << endl;
cout << "依次输出顺序表的元素:" << endl;
for (i = toal; i < sum ; i++)
{
cout << Q->data[i] << ' '; //遍历顺序表,并打印在控制台上
}
}
int main()
{
SqQueue* Q = InitSqQueue(); //创建循环队列
Elemtype k, u;
if (Q == 0)
{
cout << "初始化失败" << endl;
}
else
{
cout << "初始化成功" << endl;
AssignQueue(Q);
OputSqlist(Q);
cout << endl;
cout << "请输入需要入队的值:" << endl;
cin >> k;
EnQueue(Q,k);
OputSqlist(Q);
cout << endl;
cout << "出队显示为:" << endl;
DeleQueue(Q, &u);
OputSqlist(Q);
}
return 0;
}
三.链队列
3.1.链队列介绍
队列不仅可以采用顺序的存储结构,也可以采用链表的存储结构,称为链队列;与链表类似,通常为了操作方便会比我们前面链表的程序多设置一个头节点,并设置一个队头指针和一个队尾指针,但与链表的不同之处在于,链队列的插入在队尾进行,删除操作在对头进行。
3.2链队列的基本运算
链队列的基本运算包括初始化,判空,入队,出队;
下面附代码,方便理解:
#include<iostream>
#include <stdlib.h>
using namespace std;
typedef int Elemtype;
//结点定义
typedef struct Qnode
{
Elemtype data;
struct Qnode* next;
}Qnode;
//链队列定义
typedef struct
{
Qnode* front; //设置队头指针
Qnode* rear; //设置队尾指针
int length; //计算队的长度
}QLinklist;
int InitQueue(QLinklist* Q) //队列初始话
{
Q->front = Q->rear = (Qnode*)malloc(sizeof(Qnode)); //将两个指针申请空间
if (!Q->front)
{
return 0;
}
else
{
Q->front->next = NULL; //让队头指针下一个元素空间指向空
Q->length = 0;
return 1;
}
}
int QueueEmpty(QLinklist* Q)
{
if (Q->front == Q->rear) //判断队头指针于队尾指针是否为空
{
return 1;
}
else
{
return 0;
}
}
int EnQueue(QLinklist* Q, Elemtype e)
{
Qnode* p;
p = (Qnode*)malloc(sizeof(Qnode)); //设置一个插入指针
if (!p)
{
return 0;
}
else
{
p->data = e; //取得需要插入的新值
p->next = NULL; //指针指向的值一定是从队尾插入的
Q->rear->next = p;
Q->rear = p; //赋值给队尾,这样队尾就是空的的了
Q->length++;
return 1;
}
}
int DeQueue(QLinklist* Q, Elemtype* e)
{
Qnode* p;
if (QueueEmpty(Q)) //先判断队列是否为空
{
return 0;
}
p = Q->front->next; //先让头结点指向下一个有数据的值(即首元素结点)
*e = p->data; //再将这个首元素结点用中间变量替换
Q->front->next = p->next; //最后让首元素结点的下一个指针,作为头节点的下一个首元素结点
//如果删除的结点是尾结点,将队尾结点指针指向头节点
if (p == Q->rear)
{
Q->rear = Q->front;
}
free(p);
Q->length--;
return 1;
}
void OutputQueue(QLinklist* Q)
{
Qnode* p;
int i;
p = Q->front->next; //从头节点的首元素开始打印,毕竟,先进先出
while (p != NULL)
{
cout << p->data << " ";
p = p->next;
}
}
int main()
{
QLinklist* Q = (QLinklist*)malloc(sizeof(QLinklist));
Elemtype k, e;
k = InitQueue(Q);
if (k == 0)
{
cout << "初始化失败" << endl;
}
else
{
cout << "初始化成功" << endl;
EnQueue(Q, 5); //将52022依次入队
EnQueue(Q, 2);
EnQueue(Q, 0);
EnQueue(Q, 2);
EnQueue(Q, 2);
cout << "入队显示:" << endl;
OutputQueue(Q);
cout << endl;
cout << "出队显示:" << endl;
DeQueue(Q, &e);
OutputQueue(Q);
}
return 0;
}
四.例题
4.1字符串回文的判断。
“回文”是指正读和反读均相同的字符串;例如,ababa是“回文”字符串。试设计一个算法,利用顺序栈和循环队列的基本运算,判断一个字符串是否是回文。
分析:所谓“回文”,是指正读和反读均相同的字符串。 顺序栈的特点是“先进后出”,即,出栈是逆序;而循环队列的特点是“先进先出”,即出队是顺序。所以只要先将字符串依次入栈、入队,然后依次将出栈的字符和出队的字符进行比较,如果完全一致即是回文;否则不是回文。
代码参考:
#include <iostream>
#include <stdlib.h>
using namespace std;
//创建队列顺序队列的自定义类型
#define maxsize 100
typedef int Elemtype;
typedef struct Snode
{
Elemtype data[maxsize];
int front, rear; //队头指针,队尾指针
}SqQueue;
SqQueue* InitSqQueue() //初始化队列
{
SqQueue* Q;
Q = (SqQueue*)malloc(sizeof(SqQueue)); //给队列申请空间
if (!Q)
{
return 0;
}
else
{
Q->front = Q->rear = 0; //初始队首与队尾指针都指向0
return Q;
}
}
typedef struct
{
Elemtype date[maxsize];
int top; //top栈顶元素,top实际并不是指针类型,存放的是栈顶元素的下标
}SqStack;
SqStack* InitSqStack() //顺序栈的初始化
{
SqStack* s;
s = (SqStack*)malloc(sizeof(SqStack)); //申请顺序栈的空间
if (!s)
{
return NULL; //申请失败返回0
}
else
{
s->top = -1; //申请成功设置为空栈,这样初始化成功
return s;
}
}
//顺序栈的插入
//将一个新的元素加入栈中,通常称为入栈和压栈
//算法思路:
//1.判断栈是否满,如果栈满了,给出提示,出错返回
//2.栈顶加1
//3.新元素赋值给栈顶
//4.完成返回
int Push(SqStack* s, Elemtype x)
{
if (s->top == maxsize - 1) //判断栈是否满了
{ //可以改改define的值,这样看看程序会不会出错
return 0;
}
else
{
s->top++; //栈顶加1
s->date[s->top] = x; //将新元素的值赋值新栈顶
return 1;
}
}
//顺序栈的删除(出栈)
//将栈顶的元素删除
//算法思路:
//1.判断栈是否为空,若是空栈,则没有元素出栈,给出提示出错返回
//2.将栈顶元素赋值给x指针所指向的变量
//3.栈顶指针减1
//4.完成返回
int delepop(SqStack* s, Elemtype* u)
{
if (s->top == -1)
{
cout << "此栈为空栈" << endl; //判断是否为空栈(插入的就判断是否已经满了,删除判断是否为空)
return 0;
}
else
{
*u = s->date[s->top]; //将栈的元素赋值给一个新元素指针存着,之后在栈顶减一
s->top--; //其实这个函数还是在这个数组中,但它不在这个栈中,如果你加回来,在输出,还是能看到原来的数
return 1;
}
}
//循环队列的入队操作
//算法思路
//1.判断队列是否已满,若已经满了,给出溢出提示,若没有,则进行下一步
//2.将新元素赋值给队尾元素位置
//3.队尾指针加1
int EnQueue(SqQueue* Q, Elemtype x)
{
if ((Q->rear + 1) % maxsize == Q->front) //判断队列是否已满
{
cout << "队列已经满了" << endl;
return 0;
}
else
{
Q->data[Q->rear] = x; //将新的值赋值给队列的队尾
Q->rear = (Q->rear + 1) % maxsize; //循环队列的队尾始终指向队尾的下一个空间
return 1;
}
}
//循环队列的出队运算
//删除循环队列的队首元素,并通过指针变量将队首元素的值带出,即出队运算
//1.判断是否队空,对空则无元素出队,给出提示返回信息,若是非空队列的值出队,接下来下一步
//2.将队首元素的值赋值给指针变量所指的存储空间
//3.队首值加1;
int DeleQueue(SqQueue *Q, Elemtype *x)
{
if (Q->rear == Q->front) //判断是否队空,对空则无元素出队
{
return 0;
}
else
{
*x = Q->data[Q->front]; //将队首释放
Q->front = (Q->front + 1) % maxsize; //队首位置向后移动
return 1;
}
}
int main()
{
SqQueue* Q = InitSqQueue(); //创建循环队列
SqStack* S = InitSqStack(); //创建顺序栈
Elemtype ch, x,y;
if (S == 0 || Q == 0)
{
cout << "初始化失败" << endl;
}
else
{
cout << "请依次输入字符" << endl;
ch = getchar();
while (ch != '\n')
{
Push(S, ch); //字符串入栈
EnQueue(Q, ch); //字符串入队
ch = getchar();
}
while (S->top != -1)
{
delepop(S, &x);
DeleQueue(Q, &y);
if (x != y)
{
cout << "输入的不是回文数" << endl;
break;
}
}
if (S->top == -1)
{
cout << "输入的字符串是回文数" << endl;
}
}
return 0;
}