UNIT III Data Structures OU
UNIT III Data Structures OU
Stacks and Queues: ADT Stack, operations and its applications like Expression Conversion and evaluation,
Queue ADT and its operations: Linear Queue, Circular Queue, Dequeue.
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 LIFO data structure.
LIFO stands for Last-in-first-out. Here, the element which is placed (inserted or added) last, is accessed first. In
stack terminology, insertion operation is called PUSH operation and removal operation is called POP operation.
OPERATIONS ON A STACK :
A stack supports three basic operations: push, pop, and peek.
Push Operation
Pop Operation
The pop operation is used to delete the topmost element from the stack.
However, before deleting the value, we must first check if TOP=NULL because if that is the case, then
it means the stack is empty and no more deletions can be done.
To delete the topmost element, we first check if TOP=NULL. If the condition is false, then we
decrement the value pointed by TOP.
Pop operation may involve the following steps −
Step 1 − Checks if the stack is empty.
Step 2 − If the stack is empty, produces an error and exit.
Step 3 − If the stack is not empty, accesses the data element at which top is pointing.
Step 4 − Decreases the value of top by 1.
Step 5 − Returns success.
Peek Operation
Peek is an operation that returns the value of the topmost element of the stack without deleting it from
the stack.
However, the Peek operation first checks if the stack is empty, i.e., if TOP = NULL, then an
appropriate message is printed, else the value is returned.
Here, the Peek operation will return 5, as it is the value of the topmost element of the stack.
In the computer’s memory, stacks can be represented as a linear array. Every stack has a variable called TOP
associated with it, which is used to store the address of the topmost element of the stack. It is this position where
the element will be added to or deleted from.
There is another variable called MAX, which is used to store the maximum number of elements that the stack
can hold. If TOP = NULL, then it indicates that the stack is empty and if TOP = MAX–1, then the stack is full.
(You must be wondering why we have written MAX–1. It is because array indices start from 0.)
We have seen how a stack is created using an array. This technique of creating a stack is easy, but the drawback
is that the array must be declared to have some fixed size. In case the stack is a very small one or its maximum
size is known in advance, then the array implementation of the stack gives an efficient implementation. But if
the array size cannot be determined in advance, then the other alternative, i.e., linked representation, is used.
The storage requirement of linked representation of the stack with n elements is O(n), and the typical time
requirement for the operations is O(1).
In a linked stack, every node has two parts—
One that stores data and another that stores the address of the next node. The START pointer of the linked list is
used as TOP. All insertions and deletions are done at the node pointed by TOP. If TOP = NULL, then it indicates
that the stack is empty. The linked representation of a stack is shown in below figure.
Push Operation
The push operation is used to insert an element into the stack. The new element is added at the topmost
position of the stack. Consider the linked stack shown in below figure.
To insert an element with value 9, we first check if TOP=NULL. If this is the case, then we allocate memory
for a new node, store the value in its DATA part and NULL in its NEXT part. The new node will then be called
TOP. However, if TOP!=NULL, then we insert the new node at the beginning of the linked stack and name this
new node as TOP.
the algorithm to push an element into a linked stack. In Step 1, memory is allocated for the new node. In Step
2, the DATA part of the new node is initialized with the value to be stored in the node. In Step 3, we check if
the new node is the first node of the linked list. is done by checking if TOP = NULL. In case the IF statement
evaluates to true, then NULL is stored in the NEXT part of the node and the new node is called TOP. However,
if the new node is not the first node in the list, then it is added before the first node of the list (that is, the TOP
node) and termed as TOP.
Pop Operation
The pop operation is used to delete the topmost element from a stack. However, before deleting the value, we
must first check if TOP=NULL, because if this is the case, then it means that the stack is empty and no more
deletions can be done. If an attempt is made to delete a value from a stack that is already empty, an
UNDERFLOW message is printed. Consider the stack shown in below figure.
In case TOP!=NULL, then we will delete the node pointed by TOP, and make TOP point to the second element
of the linked stack. Thus, the updated stack becomes as shown in below figure.
APPLICATIONS OF STACKS:
Reversing a list
Parentheses checker
Conversion of an infix expression into a postfix expression
Evaluation of a postfix expression
Conversion of an infix expression into a prefix expression
Evaluation of a prefix expression
Recursion
Tower of Hanoi
Reversing list
A list of numbers can be reversed by reading each number from an array starting from the first index and pushing
it on a stack. Once all the numbers have been read, the numbers can be popped one at a time and then stored in
the array starting from the first index.
Evaluation of Arithmetic:
ExpressionsPolish Notations:
Infix, postfix, and prefix notations are three different but equivalent notations of writing algebraic expressions.
While writing an arithmetic expression using infix notation, the operator is placed in between the operands. For
example, A+B; here, plus operator is placed between the two operands A and B. Although it is easy for us to
write expressions using infix notation, computers find it difficult to parse as the computer needs a lot of
information to evaluate the expression. Information is needed about operator precedence and associativity rules,
and brackets which override these rules.
So, computers work more efficiently with expressions written using prefix and postfix notations. Postfix
notation was developed by Jan Łukasiewicz who was a Polish logician, mathematician, and philosopher. His
aim was to develop a parenthesis-free prefix notation (also known as Polish notation) and a postfix notation,
which is better known as Reverse Polish Notation or RPN.
To evaluate the expressions, one needs to be aware of the standard precedence rules for arithmetic
expression. The precedence rules for the five basic arithmetic operators are:
Examples of familiar
• operators include +, -, *, /, ^ etc.
• An algebraic expression can be represented using three different notations. They are
– Infix notation
– postfix notation and
– prefix notations
• Infix: It is the form of an arithmetic expression in which we fix (place) the arithmetic operator in between
the two operands.
– Example: (A + B) * (C - D)
• Prefix: It is the form of an arithmetic notation in which we fix (place) the arithmetic operator before (pre)
its two operands. The prefix notation is called as polish notation (due to the polish mathematician Jan
Lukasiewicz in the year 1920).
– Example: * + A B – C D
• Postfix: It is the form of an arithmetic expression in which we fix (place) the arithmetic operator after (post)
its two operands. The postfix notation is called as suffix notation and is also referred to reverse polish
notation.
– Example: A B + C D - *
–
The three important features of postfix expression are:
1. The operands maintain the same order as in the equivalent infix expression.
2. The parentheses are not needed to designate the expression unambiguously.
3. While evaluating the postfix expression the priority of the operators is no longer relevant.
Converting expressions using Stack:
Let us convert the expressions from one type to another. These can be done as follows:
1. Infix to postfix
2. Infix to prefix
3. Postfix to infix
4. Postfix to prefix
5. Prefix to infix
6. Prefix to postfix
Example 1:
Let's take the example of Converting an infix expression into a postfix (A+B/C+D*(E-F) ^G)
Example:
2+3*4
Example 2:
Convert a + b * c + (d * e + f) * g the infix expression into postfix form.
Try by yourself
Example 3:
Convert the following infix expression A + B * C – D / E * H into its equivalent postfix Expression
Example 4:
Convert the following infix expression A + (B * C – (D / E ↑ F) * G) * H into its equivalent postfix expression.
Both these tasks—converting the infix notation into postfix notation and evaluating the postfix expression—
make extensive use of stacks as the primary tool.
Using stacks, any postfix expression can be evaluated very easily. Every character of the postfix expression is
scanned from left to right. If the character encountered is an operand, it is pushed on to the stack. However, if
an operator is encountered, then the top two values are popped from the stack and the operator is applied on
these values. The result is then pushed on to the stack.
Algorithm to evaluate a postfix expression Evaluation of a postfix expression
Let us now take an example that makes use of this algorithm.
Consider the infix expression given as 9 – ((3 * 4) + 8) / 4. Evaluate the expression.
The infix expression 9 – ((3 * 4) + 8) / 4 can be written as 9 3 4 * 8 + 4 / – using postfix notation
OR
Begin
For each Character Symbol do the following
1. Scan the expression left to right
2. if the scanned symbol is an Operand(Number or Character) push it in to the stack
Else if the scanned symbol is an Operator pop two elements from the stack
A- top element
B next to top element
Result= B operator A
Push the result on to the Stack
3. Repeat above steps until end of the expression
4. Return the top of most element of the stack
The time complexity is O(n) because each operand is scanned once, and each operation is performed once.
2 3 4 * + Empty Push 2
34*+ 2 Push 3
4*+ 32 Push 4
*+ 432 Pop 4 and 3, and perform 4*3 = 12. Push 12 into the stack.
+ 12 2 Pop 12 and 2 from the stack, and perform 12+2 = 14. Push 14 into
the stack.
Input Stack
4*25*+ 3 Push 4
*2 5 * + 43 Pop 3 and 4 from the stack and perform 3*4 = 12. Push 12 into
the stack.
25*+ 12 Push 2
5*+ 2 12 Push 5
*+ 5 2 12 Pop 5 and 2 from the stack and perform 5*2 = 10. Push 10 into
the stack.
+ 10 12 Pop 10 and 12 from the stack and perform 10+12 = 22. Push 22
into the stack.
Example 4:
Evaluate the following postfix expression: 6 2 3 + - 3 8 2 / + * 2 ↑ 3 +
QUEUES
Queue is an abstract data structure, is a FIFO (First-In, First-Out) data structure in which the element that is
inserted first is the first one to be taken out. Unlike stacks, a queue is open at both its ends.
The elements in a queue are added at one end called the REAR (en queue) and removed from the other end called
the FRONT(dequeue). Queues can be implemented by using either arrays or linked lists.
Let us explain the concept of queues using the analogies given below.
People moving on an escalator. The people who got on the escalator first will be the first one to step
out of it.
People waiting for a bus. The first person standing in the line will be the first one to get into the bus.
People standing outside the ticketing window of a cinema hall. The first person in the line will get the
ticket first and thus will be the first one to move out of it.
Luggage kept on conveyor belts. The bag which was placed first will be the first to come out at the
other end.
Cars lined at a toll bridge. The first car to reach the bridge will be the first to leave.
Basic Operations
Insert: enqueue() − add (store) an item to the queue.
Suppose we want to add another element , then REAR would be incremented by 1 and the value
would be stored at the position pointed by REAR.
However, before inserting an element in a queue, we must check for overflow conditions. An overflow will
occur when we try to insert an element into a queue that is already full. When REAR = MAX – 1, where MAX
is the size of the queue, we have an overflow condition. Note that we have written MAX – 1 because the index
starts from 0.
Similarly, before deleting an element from a queue, we must check for underflow conditions. An underflow
condition occurs when we try to delete an element from a queue that is already empty. If FRONT = –1 and
REAR = –1, it means there is no element in the queue.
NOTE: The process of inserting an element in the queue is called enqueue, and the process of deleting an
element from the queue is called dequeue.
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.
peek() : This function helps to see the data at the front of the queue. The algorithm of peek() function is as
follows –
isfull()
As we are using 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 –
isempty()
Algorithm of is empty() function
If the value of front is less than MIN or 0, it tells that the queue is not yet initialized, hence empty.
Here's the C programming code –
Queues can be easily represented using linear arrays. As stated earlier, every queue has front and rear variables
that point to the position from where deletions and insertions can be done, respectively.
The array representation of a queue is shown in Fig. 3.1.
All insertions will be done at the rear end and all the deletions will be done at the front end. If FRONT =
REAR = NULL, then it indicates that the queue is empty.
The storage requirement of linked representation of a queue with n elements is O(n) and the typical time
requirement for operations is O(1).
Insert Operation
The insert operation is used to insert an element into a queue. The new element is added as the last element
of the queue.
we first check if FRONT=NULL. If the condition holds, then the queue is empty. So, we allocate memory
for a new node, store the value in its data part and NULL in its next part. The new node will then be called both
FRONT and rear.
if FRONT != NULL, then we will insert the new node at the rear end of the linked queue and name this
new node as rear.
The algorithm shows that inserting an element in a linked queue. In Step 1, the memory is allocated for the
new node. In Step 2, the DATA part of the new node is initialized with the value to be stored in the node. In Step
3, we check if the new node is the first node of the linked queue. This is done by checking if FRONT = NULL. If
this is the case, then the new node is tagged as FRONT as well as REAR. Also NULL is stored in the NEXT part
of the node (which is also the FRONT and the REAR node). However, if the new node is not the first node in the
list, then it is added at the REAR end of the linked queue (or the last node of the queue).
OR
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.
Delete Operation
The delete operation is used to delete the element that is first inserted in a queue, i.e., the element whose address
is stored in FRONT. However, before deleting the value, we must first check if FRONT=NULL because if this
is the case, then the queue is empty and no more deletions can be done. If an attempt is made to delete a value
from a queue that is already empty, an underflow message is printed. Consider the queue shown in Fig. 3.7.
OR
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
4. Queues are widely used as waiting lists for a single shared resource like printer, disk, CPU.
5. Queues are used to transfer data asynchronously (data not necessarily received at same rate as sent)between
two processes (IO buffers), e.g., pipes, file IO, sockets.
6. Queues are used as buffers on MP3 players and portable CD players, iPod playlist.
7. Queues are used in Playlist for jukebox to add songs to the end, play from the front of the list.
8. Queues are used in operating system for handling interrupts. When programming a real-time system that can
be interrupted, for example, by a mouse click, it is necessary to process the interrupts immediately, before
proceeding with the current job. If the interrupts have to be handled in the order of arrival, then a FIFO queue is
the appropriate data structure.
TYPES OF QUEUES
In linear queues, we have discussed so far that insertions can be done only at one end called the REAR and
deletions are always done from the other end called the FRONT. Look at the queue shown in Fig. 3.9.
Now, if you want to insert another value, it will not be possible because the queue is completely full. There is
no empty space where the value can be inserted. Consider a scenario in which two successive deletions are
made. The queue will then be given as shown in Fig. 3.10.
To resolve this problem, we have two solutions. First, shift the elements to the left so that the vacant space can
be occupied and utilized efficiently. But this can be very time-consuming, especially when the queue is quite
large.
The second option is to use a circular queue. In the circular queue, the first index comes right after the last
index. Conceptually, you can think of a circular queue as shown in Fig. 3.11.
Circular Queue
A more efficient queue representation is obtained by regarding the array Q[MAX] as circular. Any number of items
could be placed on the queue. This implementation of a queue is called a circular queue because it uses its storage
array as if it were a circle instead of a linear list.
A circular queue is an improvement over the standard queue structure. In a standard queue when an element is
deleted, the vacant space is not reutilized. However, In a circular Queue, vacant spaces are reutilized.
While inserting elements, when you reach end of an array and you want to insert another element, you must
insert that element at the at the beginning (given that the first element has been deleted and the space is vacant)
Variables used:
In addition to all the variables that are used in a standard queue, circular support one extra variable:
Count: Number of elements present in a queue
If front = 0 and rear = MAX – 1, then the circular queue is full. Look at the queue given in Fig. 3.12
which illustrates this point.
If rear != MAX – 1, then rear will be incremented and the value will be inserted as illustrated in Fig.
3.13.
If front != 0 and rear = MAX – 1, then it means that the queue is not full. So, set rear = 0 and insert the
new element there, as shown in Fig. 3.14.
Enqueue
Void enqueue(int queue[], int element, int rear, int arraysize, int count)
{
If(count==arraysize) // queue is full
Printf(“ Overflow”);
Else
{
Queue[rear]=element;
Rear=(rear+1)%arraysize;
Count=count+1:
}
}
C-function to delete an item from Circular queue
deletions are performed in this case. To delete an element, again we check for three conditions.
Look at Fig. 3.15. If front = –1, then there are no elements in the queue. So, an underflow condition
will be reported.
If the queue is not empty and front = rear, then after deleting the element at the front the queue
becomes empty and so front and rear are set to –1. This is illustrated in Fig. 3.16.
If the queue is not empty and front = MAX–1, then after deleting the element at the front, front is set to
0. This is shown in Fig. 3.17.
Dequeue
Void enqueue(int queue[], int &front, int rear, int count)
{
If(count==0) // queue is empty
Printf(“Underflow”);
Else
{
Queue[front]=0; // Delete the front element
front=(front+1)%arraysize;
Count=count-1:
}
}
A deque (pronounced as ‘deck’ or ‘dequeue’) is a list in which the elements can be inserted or
deleted at eitherend. It is also known as a head-tail linked list because elements can be added
to or removed from either the front (head) or the back (tail) end.
However, no element can be added and deleted from the middle. In the computer’s memory, a
deque is implemented using either a circular array or a circular doubly linked list.
In a deque, two pointers are maintained, LEFT and RIGHT, which point to either end of the
deque. The elements in a deque extend from the LEFT end to the RIGHT end and since it is
circular, Dequeue[N–1] is followed by Dequeue[0]. Consider the deques shown in Fig. 3.18.
Input restricted deque In this dequeue, insertions can be done only at one of the ends,
while deletionscan be done from both ends.
Output restricted deque In this dequeue, deletions can be done only at one of the ends,
while insertionscan be done on both ends.
A deque provides four operations. Figure 4.6 shows the basic operations on a deque.
• enqueue_front: insert an element at front.
• dequeue_front: delete an element at front.
• enqueue_rear: insert element at rear.
• dequeue_rear: delete element at rear
There are two variations of deque. They are:
• Input restricted deque (IRD)
• Output restricted deque (ORD)
An Input restricted deque is a deque, which allows insertions at one end but allows
deletions at both ends of the list.
An output restricted deque is a deque, which allows deletions at one end but allows insertions at
both ends of the list.