栈和队列

二、栈和队列

栈的应用:
数制转换、括号匹配、行编辑程序、迷宫求解、表达式求值、八皇后问题、函数调用、递归调用的实现

队列的应用:
1、脱机打印输出

2、多用户系统用户排队分时循环使用CPU和主存

3、按用户优先级排队,每个优先级一个队列

4、实时控制系统,信号按接受顺序依次处理

5、网络电文传输,按到达的时间先后顺序依次进行

2.1 栈

1.定义及特点

限定只能在表的一端进行插入和删除运算的线性表(只能在栈顶操作),后进先出(Last In First Out),简称LIFO

表尾叫栈顶 Top
表头叫栈底 Base 或 Bottom

存储结构:顺序栈或链栈,以顺序栈更为常见

栈通常用 S 表示(stack)
插入元素到栈顶,称为入栈或压栈 PUSH(x)
从栈顶删除最后一个元素,称为出栈或弹栈 POP(x)
在这里插入图片描述

2.1.1 顺序栈的表示与操作实现

首先设top指针指向栈顶元素之上的下标地址

另设base指针,指向栈底元素的位置

另外可以用stacksize表示栈可以使用的最大容量

顺序栈的操作:
base == top是栈空的表现

栈满了top - base == stacksize

栈满还加元素的处理方法:
1.报错,返回操作系统(√)
2.分配更大空间(麻烦)

上溢(overflow):栈满还要加元素
下溢(underflow):栈空了还要弹出元素

注:上溢是一种错误,使问题的处理无法进行,但是下溢一般认为是一种结束条件,即问题处理结束

#define MAXSIZE 100
#define OVERFLOW -2
#define OK 1
#define ERROR 0
typedef struct {
    SElemType *base;//栈底指针
    SElemType *top; //栈顶指针
    int stacksize;  //栈可用的最大容量
} SqStack;

算法1:顺序栈的初始化
Status InitStack(SqStack &S) {	//构造空栈 
    S.base = new SElemType[MAXSIZE];//栈底指针指向一片连续的空间
    //或S.base = (SElemType*)malloc(MAXSIZE*sizeof(SElemType));
    if (!S.base) exit (OVERFLOW);//空间分配失败
    //注:exit函数的头文件在#include <stdlib.h>中
    S.top = S.base;//栈顶指针等于栈底指针(指向栈底)
    S.stacksize = MAXSIZE;//stacksize是栈的最大容量
    return OK;
}

算法2:判断顺序栈是否为空
Status StackEmpty(SqStack S) {
    //若栈为空,返回TRUE;否则返回 FALSE 
	if (S.top == S.base) return TRUE;//top指针和base重合
	else return FALSE;
} 
 
算法3:求栈的长度
int StackLength(SqStack S) {
    return S.top - S.base;
}
 
算法4:清空顺序栈
Status ClearStack(SqStack &S) {//不一定要把内存置为空,只要把它当成空就行
    if (S.base) //前提是栈要存在
    S.top = S.base;//逻辑清空
    return OK;
}
 
算法5:销毁顺序栈
Status DestroyStack( SqStack &S) {
    if (S.base) {
        delete S.base; 
//delete释放了内存空间,但是没有销毁指针,base指针成了野指针,所以还需要指向空
		S.stacksize = 0; 
		S.base = S.top = NULL;
    }
	return OK. 
}

算法6:顺序栈的入栈
Status Push(SqStack &S, SElemType e) {
    if (S.top - S.base == S.stacksize) return ERROR;//栈满了加不了了
    *S.top = e;
    ++S.top; 
    return OK;
}

算法7:顺序栈的出栈
Status Pop(SqStack &S, SElemType &e) {
    //若栈非空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
    if (S.top == S.base) return ERROR;//等价于if (StackEmpty(S))
    --S.top;
    e = *S.top;
    return OK;
}

2.1.2 链栈的表示与操作实现

链栈是单链表,只能在链表头部操作

1、链表的头指针即是栈顶
2、不需要头结点
3、基本不存在栈满情况,内存不受限
4、空栈相当于头指针指向空
5、插入和删除仅在栈顶处执行

链栈的结构体类型:
typedef struct StackNode {
    SElemType data;
    struct StackNode *next;
} StackNode, *LinkStack;
LinkStack S;//初始化头指针
 
算法1:链栈的初始化
Status InitStack(LinkStack &S) {
    S = NULL; //构造一个空栈,栈顶指针置为空
    return OK;
}
 
算法2:判断链栈是否为空
Status StackEmpty(LinkStack S) {
    if (S == NULL) return TRUE;
	else return FALSE;
}
 
算法3:链栈的入栈
Status Push(LinkStack &S, SElemType e) {
    p = new StackNode;//生成新结点p 
	p->data = e;//将新结点数据域置为e 
	p->next = S;//将新结点插入栈顶 
	S = p;//修改栈顶指针 
	return OK;
}
 
//链栈的出栈
Status Pop(LinkStack &S, SElemType &e) {
    if (S == NULL) return ERROR;//空栈,无法再出栈 
	e = S->data; 
    LinkStack p = S;
	S = S->next; 
	delete p;//不需要将p置为空来避免野指针,因为p是局部变量,推出该函数时就销毁
	return OK;
} 
 
//取栈顶元素
SElemType GetTop(LinkStack S){
    if (S != NULL) return S->data;
    else return ERROR;
}

2.2 队列

1.定义及特点

只能在表尾进行插入(队尾),在表头进行删除运算(队头)的线性表,先进先出(First In First Out),简称FIFO.
只能在队头和队尾运算(头删尾插)。

队列通常用Q表示(queue)
存储结构:顺序队或链队,以循环顺序队列更常见

2.2.1 顺序队
//队列顺序表结构类型(循环队列)
#define MAXSIZE 100	//最大队列长度
typedef struct {
    QElemType *base;//初始化的动态分配存储空间,数组基地址
    int front;	//头指针(队头的下标,并非真正的指针)
    int rear;	//尾指针(队尾的下标,~)
} SqQueue;
 
//由于每一次出队都会使头指针后移,但是内存中的数据不会像我们排队一样前面空了自动填空,就会出现假溢出的现象,一种解决方法就是当rear指向maxqsize的时候,又可以从表头使用空着的空间,当front为maxqsize也是如此,这里充分利用mol运算,就可以让越界的指针回到前面,好像把maxqsize位置接在0处,和0位置重合
 
front == rear既表示队空又表示队满,解决方法有两种:
1.另外设一个标志,用来分别标记队空和队满
2.另设一个变量标记元素个数
3.少用一个元素空间(最常用)	(rear+1) % MAXQSIZE == front 队满
 
1、队列初始化
Status InitQueue(SqQueue &Q) {
    Q.base = new QElemType[MAXQSIZE];//分配数组空间
    if (!Q.base) exit(OVERFLOW);	//存储分配失败
    Q.front = Q.rear = 0;	//头指针尾指针置为0,队列为空
    return OK;
}
 
2、求队列长度
int QueueLength(SqQueue Q) {
    return (MAXQSIZE + Q.rear - Q.front) % MAXQSIZE;	
    //相当于把rear接到MAXQSIZE位置后面继续计算,这时候把环拆开了
    //用于“少用一个元素空间”的情况
}
 
3、循环队列入队
Status EnQueue(SqQueue &Q, QElemType e) {
    if ((Q.rear+1) % MAXQSIZE == Q.front) return ERROR;//队满 
	Q.base[Q.rear] = e; //新元素加入队尾
	Q.rear = (Q.rear+1) % MAXQSIZE; //队尾指针+1 
	return OK;
}
 
4、循环队列出队
Status DeQueue(SqQueue &Q, QElemType &e) {
    if (Q.front == Q.rear) return ERROR;//队空 
	e = Q.base[Q.front]; //保存队头元素 
	Q.front = (Q.front+1) % MAXQSIZE;//队头指针+1 
	return OK; 
}
 
5、取队头指针
SElemType GetHead(SqQueue Q) {
    if (Q.front != Q.rear)//队列不为空 
		return Q.base[Q.front];//返回队头指针元素的值,队头指针不变 
   		else return ERROR;
} 

补充:利用方法1(另外设一个标志,用来分别标记队空和队满)来实现入队/出队的算法:

typedef struct {
	Elemtype *base;
	int front,rear,tag;
}SqQueue;

//构造一个空队列 
Status InitQueue(SqQueue &Q){
	Q.base = new QElemtype[M];
	if (!Q.base) exit (OVERFLOW);//存储分配失败 
	Q.front = Q.rear = 0;//头尾指针置为0,队列为空 
	Q.tag = 0;//标志初始化为0,队列为空 
	return OK;
}

//入队
Status EnQueue(SqQueue &Q, QElemtype e){
	if ((Q.tag == 1) && (Q.front == Q.rear)) return ERROR; 
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % M;
	Q.tag = 1;//标志改1,表示队列非空 
	return OK;
} 

//出队
Status DeQueue(SqQueue &Q, QElemtype &e){
	if ((Q.tag == 0) && (Q.front == Q.rear)) return ERROR;
	e = Q.base[Q.front];
	Q.front = (Q.front + 1) % M;
	Q.tag = 0;//标志改0,表示队列非满 
	return OK;
} 
2.2.1 链队
//队列结点类型
typedef struct Qnode {	
    QElemType data;
    struct Qnode *next;
} QNode, *QueuePtr;
 
//链队列类型,里面包含了头尾指针
typedef struct { 
    QueuePtr front; //队头指针
    QueuePtr rear;  //队尾指针
} LinkQueue;
 
1、链队列的初始化(构造一个空队列)
Status InitQueue(LinkQueue &Q) {
    Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode)); 
	if (!Q.front) exit(OVERFLOW); 
	Q.front->next = NULL; 
	return OK;
}
 
2、链队列的销毁
Status DestroyQueue(LinkQueue &Q) {
    while(Q.front) {//表头非空表示还有元素
        //p=Q.front->next; free(Q.front); Q.front=p;
        Q.rear = Q.front->next; 
        free (Q.front); 
        Q.front = Q.rear;
    } //用p保存下一结点地址,然后销毁表头结点,表头结点指向p
    //反正rear指针没用用rear代替p刚刚好
	return OK;
} 
 
3、入队
Status EnQueue(LinkQueue &Q, QElemType e) {
	QueuePtr p=(QueuePtr)malloc(sizeof(QNode)); 
	if(!p) exit(OVERFLOW); 
	p->data = e; p->next = NULL; 
	Q.rear->next = p; //入队
	Q.rear = p; //队尾元素更改,尾指针修改为指向队尾元素
	return OK;     
} 
 
4、出队
Status DeQueue(LinkQueue &Q, QElemType &e) {
    if (Q.front == Q.rear) return ERROR;//队列为空,无法再出队 
	QueuePtr p = Q.front->next; 
	e = p->data; 
	Q.front->next = p->next; 
	if(Q.rear == p) Q.rear = Q.front;
    //如果尾指针指向的就是头结点的下一节点,就是说所有元素删完了,要把头尾指针都指向头指针(此时p->next为NULL)
	delete p;
	return OK; 
}
 
5、求链队列中的队头元素
Status GetHead(LinkQueue Q, QElemType &e) {
    if (Q.front == Q.rear) return ERROR;//若队空,则无队头元素
	e = Q.front->next->data;
    return OK;
}
案例引入

1、进制转换
把十进制159转换为八进制,倒序取余数可以用栈来进行
在这里插入图片描述

2、括号匹配
假设有两种括号()[]

它们的嵌套顺序任意,即是:

1.() 或者[([][])] 是正确格式;

2.[(]) 为错错误格式;

3.([()) 或 (()]) 是错误格式。

利用栈的特点将左括号和右括号进行匹配,左括号入栈,右括号进行匹配,匹配则出栈,不匹配break,一下是几种不匹配的情况:

当遇到某一个右括号时,栈已空,说明目前为止,右括号多于左括号

从栈弹出的左括号与当前检验的右括号类型不同,说明出现了括号交叉

算术表达式输入完毕,但栈中还没有匹配的左括号,说明左括号多于右括号

3、表达式求值
表达式的组成:

操作数(operand):常数、变量

运算符(operator):算术运算符、关系运算符和逻辑运算符

界限符(delimiter):左右括号和表达式结束符,如‘#’

例如:#3*(7-2)#

为了求解这样一个表达式就要两个栈一个是算符栈OPTR,一个是操作数栈OPND。前者存放运算符,另一个存运算结果。

从左到右扫描表达式,如果遇到运算数就放入OPND,如果是运算符,如果该运算符比OPTR的栈顶优先级高,则入栈OPTR,继续向后处理;否则从OPND中弹出两个运算数,从OPTR中弹出栈顶运算符进行运算,将结果压入OPND。如此向后处理,直到碰到结束符。

4、舞伴问题
假设在舞会上,男士和女士各自排成一队。舞会开始时, 依次从男队和女队的队头各出一人配成伴如果两队 初始人数不相同,则较长的那一队中未配对者等待下一 轮舞曲。现要求写一算法模拟上述舞伴配对问题。

这题显然用队列解,分别构造男女两个队列来解决问题

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值