【数据结构与算法学习笔记-Stack】

本文介绍了线性结构的基本概念,包括栈、队列、双端队列和列表,并详细讲解了栈的抽象数据类型及其Python和JavaScript实现。接着,文章探讨了括号匹配问题,提供了简单的括号匹配和通用括号匹配的算法及代码实现。此外,还介绍了十进制转二进制和中缀表达式转后缀表达式的方法。最后,展示了如何利用栈计算后缀表达式的值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文为学习笔记,感兴趣的读者可在MOOC中搜索《数据结构与算法Python版》或阅读《数据结构(C语言版)》(严蔚敏)
目录链接:https://blog.csdn.net/floating_heart/article/details/123991211

1.1.1什么是线性结构Linear Structure

线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继
除了第一个没有前驱,最后一个没有后继
新的数据项加入到数据集中时,只会加入到原有某个数据项之前或之后
具有这种性质的数据集,就称为线性结构

4种最简单但功能强大的结构:
栈Stack;队列Queue;双端队列Deque;列表List

1.1.2栈抽象数据类型及实现

抽象数据类型“栈”是一个有次序的数据集,
每个数据项仅从“栈顶”一端加入到数据集中、从数据集中移除,
栈具有后进先出LIFO(Last in First out)的特性

“栈”定义的操作:

**Stack():**创建一个空栈,不包含任何数据项
**push(item):**将item加入栈顶,无返回值
**pop():**将栈顶数据项移除,并返回,栈被修改
peek():“窥视”栈顶数据项,返回栈顶的数据项但不移除,栈不被修改
**isEmpty():**返回栈是否为空栈
**size():**返回栈中有多少个数据项

实现

代码:Python

class Stack://栈顶为list尾端的实现方法,其push()和pop()为O(1),存在其他实现方法——Python
  def __init__(self):
      self.items = []
  def push(self,item):
    self.items.append(item)
  def pop(self):
    return self.items.pop()
  def peek(self):
    return self.items[len(self.items)-1]
  def isEmpty(self):
    return self.items == []
  def size(self):
    return len(self.items)

代码:JavaScript-prototype

function Stack() {//prototype方法——Javascript
  this.items = []
}
Stack.prototype.push = function (item) {
  this.items.push(item)
}
Stack.prototype.pop = function () {
  return this.items.pop()
}
Stack.prototype.peek = function () {
  return this.items[this.items.length - 1]
}
Stack.prototype.isEmpty = function () {
  return this.items.length == 0
}
Stack.prototype.size = function () {
  return this.items.length
}

代码:JavaScript-class

class Stack_c {//class方法——Javascript
  constructor() {
    this.items = []
  }
  push(item) {
    this.items.push(item)
  }
  pop() {
    return this.items.pop()
  }
  peek() {
    return this.items[this.items.length - 1]
  }
  isEmpty() {
    return this.items.length == 0
  }
  size() {
    return this.items.length
  }
}

1.1.3简单括号匹配

问题描述:

