栈与队列相关重点知识
==================

本篇的内容是栈和队列中的一些较为深入的内容，主要包括栈混洗问题以及中缀表达式求值。需要日后添加合并到`chp4/chp4.md`当中。

## 中缀表达式求值

> 特殊的运算符：阶乘和指数

对于阶乘运算符，关键在于这是一个单目运算符，并且具有最高的优先级。由于它是一个单目运算符，因此在计算时只需要对操作数栈进行一次弹栈；而由于它具有最高的优先级，因此如果它在操作符栈中，则必然存在于栈顶，并且只能有一个阶乘运算符在栈中。需要注意的是，阶乘运算符的优先级与左括号是不可比较的，因为阶乘之后不可以跟一个左括号。

指数运算符的优先级仅次于阶乘，它的特殊性在于在通常约定的语意下，指数运算符是右结合的（其他诸如加法、乘法运算符是左结合）。这就意味着`x^y^z`需要被理解为`x^(y^z)`而非`(x^y)^z`。为此，只需要对`prio`数组的这一项进行修改，使得`prio[POW][POW] = '<'`即可。

> 操作符栈的大小

以下仅考虑一个非常简单的字符集的情况，其中仅包含`+`, `-`. `*`, `/`, `^`, `(`, `)`, `\0`, `!`。

设表达式中的括号数量为`n`，则要使操作符栈达到最大，其中的组成应该大体是下面这样的模式：

```
--------------------------------------------------------------
| \0 | + | * | ^ | ( | + | * | ^ | ... | ( | + | * | ^ | !
--------------------------------------------------------------
bottom                                                     top
```

这里需要注意的点主要在于阶乘符号在栈中只能存在一个，且必须是在栈顶。因此，序列`( + * ^`一共会循环`n`次，再加上最前方的字符和最后面的`!`，操作符栈的最大值为`4n + 5`。同时也可以看出，在表达式不含括号时，操作符栈的大小为常数`O(1)`。

> 增加差错检验的功能

实际的计算器不光要能够求值，还要有差错检验的能力，即及时发现输入表达式的差错。否则，对于异常输入，程序往往会崩溃。同时对于某些特定的异常输入，却仍然可以计算出一个结果（尽管没有意义），例如`(12)3 + ! 4 * + 5`。

增加的差错检验的功能应该包含三个方面的内容：

+ 对括号匹配的检验。
+ 在进行实质的计算时，操作数栈不得为空。
+ 输入表达式是否符合中缀表达式的规范。

对于第三个方面，检验的方法是，在任意操作符即将入栈时（即操作符之间的优先级比较以及可能的弹栈与计算操作已经结束），操作数栈的规模应该恰好比操作符栈大1。需要注意，上述结论中，不应该计入`\0`以及`(`。

对这个结论可以利用数学归纳法证明，主要就是对单目运算符和双目运算符进行讨论，这里不再赘述。

> 表达式树

可以将中缀表达式和后缀表达式转化为表达式树。

对于中缀表达式，需要一个操作数栈和操作符栈。按照同样的方法遍历中缀表达式，操作数封装成单个的树节点入操作数栈，操作符按照优先级关系入栈出栈。对于出栈的双目运算符，弹出操作数栈中的两棵表达式树，以该运算符为根节点，两棵表达式树分别作为其左右子树，再将新得到的表达式入栈，如此不断进行下去即可。

对于后缀表达式，并不需要操作符栈，依序读到操作符，然后按照和中缀表达式同样的操作，即出栈两棵树，构造新树，然后再入栈，即可。

表达式树的中序遍历即为中缀表达式，后序遍历为后缀表达式。

## 卡特兰数

括号匹配问题，栈混洗问题以及异构二叉树的数量问题，都可以归入到卡特兰数的范畴，这里做一个简单的说明。

> 异构二叉树的数量

异构二叉树的数量应该是这里最明确的问题，设`SP(n)`表示节点数量为`n`的二叉树的异构数量，以其中一个节点为为根，设左子树的规模为`k`，则右子树的规模为`n - k - 1`，因此有

$$
SP(n) = \Sigma_{k = 0}^{n - 1}SP(k)SP(n - k - 1)
$$

其中，定义`SP(0) = 1`，这就是卡特兰数的递推公式。

> 括号匹配问题

对于括号匹配问题，设这里有`n`对括号，$S_n$为一个匹配的序列。我之前产生过一些错误的看法，比如可以把$S_n$分解为两个各自匹配的序列$S_k$和$S_{n - k}$，即

$$
S_n = S_k S_{n - k}
$$

此时是否可以得到

$$
SP(n) = \Sigma_{k = 0}SP(k)SP(n - k)
$$

答案当然是错误的，因为这里会产生重复计数的问题。比如设$S_3 = ()()()$，$S_1 = ()$，$S_2 = ()()$，那么$S_3 = S_1S_2 = S_2S_1$，会被计数两次。另一种划分$S_n = S_k()S_{n - k - 1}$也是同样的问题。

因此，这里正确的划分应该是$S_n = (S_k)S_{n - k - 1}$，容易证明，此时没有上面的重复计数情况。此时就可以得到

$$
SP(n) = \Sigma_{k = 0}^{n - 1}SP(k)SP(n - k - 1)
$$

该结论和二叉树的问题一致。实际上，考虑二叉树的一次中序遍历，如果把每个节点的入栈视作一个左括号，出栈视作右括号，此时两个问题是完全等效的。此时$S_n = (S_k)S_{n - k}$中包围$S_K$的括号，就对应了二叉树中根节点的入栈与出栈操作。

> 栈混洗

对于栈混洗问题也可以得到相似的结论。考虑`n`个元素的栈混洗，设首元素在第`k`个位置出栈。显然，首元素出栈后栈为空。此时不难得到

$$
SP(n) = \Sigma_{k = 0}^{n - 1}SP(k)SP(n - k - 1)
$$

实际上，把栈混洗的入栈视作一个左括号，出栈视作一个右括号，则栈混洗问题与括号匹配问题完全一致，与二叉树异构也是完全一致的。此时，首元素的入栈和出栈分别对应了根节点的入栈和出栈。
