From 59df68904cc0aafefab5428aa4c19c59b9cbdf8a Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Tue, 10 Dec 2019 16:36:31 +0800 Subject: [PATCH] add a function generateLongExpr in stackApp.c(h). Add a comprehensive conclusion on calc infix & catalan. --- thu_dsa/chp4/notice.md | 83 +++++++++++++++++++++++++++++++++++ thu_dsa/chp4/stackApp.cpp | 16 +++++++ thu_dsa/chp4/stackApp.h | 13 ++++-- thu_dsa/chp4/testStackApp.cpp | 8 +++- 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 thu_dsa/chp4/notice.md diff --git a/thu_dsa/chp4/notice.md b/thu_dsa/chp4/notice.md new file mode 100644 index 0000000..43ef0c5 --- /dev/null +++ b/thu_dsa/chp4/notice.md @@ -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) +``` + +实际上,把栈混洗的入栈视作一个左括号,出栈视作一个右括号,则栈混洗问题与括号匹配问题完全一致,与二叉树异构也是完全一致的。此时,首元素的入栈和出栈分别对应了根节点的入栈和出栈。 diff --git a/thu_dsa/chp4/stackApp.cpp b/thu_dsa/chp4/stackApp.cpp index 0b8813f..996b43b 100644 --- a/thu_dsa/chp4/stackApp.cpp +++ b/thu_dsa/chp4/stackApp.cpp @@ -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){ diff --git a/thu_dsa/chp4/stackApp.h b/thu_dsa/chp4/stackApp.h index f81b88e..67c848b 100644 --- a/thu_dsa/chp4/stackApp.h +++ b/thu_dsa/chp4/stackApp.h @@ -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 diff --git a/thu_dsa/chp4/testStackApp.cpp b/thu_dsa/chp4/testStackApp.cpp index 79fd385..c342226 100644 --- a/thu_dsa/chp4/testStackApp.cpp +++ b/thu_dsa/chp4/testStackApp.cpp @@ -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(){