堆栈

一、什么是堆栈

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

例:算术表达式\(5+6/2-3*4\),正确理解:\(5+6/2-3*4=5+3-3*4=8-3*4=8-12=-4\)

  • 由两类对象构成的:
    • 运算数,\(2、3、4\)
    • 运算符号,\(+、-、*、/\)
  • 不同运算符号优先级不一样

二、后缀表达式

  • 中缀表达式:运算符号位于两个运算数之间。如,\(a+b*c-d/e\)
  • 后缀表达式:运算符号位于两个运算数之间。如,\(abc*+de/-\)

例:\(62/3-42*+=?\)

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

  1. 遇到运算数怎么办?如何“记住”目前还不未参与运算的数?
  2. 遇到运算符号怎么办?对应的运算数是什么?

启示:需要有种存储方法,能顺序存储运算数,并在需要时“倒序”输出。

例:\(62/3-42*+=?\)

时间复杂性能:\(T(N)=O(N)\)

三、堆栈的抽象数据类型描述

堆栈(Strack):具有一定操作约束的线性表,只在一端(栈顶,Top)做插入、删除

  • 插入数据:入栈(Push)
  • 删除数据:出栈(Pop)
  • 后入先出:\(Last\,In\,First\,Out\)(LIFO)

类型名称:堆栈(Stack)

数据对象集:一个有0个或多个元素的有穷线性表。

操作集:长度为\(MaxSize\)的堆栈\(s\in{Stack}\),堆栈元素\(item\in{E}lementType\)

  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):删除并返回栈顶元素;

\(Push\)\(Pop\)可以穿插交替进行;

按照操作系列

  1. Push(S,A),Push(S,B),Push(S,C),Pop(S),Pop(S,Pop(s)堆栈输出是:\(CBA\)
  2. Push(S,A),Pop(S),Push(S,B),push(S,C),Pop(S),Pop(S)堆栈输出是:\(ACB\)

例:如果三个字符按\(ABC\)书序压入堆栈:\(ABC\)的所有排列不一定都是出栈的序列,无法产生\(CAB\)这样的序列。

四、栈的顺序存储实现

栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成。

/* c语言实现 */

# define Maxsize <储存数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode{
  ElementType Data[MaxSize];
  int Top;
}

4.1 入栈

/* c语言实现 */

void Push(Stack PtrS, ElementType item)
{
  if(PtrS->Top == Maxsize-1){
    printf("堆栈满"); return;
  }else{
    PtrS->Data[++(PtrS->Top)] = item; /* 先+再返回 */
    return;
  }
}

4.2 出栈

/* c语言实现 */

ElementType Pop(Stack PtrS)
{
  if(PtrS->Top == -1){
    printf("堆栈空");,
    return ERROR; /* ERROR是ElementType的特殊值,标志错误 */
  }else
    return (PtrS->Data[(PtrS->Top)--]); /* 先返回再- */
}

五、例:请用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就可以成功。

分析:一种比较聪明的方法是使这两个栈分别从数组的两头开始向中间生长;当两个栈的栈顶指针相遇时,表示两个栈都满了。

/* c语言实现 */

# define MaxSize <存储数据元素的最大个数>
struct DStack{
  ElementType Data[MaxSize];
  int Top1; /*堆栈1的栈顶指针 */
  int Top2; /*堆栈2的栈顶指针 */
} S;
S.Top1 = -1;
S.Top2 = MaxSize;

5.1 入栈

void Push(struct DStack *PtrS, ElementType item, int Tag)
{ /* Tag作为区分两个堆栈的标志,取值为1和2 */
  if (PtrS->Top2 - PtrS->Top1 == 1){ /*堆栈满*/
    printf("堆栈满"); return;
  }
  if (Tag == 1) /* 对第一个堆栈操作 */
    Ptrs->Data[++(PtrS->Top1)] = item;
  else /*对第二个堆栈操作 */
    Ptrs->Data[--(PtrS->Top2)] = item;
}

5.2 出栈

/* c语言实现 */

ElementType Pop(struct DStack *PtrS, int Tag)
{ /* 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)++];
  }
}

六、堆栈的链式存储实现

栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。栈顶指针Top应该在链表的开始;如果栈顶指针放在链表尾部,无法进行出栈操作,因为单链表出栈时找不到前一个结点。

/* c语言实现 */

typedef struct SNode *Stack;
struct SNode{
  ElementType Data;
  struct SNode *Next;
};

6.1 堆栈初始化

/* c语言实现 */

Stack CreateStack()
{ /* 构建一个堆栈的头结点,返回指针 */
  Stack S;
  S = (Stack)malloc(sizeof(struct SNode));
  s->Next = NULL;
  return S;
}

6.2 判断堆栈S是否为空

/* c语言实现 */

int IsEmpty(Stack S)
{ /* 判断堆栈S是否为空,若为空函数返回整数1,否则返回0 */
  return (s->Nesxt == NULL);
}

6.3 入栈

/* c语言实现 */

void Push(ElementType item, Stack S)
{ /* 将元素item压入堆栈S */
  struct SNode *TmpCell;
  TmpCell = (struct SNode *)malloc(sizeof(struct SNode));
  TmpCell->ELement = item;
  TmpCell->Next = S->Next;
  S->Next = TmpCell;
}

6.4 出栈

牢记链表头才是栈顶

/* c语言实现 */

ElementType Pop(Stack S)
{ /* 删除并返回堆栈S的栈顶元素 */
  struct SNode *FirstCell;
  ElementType TopElem;
  if (IsEmpty(S)){
    printf("堆栈空"); return NULL;
  } else {
    FirstCell = S->Next;
    S->Next = FirstCell->Next;
    TopElem = FirstCell->Element;
    free(FirstCell);
    return TopElem;
  }
}

七、堆栈应用:表达式求值

回忆:应用堆栈实现后缀表达式求值的基本过程:从做到右读入后缀表达式的各项(运算符或运算数)

  1. 运算数:入栈;

  2. 运算符:从堆栈中弹出适当数量的运算数,计算并结果入栈;
  3. 最后,堆栈顶上的元素就是表达式的结果值。

八、中缀表达式求值

基本策略:将中缀表达式转换为后缀表达式,然后求值

如何将中缀表达式转换为后缀?

观察一个简单例子:\(2+9/3-5\quad->\quad{2\,9\,3/+5-}\)

  1. 运算数相对顺序不变
  2. 运算符号顺序发生改变
    1. 需要存储“等待中”的运算符号
    2. 要将当前运算符号与“等待中”的最后一个运算符号比较
  3. 考虑括号的情况

例:\(a*(b+c)/d=?\quad-->\quad{a}\,b\,c+*d/\)

注意:\((\)的优先级大于*,括号内的\(+\)的优先级大于括号外的*,遇到\()\)应该消除前一个\((\)

时间性能:\(T(N)=O(N)\)

九、中缀表达式如何转换为后缀表达式

从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。

  1. 运算数:直接输出;
  2. 左括号:压入堆栈;
  3. 右括号:栈顶的运算符弹出输出直到遇到左括号(出栈,不输出);
  4. 运算符:
    1. 优先级大于栈顶运算符时,则把它压栈
    2. 优先级小于等于栈顶运算符时,将栈顶运算符弹出输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
  5. 若各对象处理完毕,则把堆栈中存留的运算符一并输出

十、中缀转换为后缀实例

\((2*(9+6/3-5)+4)\)

十一、堆栈的其他应用

  • 函数调用及递归实现
  • 深度优先搜索:图搜索
  • 回溯算法:迷宫算法
  • ......

十二、Python实现-堆栈

# python语言实现

class Stack(object):
    def __init__(self):
        self.stack = []

    def push(self, value):  # 进栈
        self.stack.append(value)

    def pop(self):  # 出栈
        if self.stack:
            self.stack.pop()
        else:
            raise LookupError('stack is empty!')

    def is_empty(self):  # 如果栈为空
        return bool(self.stack)

    def top(self):
        # 取出目前stack中最新的元素
        return self.stack[-1]
posted @ 2019-08-29 17:03 十七岁的有德 阅读(...) 评论(...) 编辑 收藏