算法--堆栈应用(逆波兰式与四则运算求值实现)
逆波兰式
http://www.cnblogs.com/youxin/archive/2012/07/30/2615716.html
逆波兰式也叫后缀表达式(postfix)(将运算符写在操作数之后),相应的波兰表达式叫前缀表达式(运算符在操作数之前)。
中缀表达式(infix)是我们最常使用的。
如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是:ab+
四则运算求值
四则运算求值, 是典型的逆波兰式的应用, 也是编程例子经常拿来做堆栈讲解的例子。
arithematic expression is a portion of mathematic expression,
mathematic expression is defined as wiki
https://en.wikipedia.org/wiki/Expression_%28mathematics%29
In mathematics, an expression (or mathematical expression) is a finite combination of symbols that is well-formed according to rules that depend on the context.
Mathematical symbols can designate numbers (constants), variables, operations, functions, punctuation, grouping, and other aspects of logical syntax.
from the definition, we get several symbol that relate to arithmetic expresion:
1. number(constants) --- 123
2. operations --- + - * /
3. grouping --- parenthesis ()
算法过程
首先 将 中缀表达式(正常人习惯, 1 + 2), 转换为后缀表达式(堆栈结构 2 1 +)。
此过程需要两个栈, 一个是运算符缓存栈, 另一个是 后缀表达式栈(即,目标栈 存储最终结果 2 1 +)。
在 扫描中缀表达式过程中, 如果是数值, 存储到 后缀表达式栈,
如果遇到运算符, 先缓存到 运算符缓存栈, 此栈的压入 和 弹出, 遵循一些列优先级法则, 总体上是有两个中心思想:
1、 相同优先级运算符(相邻), 则后一个运算符, 后进栈,先出栈,
在后缀表达式栈中, 此运算符, 在前, 即拥有先计算的权利。
例如 a + b - c ---> a b c - +
当扫描 a b c - +时, 先计算 b -c,值为x, 然后计算 a + x。
对应 数学表达式中的 右结合律(right associative law):
https://en.wikipedia.org/wiki/Operator_associativity
Consider the expression
7 − 4 + 2
. The result could be either(7 − 4) + 2 = 5
or7 − (4 + 2) = 1
. The former result corresponds to the case when+
and−
are left-associative, the latter to when+
and-
are right-associative.
2、 不同优先级运算符(相邻), 高优先级运算符, 先出栈(不管高优先级运算符, 在前还是后),
例如
a + b * c ---> a b c * +
a * b + c ---> a b * c +
即时,优先级高的在后缀表达式栈中在前。 这样保证, 优先级高的先得到计算。
3、 对于grouping 改变优先级次序的括号, 即(), 其内部的运算符具有 比括号外部 的运算符, 更高的计算优先级。
例如
(a + b) * c ---> a b + c *
(a + b) * (c - d) ---> a b + c d - *
注意到, 按照树的视图来看, 还是左子树会被 先计算, 对应二叉树的后序遍历(对应这种运算符的名称, 后缀表达式)。
算法详情:
http://www.cnblogs.com/youxin/archive/2012/07/30/2615716.html
中缀表达式翻译成后缀表达式的方法如下:
(1)从右向左依次取得数据ch。
(2)如果ch是操作数,直接输出。
(3)如果ch是运算符(含左右括号),则:
a:如果ch = '(',放入堆栈。
b:如果ch = ')',依次输出堆栈中的运算符,直到遇到'('为止。
c:如果ch不是')'或者'(',那么就和堆栈顶点位置的运算符top做优先级比较。
1:如果ch优先级比top高,那么将ch放入堆栈。
2:如果ch优先级低于或者等于top,那么输出top,然后将ch放入堆栈。
(4)如果表达式已经读取完成,而堆栈中还有运算符时,依次由顶端输出。
其次 对 后缀表达式 堆栈从头进行扫描, 扫描过程计算值,表达式值。
完全遵循后序遍历的流程,扫描到最后得到 根节点(优先级最低的运算符)的运算值。
算法见:
http://www.cnblogs.com/youxin/archive/2012/07/30/2615716.html
(1)从左向右扫描表达式,一个取出一个数据data
(2)如果data是操作数,就压入堆栈
(3)如果data是操作符,就从堆栈中弹出此操作符需要用到的数据的个数,进行运算,然后把结果压入堆栈
(4)如果数据处理完毕,堆栈中最后剩余的数据就是最终结果。
C语言代码实践
https://github.com/fanqingsong/code-snippet/blob/master/C/arithmeticComputer/arithmeticComputer.c
---------》》》》基础数据结构:链表 《《《《 ---------------
链表形式, 便于节点扩充, 以解决表达式中, 元素数目不确定的情况。
链表节点:
typedef struct node{ T_LIST_LINKNODE tLinkNode; char* str; // record operation element (string type), including operand 1, operator +, bracket ( int number; // translate number string to int, and save operand to this } T_NODE, *PT_NODE;
链表操作按照linux的接口实现(双向链表):
// list node typedef struct ListLinkNode{ struct ListLinkNode* prev; struct ListLinkNode* next; } T_LIST_LINKNODE, *PT_LIST_LINKNODE; // initialize the head and tail of list head as self #define INIT_LIST_HEAD(ptListHead) do { \ (ptListHead)->next = (ptListHead);\ (ptListHead)->prev = (ptListHead); \ } while (0) // insert new link node between previous link node and next link node // note: if clause is must item to add node to empty list, otherwise list_add_tail error #define _list_add(newLink, prevLink, nextLink) do{\ (newLink)->next = (nextLink);\ (newLink)->prev = (prevLink);\ if ( (prevLink) == (nextLink) ){\ (prevLink)->next = (newLink);\ (prevLink)->prev = (newLink);\ }else{\ (prevLink)->next = (newLink);\ (nextLink)->prev = (newLink);\ }\ } while(0) // delete the specific link node from list #define list_del(ptLinkNode) do{\ (ptLinkNode)->prev->next = (ptLinkNode)->next;\ (ptLinkNode)->next->prev = (ptLinkNode)->prev;\ (ptLinkNode)->prev = NULL;\ (ptLinkNode)->next = NULL;\ } while(0) // add new list node to list head #define list_add_head(ptListHead, ptListNewLink) do {\ _list_add((ptListNewLink), (ptListHead), (ptListHead)->next);\ } while(0) // add new list node to list tail #define list_add_tail(ptListHead, ptListNewLink) do {\ _list_add((ptListNewLink), ((ptListHead)->prev), (ptListHead));\ } while(0) // list for each #define list_for_each(ptListHead, ptLinkNode) \ (ptLinkNode) = (ptListHead)->next;\ for(;(ptLinkNode) != (ptListHead); (ptLinkNode)=(ptLinkNode)->next) // list for each safe -- prevent breaking list after call list_del #define list_for_each_safe(ptListHead, ptLinkNode, ptNextCache) \ (ptLinkNode) = (ptListHead)->next;\ for(;(ptNextCache)=(ptLinkNode)->next, (ptLinkNode) != (ptListHead); (ptLinkNode)=(ptNextCache) )
对于具体的节点提供链表的操作的API
PT_NODE List_DetachFirstNode(PT_LIST_LINKNODE ptListHead) { PT_NODE ptNode = NULL; PT_LIST_LINKNODE ptLinkNode = NULL; if ( IsListEmpty(ptListHead) ) { return NULL; } ptLinkNode = ptListHead->next; list_del(ptLinkNode); ptNode = list_entry(ptLinkNode, T_NODE, tLinkNode); return ptNode; } PT_NODE List_DetachLastNode(PT_LIST_LINKNODE ptListHead) { PT_LIST_LINKNODE ptLinkNode = NULL; PT_NODE ptNode = NULL; if ( IsListEmpty(ptListHead) ) { return NULL; } ptLinkNode = ptListHead->prev; list_del(ptLinkNode); ptNode = list_entry(ptLinkNode, T_NODE, tLinkNode); return ptNode; } void List_AddNode2Head(PT_LIST_LINKNODE ptListHead, PT_NODE ptNode) { list_add_head(ptListHead, &(ptNode->tLinkNode)); } void List_AddNode2Tail(PT_LIST_LINKNODE ptListHead, PT_NODE ptNode) { list_add_tail(ptListHead, &(ptNode->tLinkNode)); }
---------》》》》高层数据结构:堆栈 《《《《 ---------------
在链表结构之上, 封装堆栈的API:
/*********************************************************************** stack api based list ************************************************************************/ E_BOOL_TYPE IsStackEmpty(PT_LIST_LINKNODE ptStack) { return IsListEmpty(ptStack); } void PushEleOnStack(PT_LIST_LINKNODE ptStack, PT_NODE ptNode) { list_add_tail(ptStack, &(ptNode->tLinkNode)); } E_RETURN_TYPE PushStrOnStack(PT_LIST_LINKNODE ptStack, char* str) { PT_NODE ptNode = NULL; //MyPrintf("Insert2List str is %s", str); ptNode = (PT_NODE) malloc(sizeof(T_NODE)); if ( !ptNode ) { return FALSE; } ptNode->str = strdup(str); if ( !(ptNode->str) ) { free(ptNode); return FALSE; } PushEleOnStack(ptStack, ptNode); return TRUE; } // get top stack element, not pop PT_NODE GetTopEleFromStack(PT_LIST_LINKNODE ptStack) { PT_LIST_LINKNODE ptLinkNode = NULL; PT_NODE ptNode = NULL; //MyPrintf("enter pop0"); if ( IsStackEmpty(ptStack) ) { return NULL; } //MyPrintf("enter pop"); ptLinkNode = ptStack->prev; ptNode = list_entry(ptLinkNode, T_NODE, tLinkNode); return ptNode; } // get top stack element, then pop it PT_NODE PopEleFromStack(PT_LIST_LINKNODE ptStack) { PT_NODE ptNode = NULL; ptNode = GetTopEleFromStack(ptStack); // top stack element, delete from stack if ( ptNode ) { list_del(&(ptNode->tLinkNode)); } return ptNode; }
---------》》》》逆波兰式堆栈生成 《《《《 ---------------
// translate arithmetic expression to reverse polish expression, ie postfix epression void Translate2PostfixExp(char* str, PT_LIST_LINKNODE ptStack) { T_LIST_LINKNODE tOperatorStack = {NULL}; PT_LIST_LINKNODE ptOperatorStack = &tOperatorStack; T_STRING_OBJ tStrObj = {0}; PT_STRING_OBJ ptStrObj = &tStrObj; T_STRING_OBJ tEleStrObj = {0}; PT_STRING_OBJ ptEleStrObj = &tEleStrObj; char cChar = 0; PT_NODE ptNode = NULL; int iIndex = -1; InitStrObjStruct(ptStrObj, str, FALSE); INIT_LIST_HEAD(ptOperatorStack); // analyze string, translate 2 polish expression InitStrObjStruct(ptEleStrObj, "", TRUE); while( GetOperEleString(ptEleStrObj, ptStrObj) ) { iIndex = findOprEleIndex(ptEleStrObj->szStr[0]); // element is illegal, ie, arithmetic is invalid , warning if ( g_oprEleType_attrs[iIndex].eOpType == OPERELE_TYPE_NONE ) { StopWithMsg("element is illegal, ie, arithmetic is invalid , warning"); } // handle this operation element, relate to Operator Stack and Stack( reverse polish expression) g_oprEleType_attrs[iIndex].doOpTypeAction(ptEleStrObj, ptOperatorStack, ptStack); //check following validity if ( !g_oprEleType_attrs[iIndex].checkFollowValid(ptStrObj) ) { MyPrintf("!! this element is (%s), the following is illegal. this expression is illegal !!", ptEleStrObj->szStr); } FreeStrObjMem(ptEleStrObj); InitStrObjStruct(ptEleStrObj, "", TRUE); } FreeStrObjMem(ptEleStrObj); // if oprands stack is not empty, pop and save // for example, a+b-c // stack is a b c // operand stack is + - // after clear operand stack, stack is a b c - + , // left operand is be first computed if operands with equal right is adjacent // math term is left associative law while( !IsStackEmpty(ptOperatorStack) ) { ptNode = PopEleFromStack(ptOperatorStack); PushEleOnStack(ptStack, ptNode); } FreeList(ptOperatorStack); ptOperatorStack = NULL; }
其中, 需要解析中缀表达式字符串中, 各种操作元素, 函数定义为
// get operation element string E_BOOL_TYPE GetOperEleString(PT_STRING_OBJ ptEleStrObj, PT_STRING_OBJ ptStrObj) { int iIndex = 0; char cChar = 0; char* szCharSet = NULL; if ( !ptEleStrObj || !ptStrObj ) { return FALSE; } cChar = GetCharFromStr(ptStrObj); if ( !cChar ) { return FALSE; } iIndex = findOprEleIndex(cChar); // char is illegal, ie, arithmetic is invalid , warning if ( g_oprEleType_attrs[iIndex].eOpType == OPERELE_TYPE_NONE ) { StopWithMsg("char(%c) is illegal, ie, arithmetic is invalid , warning", cChar); } // save first element char AddChar2StrObj(ptEleStrObj, cChar); // if operation element char set can be multiple, detect whether next char is in this char set if ( g_oprEleType_attrs[iIndex].tOperEleCharSet.isMultiple ) { szCharSet = g_oprEleType_attrs[iIndex].tOperEleCharSet.szOptChars; // detect whether next char is in this char set while( cChar = GetCharFromStr_purely(ptStrObj) ) { // in this char set!save it too. if ( strchr(szCharSet, cChar) ) { AddChar2StrObj(ptEleStrObj, cChar); // discard current char from string GetCharFromStr(ptStrObj); } else { // next char is not digit, do not read, break break; } } } return TRUE; }
获得到操作元素后, 需要对此操作元素进行处理, 并检查操作元素后续字符是否合法, 这些内容定义在一个表中:
解析过程的精髓全在此表。
from the definition, we get several symbol that relate to arithmetic expresion: 1. number(constants) --- 123 2. operations --- + - * / 3. grouping --- parenthesis () */ T_OPERELE_ATTRS g_oprEleType_attrs[] = { {OPERELE_TYPE_NUMBER, {"0123456789", TRUE}, doAction_number, checkFolowValid_number}, {OPERELE_TYPE_ADD, {"+", FALSE}, doAction_add_minus, checkFolowValid_operator}, {OPERELE_TYPE_MINUS, {"-", FALSE}, doAction_add_minus, checkFolowValid_operator}, {OPERELE_TYPE_MULTIPLE, {"*", FALSE}, doAction_multiple_divide, checkFolowValid_operator}, {OPERELE_TYPE_DIVIDE, {"/", FALSE}, doAction_multiple_divide, checkFolowValid_operator}, {OPERELE_TYPE_LEFT_BRACKET, {"(", FALSE}, doAction_left_bracket, checkFolowValid_left_bracket}, {OPERELE_TYPE_RIHGT_BRACKET, {")", FALSE}, doAction_right_bracket, checkFolowValid_right_bracket}, {OPERELE_TYPE_NONE, {NULL, FALSE}, NULL}, };
---------》》》》逆波兰式堆栈值 计算 《《《《 ---------------
从逆波兰式计算出 表达式的值
T_OPERATOR_ATTRS g_operator_atrrs[] = { {"+", operator_add}, {"-", operator_minus}, {"*", operator_multiple}, {"/", operator_divide}, {NULL, NULL}, }; OPERATOR_FUNC findOperatorMethod(char* szOperator) { int iIndex = 0; for ( ;g_operator_atrrs[iIndex].szOperator; iIndex++ ) { if ( !strcmp(szOperator, g_operator_atrrs[iIndex].szOperator) ) { return g_operator_atrrs[iIndex].operatorFunc; } } return NULL; } int ClacReversePlishExp(PT_LIST_LINKNODE ptListHead) { T_LIST_LINKNODE tNumberStack = {NULL, NULL}; PT_LIST_LINKNODE ptNumberStack = &tNumberStack; PT_NODE ptNode = NULL; PT_NODE ptOperandLeft = NULL; PT_NODE ptOperandRight = NULL; OPERATOR_FUNC operatorFunc = NULL; int ret = 0; INIT_LIST_HEAD(ptNumberStack); // from head, scan list (reverse polish expression) while( ptNode = List_DetachFirstNode(ptListHead) ) { // if node is number, push it on number stack if ( isdigit(ptNode->str[0]) ) { ptNode->number = atoi(ptNode->str); PushEleOnStack(ptNumberStack, ptNode); } // if node is operator, pop two element from reverse polish stack else if ( strchr("+-*/", ptNode->str[0]) ) { operatorFunc = findOperatorMethod(ptNode->str); if ( operatorFunc ) { ptOperandRight = PopEleFromStack(ptNumberStack); ptOperandLeft = GetTopEleFromStack(ptNumberStack); ret = operatorFunc(ptOperandLeft->number, ptOperandRight->number); // the element poped is obsolete, must be freed FreeListNode(ptOperandRight); // save the calc result to stack top element ptOperandLeft->number = ret; } } else { } } ptNode = List_DetachFirstNode(ptNumberStack); ret = ptNode->number; FreeListNode(ptNode); FreeList(ptNumberStack); return ret; }
可见整个实现过程, 是一个层层递进的过程, 如何保证每个层提供的接口是是OK的?
还是之前使用的TDD方法, 先从底层开始, 每做一层开发, 先将此层要实现的功能, 通过测试用例。
此层开发完毕, 然后在进行上层功能开发。
整改开发过程脉络 : linux list api --> LIST api --> stack api --> generate reverse polish expression --> calculate reverse polish expression
show pic: