了解编程中的运算顺序

介绍

作为一个编码器,你可能习惯告诉计算机要做什么,输入一些代码,运行它,计算机就会执行任何你给出的命令。

尽管我们拥有这种强大的计算机统治权,但我们的代码中仍然存在着大量的魔力,我们倾向于忽略它,特别是如果您正在使用具有预先构建功能的高级语言,就像我们大多数人一样。

在本文中,您将仔细研究这些概念之一,您可能在某个时候都使用过:操作顺序。

假设您想要评估此样本表达式:

15 + 10 * 3

按照操作的数学顺序,你会先将10倍增至3个,然后将5添加到结果中,但你会如何准确地告诉计算机这样做?

有不同的方法可以解析这种方程式,但有些需要比其他人更多的背景。

本教程将将方程式转换为正确的格式. 一旦它处于更易于机器读的形式,那么你可以通过你的解析算法来输送它,该算法将计算它。

了解Infix到Postfix

虽然您可能还没有意识到,但您可能已经熟悉 infix 标注

15 + 10 * 3

这意味着操作员落在他们正在运作的操作符之间。

什么是Postfix Notation?

如前所述,您需要将方程式转换为计算机可以理解的格式. 此格式称为 postfix notation

在 postfix 标注中写的表达式将有所有运算符跟随其运算符。

这很重要,因为当机器在读取此格式的表达式时,它将永远不会在操作符之前遇到一个操作符,这意味着它不必向前移动。

然后是样本表达式:

15 + 10 * 3

变成:

15 10 3 * +

这可能看起来不寻常,但有方法来达到这一点。

人类友好的方式从Infix转换到Postfix

在序列中以优先级列入:

1(5 + (10 * 3))

将每个运算器移动到右侧,直接在其关闭方针之前:

1(5 (10 3 *) +)

现在完全放下关节,这会让你在postfix标注中的表达式:

15 10 3 * +

以下是另一个示例,表明操作员不一定总是在最后:

18 * 4 + 2
2    ((8 * 4) + 2)
3    ((8 4 *) 2 +)
4    8 4 * 2 +

再次,这对计算机来说并不理想,它仍然不知道在哪里放置栏,幸运的是,有一个算法可以产生相同的结果。

使用 Shunting Yard 算法

Shunting Yard 算法由 Dijkstra 开发,用来将 infix 标注转换为 postfix 标注。

在你更进一步之前,让我们快速审查你将在这里使用的两个数据结构:堆栈和队列. 你可以使用数组来保持这两个数据集. 主要的区别来自 the order 你正在添加和删除数据。

队列:当你将数据添加到队列中时,你会将其推到后面。想象一下,你正在排队参加一个事件,而队列中的每个人都是队列中的一个元素。当你走到队列中时,你会自动插入队列的后面。当事件开始让人们进入(从队列中删除元素),他们会从队列的前面拉动,因为这些人已经在队列中了很长时间。

Stack:每次你添加一个新的元素到堆栈,它将放在顶部(或在前面)而不是在后面。当你想从堆栈中删除一个元素时,你会跳出顶部的元素。

<$>[注] **注:**本教程的其余部分将使用 pushpop 术语用于堆栈。

对于这个算法,假设你有一个临时堆栈来持有操作员(operator stack)和一个队列,将持有最终结果。

如何运作

Shunting Yard 算法遵循四个基本步骤:

