第三章 栈和队列

3.1栈

栈的定义

栈的定义
作为一种限制性线性表,是将线性表的插入和删除运算限制为仅在表的一端进行。

通常将表中允许进行插入、删除操作的一端称为栈顶(Top),表的另一端被称为栈底(Bottom)。

特点:先进后出。

在这里插入图片描述

抽象数据类型
ADT Stack{
   数据对象:可以是任意类型的数据,但必须属于同一个数据对象
   D={ai|ai∈ElemSet,i=1,2,3...n,n≥0}
   数据关系:栈中数据元素之间是线性关系
   R1={<ai-1,ai>|ai-1,ai∈D,i=2...n},且约定an端为栈顶,a1端为栈底。
   基本操作:
     InitStack(S) 
     ClearStack(S)
     IsEmpty(S)
     IsFull(S)
     Push(S,X)
     Pop(S,X)
     GetTop(S,X)
}ADT Stack

栈的表示和实现(顺序栈、链栈)

顺序栈

定义:用顺序存储结构实现的栈,即利用一组连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。
顺序栈的C语言描述
#define TRUE 1
#define FALSE 0
#define Stack_Size 50
   typedef struct
   {
   StackElementType elem[Stack_Size]; /*用来存放栈顶元素下标*/
   int top; /*top=-1表示空栈*/
   }SeqStack;
顺序栈与顺序表

在这里插入图片描述

顺序栈的基本操作的实现
初始化

在这里插入图片描述

判栈空

在这里插入图片描述

判栈满

在这里插入图片描述

进栈

在这里插入图片描述

出栈

在这里插入图片描述

读栈顶元素

在这里插入图片描述

两栈共享的数据结构

在这里插入图片描述

链栈
定义:采用链表作为存储结构实现的栈。为便于操作,采用带头结点的单链表实现栈。
     因为栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针。

在这里插入图片描述

top为栈顶指针,始终指向当前栈顶元素前面的头结点
若top->next=NULL,则代表空栈。
注意:链栈在使用完毕时,应该释放其空间。
数据结构定义
typedef struct node{
   StackElementType data;
   struct node *next;
}LinkStackNode;
typedefLinkStactNode *LinkStact/*指向结点的指针*/

在这里插入图片描述

进栈(在栈顶插入元素)

在这里插入图片描述

在这里插入图片描述

出栈

在这里插入图片描述

总结

在这里插入图片描述

补充:C语言中的参数传递

函数调用时传送给形参表的实参必须与形参在类型、个数、顺序上保持一致。
参数传递有两种方式:
(1)值传递(参数为整型、实型、字符等)
把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不变。

单向传递,实参的值不会被改变,浪费内存空间

(2)址传递(参数为指针变量、数组名)
参数为指针变量时,形参变化影响实参
参数为数组名时,传递的是数组的首地址,对形参数组所做的任何改变都将反映到实参数组中。

双向传递,可以改变传递过来的参数的内容,即形参的改变会影响实参,通过指针类型将结果带进带出,只是传递的地址,节省内存空间

栈的应用

括号匹配

算法的设计思想:

1)凡出现左括弧,则进栈;

2)凡出现右括弧,首先检查栈是否空若栈空,则表明该“右括弧”多余,否则和栈顶元素比较,若相匹配,则“左括弧出栈”,否则表明不匹配。

3)表达式检验结束时,若栈空,则表明表达式中匹配正确,
否则若栈中仍有等待匹配的左括号,或者读入了一个右括得,而栈中已无等待匹配的同类型左括号,均属于不合法

算法实现

