Stacks, Queues and Resursion
Stack • A stack is also known as a Last-In-First-Out (LIFO) list. • A stack is a linear structure in which items are added or removed only at one end the top. • The depth of stack is the number of elements it contains. • An empty stack has depth zero.
Stack Example
Stack Operations • Some stack operations: – push - add an item to the top of the stack – pop - remove an item from the top of the stack – peek (or top) - retrieves the top item without removing it – empty – returns false if stack is empty(stack is empty, can't pop) – full- returns true if stack is full(stack is full, can't insert new element)
Implementing Stack using Array STACK xxx
1
yyy
2
TOP
zzz
3
3
4
5
6
7
MAXSTK
8
8
PUSH() • This procedure pushes an ITEM onto a stack PUSH(STACK,TOP,MAXSTK,ITEM) 1.
IF TOP = MAXSTK, then
Print : Overflow, and Return 1. 2. 3.
Set TOP = TOP + 1 [Increase TOP by 1] Set STACK[TOP]= ITEM [Insert ITEM in new TOP position] Return
POP() • This procedure deletes the top element of stack and assigns it to the variable ITEM POP(STACK,TOP,ITEM) 1.
IF TOP = 0, then
Print : Underflow, and Return 1. 2. 3.
Set ITEM = STACK[TOP] [Assigns TOP element to ITEM] Set TOP= TOP - 1 [Decreases TOP by 1] Return
Limitations • Limitations – The maximum size of the stack must be defined a priori and cannot be changed – Trying to push a new element into a full stack causes an implementation-specific exception
Question • Suppose STACK is allocated N=6 memory cells and initially STACK is empty. Find the output of the following procedure. 1. Set A=2 and B=5 2. Call PUSH(STACK,A) Call PUSH(STACK,4) Call PUSH(STACK,B+2) Call PUSH(STACK,9) Call PUSH(STACK,A+B) 1. Repeat while TO!= 0 Call POP(STACK,ITEM) End of lopp
Queue • A queue is also known as a First-InFirst-Out (FIFO) list. • A queue is a linear list of elements in which deletions can take place only at one end, called the front, and insertion can take place only at the other end called the rear.
Queue Operations Some queue operations: – insert - add an item into a queue – delete - deletes an item from the queue – empty - returns false if the queue is empty – full-return true if queue is full
Enqueue and Dequeue • Primary queue operations: Enqueue and Dequeue • Like check-out lines in a store, a queue has a front and a rear. • Enqueue – Insert an element at the rear of the queue • Dequeue – Remove an element from the front of the queue
Remove (Dequeue)
front
rear
Insert (Enqueue)
Implementing a Queue using Array Front:1, Rear:4 QUEUE AAA
1
BBB
2
CCC
DDD
3
4
Front:2, Rear:4 BBB
1
2
5
6
7
8
5
6
7
8
QUEUE CCC
DDD
3
4
Implementing a Queue using Array
Front:2, Rear:6
BBB
1
2
QUEUE
CCC
DDD
EEE
FFF
3
4
5
6
Front:3, Rear:6
1
2
7
8
7
8
QUEUE CCC
DDD
EEE
FFF
3
4
5
6
Circular Queue • Suppose we want to insert an element into the queue after it occupies the last part of the array i.e. REAR=n. • One way of doing it is simply move the entire queue to the beginning of the array, changing FRONT and REAR, and then inserting the element. This procedure may be very expensive. • This can be done assuming that the array QUEUE is circular that is the QUEUE[1] comes after QUEUE[N] in the array. Instead if increasing REAR to N+1 we reset REAR=1.
QINSERT • This procedure inserts an element ITEM into a queue. QINSERT(QUEUE,N,FRONT,REAR,ITEM) [Queue already fill] 1. If FRONT=1 and REAR=N, or if FRONT=REAR+1,then Write: Overflow, and Return
1. [Find new value of REAR] If FRONT=NULL, then [Queue initially empty] Set FRONT=1 and REAR=1 Else if REAR =N then Set REAR=1 Else Set REAR=REAR+1 [End of if structure]
1. Set QUEUE[REAR]=ITEM [This inserts new element] 2. Return
QDELETE • This procedure deletes an element from the queue and assigns it to the variable ITEM. QDELETE(QUEUE,N,FRONT,REAR,ITEM) [Queue already empty] 1. If FRONT=NULL, then Write: Underflow, and Return
1. Set ITEM=QUEUE[FRONT] 2. [Find new value of FRONT] If FRONT=REAR, then [Queue has only one element to start] Set FRONT=NULL and REAR=NULL Else if FRONT =N then Set FRONT =1 Else Set FRONT = FRONT +1 [End of if structure]
1. Return
Question • Consider a circular queue with six memory cells. QUEUE: _,A,C,D,_,_ Front=2,REAR=4 a) b) c) d) e) f) g) h) i) j)
Add F Two letters are Add K,L and M Two letters are Add R Two letters are Add S Two letters are One letters are One letters are
delete delete delete delete delete delete
Double-Ended-QUE • A deque is a double-ended que • Insertions and deletions can occur at either end but not in the middle • Implementation is similar to that for que • Deque are not heavily used
Double-Ended-QUE Left:4, Right:7
1
DEQUE
2
3
Left:7, Right:2 YYY
1
AAA
BBB
CCC
DDD
4
5
6
7
DEQUE
ZZZ
2
8
WWW
3
4
5
6
7
XXX
8
Double-Ended-QUE • There are two variations of deque – Input-restricted deque
An input restricted deque is a deque which allows insertion at only one end of the list but allows deletion a both end of the list. – Output-restricted deque
An outut restricted deque is a deque which allows deletion at only one end of the list but allows insertin a both end of the list.
Question • Consider a circular DEQUE with six memory cells. DEQUE:_,A,C,D,_,_ LEFT=2, RIGHT=4 a)Add F to the right b)Two letters are deleted from the right c)K,L, and M are added to the left d)One letter on the left is deleted e)R is added to the left f) S is added to the right g)T is added to the right
Priority Queues • A priority queue is a collection of elements such that each element has been assigned a priority and such that the order in which the elements are deleted and processed comes from the following rules: 1. An element of higher priority is processed before any element of lower priority. 2. Two elements of the same priority are processed according to the order in which they were add to the queue.
Priority Queues Operations • insert -Add a new item to the queue. • delete- Remove and return an item from the queue. The item that is returned is the one with the highest priority. • empty- Check whether the queue is empty.
Polish Notation • Polish notation named after Polish mathematician Jan Lukasiewiez, refers to the notation in which the operator symbol is placed before its operands. • Reverse Polish notation refers to the notation in which operator symbol is placed after its operands.
Evaluation of expression • An expression is made up of operands, operators, and delimiters. A/B-C+D*E-A*C • Arithmetic operators +,-,*,/, %, and unary minus • Relational operators <,<=,==,<.,>=, >, &&, ||, and !.
Priority of operators • To fix the order of evaluation, each operator is assigned a priority. • The C++ rule is that for all priorities, evaluation of operators of the same priority will proceed left to right. A/B*C will be evaluated as (A/B)*C. X=A/B-C+D*E-A*C will be evaluated as X=(((A/B)-C)+(D*E))-(A*C).
Priority of operators
Postfix notation • A compiler accepts an expression and produces correct code by reworking the expression into a form called postfix notation. • The conventional way of writing an expression is called infix- the operators come in-between the operands • Infix A*B/C has postfix AB*C/. Infix: A/B-C+D*E-A*C Postfix: AB/C-DE*+AC*-
Infix to Postfix: Example
Evaluating Arithmetic Expression • The computer usually evaluate an arithmetic expression written in infix notation in two steps: 1. First it converts the expression to postfix notation, and 2. Then it evaluates the postfix expression.
Evaluation of Postfix Expression • This algorithm finds the VALUE of an arithmetic expression P written in postfix notation. 1. 2. 3. 4.
Add aright parenthesis “)” at the end of P[This acts as a sentinel] Scan P from left to right and repeat step 3 and 4 for each element of P until the sentinel “)” is encountered. If an operand is encountered, put it on STACK If an operator Θ is encountered, then a) b) c)
Remove the top two elements of STACK Evaluate A Θ B Place the result of B back on STACK
[End of If structure] E[End of step 2 loop]
1. 2.
Set VALUE equal to the top element on STACK Exit
Transforming Infix Expression into Postfix Expression
• Suppose Q is an arithmetic expression written in infix notation. This algorithm finds the equivalent postfix expression P. 1. 2. 3. 4. 5.
POLISH(Q,P) Push “(“ onto STACK, and add “)” to the end of Q Scan Q from left to right and repeat Step 3 to 6 for each element of Q until the STACK is empty If an operand is encountered, add it to P If a left parenthesis is encountered, push it onto STACK If operator Θ is encountered, then a) Repeatedly pop from STACK and add to P each operator which has the same precedence as higher precedence than Θ b) Add Θ operator to STACK
[End of if structure] 6. If a right parenthesis is encountered, then a) Repeatedly pop from STACK and add to P each operator until a left parenthesis is encountered. b) Remove the left parenthesis [Do not add the left parenthesis to P]
[End of if structure] [End of step 2 loop] Exit
Quicksort Quick sort is an algorithm of divide and conquer type. That is the problem of sorting a set is reduced to the problem of sorting two smaller sets.
Example • •
•
•
Suppose A is the following list of 12 numbers 44,33,11,55,77,90,40,60,99,22,88,66 The reduction step of quick sort algorithm finds the final position of one of the numbers; in this we use the first number 44. Beginning with the last number, 66, scan the list from right to left, comparing each number with 44 and stopping at the first number less than 44. the number is 22. Interchange 44 and 22 to obtain the list. 22,33,11,55,77,90,40,60,99,44,88,66 Beginning with 22, next scan the list in opposite direction, from left to right, comparing each number with 44 and stopping at the first number greater than 44. The number is 55. Interchange 44 and 55 to obtain the list. 22,33,11,44,77,90,40,60,99,55,88,66 Beginning this time with 55, now scanning the list in original direction that is from right to left, until meeting the first number less than 44.It is 40. Interchange 44 and 40 to obtain the list. 22,33,11,40,77,90,44,60,99,55,88,66 22,33,11,40,44,90,77,60,99,55,88,66
Example •
Beginning with 77, scan the list from right to left seeking a number less than 44. We do not meet such number before meeting 44. This mean all the numbers have been scanned and compared with 44. All the numbers less tan 44 now form the sub list to the left of 44, and all numbers greater than 44 form the sub list to the right of 44 as shown below 22,33,11,40, 44, 90,77,60,99,55,88,66 First sublist Second sublist
• •
Thus 44 is correctly placed in its final position, and the task of sorting the original list A has been reduced to the task of sorting each of the above sub list. The above reduction step is repeated with each sub list containing 2 or more elements. Since we can process only one sub list at a time, we must be able o keep track of some sub lists for future processing. This is accomplished by processing two stacks, called LOWER and UPPER, to temporarily old such sub list. That is the address of the first and last elements of each sub list, called its boundary values, are pushed onto the stacks LOWER and UPPER, respectively; and the reduction set is applied to a sub list only after its boundary values are removed from the stack.
Quick sort Algorithm
This algorithm sorts an array A with N elements 1. [Initialize] TOP=NULL 2. [Push boundary values of A onto stack when A has 2 or more elements] If N>1, then TOP=TOP+1, LOWER[1]=1 and UPPER[1]=N 3. Repeat Step 4 to 7 while TOP!= NULL 4. [Pop sub list from stack] Set BEG=LOWER[TOP], END=UPPER[TOP] TOP=TOP-1 5. Call QUICK(A,N,BEG,END,LOC) 6. [Push left sub list onto stacks when it has 2 or more elements] If BEG
A is an array with N elements. Parameters BEG and END contain the boundary values of the sub list of A to which this procedure applies. LOC keeps track of the position of the first element A[BEG] of the sub list during the procedure. The local variables LEFT and RIGHT will contain the boundary values of the list of elements that have nor been scanned. QUICK(A,N,BEG,END,LOC)
1. [Initialize] Set LEFT=BEG, RIGHT= END and LOC=BEG 2. [Scan from right to left] a) Repeat while A[LOC]< A[RIGHT] and LOC!=RIGHT RIGHT=RIGHT-1 [End of Loop] b. If LOC=RIGHT, then Return c. If A[LOC]>A[RIGHT], then i. [Interchange A[LOC] and A[RIGHT], ] TEMP=A[LOC], A[LOC]=A[RIGHT] A[RIGHT]=TEMP ii. Set LOC=RIGHT iii. Go to step 3 [End of If Structure]
3. [Scan from left to right] a) Repeat while A[LEFT]< A[LOC] and LEFT!=LOC LEFT=LEFT+1 [End of Loop] b. If LOC=LEFT, then Return c. If A[LEFT]>A[LOC], then i. [Interchange A[LEFT] and A[LOC], ] TEMP=A[LOC], A[LOC]=A[LEFT] A[LEFT]=TEMP ii. Set LOC=LEFT iii. Go to step 2 [End of If Structure]
Question • Suppose S is the following list of alphabetic character: S=DATASTRUCTURES Use quick sort algorithm to find the final position of the first character D
CAADSTRUTTU RES
Quicksort Analysis • •
Assume that keys are random, uniformly distributed. What is best case running time? – Recursion: 1. Partition splits array in two sub-arrays of size n/2 2. Quicksort each sub-array
– Depth of recursion tree? O(log2n) – Number of accesses in partition? O(n)
Quicksort Analysis • •
Assume that keys are random, uniformly distributed. Best case running time: O(n log2n)
Quicksort Analysis •
Best case running time: O(n log2n)
•
Worst case running time? O(n2)
Recursion • A function is said to be recursively defined, if a function containing either a Call statement to itself or a Call statement to a second function that may eventually result in a Call statement back to the original function. •
A recursive function must have the following properties: 1. There must be certain criteria, called base criteria for which the function does not call itself. 2. Each time the function does call itself(directly or indirectly), the argument of the function must be closer to a base value.
Recursion • In some problems, it may be natural to define the problem in terms of the problem itself. • Recursion is useful for problems that can be represented by a simpler version of the same problem. • Example: the factorial function 6! = 6 * 5 * 4 * 3 * 2 * 1
We could write: 6! = 6 * 5!
Example 1: factorial function In general, we can express the factorial function as follows: n! = n * (n-1)!
The factorial function is only defined for positive integers. So we should be a bit more precise: if n<=1, then n! = 1 if n>1, then n! = n * (n-1)!
factorial function The C++ equivalent of this definition: int fac(int numb){ if(numb<=1) return 1; else return numb * fac(numb-1); }
recursion means that a function calls itself
factorial function • Assume the number typed is 3, that is, numb=3. fac(3) : 3 <= 1 ? No. fac(3) = 3 * fac(2) fac(2) : 2 <= 1 ? No. fac(2) = 2 * fac(1) fac(1) : 1 <= 1 ? Yes. return 1 int fac(int numb){ fac(2) = 2 * 1 = 2 if(numb<=1) return fac(2) return 1; else fac(3) = 3 * 2 = 6 return numb * fac(numb-1); return fac(3) } fac(3) has the value 6
factorial function For certain problems (such as the factorial function), a recursive solution often leads to short and elegant code. Compare the recursive solution with the iterative solution: Iterative
Recursive solution solution
int fac(int numb){ int fac(int numb){ if(numb<=1) int product=1; return 1; while(numb>1){ else product *= numb; return numb*fac(numb-1);numb--; } } return product; }
Recursion To trace recursion, recall that function calls operate as a stack – the new function is put on top of the caller We have to pay a price for recursion: – calling a function consumes more time and memory than adjusting a loop counter. – high performance applications (graphic action games, simulations of nuclear explosions) hardly ever use recursion.
In less demanding applications recursion is an attractive alternative for iteration (for the right problems!)
Recursion If we use iteration, we must be careful not to create an infinite loop by accident: for(int incr=1; incr!=10;incr+=2) ...
int result = 1; while(result >0){ ... result++; }
Recursion Similarly, if we use recursion we must be careful not to create an infinite chain of function calls: int fac(int numb){ return numb * fac(numb-1); }
Or: int fac(int numb){ if (numb<=1) return 1; else return numb * fac(numb+1); }
No termination condition
Recursion We must always make sure that the recursion bottoms out: • A recursive function must contain at least one non-recursive branch. • The recursive calls must eventually lead to a non-recursive branch.
Recursion • Recursion is one way to decompose a task into smaller subtasks. At least one of the subtasks is a smaller example of the same task. • The smallest example of the same task has a non-recursive solution. Example: The factorial function n! = n * (n-1)! and 1! = 1
Example 2: Fibonacci numbers
• Fibonacci numbers:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... where each number is the sum of the preceding two. • Recursive definition: – F(0) = 0; – F(1) = 1; – F(number) = F(number-1)+ F(number-2);
Example 3: Fibonacci numbers
//Calculate Fibonacci numbers using recursive function. //A very inefficient way, but illustrates recursion well
int fib(int number) { if (number == 0) return 0; if (number == 1) return 1; return (fib(number-1) + fib(number-2)); }
Trace a Fibonacci Number • Assume the input number is 4, that is, fib(4): 4 == 0 ? No; 4 == 1? No. fib(4) = fib(3) + fib(2) fib(3): 3 == 0 ? No; 3 == 1? No. fib(3) = fib(2) + fib(1) fib(2): 2 == 0? No; 2==1? No. fib(2) = fib(1)+fib(0) fib(1): 1== 0 ? No; 1 == 1? Yes. fib(1) = 1; return fib(1);
int fib(int num) { num=4:if (num == 0) return 0; if (num == 1) return 1; return (fib(num-1)+fib(num-2)); }
Trace a Fibonacci Number fib(0): 0 == 0 ? Yes. fib(0) = 0; return fib(0); fib(2) = 1 + 0 = 1; return fib(2);
fib(3) = 1 + fib(1) fib(1): 1 == 0 ? No; 1 == 1? Yes fib(1) = 1; return fib(1); fib(3) = 1 + 1 = 2; return fib(3)
Trace a Fibonacci Number fib(2): 2 == 0 ? No; 2 == 1? No. fib(2) = fib(1) + fib(0) fib(1): 1== 0 ? No; 1 == 1? Yes. fib(1) = 1; return fib(1); fib(0): 0 == 0 ? Yes. fib(0) = 0; return fib(0); fib(2) = 1 + 0 = 1; return fib(2); fib(4) = fib(3) + fib(2) = 2 + 1 = 3; return fib(4);
Fibonacci number w/o recursion //Calculate Fibonacci numbers iteratively //much more efficient than recursive solution
int fib(int n) { int f[n+1]; f[0] = 0; f[1] = 1; for (int i=2; i<= n; i++) f[i] = f[i-1] + f[i-2]; return f[n]; }
Example 3: Binary Search – Search for an element in an array • Sequential search • Binary search
– Binary search • Compare the search element with the middle element of the array • If not equal, then apply binary search to half of the array (if not empty) where the search element would be.
Binary Search with Recursion // Searches an ordered array of integers using recursion int bsearchr(const int data[], // input: array int first, // input: lower bound int last, // input: upper bound int value // input: value to find )// output: index if found, otherwise return –1
{
}
int middle = (first + last) / 2; if (data[middle] == value) return middle; else if (first >= last) return -1; else if (value < data[middle]) return bsearchr(data, first, middle-1, value); else return bsearchr(data, middle+1, last, value);
Binary Search int main() { const int array_size = 8; int list[array_size]={1, 2, 3, 5, 7, 10, 14, 17}; int search_value; cout << "Enter search value: "; cin >> search_value; cout << bsearchr(list,0,array_size-1,search_value) << endl; return 0; }
Binary Search w/o recursion // Searches an ordered array of integers int bsearch(const int data[], // input: array int size, // input: array size int value // input: value to find ){ // output: if found,return // index; otherwise, return -1
}
int first, last, upper; first = 0; last = size - 1; while (true) { middle = (first + last) / 2; if (data[middle] == value) return middle; else if (first >= last) return -1; else if (value < data[middle]) last = middle - 1; else first = middle + 1; }
Example 4: Towers of Hanoi 1 2 3 A
B
C
– Only one disc could be moved at a time – A larger disc must never be stacked above a smaller one – One and only one extra needle could be used for intermediate storage of discs
Towers of Hanoi • From the moves necessary to transfer one, two, and three disks, we can find a recursive pattern - a pattern that uses information from one step to find the next step. • If we want to know how many moves it will take to transfer 64 disks from post A to post C, we will first have to find the moves it takes to transfer 63 disks, 62 disks, and so on.
a) The initial state; b) move n - 1 disks from A to C
c) move one disk from A to B; d) move n - 1 disks from C to B
Towers of Hanoi • The recursive pattern can help us generate more numbers to find an explicit (nonrecursive) pattern. Here's how to find the number of moves needed to transfer larger numbers of disks from post A to post C, when M = the number of moves needed to transfer n-1 disks from post A to post C: • for 1 disk it takes 1 move to transfer 1 disk from post A to post C; • for 2 disks, it will take 3 moves: 2M + 1 = 2(1) + 1 = 3 • for 3 disks, it will take 7 moves: 2M + 1 = 2(3) + 1 = 7 • for 4 disks, it will take 15 moves: 2M + 1 = 2(7) + 1 = 15 • for 5 disks, it will take 31 moves: 2M + 1 = 2(15) + 1 = 31 • for 6 disks... ?
Towers of Hanoi Number of Disks (n) Number of Moves 1 21 - 1 = 2 - 1 = 1 2 22 - 1 = 4 - 1 = 3 3 23 - 1 = 8 - 1 = 7 4 24 - 1 = 16 - 1 = 15 5 25 - 1 = 32 - 1 = 31 6 26 - 1 = 64 - 1 = 63 • So the formula for finding the number of steps it takes to transfer n disks from post A to post C is: 2 n- 1
Binomial coefficient • Given a non-negative integer n and an integer k, the binomial coefficient is defined to be the natural number
• Example: