postfix and prefix notation evaluating postfix expressions
play

Postfix (and prefix) notation Evaluating postfix expressions Also - PDF document

Postfix (and prefix) notation Evaluating postfix expressions Also called reverse Polish reversed form of Algorithm (start with an empty stack): notation devised by mathematician named Jan while expression has tokens {


  1. Postfix (and prefix) notation Evaluating postfix expressions � Also called “reverse Polish” – reversed form of � Algorithm (start with an empty stack): notation devised by mathematician named Jan while expression has tokens { Ł ukasiewicz (so really lü-kä-sha-vech notation) if next token is operand /* e.g., number */ � Infix notation is: operand operator operand push it on the stack; – Like 4 + 22 else /* next token should be an operator */ – Requires parentheses sometimes: 5 * (2 + 19) pop two operands from stack; � Postfix form is: operand operand operator perform operation; – So 4 22 + push result of operation on stack; } – No parentheses required: 5 2 19 + * pop the result; /* should be only thing left on stack */ � Prefix is operator operand operand : + 4 22 Postfix evaluation example Evaluating infix expressions � Simplest type: fully parenthesized � Expression: 5 4 + 8 * – e.g., ( ( ( 6 + 9 ) / 3 ) * ( 6 - 4 ) ) – Step 1: push 5 � Still need 2 stacks: 1 numbers, 1 operators – Step 2: push 4 – Step 3: pop 4, pop 5, add, push 9 while tokens available { if (number) push on number stack; – Step 4: push 8 if (operator) push on operator stack; – Step 5: pop 8, pop 9, multiply, push 72 if ( ‘(‘ ) do nothing; else { /* must be ‘)’ */ – Step 6: pop 72 – the result pop two numbers, and one operator; � A bad postfix expression is indicated by: calculate; push result on number stack; – Less than two operands to pop when operator occurs } } /* should be one number left on stack at end: the result */ – More than one value on stack at end Converting infix to postfix Infix to postfix (cont.) /* call current token the “new operator” */ � Operator precedence matters while (stack is not empty) – e.g., 3+(10–2)*5 � 3 10 2 - 5 * + { peek at top operator on stack; � Algorithm uses one stack; prints results if (top operator precedence (alternatively, could append results to a string) >= new operator precedence) – For each token in the expression: pop and print top operator; if ( number ) print it; else break out of while loop; } if ( ‘(‘ ) push on stack; push new operator on stack after loop ends; if ( ‘)’ ) – At end, pop and print all remaining operators pop and print all operators until ‘(‘; � This algorithm does not account for all bad expressions – discard ‘(‘; e.g., does not check for too many operators left at end if ( operator ) /* more complicated – next slide */ � But can verify that parentheses are balanced 1

  2. Queues Some queue applications � Many operating system applications Rear Front – Time-sharing systems rely on process queues � Often separate queues for high priority jobs that take little last enqueued 1st enqueued time, and low priority jobs that require more time (see last part of section 7.8 in text) last dequeued 1st dequeued – Printer queues and spoolers � Printer has its own queue with bounded capacity � FIFO data structure – First In, First Out � Spoolers queue up print jobs on disk, waiting for print queue � Typical operations: enqueue (an item at rear of – Buffers – coordinate processes with varying speeds queue), dequeue (item at front of queue), peek � Simulation experiments (at front item), empty , full , size , clear – Models of queues at traffic signals, in banks, etc., used to “see what happens” under various conditions – i.e., very similar to a stack – limited access to items A palindrome checker Implementing queues � Palindrome - same forward and backward � Easy to do with a list: – Mostly same as stack implementation – e.g., Abba, and “Able was I ere I saw Elba.” – Enqueue: insertLast(item, list); � Lots of ways to solve, including recursive – Then to dequeue and peek: refer to first item � Can use a queue and a stack together � Array implementation is trickier: – Fill a queue and a stack with copies of letters – Must keep track of front and rear indices – Then empty both together, verifying equality – Increment front/rear using modulus arithmetic � Reminder – we’re using an abstraction � Indices cycle past last index to first again – idea is to reuse the beginning of the array after dequeues – We still don’t know how queues are implemented!!! – More efficient – but can become full To use them, it does not matter! � Usually okay, but some queues should be unbounded Linked lists revisited: variations Implementing “better” lists � Some are meant to speed up operations � Using double-linked lists – both harder and easier – e.g., O(n) complexity to access last item – Must keep track of twice as many pointers � Way to make it O(1): maintain pointer to last – easy and – Additional work required for most special cases worth it! – But easy insert before, traverse backwards, access last � Another way: circular, double-linked list – not so easy � Can use sentinel nodes that are hidden from user � Some are meant to increase usefulness – e.g., first and last sentinals – list is never really empty – e.g., circular list to solve Josephus problem � Eliminates lots of special cases – just have to “lie” to user – e.g., generalized lists (lists of lists – upcoming topic) – e.g., n th position sentinels – to speed access to i th item � Trade-offs: use more space, harder to program � Usually trading off: speed ↔ space ↔ effort 2

  3. Is <string.h> an ADT? Generalized lists � When list items may be sublists � Combined with (char *) data it is! � Easy to formalize – say String.h : – May also contain just single items – called “atoms” � Usually implement with union in node structure typedef char *String; /* the data type */ – e.g., instead of just info field, have info or sublist: int strlen(String); /* length of string */ int strcmp(String,String); /* compare 2 strings */ union SubNodeTag{ String strcpy(String,String); /* copy 2 nd to 1 st */ InfoPointer info; NodePointer sublist; /* and so on */ } SubNode; � Note what doesn’t matter: – Also need field to identify a node as atom or sublist – How strings are represented internally � Lots of applications – see text section 8.4 – How these functions are implemented Trees Binary trees � Each node can have 0, 1, or 2 children only root � i.e., a binary tree node is a subtree that is either R child of R empty, or has left and right subtrees internal – Notice this is a recursive definition S T – Concept: a leaf’s “children” are two empty subtrees node parent � Half (+1) of all nodes in full binary tree are leaves of Y, Z – All nodes except leaves have 2 non-empty subtrees X U V W – Exactly 2 k nodes at each depth k, ∀ k < (leaf level) � A complete binary tree satisfies two conditions – Is full except for leaf level Y Z leaf – All leaves are stored as far to the left as possible descendants of S Representing trees by links Traversing binary trees � Much more flexible than array representation � Example: an expression tree (a type of “parse tree” built – Because most trees are not as “regular” as heaps (later) by advanced recursion techniques discussed in chapter 14) – Array representation usually wastes space, and does representing this infix expression: 4 + 7 * 11 not accommodate changes well � Binary tree node has two links, one for each child � Infix is in-order traversal + typedef struct treenode { – Left subtree � node � right subtree DataType info; /* some defined data type */ � But can traverse in other orders struct treenode *left; /* one child */ 4 * – Pre-order: node � left � right, struct treenode *right; /* other child */ gives prefix notation: + 4 * 7 11 } TreeNode, *TreeNodePointer; /* types */ 7 – Post-order: left � right � node, � Not a binary tree? – keep list of children instead 11 gives postfix notation: 4 7 11 * + 3

Recommend


More recommend