栈是限定仅在表尾进行插入和删除操作的线性表
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表
栈(stack)的定义
栈是限定仅在表尾(栈顶)进行插入和删除操作的线性表,又称后进先(Last In First Out)出的线性表,简称LIFO结构。
进栈出栈的变化形式:例三个整数1、2、3依次进栈,会有五种出栈次序
栈的作用:
栈的引人简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。反之,像数组等,因为要分散精力去考虑、数组的下标增减等细节问题,反而掩盖了问题的本质。
所以现在的许多高级语言,比如Java 、C#等都有对技结构的封装, 你可以不用关注它的实现细节,就可以直接使用Stack 的push 和pop 方法,非常方便。
栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitStack(*s);
DestroyStack(*s);
ClearStack(*s);
StackEmpty(*s);
GetTop(S,*e);
Push(*S,e);
Pop(*S,e);
StackLength(s);
endADT
栈本身就是线性表,线性表的顺序存储和链式存储同样适用。
栈的顺序存储结构及实现
下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化小。
当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1。
typedef int SElemType;
typedef struct
{
SElemType data[MAXSIZE];
int top; //栈顶指针
}SqStack;
进栈/出栈:
Status Push(SqStack *S, SElemType e)
{
if(S->top==MAXSIZE-1) //到达栈顶 装不下
return ERROR;
S->top++;
S->data[S->top]=e;
return OK;
}
//若栈不为空,删除栈顶元素,用e返回其值,并返回OK,否则返回ERROR
Status Pop(SqStack *S, SElemType *e)
{
if(S->top== -1) //到达栈底
return ERROR;
*e=S->data[S->top];
S->top--;
return OK;
}
两栈共享空间
方法:让数组的始端为一个栈的栈底,末端为另一个栈的栈底。两个栈向数组中间生长。
可见当栈1为空,top1=-1;栈2为空top2=n。满栈top1+1=top2。
typedef struct
{
SElemType data[MAXSIZE];
int top1; //栈顶指针
int top2;
}SqDoubleStack;
Status Push(SqDoubleStack *S, SElemType e,int stackNumber)
{
if(S->top+1==s->top2) //栈满 装不下
return ERROR;
if(stackNumber==1)
S->data[++S->top1]=e;
else if(stackNumber==2)
S->data[--S->top2]=e;
return OK;
}
//若栈不为空,删除栈顶元素,用e返回其值,并返回OK,否则返回ERROR
Status Pop(SqStack *S, SElemType *e)
{
if(stackNumber==1)
{
if(S->top1==-1) return ERROE;
*e=S->data[S->top1--];
}
else if(stackNumber==2)
{
if(S->top2==MAXSIZE) return ERROE;
*e=S->data[S->top2++];
}
return OK;
}
栈的链式存储结构及实现
由于单链表有头指针,栈的操作通常在顶部,所以把栈顶放在单链表的头部。
对于链栈们不需要头结点,链栈不需要提前定义空间。
链栈为空就是top=NULL
链栈的结构代码:
typedef struct StackNode //数据和指向下一个Node的指针构成
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct LinkStack //栈顶指针+Node个数
{
LinkStackPtr top;
int count;
}LinkStack;
链栈的进栈操作:
Status Push(LinkStack *S,SElemType e)
{
LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
s->data=e;
s->next=S->top;
S->top=s;
S->count++;
return OK;
}
链栈的出栈:(判断是否为空;给返回值赋值;对栈顶top指针的操作;释放指针)
Status Pop(LinkStack *S,SElemType *e)
{
if(StackEmpty(*s)) return ERROR;
LinkStackPtr p;
*e=S->top->data
p=S->top;
S->top=S->top->next;
free(p);
S->count--;
return OK;
}
栈的递归
递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。
这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合钱这样的数据结构,因此, 编译器使用栈实现递归就没什么好惊讶的了。
简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是'恢复了调用的状态。
当然,对于现在的高级语言,这样的递归问题是不需要用户来管理这个栈的, 一
切都由系统代劳了。
栈的应用——四则运算求值
只有碰到左括号,就将此左括号进枝,不管表达式有多少重括号,反正遇到左括号就进枝,而后面出现右括号时,就让樵顶的左括号出拢, 期间让数字运算,这样,最终有括号的表达式从左到右巡查一遍,技应该是由空到有元素,最终再因全部匹配成功后成为空桔的结果。但是上述方法没有涉及对先加减后乘除。
逆波兰( Reverse Polish Notation, RPN) 表示:不需要括号的后缀表达法
计算方法:
"如9+(3 -1 ) X3 +10÷2 ",后缀表达式: 9 3 1- 3 * + 10 2 / +
1. 初始化一个空栈,用来对要运算的数字进出使用。
2. 后缀表达式中前三个都是数字,所以9 、3 、1 进栈
3. 接下来是“-”所以将栈中的1 出作为减数, 3 出校作为被减数,并运算3 - 1
得到2 ,再将2 进栈。
4. 接着是数字 3进栈
中缀表达式转后缀表达式
规则: 从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号则栈顶元素依次出钱并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
9+(3 -1 ) X3 +10÷2 ",后缀表达式: 9 3 1- 3 * + 10 2 / +
队列
同样是线性表,队列也有类似线性表的各种操作,不同的就是插入数据只能在队尾进行,删除数据只能在队头进行。
先进先出线性表。FIFO
例如,键盘上字母数字的输入
队列的抽象数据类型
ADT 队列(queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitQueue (*Q);
DestroyQueue (*Q);
ClearQueue (*Q);
QueueEmpty(*Q);
GetHead(Q,*e);
EnQueue (*Q,e);
DeQueue (*Q,e);
QueueLength(Q);
endADT
队列顺序存储
为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指
针,front 指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front 等
于rear,此队列不是还剩一个元素,而是空队列。
"假滥出":假设这个队列的总个数不超过5 个,但目前如果接着人队的话,因数组末尾元素已经占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0 和1 的地方还是空闲的。
循环队列——解决假溢出
判断空和满的条件是一样的?
- 办法一是设置一个标志变量flag,当front == rear,且flag = 0 时为队列空,当front == near,且flag= 1时为队列满。
• 办法二是当队列空时,条件就是front = near,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元。我们就认为此队列已经满了
对于方法二,队列满的条件是(rear+1) %QueueSlze ==front,因为rear和front可能差了一圈。当rear> front 时,队列的长度为rear一front但当rear < front时,队列长度分为两段,一段是QueueSize - front,另一段是0+ rear,加在一起,队列长度为rear-front
+ QueueSize。因此通用的计算队列长度公式为:
( 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;
}
循环队列 队列长度:
int QueueLength(SqQueue Q)
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
循环队列 入队列:
Status EnQueue(SqQueue *Q,QElemType e)
{
if((Q->rear+1)%MAXSIZE==Q->front)
return ERROR;
Q->data[rear]=e;
Q->rear= (Q->rear+1)%MAXSIZE;
return OK;
}
循环队列 出队
Status DeQueue(SqQueue *Q,QElemType *e)
{
if((Q->rear) ==Q->front)
return ERROR;
*e= Q->data[front];
Q->front= (Q->front+1)%MAXSIZE;
return OK;
}
队列的链式存储——解决循环队列的溢出
队列的链式存储结构,就是线性表的单链表,只不过他只能尾进头出。
链队列的结构
typedef int QElemType;
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct
{
QueuePtr front,rear;
}LinkQueue;
链队列 入队
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; //原来队尾的元素的next指向s
Q->rear=s;
return OK;
}
链队列 出队
Status DeQueue(LinkQueue *Q,QElemType *e)//注意头结点不储存值
{
QueuePtr P;
if(Q->front==Q->rear) return ERROR;
p=Q->front->next;
*e=Q->front->data;
Q->front->next=p->next
if(Q->rear==p) //若队头是队尾,则删除后将rear指向头结点
Q->rear=Q->front;
free(P);
return OK;
}