括号的使用必须遵循“平衡”规则
首先,每个开括号要恰好对应一个闭括号;
其次,每对开闭括号要正确的嵌套
正确的括号:
(()()()()),(((()))),(()((())()))
错误的括号:
((((((()),())),(()()(()

算法说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXRsLFbH-1649231855041)(.\Python3 Data Structure-Linear Structure.assets\image-20211110171029109.png)]

实现

代码:Python

from Stack import Stack

def parChecker(symbolstring):
  s = Stack()
  balanced = True
  index = 0
  while index < len(symbolstring) and balanced:
    symbol = symbolstring[index]
    if symbol == '(':
      s.push(symbol)
    else:
      if s.isEmpty():
        balanced = False
      else:
        s.pop()
    index += 1
  if balanced and s.isEmpty():
    return True
  else:
    return False

代码:JavaScript

function parChecker(symbolstring) {//JavaScript
  var s = new Stack()
  var balanced = true
  var index = 0
  while (index < symbolstring.length && balanced) {
    var symbol = symbolstring[index]
    if (symbol == '(') {
      s.push(symbol)
    } else {
      if (s.isEmpty()) {
        balanced = false
      } else {
        s.pop()
      }
    }
    index += 1
  }
  if (balanced && s.isEmpty()) {
    return true
  } else {
    return false
  }
}

扩展:通用括号匹配

问题描述:

下面这些是匹配的:
{ { ( [ ] [ ] ) } ( ) }
[ [ { { ( ( ) ) } } ] ]
[ ] [ ] [ ] ( ) { }
下面这些是不匹配的:
( [ ) ] ( ( ( ) ] ) )
[ { ( ) ]

实现

代码:Python

from Stack import Stack

def parChecker(symbolstring):
  s = Stack()
  balanced = True
  index = 0
  while index < len(symbolstring) and balanced:
    symbol = symbolstring[index]
    if symbol in '([{':
      s.push(symbol)
    else:
      if s.isEmpty():
        balanced = False
      else:
        top = s.pop()
        if not matches(top,symbol):
            balanced = False
    index += 1
  if balanced and s.isEmpty():
    return True
  else:
    return False

def matches(open,close):
    opens = '([{'
    closes = ')]}'
    retrun opens.index(open) == closes.index(close)

代码:JavaScript

function matches(open, close) {
  var opens = '([{'
  var closes = ')]}'
  return opens.indexOf(open) == closes.indexOf(close)
}
function parChecker(symbolstring) {
  var s = new Stack()
  var balanced = true
  var index = 0
  while (index < symbolstring.length && balanced) {
    var symbol = symbolstring[index]
    if ('([{'.indexOf(symbol) != -1) {
      s.push(symbol)
    } else {
      if (s.isEmpty()) {
        balanced = false
      } else {
        var top = s.pop()
        if (!matches(top, symbol)) {
          balanced = false
        }
      }
    }
    index += 1
  }
  if (balanced && s.isEmpty()) {
    return true
  } else {
    return false
  }
}

1.1.4十进制转换为二进制

问题描述:

算法说明:

连续除以2求余数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUG7Uokx-1649231855044)(.\Python3 Data Structure-Linear Structure.assets\image-20211112085012636.png)]

实现

代码:Python

from Stack import Stack

def divideBy2(decNumber):
  remstack = Stack()

  while decNumber > 0:
    rem = decNumber % 2
    remstack.push(rem)
    decNumber //= 2
  
  binString = ''
  while not remstack.isEmpty():
    binString += str(remstack.pop())

  return binString

代码:JavaScript

function divideBy2(decNumber) {
  var remstack = new Stack()

  while (decNumber > 0) {
    var rem = decNumber % 2
    remstack.push(rem)
    decNumber = parseInt(decNumber / 2)
  }

  var binString = ''
  while (!remstack.isEmpty()) {
    binString += String(remstack.pop())
  }

  return binString
}

1.1.5 表达式转换-中缀转后缀

中缀表达式“(A+B)C”:
按照转换的规则,前缀表达式是“+ABC”,而后缀表达式是“AB+C

无论表达式多复杂,需要转换成前缀或者后缀,只需要两个步骤:
1.将中缀表达式转换为全括号形式;
2.将所有的操作符移动到子表达式所在的左括号(前缀)或者右括号(后缀)处,替代之,再删除所有的括号。

问题描述:

在中缀表达式转换为后缀形式的处理过程中,操作符比操作数要晚输出;
而这些暂存的操作符,由于优先级的规则,还有可能要反转次序输出。
后缀表达式中操作符应该出现在左括号对应的右括号位置

即:

在从左到右扫描逐个字符扫描中缀表达式的过程中,采用一个栈来暂存未处理的操作符
栈顶的操作符就是最近暂存进去的,当遇到一个新的操作符,就需要跟栈顶的操作符比较下优先级,再行处理。

算法说明:

前提说明
约定中缀表达式是由空格隔开的一系列单词(token)构成,
操作符单词包括*/±(),而操作数单词则是单字母标识符A、B、C等。

1.从左到右扫描中缀表达式单词列表:
如果单词是操作数,则直接添加到后缀表达式列表的末尾
如果单词是左括号“(”,则压入opstack栈顶
如果单词是右括号“)”,则反复弹出opstack栈顶操作符,加入到输出列表末尾,直到碰到左括号
如果单词是操作符“*/±”,则压入opstack栈顶

  • 但在压入之前,要比较其与栈顶操作符的优先级
  • 如果栈顶的高于或等于它,就要反复弹出栈顶操作符,加入到输出列表末尾
  • 直到栈顶的操作符优先级低于它

2.中缀表达式单词列表扫描结束后,把opstack栈中的所有剩余操作符依次弹出,添加到输出列表末尾

3.把输出列表再用join方法合并成后缀表达式字符串,算法结束。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eV61gdw1-1649231855046)(.\Python3 Data Structure-Linear Structure.assets\image-20211206092853785.png)]

实现

代码:Python

from Stack import Stack

