Chapter 4: Stacks

Chapter 4: Stacks

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.

A
A+B
(A+B)+(C-D)

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.

A
+AB
++AB-CD

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.

A
AB+
AB+CD-+

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 CharacterOperation on StackStackPostfix Expression
AemptyA
*push()*A
B*AB
-check() push()-AB*
(push()-(AB*
C-(AB*C
+check() push()-(+AB*C
DAB*CD
)pop() and append to postfix till ‘(’-AB*CD+
+check() push()+AB*CD+ -
E+AC*CD+ -E
End of inputpop() till emptyAB*CD+ -E+

The table shows the precedence and their associativity (order of evaluation) among operators.

TokenOperatorPrecedenceAssociativity
()
[]
->
.
function call
array element
struct
union member
1
high
left-to-right
- -
++
Postposition decrement
Postposition increment
2left-to-right
- - ++
!
-
- +
& *
sizeof
Preposition decrement / increment
Logic Not
one’s Complement
Unary minus / plus
Address / Indirection
size (in Bytes)
3right-to-left
*
(type)Type Cast4right-to-left
*
* / %Multiplicative5left-to-right
+ -Binary Add / Subtract6left-to-right
<< >>Shift7left-to-right
> >=
> >=
Relationship8left-to-right
== !=Equality9left-to-right
&Bitwise AND10left-to-right
^Bitwise Exclusive or (XOR)11left-to-right
|Bitwise OR12left-to-right
&&Logical AND13left-to-right
||Logical OR14left-to-right
? :Conditional15right-to-left
*
= += -= /= *= %= <<= >>= &= ^=Assignment16right-to-left
*
,Comma17
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 :
    1. Scan the postfix string from left to right.
    2. Initialize an empty stack.
    3. Repeat steps 4, 5 till all the character are scanned.
    4. If the scanned character is operand, push it onto the stack.
    5. 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.
    6. After all characters are scanned, we will have only one element in the stack.
    7. 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+...nO(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)=10nlognO(nlogn)

    The cost of per operation is O(logn).

Pro 10.1: Search for the Next Element Greater than x

Solution : Monotonous Stack

index0123456
element1345296
res12355-1-1
res211121-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.

index0123456
stock price100806070607585
span1112146

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);
	}
}		
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值