Datastructures unitII
Datastructures unitII
UNIT II - DS Notes
STACK
A stack is an Abstract Data Type (ADT), that is popularly used in most programming languages.
It is named stack because it has the similar operations as the real-world stacks, for example – a
pack of cards or a pile of plates, etc.
A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle. Stack has
one end at any given time, we can only access the top element of a stack, whereas the Queue has
two ends (front and rear). It contains only one pointer top pointer pointing to the topmost
element of the stack. Whenever an element is added in the stack, it is added on the top of the
stack, and the element can be deleted only from the stack. In other words, a stack can be
defined as a container in which insertion and deletion can be done from the one end known
as the top of the stack.
Some key points related to stack
It is called as stack because it behaves like a real-world stack, piles of books, etc.
A Stack is an abstract data type with a pre-defined capacity, which means that it can store
the elements of a limited size.
It is a data structure that follows some order to insert and delete the elements, and that
order can be LIFO or FILO.
Stack Representation
The following diagram depicts a stack and its operations −
A stack can be implemented by means of 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 stack using arrays, which makes it a fixed size stack implementation.
lOMoARcPSD|287 285 20
Stack operations usually are performed for initialization, usage and, de-initialization of the stack
ADT.The most fundamental operations in the stack ADT include: push(), pop(), peek(), isFull(),
isEmpty(). These are all built-in operations to carry out data manipulation and to check the status
of the stack.Stack uses pointers that always point to the topmost element within the stack, hence
called as the top pointer.
Working of Stack
Stack works on the LIFO pattern. As we can observe in the below figure there are five memory
blocks in the stack; therefore, the size of the stack is 5.
Suppose we want to store the elements in a stack and let's assume that stack is empty. We have
taken the stack of size 5 as shown below in which we are pushing the elements one by one until
the stack becomes full.
Since our stack is full as the size of the stack is 5. In the above cases, we can observe that it goes
from the top to the bottom when we were entering the new element in the stack. The stack gets
filled up from the bottom to the top. When we perform the delete operation on the stack, there is
only one way for entry and exit as the other end is closed. It follows the LIFO pattern, which
means that the value entered first will be removed last. In the above case, the value 5 is entered
first, so it will be removed only after the deletion of all the other elements.
Insertion: push()
PUSH operation
The steps involved in the PUSH operation is given below:
Before inserting an element in a stack, we check whether the stack is full.
If we try to insert the element in a stack, and the stack is full, then the overflow condition
occurs.
When we initialize a stack, we set the value of top as -1 to check that the stack is empty.
When the new element is pushed in a stack, first, the value of the top gets incremented,
i.e., top=top+1, and the element will be placed at the new position of the top.
The elements will be inserted until we reach the max size of the stack.
The time complexity for Push operation is O(1). The method for Push will look like this.
Deletion: pop()
pop() is a data manipulation operation which removes elements from the stack.
POP operation
The steps involved in the POP operation is given below:
Before deleting the element from the stack, we check whether the stack is empty.
If we try to delete the element from the empty stack, then the underflow condition
occurs.
If the stack is not empty, we first access the element which is pointed by the top
Once the pop operation is performed, the top is decremented by 1, i.e., top=top-1.
The time complexity for Pop operation is O(1).
lOMoARcPSD|287 285 20
peek()
The peek() is an operation retrieves the topmost element within the stack, without deleting it.
This operation is used to check the status of the stack with the help of the top pointer.
Algorithm
1. START
2. return the element at the top of the stack
3. END
Algorithm
If the S
tack is empty, terminate the method as it is Stack underflow.
If the Stack is not empty, return the element pointed by the top.
The time complexity for Peek operation is O(1).
isFull()
isFull() operation checks whether the stack is full. This operation is used to check the status of
the stack with the help of top pointer.
Algorithm
1. START
2. If the size of the stack is equal to the top position of the stack, the stack is full. Return 1.
3. Otherwise, return 0.
4. END
isEmpty()
The isEmpty() operation verifies whether the stack is empty. This operation is used to check the
status of the stack with the help of top pointer.
Algorithm
1. START
2. If the top value is -1, the stack is empty. Return 1.
3. Otherwise, return 0.
4. END
lOMoARcPSD|287 285 20
In programming terms, putting an item on top of the stack is called push and removing an item is
called pop.
In the above image, although item 3 was kept last, it was removed first. This is exactly how
the LIFO (Last In First Out) Principle works.
We can implement a stack in any programming language like C, C++, Java, Python or C#, but
the specification is pretty much the same.
There are some basic operations that allow us to perform different actions on a stack.
1. Push: Add an element to the top of a stack
2. Pop: Remove an element from the top of a stack
3. IsEmpty: Check if the stack is empty
4. IsFull: Check if the stack is full
5. Peek: Get the value of the top element without removing it
For the array-based implementation of a stack, the push and pop operations take constant time,
i.e. O(1).
Although stack is a simple data structure to implement, it is very powerful. The most common
uses of a stack are:
1. To reverse a word - Put all the letters in a stack and pop them out. Because of the LIFO
order of stack, you will get the letters in reverse order.
2. In compilers - Compilers use the stack to calculate the value of expressions like 2 + 4 / 5
* (7 - 9) by converting the expression to prefix or postfix form.
3. In browsers - The back button in a browser saves all the URLs you have visited
previously in a stack. Each time you visit a new page, it is added on top of the stack.
When you press the back button, the current URL is removed from the stack, and the
previous URL is accessed.
4. Stack can be used to implement back/forward button in the browser.Undo feature in the
text editors are also implemented using Stack.
5. It is also used to implement recursion.
6. Call and return mechanism for a method uses Stack.
7. It is also used to implement backtracking.
8. Balancing of symbols: Stack is used for balancing a symbol.
For example, we have the following program:
int main()
{
cout<<"Hello";
cout<<"javaTpoint";
}
As we know, each program has an opening and closing braces; when the opening braces come,
we push the braces in a stack, and when the closing braces appear, we pop the opening braces
from the stack. Therefore, the net value comes out to be zero. If any symbol is left in the stack, it
means that some syntax occurs in a program.
9. String reversal: Stack is also used for reversing a string. For example, we want to
reverse a "javaTpoint" string, so we can achieve this with the help of a stack.
First, we push all the characters of the string in a stack until we reach the null character.
After pushing all the characters, we start taking out the character one by one until we
reach the bottom of the stack.
10. UNDO/REDO: It can also be used for performing UNDO/REDO operations. For
example, we have an editor in which we write 'a', then 'b', and then 'c'; therefore, the text
written in an editor is abc. So, there are three states, a, ab, and abc, which are stored in a
stack. There would be two stacks in which one stack shows UNDO state, and the other
shows REDO state.
If we want to perform UNDO operation, and want to achieve 'ab' state, then we
implement pop operation.
11. Recursion: The recursion means that the function is calling itself again. To maintain the
previous states, the compiler creates a system stack in which all the previous records of
the function are maintained.
12. DFS(Depth First Search): This search is implemented on a Graph, and Graph uses the
stack data structure.
13. Backtracking: Suppose we have to create a path to solve a maze problem. If we are
lOMoARcPSD|287 285 20
moving in a particular path, and we realize that we come on the wrong way. In order to
come at the beginning of the path to create a new path, we have to use the stack data
structure.
14. Expression conversion: Stack can also be used for expression conversion. This is one of
the most important applications of stack. The list of the expression conversion is given
below:
Infix to prefix
Infix to postfix
Prefix to infix
Prefix to postfix
Postfix to infix
15. Memory management: The stack manages the memory. The memory is assigned in the
contiguous memory blocks. The memory is known as stack memory as all the variables
are assigned in a function call stack memory. The memory size assigned to the program is
known to the compiler. When the function is created, all its variables are assigned in the
stack memory. When the function completed its execution, all the variables assigned in
the stack are released.
EXPRESSION PARSING
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
Infix Notation
We write expression 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, 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.
lOMoARcPSD|287 285 20
The following table briefly tries to show the difference in all three notations −
S.No. Infix Notation Prefix Notation Postfix Notation
1 a+b +ab ab+
2 (a + b) ∗ c ∗+abc ab+c∗
3 a ∗ (b + c) ∗a+bc abc+∗
4 a/b+c/d +/ab/cd ab/cd/+
5 (a + b) ∗ (c + d) ∗+ab+cd ab+cd+∗
6 ((a + b) ∗ c) - d - ∗+abcd ab+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 has precedence over 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) –
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.
lOMoARcPSD|287 285 20
Before understanding the conversion from infix to postfix notation, we should know about the
infix and postfix notations separately.
An infix and postfix are the expressions. An expression consists of constants, variables, and
symbols. Symbols can be operators or parenthesis. All these components must be arranged
according to a set of rules so that all these expressions can be evaluated using the set of rules.
5+6
A- B
(P * 5)
All the above expressions have a common structure, i.e., we have an operator between the two
operands. An Operand is an object or a value on which the operation is to be performed. In the
above expressions, 5, 6 are the operands while '+', '-', and '*' are the operators.
When the operator is written in between the operands, then it is known as infix notation. Operand
does not have to be always a constant or a variable; it can also be an expression itself.
For example:
(p + q) * (r + s)
In the above expression, both the expressions of the multiplication operator are the operands,
i.e., (p + q), and (r + s) are the operands.
In the above expression, there are three operators. The operands for the first plus operator are p
and q, the operands for the second plus operator are r and s. While performing the operations on
the expression, we need to follow some set of rules to evaluate the result. In the above
expression, addition operation would be performed on the two expressions, i.e., p+q and r+s, and
then the multiplication operation would be performed.
Operators Symbols
Parenthesis ( ), {}, [ ]
Exponents ^
Multiplication and Division *, /
Addition and Subtraction +,-
The first preference is given to the parenthesis; then next preference is given to the exponents. In
the case of multiple exponent operators, then the operation will be applied from right to left.
For example:
2^2^3 = 2 ^ 8
= 256
After exponent, multiplication, and division operators are evaluated. If both the operators are
present in the expression, then the operation will be applied from left to right.
The next preference is given to addition and subtraction. If both the operators are available in the
expression, then we go from left to right.
The operators that have the same precedence termed as operator associativity. If we go from left
to right, then it is known as left-associative. If we go from right to left, then it is known as right-
associative.
To evaluate the infix expression, we should know about the operator precedence rules, and if the
operators have the same precedence, then we should follow the associativity rules. The use of
parenthesis is very important in infix notation to control the order in which the operation to be
performed. Parenthesis improves the readability of the expression. An infix expression is the
most common way of writing expression, but it is not easy to parse and evaluate the infix
expression without ambiguity. So, mathematicians and logicians studied this problem and
discovered two other ways of writing expressions which are prefix and postfix. Both expressions
do not require any parenthesis and can be parsed without ambiguity. It does not require operator
precedence and associativity rules.
Postfix Expression
The postfix expression is an expression in which the operator is written after the operands. For
example, the postfix expression of infix notation ( 2+3) can be written as 23+.
Some key points regarding the postfix expression are:
In postfix expression, operations are performed in the order in which they have written
from left to right.
It does not any require any parenthesis.
We do not need to apply operator precedence rules and associativity rules.
lOMoARcPSD|287 285 20
Scan the expression from left to right until we encounter any operator.
Perform the operation
Replace the expression with its computed value.
Repeat the steps from 1 to 3 until no more operators exist.
Let's understand the above algorithm through an
Expression = 2 + 34*
= 2 + 12
Again, we will scan from left to right, and the expression would be:
Expression = 2 12 +
= 14
1. Read a character
2. If the character is a digit, convert the character into int and push the integer into the stack.
3. If the character is an operator,
Pop the elements from the stack twice obtaining two operands.
Perform the operation
Push the result into the stack.
Conversion of infix to postfix
Here, we will use the stack data structure for the conversion of infix expression to prefix
expression. Whenever an operator will encounter, we push operator into the stack. If we
encounter an operand, then we append the operand to the expression.
Illustration:
2nd Step: Here i = 1 and exp[i] = ‘+’ i.e., an operator. Push this into the stack. postfix =
“a” and stack = {+}.
3rd Step: Now i = 2 and exp[i] = ‘b’ i.e., an operand. So add this in the postfix
expression. postfix = “ab” and stack = {+}.
4th Step: Now i = 3 and exp[i] = ‘*’ i.e., an operator. Push this into the stack. postfix =
“ab” and stack = {+, *}.
6th Step: Now i = 5 and exp[i] = ‘+’ i.e., an operator. The topmost element of the stack has
higher precedence. So pop until the stack becomes empty or the top element has less precedence.
‘*’ is popped and added in postfix. So postfix = “abc*” and stack = {+}.
Final Step: Now no element is left. So empty the stack and add it in the postfix expression.
postfix = “abc*+d+”.
V +/ KL + MN*-OP^W*U/V
* +* KL+MN*-OP^W*U/V/
T +* KL+MN*-OP^W*U/V/T
lOMoARcPSD|287 285 20
+ + KL+MN*-OP^W*U/V/T*
KL+MN*-OP^W*U/V/T*+
Q + KL+MN*-OP^W*U/V/T*Q
KL+MN*-OP^W*U/V/T*+Q+
The final postfix expression of infix expression (K + L - M*N + (O^P) * W/U/V * T + Q) is
KL+MN*-OP^W*U/V/T*+Q+.
A Queue is also a linear data structure where insertions and deletions are performed from two
different ends. A new element is added from the rear of Queue and deletion of existing element
occurs from the front. Since we can access elements from both ends and the element inserted
first will be the one to be deleted first, hence Queue is called First in First Out data structure
(FIFO).
A Queue is like a line waiting to purchase tickets, where the first person in line is the first
person served. (i.e. First come first serve).
Position of the entry in a queue ready to be served, that is, the first entry that will be
removed from the queue, is called the front of the queue(sometimes, head of the queue),
similarly, the position of the last entry in the queue, that is, the one most recently added, is
called the rear (or the tail) of the queue. See the below figure.
Characteristics of Queue:
Queue can handle multiple data.
We can access both ends.
They are fast and flexible.
Queue Representation:
Like stacks, Queues can also be represented in an array: In this representation, the Queue is
implemented using the array. Variables used in this case are
Queue: the name of the array storing queue elements.
Front: the index where the first element is stored in the array representing the queue.
Rear: the index where the last element is stored in an array representing the queue.
lOMoARcPSD|287 285 20
for the last element, reset the values of FRONT and REAR to -1
lOMoARcPSD|287 285 20
Limitations of Queue
As you can see in the image below, after a bit of enqueuing and dequeuing, the size of the queue
has been reduced.
And we can only add indexes 0 and 1 only when the queue is reset (when all the elements have
been dequeued).
After REAR reaches the last index, if we can store extra elements in the empty spaces (0 and 1),
we can make use of the empty spaces. This is implemented by a modified queue called
the circular queue.
Complexity Analysis
The complexity of enqueue and dequeue operations in a queue using an array is O(1). If you
use pop(N) in python code, then the complexity might be O(n) depending on the position of the
item to be popped.
lOMoARcPSD|287 285 20
Applications of Queue
CPU scheduling, Disk Scheduling
When data is transferred asynchronously between two processes.The queue is used for
synchronization. For example: IO Buffers, pipes, file IO, etc
Handling of interrupts in real-time systems.
Call Center phone systems use Queues to hold people calling them in order.
Types of Queues
In this tutorial, you will learn different types of queues with along with illustration.
A queue is a useful data structure in programming. It is similar to the ticket queue outside a
cinema hall, where the first person entering the queue is the first person who gets the ticket.
There are four different types of queues:
1. Simple Queue
2. Circular Queue
3. Priority Queue
4. Double Ended Queue
Simple Queue
In a simple queue, insertion takes place at the rear and removal occurs at the front. It strictly
follows the FIFO (First in First out) rule.
The main advantage of a circular queue over a simple queue is better memory utilization. If the
last position is full and the first position is empty, we can insert an element in the first position.
This action is not possible in a simple queue.
A circular queue is the extended version of a regular queue where the last element is connected
to the first element. Thus forming a circle-like structure.
1. Enqueue Operation
check if the queue is full
for the first element, set value of FRONT to 0
circularly increase the REAR index by 1 (i.e. if the rear reaches the end, next it would be
at the start of the queue)
add the new element in the position pointed to by REAR
2. Dequeue Operation
check if the queue is empty
return the value pointed by FRONT
circularly increase the FRONT index by 1
for the last element, reset the values of FRONT and REAR to -1
However, the check for full queue has a new additional case:
Case 1: FRONT = 0 && REAR == SIZE - 1
Case 2: FRONT = REAR + 1
The second case happens when REAR starts from 0 due to circular increment and when its value
is just 1 less than FRONT, the queue is full.
lOMoARcPSD|287 285 20
lOMoARcPSD|287 285 20
Priority Queue
A priority queue is a special type of queue in which each element is associated with a priority
and is served according to its priority. If elements with the same priority occur, they are served
according to their order in the queue.
The element with the highest value is considered the highest priority element. However, in other
cases, we can assume the element with the lowest value as the highest priority element.
If there is no node,
create a newNode.
else (a node is already present)
insert the newNode at the end (last node from left to right.)
heapify the array
For Min Heap, the above algorithm is modified so that parentNode is always smaller
than newNode.
2. Deleting an Element from the Priority Queue
Deleting an element from a priority queue (max-heap) is done as follows:
Select the element to be deleted
lOMoARcPSD|287 285 20
For Min Heap, the above algorithm is modified so that the both childNodes are smaller
than currentNode.
3. Peeking from the Priority Queue (Find max/min)
Peek operation returns the maximum element from Max Heap or minimum element from Min
Heap without deleting the node.
For both Max heap and Min Heap
return rootNode
4. Extract-Max/Min from the Priority Queue
Extract-Max returns the node with maximum value after removing it from a Max Heap whereas
Extract-Min returns the node with minimum value after removing it from Min Heap.
Priority Queue Applications
Some of the applications of a priority queue are:
Dijkstra's algorithm
for implementing stack
for load balancing and interrupt handling in an operating system
for data compression in Huffman code
Deque Representation
Types of Deque
1. Input Restricted Deque
In this deque, input is restricted at a single end but allows deletion at both the ends.
2. Output Restricted Deque
In this deque, output is restricted at a single end but allows insertion at both the
ends.
lOMoARcPSD|287 285 20
Operations on a Deque
Below is the circular array implementation of deque. In a circular array, if the array is full, we
start from the beginning.
But in a linear array implementation, if the array is full, no more elements can be inserted. In
each of the operations below, if the array is full, "overflow message" is thrown.
Before performing the following operations, these steps are followed.
1. Take an array (deque) of size n.
2. Set two pointers at the first position and set front = -1 and rear = 0.
If the deque is empty (i.e. front = -1), deletion cannot be performed (underflow condition).
If the deque has only one element (i.e. front = rear), set front = -1 and rear = -1.
Else if front is at the end (i.e. front = n - 1), set go to the front front = 0.
Else, front = front + 1.
If the deque is empty (i.e. front = -1), deletion cannot be performed (underflow
condition).
If the deque has only one element (i.e. front = rear), set front = -1 and rear = -1, else
follow the steps below.
If rear is at the front (i.e. rear = 0), set go to the front rear = n - 1.
Else, rear = rear - 1.
5. Check Empty
This operation checks if the deque is empty. If front = -1, the deque is empty.
6. Check Full
This operation checks if the deque is full. If front = 0 and rear = n - 1 OR front = rear + 1, the
deque is full.
Time Complexity
The time complexity of all the above operations is constant i.e. O(1).