树的应用-化学方程式配平器


由于疫情的影响,高考改到前几天进行。想起我参加高考已经过去6年了,然后想到之前写过一个化学方程式配平的小工具,就顺便拿出来讲讲~

当时做往年的高考真题卷,2个半小时的理综卷,我一般都是1个半小时(生物20min+化学30min+物理40min)就搞定了,考试前信心满满啊。结果到了高考理综的时候,可能是最后一科了,既兴奋且紧张,理综考卷勉强做完了。最终理综考砸了,sad~

好像有点走偏了主要是想吹嘘一下,下面开始进入正题吧~

项目代码传送门

1. 输入输出

>>> left, right = ('CH2OH(CHOH)4CHO', 'Ag(NH3)2OH'), ('CH2OH(CHOH)4COONH4', 'Ag', 'NH3', 'H2O')
>>> left_solve, right_solve = balance(left, right)
>>> left_solve
[1 2]
>>> right_solve
[1 2 3 1]
>>> get_text(left, right, left_solve, right_solve)
1CH2OH(CHOH)4CHO + 2Ag(NH3)2OH = 1CH2OH(CHOH)4COONH4 + 2Ag + 3NH3 + 1H2O

下面讲解都基于上述例子。

这个配平器是基于质量守恒定律,可以支持绝大部分的化学方程式配平,但不支持下面情况的化学方程式。

  • 离子方程式

    例如,Fe + 2Fe3+ = 3Fe2+ 这种方程式暂时不在考虑的范围之内。

  • 符合质量守恒定律但不一定会符合电荷守恒定律的方程式

    例如

    3H2O2 + 2KMnO4 + 3H2SO4 = 4O2 + K2SO4 + 2MnSO4 + 6H2O
    5H2O2 + 2KMnO4 + 3H2SO4 = 5O2 + K2SO4 + 2MnSO4 + 8H2O
    7H2O2 + 2KMnO4 + 3H2SO4 = 6O2 + K2SO4 + 2MnSO4 + 10H2O
  • 左右两边存在相同的物质

    例如生物层面上的呼吸作用表达式

    C6H12O6 + 6H2O + 6O2 = 6CO2 + 12H2O

2. 输入字符串解析

我把每个输入物质的字符串分成3个部分:

  • 化学元素值。每个元素的首字母为大写,其余字母为小写
  • 左右括号。括号内的内容可以看出一个整体的元素。
  • 数量值(可省略)。跟在每个元素和右括号后面,表示该元素有多少个。该值可以省略,且默认为1

3. 构建元素生成树

每个输入的字符串构建一棵树(这里我没有选择建立二叉树,因为并没有涉及到运算符操作),建树的流程如下:

  1. 定义两个缓存数据域,用于缓存化学元素和数量值。定义节点结构,由3份内存空间组成——数据域,父节点指针,子节点域,其中数据域由元素和数值组成。生成一个数据域的数值为1的节点作为当前节点。然后从左往右遍历输入字符串。
  2. 遇到大写字母时,
  3. 如果当前元素缓存域为空,则将该字符压入元素缓存域,否则,则说明这是一个新的元素的开端,则先把元素和数字缓存域全部压出,分别作为数据域的元素和数值(如果数字域为空,则默认置1),以此生成一个新的节点,把该节点的父节点指针指向当前节点,并作为当前节点的子节点。
  4. 最后再将该字符压入元素缓存域。
  5. 遇到小写字母时,压入元素缓存域。
  6. 遇到数字时,压入数字缓存域。
  7. 遇到左括号时,重复步骤3,然后生成一个新的节点,把该节点的父节点指针指向当前节点(注意这里并不会立即把该节点作为当前节点的子节点,因为数字域的数值还没确定),最后把该节点作为当前节点。
  8. 遇到右括号时,重复步骤3,然后把当前节点压入元素缓存域,最后把当前节点的父节点作为当前节点。
  9. 重复步骤2-8,直到字符串遍历完毕,如果元素缓存域不为空,则重复3.

通过上述流程,Ag(NH3)2OH 的生成树如下:

[[node,1]]
   |
[[Ag,1],[node,2],[O,1],[H,1]]
          |
        [[N,1],[H,3]]

4. 解析生成树

深度优先遍历每棵生成树,并把相同的元素合并。

这一部分比较简单,看代码理解即可。

Ag(NH3)2OH 的遍历结果如下

{'Ag': 1, 'O': 1, 'H': 7, 'N': 2}

5. 线性代数配平

为每个元素构建如下方程式, \[ \vec{A} \vec{X} - \vec{B} \vec{Y} = 0 \] 其中,\(\vec{A} = [a_1,a_2...a_i]\)\(\vec{B} = [b_1,b_2...b_j]\) 分别表示左右输入中每一个输入物质中包含该元素的数量。

\(n\) 个元素即可得到一个具有 \(n\) 个方程的方程组。

拼接 \(\vec{A}\)\({\vec{B}}\) ,得到矩阵行列式,上面例子中得到的行列式为

[[  0   1   0  -1   0   0]
 [  0   2  -1   0  -1   0]
 [  6   0  -6   0   0   0]
 [  6   1  -7   0   0  -1]
 [ 12   7 -15   0  -3  -2]]

\(n \ge i+j-1\) 时,方程有唯一解。

解上述矩阵行列式的通解和特解,可得方程的解为

[1. 2. 1. 2. 3. 1.]

6. 化整

最后得出来的解可能是浮点型形式,需要将其全部转化为整型形式。问题就转化为求这些浮点型的最大公约数。

一般的做法是先把小数转化为分数,然后求分母的最小公倍数和分子的最大公约数的比值,但因为精确度的原因,小数转化为分数在计算机层面上很难实现(据说python有现成的包,但我还没有试过其实还是比较懒)。

由于化学配平的系数中,最小一项基本都是小于100,所以最后索性写了个暴力化整的方法,遍历100以内所有最小一项系数的整数形式,输出使所有系数都为整型的最小的结果即可。


评论
  目录