栈与队列

旧巷老猫 提交于 2020-02-21 19:37:30

目录

 

 

1 栈的定义

2 栈的顺序存储结构及实现

2.1 栈的顺序存储结构

2.2 栈的顺序存储结构--进栈出栈操作

3 两栈共享空间

4 栈的链式存储结构及实现

4.1 栈的链式存储结构--进栈操作

4.2 栈的链式存储结构--出栈操作

5 栈的应用--四则运算表达式求值

5.1 后缀表达式计算结果

5.2 中缀表达式转后缀表达式

6 队列的定义

7 循环队列

7.1 队列顺序存储

7.2 循环队列定义

8 队列的链式存储结构及实现

8.1 队列的链式存储结构--入队操作

8.2 队列的链式存储结构--出队操作


 

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;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!