数据结构第二周笔记(3)——线性结构(慕课浙大版本--XiaoYu)

141 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

2.2.1 什么是堆栈

计算机如何进行表达式求值?

image-20220628024357415

由两类对象构成的:

  1. 运算数,如2、3、4
  2. 运算符号,如+、-、*、/

不同运算符号优先度不一样

后缀表达式: 运算符号位于两个运算数之后。如abc*+de/-

中缀表达式: 运算符号位于两个运算数之间。如a+b*c-d/e

两个表达式其实是同一个意思

还有一种表达式叫“前缀表达式”,即运算符号位于运算数之前,比如a+bc的前缀表达式是+abc。

你能写出a+bc-d/e的前缀表达式吗? 正确答案:-+abc/de

【例】6 2 / 3 - 4 2 * + = ? => 先遇到6然后2接着是/,组成式子得到3,再遇到3然后再遇到-组成式子3-3=0,0接着遇到4跟2然后是*,4 * 2 = 8,然后目前是有0跟8,接着遇到+,相加为8

后缀表达式求值策略:从左往右"扫描",逐个处理运算数和运算符号

  1. 遇到运算数怎么办?如何"记住"目前还未参与运算的数?
  2. 遇到运算符号怎么办?对应的运算数是什么?
  3. 启示:需要有种存储方法,能顺序存储运算数,并在需要时"倒序"输出!
  4. 先放进去的后拿出来,后放进去的先拿出来做运算 =>这就是堆栈
  5. T(N) = O(N)

堆栈的抽象数据类型描述

堆栈(Stack): 具有一定操作约束的线性表

  1. 只在一端(栈顶,Top)做插入、删除
  2. 插入数据:入栈(Push)
  3. 删除数据:出栈(Pop)
  4. 后入先出:Last In First Out(LIFO) 这是特点

数据对象集(堆栈): 一个有0个或者多个元素的有穷线性表

操作集(堆栈): 长度为MaxSize的堆栈S 属于Stack,堆栈元素item 属于ElementType

1. Stack CreateStack( int MaxSize):生成空堆栈,其最大长度为MaxSize;
2. int IsFull(Stack S,int MaxSize):判断堆栈S是否已满;
3. void Push(Stack S, ElementType item ):将元素item压入堆栈;(重点)相当于插入操作,需要判别堆栈有没有满或空
4. int IsEmpty(Stack S):判断堆栈S是否为空;
5. ElementType Pop(Stack S):删除并返回栈顶元素;(重点)相当于删除操作,需要判别堆栈有没有满或空。空了就不能删除了

image-20220628032111469

Push和Pop可以穿插交替进行;

image-20220628032222743

image-20220628032242421

按ABC顺序入栈,可以产生CAB这样的出栈序列?不可以,是CBA序列

Push:推(压入)

Pop:弹出

2.2.2 堆栈的顺序存储实现

栈的顺序存储实现

栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成
#define MaxSize <存储数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode{
    ElementType Data[MaxSize];
    int Top;//用来指示栈顶的位置,Top不是地址而是一个整型变量,代表了栈顶位置它的数组下标在哪里
};
//(1)入栈
void Push (Stack PtrS, ElementType item)//包含两个参数,一个是堆栈本身,一个是用指针表示PtrS(Stack类型指针,Stack本身就是堆栈的意思)
{
    if( PtrS -> Top == MaxSize -1 ){//入栈需要判断堆栈满了没有,满了就不能再加了,有极限的。下标从0开始,所以到MaxSize-1就已经满了
        printf("堆栈满") return;
    }else{
        PtrS ->Data[++(PtrS -> Top)] = item;//item放在Top上面的一个位置,这一个语句实际做了两个事情,一是把item放到Top+1这个位置上,同时把Top值加实现了入栈的操作
        return;
    }
}
//(2)出栈
ElementType Pop(Stack PtrS)
{
    if(PtrS -> Top == -1 ){
        printf("堆栈空");//一样的操作,出栈之前要检查以下是不是空了,空了可就没东西往外掏了
        return ERROR;//ERROR是ElementType的特殊值,标志错误
    }else{
        return(PtrS -> Data[(PriS -> Top)--]);
    }
}
【例】请用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就可以成功
//简单将把数组对分的话来做两个堆栈会有一个问题:有一个堆栈满了,但另一个堆栈是空的,还有空余空间
//要求是数组还有空余空间,就允许有入栈操作
//用法就是一个从最左边往里放,另一个从最右边往里放。中间就是空余的大家都可以用//根据刚才讲的方法,用一个数组来表示双堆栈,如果这两个堆栈的栈顶位置分别是top1和top2,那么可以用top1+top2==MaxSize(数组大小)来判别堆栈是否满?不可以
//因为top1跟top2代表的是数组的下标,所以top1代表的距离是中间空余的位置+top2,top2也是同理//正确的分析:使这两个栈分别从数组的两头开始向中间生长;当两个栈的栈顶指针相遇时,表示两个栈都满了
    #define MaxSize<存储数据元素的最大个数>
    struct DStack{
        ElementType Data[MaxSize];
        int Top1;//堆栈1的栈顶指针
        int Top2;//堆栈2的栈顶指针
    }S;
    S.Top1 = -1;//这是堆栈1空的位置
    S.Top2 = MaxSzie;//堆栈2空的位置,数组最后一个位置是MaxSize - 1//Push(弹出)具体操作
void Push (struct DStack *PtrS,ElementType item,int Tag )
{
    //Tag作为区分两个堆栈的标志,取值为1和2,Tag的值1和2分别代表第一个堆栈跟第二个堆栈
    if( PtrS -> Top2 - PtrS -> Top1 == 1){
        printf("堆栈满"); return;
    }
    if( Tag == 1)//对第一个堆栈操作
        PtrS -> Data[++(PtrS -> Top1)] = item;//Top1后面一个位置
    else//对第二个堆栈进行操作
        PtrS -> Data[--(PtrS -> Top2)] = item;//Top2前面一个位置
}
​
//Pop(压入)操作
ElementType Pop (struct DStack *PtrS,int Tag )
{
    //Tag作为区分两个堆栈的标志,取值为1和2,Tag的值1和2分别代表第一个堆栈跟第二个堆栈
    if( Tag == 1){//对第一个堆栈进行操作
        if( PtrS -> Top1 == -1){//堆栈1空
            printf("堆栈1空"); return NULL;
        }else {
            return PtrS -> Data [(PtrS -> Top1)--];
        }
    }else{//对第二个堆栈操作
    if( PtrS -> Top2 == MaxSize){//堆栈2空
        printf("堆栈2空"); return NULL;    
    }else{
     return PtrS -> Data[(PtrS -> Top2)++];   
    }
}

以下对堆栈的总结

堆栈严格来说应该叫做栈(stack),先入后出
​
四种类型:满增栈、满减栈、空增栈、空减栈。
​
 
​
满、空栈区别:根据当前指针所在位置是否有东西。
​
满栈(full stack):栈指针指向最后压入栈的数据,数据入栈时,sp先减一(或加一)再入栈。
​
空栈(empty stack):栈指针指向下一个将要放入数据的位置,数据入栈时,先入栈sp再减一(或加一)。
​
 
​
增、减栈区别:根据堆栈的生成方向不同。
​
递增堆栈(ascending stack):堆栈由低地址向高地址生长。
​
递减堆栈(secending stack):堆栈由高地址向低地址生长。
​
 
​
总结:
​
 
​
满栈进栈是先移动指针再存;
​
满栈出栈是先出数据再移动指针;
​
空栈进栈先存再移动指针;
​
空栈出栈先移动指针再取数据。
​