目录
1 栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈,栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈是一个线性表,栈元素具有线性关系,即前驱后继关系,栈的表尾是指栈顶,而不是栈底。栈底是固定的,最先进栈的只能在栈底,栈的插入操作,叫作进栈,栈的删除操作,叫作出栈。
2 栈的顺序存储结构及实现
2.1 栈的顺序存储结构
栈的顺序存储其实也是线性表存储的简化,称为顺序栈;线性表是用数组来实现的,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以让它作栈底。
定义一个top变量指示栈顶元素在数组中的位置,若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize,当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1。
/*栈的结构定义*/
type int SElemType; /*SElemType类型根据实际情况而定,这里假设为int*/
typedef struct{
SElemType data[MAXSIZE];
int top; /*用于栈顶指针*/
}SqStack;
2.2 栈的顺序存储结构--进栈出栈操作
/*插入元素e为新的栈顶元素*/
Status push(SqStack *s, SElemType e) {
if(s->top == MAXSIZE - 1) {/*栈满*/
return ERROR;
}
s->top++; /*栈顶指针增加1*/
s->data[s->top] = e;/*将新插入元素赋值给栈顶空间*/
return OK;
}
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status pop(SqStack *s, SElemType *e) {
if(s->top == -1) {
return ERROR;
}
*e = s->data[s->top]; /*将要删除的栈顶元素赋值给e*/
s->top--; /*栈顶指针减1*/
return OK;
}
3 两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为栈的末端,即下标为数组长度n-1处,这样两个栈如果增加元素,就是两端点向中间延伸。
关键思路是:它们是在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针。栈1为空时,就是top1等于-1时;当top2等于n时,就是栈2为空时。
极端情况,若栈2为空时,栈1的top1等于n-1时,就是栈1满了,反之当栈1为空栈时,top2等于0时,为栈2满。更多情况下当两个指针之间相差1时,即top1 + 1 == top2为栈满。
/*两栈共享空间结构*/
typedef struct{
SElemType data[MAXSIZE];
int top1; /*栈1栈顶指针*/
int top2; /*栈2栈顶指针*/
}SqDoubleStack;
/*两栈共享空间push方法*/
Status push(SqDoubleStack *s, SElemType e, int stackNumber) {
if (s->top1+1 == s->top2) { /*栈已满,不能再push新元素了*/
return ERROR;
}
if (stackNumber == 1) { /*栈1有元素进栈*/
s->data[++s->top] = e; /*若栈1则先top1+1后数组元素赋值*/
}else if(stackNumer == 1) {/*栈2有元素进栈*/
s->data[--s->top2] = e; /*若栈2则先top2-1后数组元素赋值*/
}
return OK;
}
/*若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status pop(SqDoubleStack *s, SElemType *e, int stackNumber) {
if(stackNumber == 1) {
if(s->top1 == -1) {
return ERROR; /*说明栈1已经时空栈,溢出*/
}
*e = s->data[s->top--]; /*将栈1的栈顶元素出栈*/
}else if(stackNumber == 2) {
if(s->top2 == MAXSIZE) {
return ERROR; /*说明栈2已经时空栈,溢出*/
}
*e = s->data[s->top2++]; /*将栈2的栈顶元素出栈*/
}
return OK;
}
使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
4 栈的链式存储结构及实现
栈的链式存储结构,简称链栈。
栈顶放在单链表的头部,不需要头结点。对于链栈,基本不存在栈满的情况,对于空栈来说,其实就是top==NULL。
/*链栈的结构*/
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStackPtr;
typedef struct LinkStack{
LinkStackPtr top;
int count;
}LinkStack;
4.1 栈的链式存储结构--进栈操作
/*插入元素e为新的栈顶元素*/
Status push(LinkStack *s, SElemType e) {
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = s->top; /*把当前的栈顶元素赋值给新结点的直接后继,如上图1*/
s->top = s; /*将新的绩点s赋值给栈顶指针,如上图2*/
s->count++;
return OK;
}
4.2 栈的链式存储结构--出栈操作
/*若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status pop(LinkStack *s, SElemType *e) {
LinkStackPtr p;
if (stackEmpty(*s)) {
return ERROR;
}
*e = s->top->data;
p = s->top; /*将栈顶结点赋值给p,如上图3*/
s->top = s->top->next; /*使得栈顶指针下移一位,指向后一结点,如上图4*/
free(p);
s->count--;
return OK;
}
5 栈的应用--四则运算表达式求值
计算9+ (3-1)*3+10/2
5.1 后缀表达式计算结果
后缀表达式:9 3 1 - 3 * + 10 2 / +
规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。
1,初始化一个空栈,此栈用来对要运算的数字进行使用。
2,后缀表达式中前三个都是数字,所以9、3、1进栈
3,接下来是“-”,所以将栈中的1出栈作为减数,3出栈作为被减数,并运算3-1得到2,再将2进栈
4,接着是数字3进栈
5,后面是‘*“,也就意味着栈中3和2出栈,2与3相乘,得到6,并将6进栈
6,下面是“+”,所以栈中6和9出栈,9与6相加,得到15,将15进栈
7,接着是10与2两数字进栈
8,接下来是符号“/”,因此,栈顶的2与10出栈,10与2相除,得到5,将5进栈
9,最后一个符号“+”,所以15与5出栈并相加,得到20,将20进栈
10,结果是20出栈,栈变空
5.2 中缀表达式转后缀表达式
中缀表达式“9+ (3-1)*3+10/2”转后缀表达式“9 3 1 - 3 * + 10 2 / +”
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即称为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
1,初始化一空栈,用来对符号进出栈使用
2,第一个字符是数字9,输出9,后面是符号“+”,进栈
3,第三个字符是“(”,依然是符号,因其只是左括号,还未配对,故进栈
4,第四个字符是数字3,输出,总表达式为9 3,接着是“-”,进栈
5,接下来是数字1,输出,总表达式9 3 1,后面是符号“)”,此时,我们需要去匹配此前的“(”,所以栈顶依次出栈,并输出,直到“(”出栈为止,此时左括号上方只有“-”,因此输出“-”。总的输出表达式为9 3 1 -
6,接着是数字3,输出,总的表达式为9 3 1 - 3 ,紧接着是符号“*”,因为此时的栈顶符号为“+”号,优先级低于“*”,因此不输出,“*”进栈
7,之后是符号“+”,此时当前栈顶元素“*”比这个“+”的优先级高,因此栈中元素出栈并输出(没有比“+“号更低的优先级,所以全部出栈),总输出表达式9 3 1 - 3 * + ,然后将当前这个符号“+”进栈,也就是说,前6张图的栈底的“+”是指中缀表达式中开头的9后面哪个“+”,而下图中的栈底(也是栈顶)的“+”是指“9+(3-1)*3+”中的最后一个“+”
8,紧接着数字10,输出,总表达式变为9 3 1 - 3 * + 10,后是符号“/”,所以“/”进栈,如上图
9,最后一个数字2,输出,总的表达式为9 3 1 - 3 * + 10 2
10,已经到最后,所以将栈中符号全部出栈并输出。最终输出的后缀表达式结果为9 3 1 - 3 * + 10 2 / +
6 队列的定义
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO,允许插入的一端称为队尾,允许删除的一端称为队头。
7 循环队列
7.1 队列顺序存储
队列元素的出列是在队头,即下标为0的位置,那也就意味着,队列中的所有元素都得向前移动,以保证队列的队头,也就是下标为0的位置不为空,此时时间复杂度为O(n)
7.2 循环队列定义
把队列的这种头尾相接的顺序存储结构称为循环队列。
数据进入队列:
数据出队列:
空队列:front==rear
满队列:
1,这是一个标志变量flag,当front ==rear,且flag=0时为队列空,当front==rear,且flag=1时为队列满
2,当队列空时,条件就是front=rear,当队列满时,修改其条件,保留一个元素空间,也局势说队列满时,数组中还有一个空闲单元。
队列满的条件时(rear+1)%queueSize == front
通用的计算队列长度公式为:(rear-front+queueSize)%queueSIze
/*循环队列顺序存储结构*/
typedef int QElemType; /*QElemType类型根据实际情况而定,这里假设为int*/
typedef struct{
QElemType data[MAXSIZE];
int front; /*头指针*/
int rear; /*尾指针,若队列不空,指向队列尾元素的下一个位置*/
}SqQueue;
/*初始一个空队列*/
Status initQueue(SqQueue *q) {
q->front = 0 ;
q->rear = 0;
return OK;
}
/*返回q的元素个数,也就是队列的当前长度*/
int queueLength(SqQueue q) {
return (q.rear - q.front + MAXSIZE)%MAXSIZE;
}
/*若队列未满,则插入元素e为q新的队尾元素*/
Status enQueue(Squeue *q, QElemType e) {
if((q->rear+1)%MAXSIZE == q->front) { /*队列满的判断*/
return ERROR;
}
q->data[q->rear] = e; /*将元素e赋值为队尾*/
q->rear = (q->rear+1)%MAXSIZE; /*rear指针向后移动一位置,若到最后则赚到数组头部*/
return OK;
}
/*若队列不为空,则删除q中队头元素,用e返回其值*/
Status deQueue(SqQueue *q, QElemType *e) {
if(q->front == q->rear) {/*队列空的判断*/
return ERROR;
}
*e = q->data[q->front]; /*将队头元素赋值给e*/
q->front = (q->front+1)%MAXSIZE; /*front指针向后移一位置,若到最后则转到数组头部*/
return OK;
}
8 队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,称为链队列。将队头指针指向链队列的头结点,而队尾指针指向终端结点。
空队列时,front和rear都指向头结点。
/*链队列的结构*/
typedef int QElemType;
typedef struct QNode{ /*结点结构*/
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct{ /*队列的链表结构*/
QueuePtr front,rear; /*队头、队尾*/
}LinkQueue;
8.1 队列的链式存储结构--入队操作
/*插入元素e尾q的新的队尾元素*/
Status enQueue(LinkQueue *q, QElemType e) {
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s) { /*存储分配失败*/
exit(OVERFLOW);
}
s->data = e;
s->next = NULL;
q->rear->next = s; /*把拥有元素e新结点s赋值给原队尾结点的后继,如上图1*/
q->rear = s; /*把当前s设置尾队尾结点,rear指向s,如上图2*/
return OK;
}
8.2 队列的链式存储结构--出队操作
/*若队列不空,删除q的队头元素,用e返回其值,并返回ok,否则返回error*/
Status deQueue(LinkQueue *q, QElemType *e) {
QueuePtr p;
if(q->front == q->rear) {
return ERROR;
}
p = q->front->next; /*将欲删除的队头结点暂存给p,如上图1*/
*e = p->data; /*将要删除的队头结点的值赋值给e*/
q->front->next = p->next; /*将原队头结点后继p->next赋值给其头结点后继,如上图2*/
if(q->rear == p) { /*若队头是队尾,则删除给将rear指向头结点,如上图3*/
q->rear = q->front;
}
free(p);
return OK;
}
来源:CSDN
作者:金政权
链接:https://blog.csdn.net/jinzhengquanqq/article/details/104428581