数据结构复习(3.栈和队列)

目录

3.1栈

3.1.1栈的逻辑结构

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

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

3.2栈的应用举例

3.4队列

3.4.1队列的逻辑结构

3.4.2队列链式存储实现

思路:

3.4.3队列顺序存储实现


3.1栈

重点:逻辑结构唯一 

两种特殊的线性表:栈和队列

首先从数据结构的角度来看:栈和队列是操作受限的线性表。他们的逻辑结构都是一对一的关系。

3.1.1栈的逻辑结构

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

a1, a2, ……, an

                                      空栈:不含任何数据元素的栈。

     允许插入和删除的一端称为栈顶,另一端称为栈底

 

栈的示意图 

栈的插入:入栈,进栈,压栈

栈的删除:出栈,弹栈

栈的操作原则:后进先出原则

例题:例:有三个元素按abc的次序依次进栈,且每个元素只允许进一次栈,则可能的出栈序列有多少种?

可以通过穷举所有可能性来求解:

11出, 22出,33出, 即123

11出, 2332出,   即132

12入,2出, 33出,    即231

12入,21出,33出,  即213

123入,321出,    即321

合计有5种可能性。

例题2:提问:一个栈的输入序列是12345,若在入栈的过程中允许出栈,则栈的输出序列43512可能实现吗?12345的输出呢?

答:不可能43512中的12部分不可能实现,只可能是21.

        12345可以实现,只要进去一个弹出一个即可。‘’


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

顺序栈——栈的顺序存储结构

改造数组实现栈的顺序存储。

附设指针top指示栈顶指针的下一个位置。

进栈:top加一                                                        栈空:top==base  (base是指向栈底的指针)

出栈:top减一                                                        栈满:top-base=stacksize


 

1.栈的定义:

#define stacksize 100;

typedef struct{

ElementType *top;

ElementType *base;  //在栈构造之前和销毁之后base的值为NULL;

int stacksize;

}sqStack; 

