###栈和队列区别于线性表的特点###
栈和队列是只限定插入和删除操作只在端点
栈只在队尾插入队尾删除,队列只在队尾插入对头删除
栈——后进先出
-
数制转换
-
括号匹配
-
行编辑程序
-
迷宫求解
-
表达式求值
-
八皇后问题
-
函数调用
-
递归调用的实现
队列——前进先出
-
脱机打印输出
-
多用户系统用户排队分时循环使用CPU和主存
-
按用户优先级排队,每个优先级一个队列
-
实时控制系统,信号按接受顺序依次处理
-
网络电文传输,按到达的时间先后顺序依次进行
###栈的定义特点###
后进先出(Last In First Out)的线性表,简称LIFO
表尾叫栈顶Top
表头叫栈底Base或Bottom
栈通常用S表示,stack
插入元素到栈顶,称为入栈或压栈 PUSH(x)
从栈顶删除最后一个元素,称为出栈或弹栈 POP(x)
###队列的定义特点###
先进先出(First In First Out)的线性表,简称FIFO
通常用Q表示,queue
队头,队尾,头删尾插
入队,出队
###案例引入###
进制转换
把十进制159转换为八进制,倒序取余可以用栈来进行
括号匹配
假设有两种括号()[]
它们的嵌套顺序任意,即是:
1.([]()) 或者[([][])] 是正确格式;
2.[(]) 为错错误格式;
3.([()) 或 (()]) 是错误格式。
利用栈的特点将左括号和右括号进行匹配,左括号入栈,右括号进行匹配,匹配则出栈,不匹配break,一下是几种不匹配的情况:
-
当遇到某一个右括号时,栈已空,说明目前为止,右括号多于左括号
-
从栈弹出的左括号与当前检验的右括号类型不同,说明出现了括号交叉
-
算术表达式输入完毕,但栈中还没有匹配的左括号,说明左括号多于右括号
表达式求值
表达式的组成:
-
操作数(operand):常数、变量
-
运算符(operator):算术运算符、关系运算符和逻辑运算符
-
界限符(delimiter):左右括号和表达式结束符,如‘#’
例如:#3*(7-2)#
为了求解这样一个表达式就要两个栈一个是算符栈OPTR,一个是操作数栈OPND。前者存放运算符,另一个存运算结果。
从左到右扫描表达式,如果遇到运算数就放入OPND,如果是运算符,如果该运算符比OPTR的栈顶优先级高,则入栈OPTR,继续向后处理;否则从OPND中弹出两个运算数,从OPTR中弹出栈顶运算符进行运算,将结果压入OPND。如此向后处理,直到碰到结束符。
舞伴问题
假设在舞会上,男士和女士各自排成一队。舞会开始时, 依次从男队和女队的队头各出一人配成伴如果两队 初始人数不相同,则较长的那一队中未配对者等待下一 轮舞曲。现要求写一算法模拟上述舞伴配对问题。
这题显然用队列解,分别构造男女两个队列来解决问题
###栈的表示和操作实现###
栈也是一种线性表,也有顺序表和链表两种方式
首先设top指针指向栈顶元素,但通常为了方便操作,top真正指向栈顶元素之上的下标地址
另设base指针,指向栈底元素的位置
另外可以用stacksize表示栈可以使用的最大容量
顺序栈的操作
base==top是栈空的表现
栈满了top-base==stacksize
栈满还加元素的处理方法:1.报错返回操作系统(√) 2.分配更大空间(麻烦)
上溢(overflow):栈满还要加元素
下溢(underflow):栈空了还要弹出元素
注:上溢是一种错误,是问题无法处理,但是下溢一般认为是一种结束条件,即问题处理结束
//顺序栈的表示
#define MAXSIZE 100
typedef strct {
SElemType *base;//栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用的最大容量
} SqStack;
//顺序栈的初始化
Status InitStack(SqStack &S) { //构造空栈
S.base=new SElemType[MAXSIZE];
//S.base =(SElemType*)malloc(MAXSIZE*sizeof(SElemType));
if (!S.base) exit (OVERFLOW);//空间分配失败
S.top=S.base;//栈顶指针等于栈底指针
S.stacksize=MAXSIZE;//stacksize是栈的最大容量
return OK;
}
//判断顺序栈是否为空
Status StackEmpty(SqStack S) {
//若栈为空,返回TRUE;否则返回 FALSE
if (S.top==S.base) return TRUE;//top指针和base重合
else return FALSE;
}
//求栈的长度
int StackLength(SqStack S) {
return S.top-S.base;
}
//清空顺序栈
Status ClearStack(SqStack &S) {//不一定要把内存置为空,只要把它当成空就行
if (S.base) S.top=S.base;//前提是栈要存在
return OK;
}
//销毁顺序栈
Status DestroyStack( SqStack &S) {
if (S.base) {
delete S.base;
//delete释放了内存空间,但是没有销毁指针,base指针成了野指针,所以还需要指向空
S.stacksize=0;
S.base=S.top=NULL;
}
return OK.
}
//顺序栈的入栈
Status Push(SqStack &S, SElemType e) {
if (S.top-S.base==S.stacksize) return ERROR;//栈满了加不了了
*S.top++=e;//这个写法很简洁!
return OK;
}
//顺序栈的出栈
Status Pop(SqStack &S, SElemType &e) {
//若栈非空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
if (S.top==S.base) return ERROR;//等价于if (StackEmpty(S))
e=*--S.top;//先指针下移然后取值
return OK;
}
链栈的表示
链栈是单链表,只能在链表头部操作
-
链表的头指针即是栈顶
-
不需要头结点
-
基本不存在栈满情况,内存不受限
-
空战相当于头指针指向空
-
插入和删除仅在栈顶处执行
//链栈的结构体类型
typedef struct StackNode {
SElemType data;
struct StackNode *next;
} StackNode, *LinkStack;
LinkStack S;//初始化头指针
//链栈的初始化
void InitStack(LinkStack &S) {
//构造一个空栈,栈顶指针置为空
S=NULL;
return OK;
}
//判断链栈是否为空
Status StackEmpty(LinkStack S) {
if(S==nUll) return TRUE;
else return FALSE;
}
//链栈的入栈
Status Push(Link Stack &S, SElemType e) {
p= new StackNode;//生成新结点p
if (!p) return ERROR;//分配空间失败返回错误,这一步一般可以省去
p->data=e;//将新结点数据域置为e
p->next=S;//将新结点插入栈顶
S=p;//修改栈顶指针
return oK;
}
//链栈的出栈
Status Pop(Link Stack &S, SElem Type &e) {
if(S==NULL)return ERROR;//空栈了删不了了
e=S->data;
LinkStack p=S;
S=S->next;
delete p;//不需要将p置为空来避免野指针,因为p是局部变量,推出该函数时就销毁
return OK;
}
//取栈顶元素
SElemType GetTop(Link Stack S){
if (!S) return S->data;
else return ERROR;
}
###栈与递归###
递归是一个对象部分包含它自己,或用他自己给自己定义,则称这个对象递归
一个过程直接或者间接调用自己,称这个过程递归,例如:求阶乘函数
三种情况常用到递归方法:
-
递归定义数学函数
-
具有递归特性的数据结构:二叉树、广义表
-
可递归求解的问题:迷宫问题、汉诺塔的问题
递归实际是一种分治思想,分而治之,它需要三个条件:
-
能将问题转化成新问题
-
新问题更简单
-
要有一个出口可以直接明确解决,称为递归边界
//分治法求解递归问题算法的一般形式
void p(/*参数表*/) {
if (/*递归结束条件*/) //可直接求解步骤;——基本项
else p(/*较小的参数*/);//——归纳项
}
###队列的表示和操作实现###
队列的顺序表示 用一维数组base[MAXSIZE]表示
//队列顺序表结构类型(循环队列)
#define MAXSIZE 100 //最大队列长度
typedef struct {
QElemType *base;//初始化的动态分配存储空间,数组基地址
int front; //头指针
int rear; //尾指针
} SqQueue;
//由于每一次出队都会使头指针后移,但是内存中的数据不会像我们排队一样前面空了自动填空,就会出现假溢出的现象,一种解决方法就是当rear指向maxqsize的时候,又可以从表头使用空着的空间,当front为maxqsize也是如此,这里充分利用mod运算,就可以让越界的指针回到前面,好像把maxqsize位置接在0处,和0位置重合
//front==rear既表示队空又表示堆满。解决方法有两种:
//1.另外设一个标志,用来分别标记队空和队满
//2.另设一个变量标记元素个数
//3.少用一个元素空间(√) (rear+1)%MAXQSIZE==front队满
//队列初始化
Status InitQueue(SqQueue &Q) {
Q.base=new QElemType[MAXQSIZE];//分配数组空间
if (!Q.base) exit(OVERFLOW); //存储分配失败
Q.front=Q.rear=0; //头指针尾指针置为0,队列为空
return OK;
}
//球队列长度
int QueueLength(SqQueue Q) {
return (MAXQSIZE+Q.rear-Q.front)%MAXQSIZE;
//相当于把rear接到MAXQSIZE位置后面继续计算,这时候把环拆开了
}
//循环队列入队
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;
}
//循环队列出队
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;
}
//取队头指针
SElemType GetHead(SqQueue Q) {
if (Q.front!=Q.rear)//队列不为空
return Q.base[Q.front];//返回队头指针元素的值,队头指针不变
else return ERROR;
}
队列的链式表示和实现
//队列结点类型
typedef struct Qnode {
QElemType data;
struct Qnode *next;
} QNode, *QueuePtr;
//链队列类型,里面包含了头尾指针
typedef struct {
QueuePtr front; //对头指针
QueuePtr rear; //队尾指针
} LinkQueue;
//链队列的初始化
Status InitQueue(LinkQueue &Q) {
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if (!Q.front) exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
//链队列的销毁
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;
}
//将元素e入队
Status EnQueue(LinkQueue &Q, QElemType e) {
p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(OVERFLOW);
p->data=e; p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}
//链队列出队
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;
//如果尾指针指向的就是头结点的下一节点,就是说所有元素删完了,要把头尾指针都指向头指针
delete p;
return OK;
}
//求链队列中的对头元素
Status GetHead(LinkQueue Q, QElemType &e) {
if(Q.front==Q.rear) return ERROR;
e=Q.front->next->data;//把第一个元素的值赋给e
return OK;
}
栈和队列都是很重要且很常用的数据结构,要掌握核心思想和操作方法