add a function generateLongExpr in stackApp.c(h). Add a comprehensive conclusion on calc infix & catalan.
This commit is contained in:
83
thu_dsa/chp4/notice.md
Normal file
83
thu_dsa/chp4/notice.md
Normal 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)
|
||||
```
|
||||
|
||||
实际上,把栈混洗的入栈视作一个左括号,出栈视作一个右括号,则栈混洗问题与括号匹配问题完全一致,与二叉树异构也是完全一致的。此时,首元素的入栈和出栈分别对应了根节点的入栈和出栈。
|
||||
@@ -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){
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(){
|
||||
|
||||
Reference in New Issue
Block a user