2.栈的初始化:

 Status  InitStack ( SqStack  &S)
{    S.base=(SElemType *) malloc (STACK_INIT_SIZE *
                                                   sizeof (SElemType);
       if (!S.base)  exit (OVERFLOW);  
      S.top=S.base
      S.stacksize= STACK_INIT_SIZE;
return OK;
}

初始化函数的几个点:

1.为s.base分配空间

2.因为现在栈为空,所以让s.top  s.base指向同一个空间

3.规定栈的大小stacksize

3.获取栈顶元素

Status  GetTop( SqStack  S, SElemType  &e)
{ //若栈不空,则获取S的栈顶元素,用e返回其值,
        并返回OK;否则返回ERROR

      if ( S.top == S.base )   return ERROR;
      e=*(S.top-1)
	return OK;
 }

两个点:

1.判断栈是否为空。

2.利用s.top是指向栈顶元素下一块空间的特点。e=*(s.top-1);

4.入栈。

Status  Push ( SqStack  &S, SElemType  e)
//如果栈满,需要重新申请空间,再插入元素e
{if ( S.top-S.base>=S.stacksize )  
   {  S.base=(SElemType *) realloc ( S.base,
      (S.stacksize+STACKINCREMENT)*sizeof (SElemType);

       if (!S.base)  exit (OVERFLOW);  
      S.top=S.base + S.stacksize;
      S.stacksize+=STACKINCREMENT;
     } //if 

     *S.top++ = e;
     return OK;
}//Push

三个点:

1.判满,重新分配空间

s.base=(ElementType *)malloc((s.stacksize+STACKINCREASEMENT)*sizeof(ElementType);

2赋值3.top指针加一

*s.top++=e; 先赋值后加

5.出栈

Status  Pop ( SqStack  &S, SElemType & e)
 {   //若栈不空,则删除S的栈顶元素,用e返回其值,
        并返回OK;否则返回ERROR
     if ( S.top == S.base )   return ERROR;  
     e=*--S.top;
     return OK;
}//Pop

唯一要注意:e=*--s.top;

总结提问:

顺序栈S不存在的条件:  

 S.base=NULL;

顺序栈为空 的条件 :   

  S.base==S.top;

顺序栈满的条件  :        

 S.top-S.base=S.stacksize;


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

链栈的结构

 将哪一端作为栈顶?

将链栈头作为栈顶。

链栈需要加头结点吗?

链栈不需要附设头结点。

即:

                             栈顶                                                                                             栈底


1.链栈定义:

typedef struct SNODE

{

    SElemType   data;

    struct snode *link;

}SNODE,*LinkStack;

LinkStack  top, p;

int  m=sizeof (SNODE);                                 以头指针为栈顶,在头指针处插入或者删除!

2.入栈

void Push ()  
{ p=(NODE*) malloc (m);
   if (!p){上溢}
   else { p->data=x; 
             p->link=top; 
             top=p;}
}

注意:

1.p->link=top;      //给新节点分配空间后时其指向以前的头结点。

 2.top=p               //使头指针指向变更为新首元结点。

3.删除

void pop(linkstack &TOP,ElementType e){
    if(top==NULL)   //下溢
    else{
        linkstack p=top;
        e=TOP->data;
        top=top->link;
        free(p);
    }
}

讨论:顺序栈和链栈的比较

时间性能:

相同,都是常数时间O(1)

空间性能:

Ø 顺序栈: 有元素个数的限制和空间浪费的问题。
Ø 链栈: 没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。

总之,当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。


3.2栈的应用举例

1.数制转换(例子中为10转2)

Void conversion(){
	 InitStack(S);    Scanf(“%d”,n);
While(n){
Push(S,n%2)
n=n/2}
//当n不为零时 n除以2的余数入栈。

while(!StackEmpty(S)){
Pop(S,e);
Printf(“%d”,e);        } //while  
//当栈中不为空时,栈中元素出栈

}//conversion

2.表达式求值(算符优先法)

例如:3*7 – 2

思路:规则是  从左到右  先乘除后加减  先算括号内部后算括号外

          根据以上的规则:

            根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符q1q2 要比较,优先              权关系。

比较过程略。

算法思想:

设定两栈: 操作符栈 OPTR ,操作数栈 OPND
栈初始化 :设操作数栈 OPND 为空;操作符栈 OPTR 的栈底元素为表达式起始符 ‘ #’
依次读入字符 :是操作数则入 OPND 栈,是 操作符 则要判断:

If  栈顶元素<操作符,压入OPTR

    栈顶元素 =操作符且不为‘#’,脱括号(弹出左括号);

    栈顶元素 >操作符,则退栈、计算,结果压入OPND栈; 。


3.3栈与递归的实现

此处略,提供两例题:

1.

int X(int n)

{

if(n<=3) return 1;

else return X(n-2)+X(n-4)+1

}

则计算 X(8)的结果是:_____

A.8      B. 9    C.16    D. 18

b

2.

1. 一个栈的输入序列为123…n,若输出序列的第一个元素是n,输出第i1<=i<=n)个元素是(    )。

A. 不确定          B. n-i+1          C.  i           D. n-i

中山大学 1999 一、9(1)】

2. 若一个栈的输入序列为1,2,3,…,n,输出序列的第一个元素是i,则第j个输出元素是(     )。

 A. i-j-1          B. i-j            C. j-i+1      D. 不确定的

bd

3.4队列

3.4.1队列的逻辑结构

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

空队列:不含任何数据元素的队列。

允许插入(也称入队、进队)的一端称为队尾,允许删除(也称出队)的一端称为队头

队列的操作特性:先进先出FIFO

                     front                                                                                                              rear

 队头指针即为链表的头指针。

非空链队列

 空链队列

 如何判空队列?

3.4.2队列链式存储实现

队列的定义:

typedef struct QNode{
     QElemType   data;  
     struct QNode  *next;   
}QNode, *QueuePtr;      

typedef struct {
     QueuePtr  front;  // 队头指针
     QueuePtr  rear;   // 队尾指针
}LinkQueue;      // 链队列

1.构造空队列

Status InitQueue (LinkQueue &Q) 
{ // 构造一个空队列 Q
Q.front = Q.rear = 
  (QueuePtr) malloc (sizeof(QNode));
if (!Q.front)  exit (OVERFLOW);   //存储分配失败
Q.front->next = NULL;
return OK;

} 

空队列的头指针和尾指针都指向头结点。

2.销毁队列

Status  DestroyQuene ( LinkQuene &Q) 
{    //销毁队列Q
   while ( Q.front )
   {  
       Q.rear = Q.front—>next;
       free ( Q.front);
       Q.front = Q.rear;
    }
    return  OK;
 }

思路:

利用while循环():

1:尾指针指向头指针指向结点的下一个。

2.free头指针所指内存。

3.使头指针指向为指针所示结点。

3.入队操作

Status EnQueue (LinkQueue &Q, QElemType  e) 
{ //插入元素e为Q的新的队列尾元素

p =(QueuePtr) malloc (sizeof (QNode));
if ( !p )  exit (OVERFLOW);        // 存储分配失败
p->data = e;  p->next = NULL;
Q.rear->next = p;                          // 修改尾结点的指针
Q.rear = p;                                    // 移动队尾指针
return OK;
} 

4.出队操作

Status DeQueue (Queue &Q, QElemType &e) 
{ // 若队列不空,则删除Q的队头元素,用e返回其值, 
    并返回OK;否则返回ERROR
if ( Q.front == Q.rear )   return ERROR; // 队列为空    

p = Q.front->next;
e = p->data;                   // 返回被删元素
Q.front->next = p->next;       // 修改头结点指针
   if (Q.rear == p)   Q.rear = Q.front;

free (p);                              // 释放被删结点
return OK;
}//DeQueue

例题:

 cdcd

例:

yhar

 例:

利用堆栈做辅助,将队列中的数据元素进行逆置

链队列的总结讨论:

1.空队列的特征?

Q->front=Q->rear;

2.链队列是否会慢?

不会,除非 申请不到内存空间。

3.链队列的时间性能和空间性能。

时间性能o(1)空间性能o(n)

3.4.3队列顺序存储实现

如何改造数组实现队列的顺序存储?

答:要求队列中的元素存储在数组中的连续位置。需设置队头,队尾两个指针。

 入队出队的流程示意图

同样的,出队的时间性能也为O(1);

顺序队列整体的移动有什么特点?

答:队列整体向数组下标增大的方向移动。(单项移动性

答:会出现“假溢出”原因在于队列的后端空间用尽了,但是队列前端还有空间,这种现象叫“假溢出” 。

如何解决假溢出?

采用循环队列。

即将存储队列的数组头尾相接。

不存在物理的循环结构,用软件方法实现。             

求模rear=rear1modMAXSIZE                                                                                                                  

例题:

                                                                                          

 顺序队列总结:

队空条件:

front=rear

队满条件:

front = (rear+1) % N         (N=maxsize)

队列长度:

L=Nrearfront% N  重点

1左图中队列容量

     maxsize N=6

2左图中队列长度L=?5

3在具有n个单元的循环队列中,队满时共有多少个元素? n-1

问:为什么要设计队列?它有什么独特用途?

1. 离散事件的模拟(模拟事件发生的先后顺序);
2. 操作系统中多道作业的处理(一个 CPU 执行多个作业);

3.  简化程序设计。

线性表、栈与队的异同点

相同点:逻辑结构相同,都是线性的;都可以用顺序存储或链表存储;栈和队列是两种特殊的线性表,即受限的线性表(只是对插入、删除运算加以限制)。

不同点:

① 运算规则不同,线性表为随机存取,而栈是只允许在一端进行插入和删除运算,因而是后进先出表LIFO;队列是只允许在一端进行插入、另一端进行删除运算,因而是先进先出表FIFO

② 用途不同,线性表比较通用;堆栈用于函数调用、递归和简化设计等;队列用于离散事件模拟、多道作业处理和简化设计等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值