void BracketMatch(char *str)
{ Stack S; int i; char ch;
  lnitStack(&S);
  for(i=0; str[i]!=\O"; i++){switch(str[i])
   { case '(': case '[': case '{":
     Push(&S,str[i]); break;
     case ')":case ']:case '}:
      if(IsEmpty(S))
         {printf(""n右括号多余!"); return; }
       else { GetTop (&s,&ch);
             if(Match(ch,str[ü]))Pop[&S,&ch); 
             else
             {printf(""In对应的左右括号不同类!"); return; }
      }
    }
  }
 if(lsEmpty(S))  printf("In括号匹配!");
 else     printf(""n左括号多余!");
}              
表达式求值(限于二元运算符的表达式)
表达式:操作数(operand)、运算符(operator)、界限符(delimiter)

运算符和界限符统称为运算符(操作符)

操作数:整型常数

运算符:加、减、乘、除

界限符:表达式起始符“#”、表达式结束符“#”

表达式格式:#1+2*3-4/2#

3.2递归

递归的定义
在定义自身的同时又出现了对自身的调用。

直接递归函数:如果一个函数在其定义体内直接调用自己,则称为直接递归函数。
间接递归函数:如果一个函数经过一系列的中间调用语句,通过其他函数间接调用自己,则称为间接递归函数。
栈是函数过程嵌套调用及递归调用管理的有效手段。
具有递归特性的问题
(1)定义的数学函数
(2)具有递归特性的数据结构
(3)可递归求解的问题
递归算法的两个基本特征
递归归纳:将问题转化为比原问题小的同类规模,归纳出一般递推公式,故处理的对象要有规律的递增或递减。

递归终止:当规模小到一定的程度应该结束递归调用,逐层返回;常用条件语句来控制何时结束递归。
总结
优点:递归算法是一种分而治之、把复杂问题分解为简单问题的求解方法。递归程序结构清晰、程序可读性强,且其正确性易于证明,给用户编程带来很大方便。适合解决比较精细的问题。

当问题满足如下条件时,可设计递归算法:

(1)原问题可以层层分解为类似的子问题,子问题规模比原问题小

(2)规模最小的子问题具有直接解

设计递归算法的方法:

(1)寻找分解方法,将原问题分解为类似的子问题求解

(2)设计递归出口,按最小规模子问题确定递归终止条件

缺点:每次调用都要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。递归算法效率低(时间和空间两方面),若处理数据规模较大时,要多加权衡。

递归的消除:递归->非递归
单向递归->循环结构
单向递归是指递归函数虽然有益处以上的递归调用语句,但各次递归调用语句的参数只和主调函数有关,相互之间参数无关,并且这些递归调用语句处于算法的最后。
尾递归->循环结构
尾递归是指递归调用语句只有一个,而且是处于算法的最后,尾递归是单向递归的特例。

3.3队列

定义:限定所有的插入操作在表的一端进行,而删除操作在表的另一端进行的线性表。

通常将表中允许进行插入操作的一端称为队尾(rear),允许进行删除操作的一端称为队头(front)。

当队列中没有元素时称为空队,队列的插入叫入队;删除操作叫出队。
特点:先进先出
抽象数据类型:
ADT Queue {
数据对象:
D-{ai| ai∈ElemSet, i=1,2,....n, n≥o }
数据关系;
R1 ={ <a-1,a >a-1,a=D, i=2,...n }
约定an端为队尾,a1端为队头。
基本操作:
1. InitQueue ( &Q)
2.ClearQueue ( &Q)
3.lsEmpty ( Q)
4.IsFull ( Q)
5.EnterQueue ( &Q,x)
6.DeleteQueue ( &Q,x)
7.GetHead ( Q,&x)
8.DestoryQueue ( &Q)
}ADT Queue

队列的表示和实现:

链队列–链式映象

循环队列–顺序映象

链队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5RKj9pf-1665755507377)(D:\桌面\CSDN\数据结构\picture\16.png)]

链队列 数据结构定义
typedef struct node/*链队列中的结点类型*/
{ QueueElementType data;
  struct node *next;
}LinkQueueNode;

typedef struct/*链队列类型*/
{ LinkQueueNode *front;
   LinkQueueNode *rear;
}LinkQueue;
LinkQueue *Q;
链队列 初始化
int InitQueue(LinkQueue *Q)
{
  Q->front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));
  if(Q->front!=NULL)/*申请结点成功*/
  {
    Q->rear=Q->front;
    Q->front->next=NULL;
    return(TRUE);
  }else return(FALSE);
}
链队列 入队
int EnterQueue(LinkQueue *Q,QueueElementType x)
{
   LinkQueueNode * N;
   N=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));
    if(N!=NULL)
    {N->data=x;
     N->next=NULL;
     Q->rear->next=N;
     Q->rear=N;
     return(TRUE);
    }
