大话数据结构(栈)

226 阅读9分钟

1、栈的定义

1.栈的定义

先进去,却要后出来,而后进的反而可以先出来的数据结构---栈

例子:浏览器的后退键,点击后可以按访问顺序的逆序加载浏览过的网页

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

我们把允许插入和删除的一端称为栈顶(top),另外一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表



进栈:

栈的插入操作,也称压栈、入栈

出栈:

栈的删除操作,也叫弹栈

2.进栈出栈变化形式

最先进栈的元素,不一定最后出栈 比如,如果有3个整型数字元素1、2、3依次进栈,会有如下的出栈次序:
第一种:1、2、3进,再3、2、1出。出栈次序321
第二种:1进,1出,2进,2出,3进,3出。出栈次序123
第三种:1进,2进,2出,1出,3进,3出。出栈次序213
第四种:1进,1出,2进,3进,3出,2出。出栈次序132
第五种:1进,2进,2出,3进,3出,1出。出栈次序231

3.栈的抽象数据类型

ADT   栈(stack)

Data

   同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。

Operation

   InitStack(*S): 初始化操作.建立一个空栈S。

   DestroyStack(*S): 若栈存在,則销毁它。

   ClearStack(*S): 将栈清空。

   StackEmpty(S): 若栈为空,返回true,否則返回 false。

   GetTop(S, *e): 若栈存在且非空,用e返回S的栈顶元素。

   Push(*S, e): 若栈S存在,插入新元素e到栈S中并成为栈顶元素。

   Pop(*S, *e): 删除栈S中栈顶元素,并用e返回其值。

   StackLength(S): 返回栈S的元素个数。

endADT

4.栈的顺序存储结构及实现

4.1 栈的顺序存储结构

定义一个top变量指示栈顶元素在数组中的位置,通常把空栈的判定条件定位top等于-1

typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */

/* 顺序栈结构 */
typedef struct
{
        SElemType data[MAXSIZE];
        int top; /* 用于栈顶指针 */
}SqStack;

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

/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
        if(S->top == MAXSIZE -1) /* 栈满 */
        {
                return ERROR;
        }
        S->top++;				/* 栈顶指针增加一 */
        S->data[S->top]=e;  /* 将新插入元素赋值给栈顶空间 */
        return OK;
}

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

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{ 
        if(S->top==-1)
                return ERROR;
        *e=S->data[S->top];	/* 将要删除的栈顶元素赋值给e */
        S->top--;				/* 栈顶指针减一 */
        return OK;
}



5.两栈共享空间

两栈共享空间的进一步理解

数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为数组的末端,即下标为数组长度n-1处 在这里插入图片描述 关键思路

它们是在数组的两端,向中间靠拢

栈1为空时,就是top1等于-1时;而当top2等于0时,即是栈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;



插入元素代码: (stackNumber用于判断是栈1还是栈2)

/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{
        if (S->top1+1==S->top2)	/* 栈已满,不能再push新元素了 */
                return ERROR;	
        if (stackNumber==1)			/* 栈1有元素进栈 */
                S->data[++S->top1]=e; /* 若是栈1则先top1+1后给数组元素赋值。 */
        else if (stackNumber==2)	/* 栈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->top1--]; /* 将栈1的栈顶元素出栈 */
        }
        else if (stackNumber==2)
        { 
                if (S->top2==MAXSIZE) 
                        return ERROR; /* 说明栈2已经是空栈,溢出 */
                *e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */
        }
        return OK;
}

使用两栈共享空间这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况

只针对两个具有相同数据类型的栈的设计

6.栈的链式存储结构及实现

6.1 栈的链式存储结构

typedef struct StackNode{
    SElemType data;
    struct StackNode *next;
}StackNode, *LinkStackPtr;
 
typedef struct LinkStack{
    LinkStackPtr top;
    int count;
}LinkStack;

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

对于链栈的进栈push操作,假设原素值为e的新结点是s,top为栈顶指针

在这里插入图片描述

/*插入元素e为新的栈顶元素*/
Status Push(LinkStack *S, SElemType e){
    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    s->data = e;
    s->next = S->top;/*把当前的栈顶元素赋值给新结点的直接后继,如图中①*/
    S->top = s;/*将新的结点s赋值给栈顶指针,如图中②*/
    S->count++;
    return OK;
}



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

链栈的出栈pop操作,也是简单的三句操作。假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可

在这里插入图片描述

/* 若栈不空,则删除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,见图中③ */
        S->top=S->top->next;    /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
        free(p);                    /* 释放结点p */        
        S->count--;
        return OK;
}

链栈的进栈push和出栈pop操作都很简单,没有任何循环操作,时间复杂度均为0(1)

对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费问题,但它的优势是存取时定位很方便,而栈链则要求每个元素都有指针域,这同时增加了一些内存开销,但对于栈的长度无限制。

如果栈的使用过程中元素变化不可预料,有时很小,有时很大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些

7.栈的作用

8.栈的应用---递归

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

9.1 后缀(逆波兰)表示法定义

对于“9 + (3 - 1) * 3 + 10 / 2”,可以用后续表示法表示为“9 3 1 - 3 * + 10 2 / +”

一种不需要括号且所有的符号都是在要运算数字的后面出现,即为后缀表示法

9.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出栈,栈变为空

9.3 中缀表达式转后缀表达式

平时所用的标准四则运算表达式,即"9+(3-1)X3+10÷2 "叫做中缀表达式

规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,直到最终输出后缀表达式为止

  1. 初始化一空栈,用来对符号进出栈使用
  2. 第一个字符是数字9,输出9,后面是符号“+”,进栈
  3. 第三个字符是“(”,依然是符号,因其只是左括号,还未配对,故进栈
  4. 第四个字符是数字3,输出,总表达式为9 3,接着是“-”,进栈
  5. 接下来是数字1,输出,总表达式为9 3 1,后面是符号“)”,此时,我们需要去匹配此前的“(”,所以栈顶依次出栈,并输出,直到“(”出栈为止。此时左括号上方只要“-”,因此输出“-”。总的输出表达式为9 3 1-
  6. 紧接着是符号“X”,因为此时的栈顶符号为“+”号,优先级低于“X”,因此不输出,“*”进栈,紧接着是数字3,输出,总的表达式为9 3 1 - 3
  7. 之后是符号“+”,此时当前栈顶元素“*”比这个“+”的优先级高,因此栈中元素出栈并输出(没有比“+”号更低的优先级,所以全部出栈),总输出表达式为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 /+