队列基础
也是,一般写程序时,都会用到或多或少的数据结构。
队列
队列(Oueue)是一种操作受限的线性表,它只允许在表的一端进行元素插入,而在另一端进行元素删除。新插入的结点只能添加到队尾,被删除的只能是排在队头的结点,因此,队列又称为先进先出(First In First Out)的线性表,简称为FIFO表。
队列的基本运算
(1)置空队列InitQueue(Q),构造一个空队列。
(2)判队空QueueEmpty(Q),若Q为空队列,则返回TRUE,否则返回FALSE。
(3)入队列En(jueue(Q,x),若队列不满,则将数据x插入到Q的队尾。
(4)出队列DeQueue(Q),若队列不空,则删除队头元素,并返回该元素。
(5)取队头GetFront(Q),若队列不空,则返回队头元素。
顺序队
队列的顺序存储结构称为顺序队列。队列的顺序存储结构是利用一块连续的存储单元存放队列中的元素的,设置两个指针front和rear分别指示队头和队尾元素在表中的位置。
循环队列
为了解决顺序队的"假溢"问题采用循环队。约定循环队的队头指针指向队头元素在数组中实际位置的前一个位置,队尾指针指示队尾元素在数组中的实际位置。其次再约定队头指针指示的结点不用于存储队列元素,只起标志作用。这样当队尾指针"绕一圈"后赶上队头指针时视为队满。
总结:队中元素个数:
{
rear-front
(rear>=front)
QueueSize + rear-front
(rear<front)
\left\{\begin{array}{lc}\text { rear-front } & \text { (rear>=front) } \\ \text { QueueSize + rear-front } & \text { (rear<front) }\end{array}\right.
{ rear-front QueueSize + rear-front (rear>=front) (rear<front)
两种情况合并为:(QueueSize +rear-front)% QueueSize;
如下图所示:
循环队顺序存储数据结构定义:
#define QueLleSize 100
typedef char DataType; //假设数据为字符型
typedef struct {
DataType data[QueueSize];
int front,rear;
} CirQueue;
循环队列基本运算
置空队
void InitQueue(CirQueue *Q)
{
Q->front = Q->rear = 0 ;
}
判队空
int QueueEmpty(CirQueue *Q)
{
return Q->rear == Q->front;//处于初始状态的情形
}
判队满
int QueueFull(CirQueue *Q)
{
return (Q->rear + 1) % QueueSize == Q->front;
}
入队
void EnQueue(CirQueue *Q, DataType x)
{
//插入元素x为队列Q新的队尾元素
if (QueueFull(Q))
printf("Queue overflow");
else {
Q->data[Q->rear] = x;
Q->rear = (Q->rear + 1) % QueueSize; //循环意义下的加1,脑补出环状的情形,取余就成为逻辑环的技巧
}
}
取队头
DataType GetFront(CirQueue *Q)
{
//获取Q的队头元素值
if (QueueEmpty(Q)) {
printf("Queue empty");
exit(0);
} else
return Q->data[Q->front];
}
出队
DataType DeQueue(CirQueue *Q)
{
//删除Q的队头元素,并返回其值
DataType x;
if (QueueEmpty(Q)) {
printf("Queue empty");
exit(0);
} else {
x = Q->data[Q->front]; //保存待删除元素值
Q->front = (Q->front + 1)%QueueSize; //头指针加1
return x; //返回删除元素值
}
}
应用实例
设栈S=(1,2,3,4,5,6,7),其中7为栈顶元素。请写出调用函数Algo(S)后栈S的状态。其函数为:
void Algo(SeqStack S)//s为1,2,3,4,5,6,7
{
int i = 1;
CirQueue Q;
SeqStack T; //定义一个循环队列和一个栈
InitQueue(&Q);
Initstack(&T); //初始化队列和栈
while (!StackEmpty(&S)) {
if (i % 2 != 0)
Push(&T, Pop(&S)); //栈中序号为奇数的元素入T栈中,即7、5、3、1顺序入栈T。
else
EnQueue(&Q, Pop(&S));//序号为偶数的元素入队列Q中,6、4、2顺序入队Q。
i++;
}
while (!QueueEmpty(&Q))
Push(&S, DeQueue(&Q)); //队Q中6、4、2顺序出队并入栈S
while (!StackEmpty(&T))
Push(&S, Pop(&T)); //队栈T中按1、3、5、7顺序出栈并入栈S
}//栈S中的元素为从栈底往栈顶:6、4、2、1、3、5、7
链队
队列的链式存储结构称为链队列。它是一种限制在表头删除和表尾插入操作的单链表。
数据结构定义格式:
typedef struct qnode
{
DataType data ;
struct qnode *next;
} QueueNode;//结点定义
typedef struct {
QueueNode *front; //队头指针
QueueNode *rear; //队尾指针
} LinkQueue;
LinkQueue Q;
链队相关运算
构造空队列,如下图所示:
void InitQueue(LinkQueue *Q)
{
Q->front = (QueueNode *)malloc(sizeof(QueueNode)); //申请头结点
Q->rear = Q->front; //尾指针也指向头结点
Q->rear->next = NULL;
}
判断队空
int QueueEmpty(LinkQueue *Q)
{
return Q->rear==Q->front; //头尾指针相等队列为空
)
入队
void EnQueue(LinkQueue *Q,DataType x)
{
//将元素x插入链队列尾部
QueueNode *p = (QueueNode *)malloc(Sizeof(QueueNode)); //申请新结点
p->data = x ;
p->next = NULL;
Q->rear->next = p; //*p链到原队尾结点之后
Q->rear = p; //队尾指针指向新的队尾结点
}
取队头元素值
DataType GetFront(LinkQueue *Q)
{
//取链队列的队头元素值
if (QueueEmpty(Q)) { //判队空
printf("Queue underflow");
exit(0); //出错退出处理
} else
return Q->front->next->data; //返回原队头元素值
}
出队列
- 当队列的长度大于1时,则出队操作只需要修改头结点的指针域即可,尾指针不变,类似于单链表删除首结点,操作步骤:
s=Q->front->next;//s指向头结点的下一个结点
Q->fron->next=s->next;//头结指指针域指向下一个结点
x=s->data;
free(s);
return x; //释放队头结点,并返回其值
- 若列队长度等于l,则出队时不仅要修改头结点指针域,而且还需要修改尾指针。
s=Q->front->next;//除头结点外,只有一个结点,不用图,应该可以脑补出来
Q->front->next=NULL; //修队头指针
Q->rear=Q->front; //修改尾指针
x=s->data;
free(s); return x; //释放队头结点,并返回其值
合在一起的算法,将原来的队头结点当头结点使如下图所示:
DataType DeQueue(LinkQueue *Q)
{
//删除链队列的头结点,并返回头结点的元素值
QueueNode *P;
if (QueueEmpty(Q)) {//也就是说头结点不再为空了,而是变成了有效数据结点,也可以说没有头结点了
printf("Queue underflow");
exit(0); //出错退出处理
} else {
p = Q->front; //p指向头结点
Q->front = Q->front->next; //头指针指向原队头结点
free(p); //删除释放原头结点
return (Q->front->data); //返回原队头结点的数据值
}
}
循环链队
如下图所示:
数据结构定义如下:
typedef struct queuenode {
DataType data;
struct queuenode *next;
} QueueNode;
QuelaeNode *rear;
循环链队相关操作
初始化空队列
还是带个头结点
QueueNode *InitQueue(QueueNode *rear)
{
rear = (QueueNode *)malloc(sizeof(QueueNode)); //申请头结点
rear->next = rear;//指针域指向本身
return rear;
}
入队列
如图:
void EnQueue(QueueNode *rear, Datatype x)
{
QueueNode *s = (QueueNode *)malloc(sizeof(QueueNode)); //申请新结点
s->data = x;
s->next = rear->next;//还是先链循环链插入位置的后面一个结点
rear->next = s;//再将前趋结点链好
rear = s;//更新rear
}
出队列
如下图所示:
仍然是将队头结点当作头结点来用。注意与链队操作的不同,链队的出队列是直接就没有头结点,而是将队头结点当成头结点。而这里循环链队出队,是有头结点,先删除头结点,再将将要出队的值返回,再把队头当成头结点,下一次循环中删除。
DataType DelQueue(QueueNode *rear)
{
QueueNode *s, *t;
DataType x;
if (rear->next == rear) { //判队空
printf("Queue Empty");
exit(0);
} else {
s = rear->next; //s指向头结点
rear->next = s->next; //删除头结点,因为带头结点,所以先删除头结点,以后的话是将元素也当成头结点,形成循环
t = s->next; //t指向原队头结点
x = t->data; //保存原队头结点数据
free(s); //释放被删除的原头结点
return x; //返回 将要出队 结点的数据
}
}
如何转成计算机处理的后缀表达式问题
中缀表达式:9-(2+4*7)/5+3 -----------适合人的习惯
后缀表达式:9247*+5/-3+ -----------适合计算机操作的特点
后缀表达式特点:
(1)从左向右数字的顺序与中缀表达式完全相同;
(2)从左向右运算符的顺序与中缀表达式的计算顺序完全相同;
(3)后缀表达式没有括号,运算规则简单,方法见后面讲解。
转换算法
中缀表达式转换为后缀表达式的算法思想:
顺序扫描中缀算术表达式
(1)当读到数字时,直接将其送至输出队列中;
(2)当读到运算符时,将栈中所有优先级高于或等于该运算符的运算符弹出,送至输出队列中,再将当前运算符入栈;
(3)当读入左括号时,即入栈;
(4)当读到右括号时,将靠近栈顶的第一个左括号上面的运算符全部依次弹出,送至输出队列中,再删除栈中的左括号。
过程
转换步骤 | 中缀表达式读入 | 运算符栈OS | 后缀表达式PostQ |
---|---|---|---|
初始 | 9-(2+4*7)/5+3#(#作为结束标志) | # (预先压入栈方便算法) | 空 |
1 | -(2+4*7)/5+3# | # | 9 |
2 | (2+4*7)/5+3# | # - | 9 |
3 | 2+4*7)/5+3# | # -( | 9 |
4 | +4*7)/5+3# | # -( | 92 |
5 | 4*7)/5+3# | # -(+ | 92 |
6 | *7)/5+3# | # -(+ | 924 |
7 | 7)/5+3# | # -(+* | 924 |
8 | )/5+3# | # -(+* | 9247 |
9 | /5+3# | # - | 9247*+ |
10 | 5+3# | # -/ | 9247*+ |
11 | +3# | # -/ | 9247*+5 |
12 | 3# | # + | 9247*+5/- |
13 | # | # + | 9247*+5/-3 |
14 | # | . | 9247*+5/-3+ |
代码实现
int Priority(DataType op)
{
//运算符优先级别判断函数
switch (op) {
case '(':
case '#':
return 0;
case '-':
case '+':
return 1;
case '*':
case '/':
return 2;
}
return -1;
}
void CTPostExp(CirQueue *Q)
{
SeqStack S;
char c, t ;
InitStack(&S);
Push(&S, '#'); //预先将#压入栈方便算法
do {
c = getchar();
switch (c) {
case ' ':
break; //去除空格符
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9':
EnQueue(Q, C);
break;//数字1~9入队
case'(':
Push(&S, c);
break; //左括号入栈
case')':
case'#': //右括号和#
do {
t = pop(&S); //出栈
if (t != '(' && t != '#') EnQueue(Q, t); //如果是运算符则入队
} while (t != '(' && S.top != -1);
break;//直到左括号出栈或栈空循环结束
case'+':
case'-':
case'*':
case'/':
while (Priority(c) <= Priority(GetTop(S))) { //当前运算符优先级小于等于栈顶运算符优先级
t = pop(&S);
EnQueue(Q, t); //运算符出栈、入队
}
push(&S, c);
break;//当前运算符入栈
} //EndSwitch
} while (c != '#'); //以'#'号结束表达式扫描
}
后缀表达式计算过程
计算步骤 | 后缀表达式的读入 | 运算结果栈 |
---|---|---|
初始 | 9247*+5/-3+ | 空 |
1 | 247*+5/-3+ | 9 |
2 | 47*+5/-3+ | 9 2 |
3 | 7*+5/-3+ | 9 2 4 |
4 | *+5/-3+ | 9 2 4 7 |
5 | +5/-3+ | 9 2 28 |
6 | 5/-3+ | 9 30 |
7 | /-3+ | 9 30 5 |
8 | -3+ | 9 6 |
9 | 3+ | 3 |
10 | + | 3 3 |
11 | 6 |
代码实现
int CPostExp(SeqQueue Q) //后缀表达式在队列Q中
{
SeqStackl S; //S是顺序栈
char ch;
int x, y;
InitStackl(&S); //初始化栈S
while (!QueueEmpty(Q)) {
ch = DeQueue(&Q); //出队
if (ch >= '0' && ch <= '9') //是1~9
Pushl(&S, ch - '0'); //入栈
else { //是运算符
y = Popl(&S); //两个操作数出栈
x = Popl(&S);
switch (ch) { //两操作数进行计算,结果入栈
case '+':
Pushl(&S, x + y);
break;
case '-':
Pushl(&S, x - y);
break;
case '*':
Pushl(&S, x * y);
break;
case '/':
Pushl(&S, x / y);
break;
}
}
}
return GetTopl(S);//最后栈顶元素就是最终结果
}