add a function generateLongExpr in stackApp.c(h). Add a comprehensive conclusion on calc infix & catalan.

This commit is contained in:
Shine wOng
2019-12-10 16:36:31 +08:00
parent ac443f791a
commit 59df68904c
4 changed files with 116 additions and 4 deletions

83
thu_dsa/chp4/notice.md Normal file
View File

@@ -0,0 +1,83 @@
栈与队列相关重点知识
==================
本篇的内容是栈和队列中的一些较为深入的内容,主要包括栈混洗问题以及中缀表达式求值。需要日后添加合并到`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}$,容易证明,此时没有上面的重复计数情况。此时就可以得到
$$
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)
```
实际上,把栈混洗的入栈视作一个左括号,出栈视作一个右括号,则栈混洗问题与括号匹配问题完全一致,与二叉树异构也是完全一致的。此时,首元素的入栈和出栈分别对应了根节点的入栈和出栈。

View File

@@ -142,6 +142,22 @@ char* toPostfix(char* infixExpr){
}
return postfixExpr;
}
/*
* @brief : to generate a very long expression in the pattern '1+0*1^(1+0*1^(1+0*1^(......)'
* @args : number of brackets in the expression
* @return: pointer to the long expression
*/
char* generateLongExpr(int n){
char* expr = new char[8 * n + 8];
int pos = 0;
for(int ix = 0; ix != n; ix++, pos += 7)
memcpy(expr + pos, "1+0*1^(", 7);
expr[pos++] = '1';
for (int ix = 0; ix != n; ++ix)
expr[pos++] = ')';
expr[pos] = '\0';
return expr;
}
/*-----------build-in functions----------*/
int readNumber(char* &expr){

View File

@@ -6,9 +6,9 @@
#define NOPTR 9
static char digits[] = { '0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f','g','h','i','j',
'k','l','m','n','o','p','q','r','s','t',
'u','v','w','x','y','z' };
'a','b','c','d','e','f','g','h','i','j',
'k','l','m','n','o','p','q','r','s','t',
'u','v','w','x','y','z' };
/*
* @brief : convert a decimal digit n to any base(2 <= base <= 36)
* @args :
@@ -52,4 +52,11 @@ double evaluate(char* infixExpr);
*/
char* toPostfix(char* infixExpr);
/*
* @brief : to generate a very long expression in the pattern 1+0*1^(1+0*1^(1+0*1^(......)
* @args : number of brackets in the expression
* @return: the long expression
*/
char* generateLongExpr(int n);
#endif

View File

@@ -10,7 +10,7 @@ void test_paren();
void test_evaluate();
void test_toPostfix();
int stackApp_test_main(){
int main(){
cout << "Running tests......" << endl;
test_convert();
test_paren();
@@ -38,6 +38,12 @@ void test_paren(){
void test_evaluate(){
assert(evaluate("2*5+(3+4-2*7)/2") == 6.5);
assert(evaluate("(0!+ 1) * 2 ^ (3!+ 4) - (5!- 67 - (8 + 9))") == 2012);
char* expr = generateLongExpr(1024);
//cout << expr << endl;
cout << evaluate(expr) << endl;
free(expr);
//cout << evaluate("1+2*3^(1+2*3)") << endl;;
//cout << evaluate("1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1+2*3^(1)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))") << endl;
}
void test_toPostfix(){