如果你看到一个操作员 3. 如果你看到一个操作员:

  • 如果操作员堆是空的,把入口的操作员推到操作员堆
  • 如果入口的操作员比目前在操作员堆的顶部具有更高的优先级,那么把入口的操作员推到堆的顶部
  • 如果入口的操作员有相同的优先级,从堆中跳出顶部的操作员,输出到排队,把入口的操作员推到堆
  • 如果入口的操作员从堆顶部有较低的优先级,输出到排队,并测试入口的新的操作员从 stack_M_KBR1(

使用Shunting Yard算法评估一个Infix表达式

没有在行动中看到这些步骤是很难理解的,所以让我们通过上一个例子去尝试用算法来格式化它!

将该方程式从 infix 标注转换为 postfix 标注:

15 + 10 * 3

让我们设置两个数组:一个用于结果输出和一个用于临时操作员堆栈:

1expression = 5 + 10 * 3
2    output = []
3    operator stack = []

首先,你开始从左到右读出表达式,所以首先你有‘5’。

1expression = + 10 * 3
2    output = [5]
3    operator stack = []

接下来,你会看到+。操作员堆栈是空的,所以你可以把它推到那里:

1expression = 10 * 3
2    output = [5]
3    operator stack = [+]

接下来是10,所以你会立即输出:

1expression = * 3
2    output = [5, 10]
3    operator stack = [+]

现在您击中另一个操作员, *. 由于操作员堆栈不是空的,您必须将其与操作员堆栈的当前顶部进行比较,以查看哪一个具有更高的优先级。

堆栈的当前顶部是+。所以比较两者,你知道增量比增量具有更高的优先级。

这意味着你可以将其推到堆栈的顶部,这会给你:

1expression = 3
2    output = [5, 10]
3    operator stack = [*, +]

现在你击中你的最终值, 3. 因为这不是一个运算器,你可以立即输出它:

1expression is now empty
2    output = [5, 10, 3]
3    operator stack = [*, +]

由于表达式现在是空的,剩下的只是从操作员堆栈中跳出所有代币并立即输出它们. 当你从堆栈中跳出时,你正在从顶部抓取,所以首先,你会拿到*来推到队列的尽头,然后你会拿到+

1output = [5, 10, 3, *, +]

正如你所看到的,它匹配了你添加注释的上一个方法,但这种方式对于计算机来说更容易。

优先规则

你可能已经注意到有一点,而不是使用算法来决定,你依靠自己的知识来选择下一步要做的事情:确定哪个操作器具有更高的优先级。

当你了解算法背后的概念时,这并不重要,但当你写下实际代码来解决这个问题时,你将不得不建立在某些优先规则中。

您必须创建一个对象,该对象将基本上排名每个操作员. 您将为倍增和分割操作员提供2级,而添加和减去操作员为1级。

当你编码它时,你会通过比较他们的数字排名来比较两个操作员. 这里的实际数字1和2是任意的,所以不要太陷入其中。

1const precedence = {
2        "*": 2,
3        "/": 2,
4        "+": 1,
5        "-": 1
6    };

评估 postfix 表达式

您终于在 postfix 标注中获得了表达式,现在,您可以使用此格式来评估它。

算法评估表达式

以下是你将如何做到这一点:

  1. 开始用一个空堆
  2. 解析表达式中的第一个代币
  3. 如果它是操作员,请将其推到堆
  4. 如果它是操作员,请将适当数量的操作员从堆放到临时变量中(例如,倍数是二进制操作员,所以如果您正在解析并击中一个倍数操作员,那么您可以解除两个操作员)。
  5. 使用当前操作员和两种操作员来评估这个表达式( _6)。

在示例中,你只处理二进制操作员,所以当你看到一个操作员时,你总是可以打开两个操作员。

通过算法行走

让我们走过一些伪代码,在那里我们使用算法来评估样本修复后标注表达式:

15 10 3 * +

首先,您可以通过将每个操作员推到堆栈,直到您击中一个操作员:

1expression = [5, 10, 3, *, +]
2
3    - push 5
4    - push 10
5    - push 3
6
7    stack = [3, 10, 5]

所以现在你到达你的第一个操作员, *,这意味着是时候开始出现了. 你会出现,直到你有两个值:

1- pop 3
2    - pop 10

现在你有你的两个操作符,310,所以你会将这与你的操作符*相结合,留给你10 * 3:

1expression = [+]
2    stack = [5]
3
4    tempOperand1 = 3
5    tempOperand2 = 10
6
7    tempOperator = *
8
9    eval(tempOperand1 + tempOperator + tempOperand2) // 3 * 10

你评估了这一点,得到30,然后把它推回堆栈。

1expression = [+]
2    stack = [30, 5]

所以你再次开始分析表达式,你立即击中一个操作员. 再次,你必须从堆栈中跳出,直到你有两个操作员:

1expression = []
2    stack = []
3
4    tempOperand1 = 30
5    tempOperand2 = 5
6
7    tempOperator = + 
8
9    eval(tempOperand1 + tempOperator + tempOperand2) // 30 + 5

你打开305,你已经准备好再次评估。5 + 30给你35,你现在可以把它推回堆栈。

回到原始表达式来分析下一个代币,你会发现它是空的!

1expression = []
2    stack = [35]

这要么意味着你已经完成了,要么原来的表达方式被扭曲了。

它只有一个值,所以这意味着你完成了,‘35’是原式表达式的最终输出,‘5 + 10 * 3’。

识别前缀笔记

prefix notation 中评估一个表达式的算法基本相同,但这一次你将从右到左读取。

如果您回到原始方法中添加和移动操作员,您可以将其转换为前缀符号,就像您 postfix 的方式一样。而不是将操作员移动到其操作员的末尾,您会将其移动到开始。

15 + 10 * 3
2    (5 + (10 * 3))
3    (+ 5 (* 10 3))
4    + 5 * 10 3

如果你想测试你的知识,试着找出你将如何在算法上做到这一点,通过对Shunting Yard算法进行小的修改。

结论

在本教程中,您创建了一种将表达式转换为后修复符号的算法,并通过评估一个表达式来测试它。

Published At
Categories with 技术
comments powered by Disqus