大话数据结构 【三】栈和队列

栈是限定仅在表尾进行插入和删除操作的线性表

队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表

栈(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;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值