def infixToPostfix(infixexpr):
  prec = {
    '*':3,
    '/':3,
    '+':2,
    '-':2,
    '(':1
  }
  opStack = Stack()
  postfixList = []
  tokenList = infixexpr.split()

  for token in tokenList:
    if token in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' or token in '0123456789':
      postfixList.append(token)
    elif token == '(':
      opStack.push(token)
    elif token == ')':
      topToken = opStack.pop()
      while topToken != '(':
        postfixList.append(topToken)
        topToken = opStack.pop()
    else:
      while (not opStack.isEmpty()) and (prec[opStack.peek()] >= prec[token]):
        postfixList.append(opStack.pop())
      opStack.push(token)
  while not opStack.isEmpty():
    postfixList.append(opStack.pop())
  return ''.join(postfixList)

代码:JavaScript

const {Stack} = require('./Stack')

function infixToPostfix(infixexpr) {
  let prec = {
    '*': 3,
    '/': 3,
    '+': 2,
    '-': 2,
    '(': 1,
  }
  let opStack = new Stack()
  let postfixList = []
  let tokenList = infixexpr.split(' ')

  for (let token of tokenList) {
    if (
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(token) != -1 ||
      '0123456789'.indexOf(token) != -1
    ) {
      postfixList.push(token)
    } else if (token == '(') {
      opStack.push(token)
    } else if (token == ')') {
      let topToken = opStack.pop()
      while (topToken != '(') {
        postfixList.push(topToken)
        topToken = opStack.pop()
      }
    } else {
      while (!opStack.isEmpty() && prec[opStack.peek()] >= prec[token]) {
        postfixList.push(opStack.pop())
      }
      opStack.push(token)
    }
  }
  while (!opStack.isEmpty()) {
    postfixList.push(opStack.pop())
  }
  return postfixList.join('')
}

1.1.6后缀表达式求值

问题描述:

在对后缀表达式从左到右扫描的过程中,由于操作符在操作数的后面,所以要暂存操作数,在碰到操作符的时候,再将暂存的两个操作数进行实际的计算(操作符只作用于离它最近的两个操作数)。

算法说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXJyaPc3-1649231855047)(.\Python3 Data Structure-Linear Structure.assets\image-20211207165115747.png)]

  1. 创建空栈operandStack用于暂存操作数
  2. 将后缀表达式用split方法解析为单词(token)的列表
  3. 从左到右扫描单词列表:
    • 如果单词是一个操作数,将单词转换为整数int,压入operandStack栈顶
    • 如果单词是一个操作符(*/±),就开始求值,从栈顶弹出2个操作数,先弹出的是右操作数,后弹出的是左操作数,计算后将值重新压入栈顶
  4. 单词列表扫描结束后,表达式的值就在栈顶
  5. 弹出栈顶的值,返回。

实现

代码:Python-1

from Stack import Stack

def postfixEval(postfixExpr)://输入的表达式中不能存在小数
  operandStack = Stack()
  tokenList = postfixExpr.split()

  for token in tokenList:
    if token in '+-*/':
      op2 = operandStack.pop()
      op1 = operandStack.pop()
      result = doMath(token,op1,op2)
      operandStack.push(result)
    else:
      operandStack.push(int(token))  
  return operandStack.pop()

def doMath(op,op1,op2):
  if op == '*':
    return op1 * op2
  elif op == '/':
    return op1 / op2
  elif op == '+':
    return op1 + op2
  else:
    return op1 - op2

代码:Python-2

from Stack import Stack

def postfixEval(postfixExpr):
  operandStack = Stack()
  tokenList = postfixExpr.split()

  for token in tokenList:
    if token in '+-*/':
      op2 = operandStack.pop()
      op1 = operandStack.pop()
      operandStack.push(eval(str(op1)+token+str(op2)))
    else:
      operandStack.push(eval(token))
  
  return operandStack.pop()

代码:JavaScript

const { Stack } = require('./Stack')

function doMath(op, op1, op2) {
  if (op == '*') {
    return op1 * op2
  } else if (op == '/') {
    return op1 / op2
  } else if (op == '+') {
    return op1 + op2
  } else {
    return op1 - op2
  }
}

function postfixEval(postfixExpr) {
  let operandStack = new Stack()
  let tokenList = postfixExpr.split(' ')

  for (let token of tokenList) {
    if ('+-*/'.indexOf(token) != -1) {
      let op2 = operandStack.pop()
      let op1 = operandStack.pop()
      let result = doMath(token, op1, op2)
      operandStack.push(result)
    } else {
      operandStack.push(parseInt(token))
    }
  }

  return operandStack.pop()
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值