Chapter 4: Stacks
Table of Contents
- Chapter 4: Stacks
- 1. Stacks
- 2. Applications
- 3. Implementation
- 4. Stacks: Problems & Solutions
- Pro 1: Checking Balancing of Symbols
- Pro 2: Infix to Postfix Conversion Algorithm
- Pro 3: Discuss Postfix Evaluation Using Stacks.
- Pro 4: Evaluate the Infix Expression in one Pass
- Pro 5: Design a Stack with O(1) GetMin() Function
- Pro 6: Check Palindrome
- Pro 7: Reverse the Elements of Stack
- Pro 8: Implement m Stacks in one Array
- Pro 9: Time Complexity Evaluation of Stack Implementation
- Pro 10.1: Search for the Next Element Greater than x
- Pro 10.2: Finding Spans
- Pro 10.3: Largest Rectangle Under Histogram
- Pro 10.4: Replace Every Element with Nearest Greater Element on the right side
1. Stacks
A stack is an ordered list in which insertion and deletion are done at one end called top. The last element inserted is the first one to be deleted. Hence, it is called Last in First out (LIFO) or First in Last out (FILO) list. Trying to pop out an empty stack is called underflow and try to push am element in a full stack is called overflow. Generally we treat them as exception.
2. Applications
- Balancing of symbols
- Infix-to-postfix conversion
- Evaluation of postfix expression
- Implementing function calls (including recursion)
- Finding of spans
- Page-visited history in a Web browser (Back Buttons)
- Undo sequence in a text editor
- Matching Tag in HTML and XML
- Auxiliary data structure for other algorithms (eg: Tree traversal algorithm)
- Component of other data structures (eg: Simulating queues)
3. Implementation
3.1 Simple Array Implementation
Limitation :
The maximum size of stacks must first be defined and it cannot be changed. Try to push a new element to a full stack causes an implementation-specific exception.
#define MAXSIZE 10
struct ArrayStack{
int top;
int capacity;
int *array;
};
struct ArrayStack *CreateStack(){
struct ArrayStack *s=malloc(sizeof(struct ArrayStack));
if(s==NULL)
return NULL;
s->capacity=MAXSIZE;
s->top=-1;
s->array=malloc((s->capacity)*sizeof(int));
if(s->array==NULL)
return NULL;
return s;
}
int IsEmptyStack(struct ArrayStack *s){
return (s->top==-1);
}
int IsFullStack(struct ArrayStack *s){
return (s->top==s->capacity-1);
}
void Push(struct ArrayStack *s, int data){
if(IsFullStack(s))
printf("exception: stack overflow");
else
/*increasing the top index and storing the value*/
s->array[++s->top]=data; //++top
}
int Pop(struct ArrayStack *s){
if(isEmptyStack(s)){
printf("exception: stack empty");
return INT_MIN;
}
else
/*Remove element and reducing top index*/
return (s->array[s->top--]); //top--
}
void DeleteStack(struct ArrayStack *s){ //time complexity O(1)
if(s){
if(s->array)
free(s->array);
free(s);
}
}
3.2 Dynamic Array Implementation
Limitation : Too many doublings may cause memory overflow exception.
struct DynArrayStack{
int top;
int capacity;
int *array;
};
struct DynArrayStack *CreateStack(){
struct DynArrayStack *s=malloc(sizeof(struct DynArrayStack));
if(s==NULL)
return NULL;
s->capacity=1; //No need to firstly define the MAXSIZE
s->top=-1;
s->array=malloc((s->capacity)*sizeof(int)); //allocate an array of size 1 initially
if(s->array==NULL)
return NULL;
return s;
}
int IsFullStack(struct DynArrayStack *s){
return (s->top==s->capacity-1);
}
void DoubleStack(struct DynArrayStack *s){
s->capacity*=2; //double the capacity
s->array=realloc(s->array, (s->capacity)*sizeof(int));
}
void Push(struct DynArrayStack *s, int data){
/*No Overflow in this Dyn implementation*/
if(IsFullStack(s))
DoubleStack(s);
s->array[++s->top]=data; //++top
}
int IsEmptyStack(struct DynArrayStack *s){
return s->top==-1;
}
int Top(struct DynArrayStack *s){
if(IsEmptyStack(s))
return INT_MIN;
return s->array[s->top];
}
int Pop(struct DynArrayStack *s){
if(IsEmptyStack(s))
return INT_MIN;
return s->array[s->top--]; //top--
}
void DeleteStack(struct DynArrayStack *s){ //time complexity O(1)
if(s){
if(s->array)
free(s->array);
free(s);
}
}
3.3 Linked List Implementation
struct ListNode{
int data;
struct ListNode *next;
};
struct Stack *CreateStack(){
return NULL;
}
void Push(struct Stack **top, int data){
struct Stack *temp=malloc(sizeof(struct Stack));
if(!temp)
return NULL;
temp->data=data;
temp->next=*top;
*top=temp;
}
int IsEmptyStack(strcut Stack *top){
return (top==NULL);
}
int Pop(struct Stack **top){
int data;
struct Stack *temp;
if(IsEmptyStack(top))
return INT_MIN;
temp=*top;
*top=*top->next;
data=temp->data;
free(temp);
return data;
}
int Top(struct Stack *top){
if(IsEmptyStack(top))
return INT_MIN;
return top->next->data;
}
void DeleteStack(struct Stack **top){ //time complexity O(n)
struct Stack *temp;
struct Stack *p=*top;
while(p->next){
temp=p->next;
p->next=temp->next;
free(temp);
}
free(p);
}
4. Stacks: Problems & Solutions
Pro 1: Checking Balancing of Symbols
Solution :
- Algorithm:
1. Create a stack
2. while (end of input is not reached){
a) if the character read is not a symbol to be balanced, ignore it.
b) if it is an opening symbol like (, [, {, push it onto the stack
c) if it is a closing symbol like ), ], }, then if the stack is empty report an error. Otherwise pop the stack.
d) if the symbol popped is not the corresponding opening symbol, report an error.
}
3. At the end of input, if the stack is not empty report an error.
Pro 2: Infix to Postfix Conversion Algorithm
Infix : An infix expression is a single letter, or an operator, proceeded by one infix string and followed by another infix string.
Prefix : A prefix expression is a single letter, or an operator, followed by two prefix strings. Every prefix string longer than a single variable contains an operator, first operand and second operand.
Postfix : A postfix expression (also called Reverse Polish Notation) is a single letter or an operator, preceded by two postfix strings. Every postfix string longer than a single variable contains first and second operands followed by an operator.
Prefix and postfix notions are methods of writing mathematical expressions without parenthesis.
Solution :
- Algorithm:
create stack
for each character in the input stream{
if(t is an operand)
output t
else if(t is a right parenthesis)
POP() and output tokens until a left parenthesis is popped (but not output)
else if(t is an operator or left parenthesis)
POP() and output tokens until
one of lower priority than t is encountered
or a left parenthesis is encountered
or the stack is empty
PUSH() t
POP and output tonkens until the stack is empty
- Example: Trace out : A × B − ( C + D ) + E A \times B -(C+D)+E A×B−(C+D)+E
Input Character | Operation on Stack | Stack | Postfix Expression |
---|---|---|---|
A | empty | A | |
* | push() | * | A |
B | * | AB | |
- | check() push() | - | AB* |
( | push() | -( | AB* |
C | -( | AB*C | |
+ | check() push() | -(+ | AB*C |
D | AB*CD | ||
) | pop() and append to postfix till ‘(’ | - | AB*CD+ |
+ | check() push() | + | AB*CD+ - |
E | + | AC*CD+ -E | |
End of input | pop() till empty | AB*CD+ -E+ |
The table shows the precedence and their associativity (order of evaluation) among operators.
Token | Operator | Precedence | Associativity |
---|---|---|---|
() [] -> . | function call array element struct union member | 1 high | left-to-right |
- - ++ | Postposition decrement Postposition increment | 2 | left-to-right |
- - ++ ! - - + & * sizeof | Preposition decrement / increment Logic Not one’s Complement Unary minus / plus Address / Indirection size (in Bytes) | 3 | right-to-left * |
(type) | Type Cast | 4 | right-to-left * |
* / % | Multiplicative | 5 | left-to-right |
+ - | Binary Add / Subtract | 6 | left-to-right |
<< >> | Shift | 7 | left-to-right |
> >= > >= | Relationship | 8 | left-to-right |
== != | Equality | 9 | left-to-right |
& | Bitwise AND | 10 | left-to-right |
^ | Bitwise Exclusive or (XOR) | 11 | left-to-right |
| | Bitwise OR | 12 | left-to-right |
&& | Logical AND | 13 | left-to-right |
|| | Logical OR | 14 | left-to-right |
? : | Conditional | 15 | right-to-left * |
= += -= /= *= %= <<= >>= &= ^= | Assignment | 16 | right-to-left * |
, | Comma | 17 low | left-to-right |
Extraneous Complement: Compare the Priority and Associative between preposition increment and postposition increment
int a=1, b=1;
int c=0, d=0;
c=++a;
d=b++;
cout<<a<<' '<<b<<' '<<c<<' '<<d<<endl;
//2 2 2 1
/*Fundamentals of compiling*/
/*for simple i++ vs. ++i the results are bot i=i+1*/
/*BUT it is quite different in i=i++ vs. i=++i */
int Pre(int &x){ //++i
x=x+1 //1st: 'i' in [local variable table] +1
return x; //2nd: Put new 'i' into [operand stack]
}
int Post(int &x){ //i++
int b=x; //1st: Put 'i' in local variable into [operand stack]
x=x+1; //2nd: 'i' in [local variable table] +1
return b;
}
/* data is stored in [local variable table], and took to [operand stack] waiting for execution (return) */
Pro 3: Discuss Postfix Evaluation Using Stacks.
Solution :
- Algorithm :
- Scan the postfix string from left to right.
- Initialize an empty stack.
- Repeat steps 4, 5 till all the character are scanned.
- If the scanned character is operand, push it onto the stack.
- If the scanned character is an operator, and if the operator is a unary operator, then pop an element form the stack. If the operator is a binary operator, then pop twp elements from the stack. After popping the element, apply the operator to those popped elements. Let the result of this operation be return value onto the stack.
- After all characters are scanned, we will have only one element in the stack.
- Return top of the stack as result.
Pro 4: Evaluate the Infix Expression in one Pass
Solution :
- Using 2 stacks operand stack and operator stack, we can evaluate an infix expression in 1 pass without converting to postfix.
- Iterate through the expression, if the current token is an operand, store it onto stack; if it is an operator, calculate the equation in stack with higher priority then store the result onto stacks.
Pro 5: Design a Stack with O(1) GetMin() Function
Solution 1: Auxiliary Stack
- Take an auxiliary stack that maintains the minimum of all values in the stack. Increase and decrease two stack at the same time.
struct AdvancedStack(){
struct Stack elementStack;
struct Stack minStack;
};
void Push(struct AdvancedStack *s, int data){
Push(s->elementStack, data);
if(IsEmptyStack(s->minStack) || Top(s->minStack)>=data)
Push(s->minStaack, data);
else
Push(s->minStaack, Top(s->minStack));
}
int Pop(struct AdvancedStack *s){
int temp;
if(IsEmptyStack(s->elementStack))
return -1;
temp=Pop(s->elementStack);
Pop(S->minStack);
return temp;
}
int GetMin(struct AdvancedStack *s){
return Top(s->minStack);
}
struct AdvancedStack *CreateAdvancedStack(){
struct AdvancedStack *s=(struct AdvancedStack*)malloc(sizeof(struct AdvancedStack));
if(!s)
return NULL;
s->elementStack=CreateStack();
s->minStack=CreateStack();
return s;
}
Solution 2: Reduce Duplicate operation
/* Only Modify the push and pop operation*/
void Push(struct AdvancedStack *s, int data){
Push(s->elementStack, data);
if(IsEmptyStack(s->minStack) || Top(s->minStack)>=data)
Push(s->minStaack, data);
}
int Pop(struct AdvancedStack *s){
int temp;
if(IsEmptyStack(s->elementStack))
return -1;
temp=Top(s->elementStack);
if(Top(s->minStack)==Pop(S->elementStack))
Pop(S->minStack);
return temp;
}
Pro 6: Check Palindrome
Given string: (abab…x…baba), x represents the middle of the list.
Solution 1: If given an array
int IsPalindrom(char *a){
for(int i=0, j=strlen(a)-1; i<j && A[i]==A[j]; ++i, --j);
return i<j ? 0 : 1;
}
Solution 2: If given a linked list
- Reverse
Solution 3: Using stack
int IsPalindrome(char *a){
int i=0;
struct Stack s=CreateStack();
while(A[i]!='x'){
Push(s, a[i]);
++i;
}
++i;
while(a[i]){
if(IsEmptyStack[s] || a[i]!=Pop(S)){
return 0;
++i;
}
return IsEmptyStack(s);
}
Pro 7: Reverse the Elements of Stack
Solution 1: Recursion
- Pop all the elements of the stack till it empty.
- For each upward step in recursion, insert the element at the bottom of the stack.
void ReverseStack(struct Stack *s){
int data;
if(IsEmptyStack(s))
return;
data=Pop(s);
ReverseStack(s);
InsertAtBottom(s, data);
}
void InsertAtBottom(struct Stack *s, int data){
int temp;
if(IsEmptyStack(s)){
Push(s, data);
return;
}
temp=Pop(s);
InsertAtBottom(s, data);
Push(s, temp);
}
- Time Compelxity: O(n2), Space Complexity: O(n)
Solution 2: Use Queue
Pro 8: Implement m Stacks in one Array
Assume that array indexes are from 1 to n, the size of each part is
n
/
m
n/m
n/m.
Solution :
void Push(int StackID, int data){
if(Top[i]==Base[i+1])
print i^th Stack is full;
do necessary action (shifting);
Top[i]=Top[i]+1;
A[Top[i]]=data;
}
int Pop(int StackID){
if(Top[i]==Base[i])
print i^th Stack is empty;
return A[Top[i]--];
}
Pro 9: Time Complexity Evaluation of Stack Implementation
-
In Simple Array Implementation, the way of incrementing the array size is too expensive. Every time we need to create a new size of array with one more space and copy all the old array to the new array, and add new element at the end.
If we want to push a new element into stack, After n push operations, the total time is proportional to
T ( n ) = 1 + 2 + 3 + . . . n ≈ O ( n 2 ) T(n)=1+2+3+...n \approx O(n^2) T(n)=1+2+3+...n≈O(n2) -
In Dynamic Array Implementation, we improve the complexity by using the array doubling technique.
For n push operation we double the array size logn times, the total time is proportional to
1 + 2 + 4 + 8 + . . . + n 4 + n 2 + n = n × ( 1 + 1 2 + 1 4 + . . . + 4 n + 2 n + 1 n ) ≈ 2 n = O ( n ) 1+2+4+8+...+ \frac{n}{4} + \frac{n}{2} + n=n \times(1+\frac{1}{2} + \frac{1}{4} +...+ \frac{4}{n} +\frac{2}{n} +\frac{1}{n} ) \approx 2n =O(n) 1+2+4+8+...+4n+2n+n=n×(1+21+41+...+n4+n2+n1)≈2n=O(n)The amortized time of one push operation is O(1).
-
We create new array k (assume k=10) more space every time instead of doubling.
For a given value n, the total time of copy operation is
n 10 + n 20 + n 30 + . . . + 1 = n 10 × ( 1 1 + 1 2 + 1 3 + . . . + 1 n ) = n 10 l o g n ≈ O ( n l o g n ) \frac{n}{10} + \frac{n}{20} + \frac{n}{30} +...+1 = \frac{n}{10} \times (\frac{1}{1} + \frac{1}{2} + \frac{1}{3} +...+\frac{1}{n} ) =\frac{n}{10} logn \approx O(nlogn) 10n+20n+30n+...+1=10n×(11+21+31+...+n1)=10nlogn≈O(nlogn)The cost of per operation is O(logn).
Pro 10.1: Search for the Next Element Greater than x
Solution : Monotonous Stack
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
element | 1 | 3 | 4 | 5 | 2 | 9 | 6 |
res | 1 | 2 | 3 | 5 | 5 | -1 | -1 |
res2 | 1 | 1 | 1 | 2 | 1 | -1 | -1 |
Res array stores the index of the first element which greater than the current element. Res2 array stores how many steps forward that the current element could find an element that is greater than itself.
void nextGreater(int a[], int n){
for(int i=n-1; i>=0; i--){
while(!s.empty() && s.top()<=a[i])
s.pop();
res[i]= s.empty() ? -1 : s.top();
s.push(a[i]);
}
}
/*find the next element's [value] which greater than x*/
struct p{
int pos;
int data;
};
stack<p> s;
void nextGreater(int a[], int n){
for(int i=n-1; i>=0; --i){
while(!s.empty() && s.top().data<=a[i])
s.pop();
res2[i]= s.empty() ? -1 : s.top().pos-i;
p temp;
temp.pos=i;
temp.data=a[i];
s.push(temp);
}
}
/*find how many [steps] forward that the current element could find an element that is greater than itself.*/
Pro 10.2: Finding Spans
Given an array, the span S[i] of A[i] is the maximum number of consecutive elements A[j] immediately preceding A[i] and satisfy that A[j]<A[i]. Spans are used in financial analysis.
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
stock price | 100 | 80 | 60 | 70 | 60 | 75 | 85 |
span | 1 | 1 | 1 | 2 | 1 | 4 | 6 |
Solution 1:
- Check how many contiguous days have a stock price that is less than the current price.
SimpleStockSpan(quotes)->spans
input:quotes, n size array
output:spans, n size array
spans<-CreateArray(n)
for i<- 0 to n do
k<-1
span_end<-FALSE
while i-k>=0 AND NOT span_end do
if quotes[i-k]<=quotes[i] then
k<-k+1
else
span_end<-TRUE
spans[i]<-k
return spans
- Time Complexity: O(n2), Space Complexity: O(1)
Solution 2: Monotonous Stack
- Span S[i] on day i can be easily calculated if we know the closest day preceding i, such that the price is greater on that day than the price on day i. So we use a stack maintains a monotonically decreasing sequence of stock prices.
StackStockSpan(quotes)->spans
input: output:
spans<-CreateArray(n)
spans[0]<-1
S<-CreateStack()
Push(S, 0)
for i<-1 to n do
while NOT IsEmptyStack(S) AND quotes[Top(S)]<=quotes[i] do
Pop(S)
if IsEmptyStack(S) then
spans[i]<-i+1
else
spans[i]<- i-Top(S)
Push(S, i)
return spans
- Time Complexity: O(n), Space Complexity: O(n)
class StockSpanner{
public:
stack<pair<int,int>> stock;
StockSpanner(){
stock.push({999,0});
}
int next(int price){
int count=1;
while(price>=stock.top().first){
count+=storck.top().second;
stock.pop();
}
stock.push({price,count)};
return count;
}
};
/*
StockSpanner object will be instantiated and called as such:
StockSpanner *obj=new StockSpanner();
int param_1=obj->next(price);
...
version from Leetcode 901
*/
Pro 10.3: Largest Rectangle Under Histogram
Solution 1:
- A straightforward answer is to go to each bar in the histogram and find the maximum possible area which require O(n2).
Solution 2: Linear Search Using a Stack of Incomplete Sub Problems
-
Process the elements in left-to-right order and maintain a stack of information about started but yet unfinished sub histograms.
If the stack is empty, open a new sub problem by pushing the element onto the stack. Otherwise compare it to the element on top of the stack. If the new one is greater we again push it. If the new one is equal we skip it. In all these cases, we continue with the new element. If the new one is less, we finish the topmost sub problem by updating the maximum area with respect to the element at the top of the stack. Then we discard the element at the top, and repeat the procedure keeping the current new element.
This way, all sub problems are finished when the stack becomes empty, or its top element is less than or equal to the new element, leading to the action described above. If all elements have been processed, and the stack is not yet empty, we finish the remaining sub problems by updating the maximum area with respect to the elements at top.
struct StackItem{
int height;
int index;
};
int MaxRectangleArea(int a[], int n){
int i, maxArea=-1, top=-1, left, currentArea;
struct StackItem *s=(struct StackItem *)malloc(sizeof(struct StackItem)* n);
for(i=0; i<n; i++{
while(top>=0 && (i==n || s[top]->height > a[i])){
if(top>0)
left=s[top-1]->index;
else
left=-1;
currentArea=(i-left-1)*(s[top]->height);
--top;
if(currentArea>maxArea)
maxArea=currentArea;
}
if(i<n){
++top;
s[top]->height=a[i];
s[top]->index=i;
}
}
return maxArea;
}
Pro 10.4: Replace Every Element with Nearest Greater Element on the right side
Solution 1:
void replaceWithNearestGreaterElement(int a[], int n){
int nextNearestGreater=INT_MIN;
int i=0, j=0;
for(;i<n;++i){
nextNearestGreater=-INT_MIN;
for(j=i+1; j<n; ++j){
if(a[i]<a[j]){
nextNearestGreater=a[j];
break;
}
}
printf("for the element %d, %d is the nearest greater element.\n", a[i], nextNearestGreater);
}
}
- Time Complexity O(n2), Space Complexity O(1)
Solution 2:
- Create a stack and push the first element. For the rest of the elements, mark the current element as nextNearestGreater. If stack is not empty, then pop an element from stack and compare it with nextNearestGreater. If nextNearestGreater is greater than the popped element, then nextNearestGreater is the next greater element for the popped element. Keep poppong from the stack while the popped element is smaller than nextNearestGreater. nextNearestGreater becomes the next greater element for all such popped elements. If nextNearestGreater is smaller than the popped element, then push the popped element back.
void replaceWithNearestGreaterElement(int a[], int n){
int i=0;
struct Stack *s=CreateStack();
int element, nextNearestGreater;
Push(s, a[0]);
for(i=1; i<n; i++){
nextNearestGreater=A[i];
if(!IsEmptyStack(s)){
element=Pop(s);
while(element<nextNearestGreater){
printf("for the element %d, %d is the nearest greater element.\n", a[i], nextNearestGreater);
if(IsEmptyStack(s))
break;
element=Pop(s);
}
if(element>nextNearestGreater)
Push(s, element);
}
Push(s, nextNearestGreater);
}
while(!IsEmptyStack(s)){
element=Pop(s);
nextNearestGreater=-INT_MIN;
printf("for the element %d, %d is the nearest greater element.\n", a[i], nextNearestGreater);
}
}