Data Structure Stack & Queue Handling #4
by Md Farrukh Asif
Ø In this article we will discuss in deep about the Stacks and
Queues.
Ø In this article you will also show the difference between
infix, prefix, and postfix.
Ø We will try to solve stack and Queue with array(static) and
linked lists(dynamic).
Here we have used our full efforts and attempts to execute the
operations for you, hence we request all of you to comment and share with
love.
Thank You…..
Stack:
A stack is an Abstract Data Type (ADT), commonly used in most
programming languages. It is named stack as it behaves like a real-world stack,
for example – a deck of cards or a pile of plates, etc.
A real-world stack
Allows operations at one end only. For example, we can place or remove a card or plate from the top of the stack only. Likewise, Stack ADT allows all data operations at one end only. At any given time, we can only access the top element of a stack.
This feature makes it a LIFO data structure. LIFO stands for
Last-In-First-Out. Here, the element which is placed (inserted or added) last,
is accessed first. In stock terminology, the insertion operation is called the PUSH operation
and the removal operation is called the POP operation.
Stack Representation
The following diagram depicts a stack and its operations −
A stack can be implemented through an Array, Structure, Pointer,
and Linked List. Stack can either be a fixed size one or it may have a sense of
dynamic resizing. Here, we are going to implement a stack using arrays, which
makes it a fixed-size stack implementation.
Basic Operations
Stack operations may involve initializing the stack, using it, and
then de-initializing it. Apart from these basic stuffs, a stack is used for the
following two primary operations −
·
push() − Pushing (storing) an element on the
stack.
·
pop() − Removing (accessing) an element from the
stack.
When data is PUSHed onto the stack.
To use a stack efficiently, we need to check the status of stack
as well. For the same purpose, the following functionality is added to stacks −
·
peek() − get the top data element of the stack,
without removing it.
·
isFull() − check if stack is full.
·
isEmpty() − check if stack is empty.
At all times, we maintain a pointer to the last PUSHed data on the
stack. As this pointer always represents the top of the stack, hence
named top. The top pointer provides the top value of
the stack without actually removing it.
First we should learn about procedures to support stack functions
−
peek()
Algorithm of peek() function −
begin
procedure peek
return stack[top]
end
procedure
Implementation of peek() function in C programming language −
Example
int peek() {
return stack[top];
}
isfull()
Algorithm of isfull() function −
begin procedure isfull
if top equals to MAXSIZE
return true
else
return false
endif
end procedure
Implementation of isfull() function in C programming language −
Example
bool isfull() {
if(top == MAXSIZE)
return true;
else
return false;
}
isempty()
Algorithm of isempty() function −
begin procedure isempty
if top less than 1
return true
else
return false
endif
end procedure
Implementation of isempty() function in C programming language is
slightly different. We initialize top at -1, as the index in array starts from
0. So we check if the top is below zero or -1 to determine if the stack is
empty. Here's the code −
Example
bool isempty() {
if(top == -1)
return true;
else
return false;
}
Push Operation
The process of putting a new data element onto a stack is known as a
Push Operation. Push operation involves a series of steps −
·
Step
1 − Check if the
stack is full.
·
Step
2 − If the stack is
full, produce an error and exit.
·
Step
3 − If the stack is
not full, increments top to point next empty space.
·
Step
4 − Adds data
element to the stack location, where top is pointing.
·
Step
5 − Returns success.
If the linked list is used to implement the stack, then in step 3,
we need to allocate space dynamically.
Algorithm
for PUSH Operation
A simple algorithm for Push operation can be derived as follows −
begin procedure push: stack, data
if stack is full
return null
endif
top ← top + 1
stack[top] ← data
end procedure
Implementation of this algorithm in C, is very easy. See the
following code −
Example
void push(int data) {
if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf("Could not insert
data, Stack is full.\n");
}
}
Pop Operation
Accessing the content while removing it from the stack, is known
as a Pop Operation. In an array implementation of pop() operation, the data
element is not actually removed, instead top is decremented to
a lower position in the stack to point to the next value. But in linked-list
implementation, pop() actually removes data element and deallocates memory
space.
A Pop operation may involve the following steps −
·
Step
1 − Check if the
stack is empty.
·
Step
2 − If the stack is
empty, produce an error and exit.
·
Step
3 − If the stack is
not empty, access the data element at which the top is pointing.
·
Step
4 − Decreases the
value of the top by 1.
· Step 5 − Returns success.
Algorithm
for Pop Operation
A simple algorithm for Pop operation can be derived as follows −
begin procedure pop: stack
if stack is empty
return null
endif
data ← stack[top]
top ← top - 1
return data
end procedure
Implementation of this algorithm in C, is as follows −
Example
int pop(int data) {
if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Could not
retrieve data, Stack is empty.\n");
}
}
The way to write arithmetic expression is known as a notation.
An arithmetic expression can be written in three different but equivalent
notations, i.e., without changing the essence or output of an expression. These
notations are −
- Infix Notation
- Prefix (Polish) Notation
- Postfix (Reverse-Polish) Notation
These notations are named as how they use operator in expression.
We shall learn the same here in this chapter.
Infix Notation
We write expressions in infix notation, e.g. a - b
+ c, where operators are used in-between operands. It is easy for
us humans to read, write, and speak in infix notation but the same does not go
well with computing devices. An algorithm to process infix notation could be
difficult and costly in terms of time and space consumption.
Prefix Notation
In this notation, the operator is prefixed to operands,
i.e. operator is written ahead of operands. For example, +ab. This
is equivalent to its infix notation a + b. Prefix notation is also
known as Polish Notation.
Postfix Notation
This notation style is known as Reversed Polish Notation.
In this notation style, the operator is postfixed to the operands
i.e., the operator is written after the operands. For example, ab+.
This is equivalent to its infix notation a + b.
The following table briefly tries to show the difference in all
three notations −
Sr.No. |
Infix Notation |
Prefix Notation |
Postfix Notation |
1 |
a + b |
+ a b |
a b + |
2 |
(a + b) ∗ c |
∗ + a b c |
a b + c ∗ |
3 |
a ∗ (b + c) |
∗ a + b c |
a b c + ∗ |
4 |
a / b + c / d |
+ / a b / c d |
a b / c d / + |
5 |
(a + b) ∗ (c + d) |
∗ + a b + c d |
a b + c d + ∗ |
6 |
((a + b) ∗ c) - d |
- ∗ + a b c d |
a b + c ∗ d - |
Parsing Expressions
As we have discussed, it is not a very efficient way to design an
algorithm or program to parse infix notations. Instead, these infix notations
are first converted into either postfix or prefix notations and then computed.
To parse any arithmetic expression, we need to take care of
operator precedence and associativity also.
Precedence
When an operand is in between two different operators, which
operator will take the operand first, is decided by the precedence of an
operator over others. For example −
As multiplication operation precedes addition, b * c
will be evaluated first. A table of operator precedence is provided later.
Associativity
Associativity describes the rule where operators with the same
precedence appear in an expression. For example, in expression a + b − c, both
+ and – have the same precedence, then which part of the expression will be
evaluated first, is determined by associativity of those operators. Here, both
+ and − are left associative, so the expression will be evaluated as (a
+ b) − c.
Precedence and associativity determines the order of evaluation of
an expression. Following is an operator precedence and associativity table
(highest to lowest) −
Sr.No. |
Operator |
Precedence |
Associativity |
1 |
Exponentiation ^ |
Highest |
Right Associative |
2 |
Multiplication ( ∗ )
& Division ( / ) |
Second Highest |
Left Associative |
3 |
Addition ( + ) & Subtraction ( − ) |
Lowest |
Left Associative |
The above table shows the default behavior of operators. At any
point of time in expression evaluation, the order can be altered by using
parenthesis. For example −
In a + b*c, the expression part b*c will
be evaluated first, with multiplication as precedence over addition. We here
use parenthesis for a + b to be evaluated first, like (a
+ b)*c.
Postfix Evaluation Algorithm
We shall now look at the algorithm on how to evaluate postfix
notation −
Step
1 − scan the expression from left to right
Step
2 − if it is an operand push it to stack
Step
3 − if it is an operator pull operand from the stack and perform the operation
Step
4 − store the output of step 3, back to stack
Step
5 − scan the expression until all operands are consumed
Step
6 − pop the stack and perform operation
Queue
Queue is an abstract data structure, somewhat similar to Stacks.
Unlike stacks, a queue is open at both its ends. One end is always used to
insert data (enqueue) and
the other is used to remove data (dequeue). Queue follows
First-In-First-Out methodology, i.e., the data item stored first will be
accessed first.
A real-world example of queue can be a single-lane one-way road,
where the vehicle enters first, exits first. More real-world examples can be
seen as queues at the ticket windows and bus-stops.
Queue Representation
As we now understand that in queue, we access both ends for
different reasons. The following diagram given below tries to explain queue
representation as data structure −
As in stacks, a queue can also be implemented using Arrays,
Linked-lists, Pointers and Structures. For the sake of simplicity, we shall
implement queues using one-dimensional array.
Basic Operations
Queue operations may involve initializing or defining the queue,
utilizing it, and then completely erasing it from the memory. Here we shall try
to understand the basic operations associated with queues −
·
enqueue() − add (store) an item to the queue.
·
dequeue() − remove (access) an item from the queue.
Few more functions are required to make the above-mentioned queue
operation efficient. These are −
·
peek() − Gets the element at the front of the
queue without removing it.
·
isfull() − Checks if the queue is full.
·
isempty() − Checks if the queue is empty.
In the queue, we always dequeue (or access) data, pointed by front pointer
and while enqueing (or storing) data in the queue we take help of rear pointer.
Let's first learn about supportive functions of a queue −
peek()
This function helps to see the data at the front of
the queue. The algorithm of peek() function is as follows −
Algorithm
begin
procedure peek
return queue[front]
end
procedure
Implementation of peek() function in C programming language −
Example
int peek() {
return queue[front];
}
isfull()
As we are using a single dimension array to implement queue, we just
check for the rear pointer to reach at MAXSIZE to determine that the queue is
full. In case we maintain the queue in a circular linked list, the algorithm
will differ. Algorithm of isfull() function −
Algorithm
begin procedure isfull
if rear equals to MAXSIZE
return true
else
return false
endif
end procedure
Implementation of isfull() function in C programming language −
Example
bool isfull() {
if(rear == MAXSIZE - 1)
return true;
else
return false;
}
isempty()
Algorithm of isempty() function −
Algorithm
begin procedure isempty
if front is less than MIN OR front is greater than rear
return true
else
return false
endif
end procedure
If the value of the front is less than MIN or 0, it
tells that the queue is not yet initialized, hence empty.
Here's the C programming code −
Example
bool isempty() {
if(front < 0 || front > rear)
return true;
else
return false;
}
Enqueue Operation
Queues maintain two data pointers, front and rear.
Therefore, its operations are comparatively difficult to implement than that of
stacks.
The following steps should be taken to enqueue (insert) data into
a queue −
·
Step
1 − Check if the
queue is full.
·
Step
2 − If the queue is
full, produce overflow error and exit.
·
Step
3 − If the queue is
not full, increment rear pointer to point the next empty
space.
·
Step
4 − Add data element
to the queue location, where the rear is pointing.
·
Step
5 − return success.
Sometimes, we also check to see if a queue is initialized or not,
to handle any unforeseen situations.
Algorithm
for enqueue operation
procedure enqueue(data)
if queue is full
return overflow
endif
rear ← rear + 1
queue[rear] ← data
return true
end procedure
Implementation of enqueue() in C programming language −
Example
int enqueue(int data)
if(isfull())
return 0;
rear = rear + 1;
queue[rear] = data;
return 1;
end procedure
Dequeue Operation
Accessing data from the queue is a process of two tasks − access
the data where front is pointing and remove the data after
access. The following steps are taken to perform dequeue operation
−
·
Step
1 − Check if the
queue is empty.
·
Step
2 − If the queue is
empty, produce underflow error and exit.
·
Step
3 − If the queue is
not empty, access the data where front is pointing.
·
Step
4 − Increment front pointer
to point to the next available data element.
·
Step
5 − Return success.
Algorithm for dequeue operation
procedure dequeue
if queue is empty
return underflow
end if
data = queue[front]
front ← front + 1
return true
end procedure
Implementation of dequeue() in C programming language −
Example
int dequeue() {
if(isempty())
return 0;
int data = queue[front];
front = front + 1;
return data;
}
We
will discuss more in the next post in detail with another way……
=====================
Share Like and Commnt Please
=====================
No comments:
Post a Comment