else return(FALSE);
}
链队列 出队
int DeleteQueue(LinkQueue *Q,QueueElementType *x
{
  LinkQueueNode * p;/*定义了一个临时指针P,指向被删结点*/
  if(Q->front==Q->rear)/*空队列*/
  return(FALSE);
  p=Q->front->next;
  Q->front->next=p->next;
 if(Q->rear ==p)Q->rear=Q->front;
   /*如果队中只有一个元素P则P出队后成为空队*/
  *X=p->data;
  fee(p);
return(TRUE);
}
循环队列–顺序存储的表示与实现
尽管链队列使用方便,但由于其指针多占存储空间,有时仍需要用顺序结构来表示队列。可以用一组地址连续的存储单元依次存放从队头到队尾的元素,如一维数组Queue[MAXSIZE]

顺序队列中也需要两个“指针”︰
头指针指示队头元素的当前位置;
尾指针指示队尾元素的后一个位置。

在这里插入图片描述

循环队列:“假溢出”现象描述

在这里插入图片描述

如何解决假溢出:

1.移动元素:麻烦,需要移动大量的元素

2.把顺序队列的数组设想为一个循环表,即规定最后一个单元的后继为第一个单元,这样就可以利用头指针前的空单元,这就构成了循环队列。

在这里插入图片描述

循环队列需要解决的两个问题?

1.下标计算问题

2.循环队列“空”与“满”的判定

1.下标计算问题
现在
入队操作为: 
          sq. element[sq.rear]=x;
          sq.rear= ( sq.rear+1 ) % maxsize;
出队操作为:
          x=sq. element[sq.front];
          sq.front= (sq.front+1)% maxsize ;
2.循环队列“空”与“满”的判定
解决方案1:
设队“空”、队“满”标志;

解决方案2: 
少用一个元素空间,当队尾指针所指向的空单元的后继单元是队头元素所在的单元时,则停止入队。
则:
  sq.front==sq.rear  为空
  sq.front== ( sq.rear+1 ) % maxsize 为满
循环队列 存储结构定义
#define maxsize 50
typedef struct
{ QueueElemghtType element [maxsize];
int front;/*头指针指示器,指示队头元素的当前位置,并不是真正的指针类型,相当于数组元素的下标;*/
int rear; /*尾指针指示器指示队尾元素的后一个位置.*/
}seqQueue;

循环队列 初始化
void lnitQueue(SeqQueue *Q)
{
Q->front=Q->rear=0;
/*0表示位置,对应数组元素的下标*/
}

循环队列 入队
int EnterQueue(SeqQueue *Q,QueueElementType x)
{
   if((Q->rear+1)% maxsize ==Q->front)
   /*尾指针加1追上头指针,标志队列已经满了*/
   return(FALSE);
   Q->element[Q->rear]=x;
   Q->rear=(Q->rear+1)% maxsize;/*重新设置队尾指针*/
   return(TRUE);

循环队列 出队
int DeleteQueue(SeqQueue *Q,QueueElementType *x)
{
    if(Q->front== Q->rear)/*队列为空*/ 
    return(FALSE);
    *x=Q->element[Q->front];
    Q->front=(Q->front+1)% maxsize;/*重新设置头指针*/
    returh(TRUE);
}
补充:双端队列

双端队列是指允许两端都可以进行入队和出队的操作的队列。

在这里插入图片描述

输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列。

在这里插入图片描述

输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列。

在这里插入图片描述

总结

栈、队列与一般线性表的区别

nt DeleteQueue(SeqQueue *Q,QueueElementType *x)
{
if(Q->front== Q->rear)/队列为空/
return(FALSE);
*x=Q->element[Q->front];
Q->front=(Q->front+1)% maxsize;/重新设置头指针/
returh(TRUE);
}


#### 补充:双端队列

双端队列是指允许两端都可以进行入队和出队的操作的队列。

[外链图片转存中...(img-272Fw3MF-1665755507380)]

输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列。

[外链图片转存中...(img-U1xjC7Jx-1665755507381)]

输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列。

[外链图片转存中...(img-se7q1SXN-1665755507381)]

#### 总结

栈、队列与一般线性表的区别

![在这里插入图片描述](https://img-blog.csdnimg.cn/0071621a6a7449089f786859ef3fc179.png#pic_center)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值