DS unit-3
DS unit-3
STACKS
A stack is a linear data structure where elements are stored in the LIFO (Last In First
Out) principle where the last element inserted would be the first element to be deleted.
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,
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.
1
UNIT-3:STACKS
1. When the stack is empty, trying to perform pop() operation causes an exception
“StackUnderFlow”
2. When the stack is full, trying to perform push() operation causes an exception
“StackUnderFlow”
3. When push() operation is performed ‘top’ will be incremented by one; similarly when
pop() is performed ‘top’ is decremented by one.
4. ‘top’ is a pointer which indicates the status of stack and topmost element on stack.
First let us see how to use an array to implement a stack Stack. First of all since the
implementation is static we need to declare the array size ahead of time. The following are
two of the attributes associated with the Stack:
MaxSize: This is the maximum size of the stack and is the size of the array.
2
UNIT-3:STACKS
Top: This is the index of the top element of stack Stack which is the array which
stores elements of stack
The following are the operations associated with the stack:
IsEmpty: This returns true if stack is empty, else false
IsFull: This returns true if stack is full, else false.This operation is only defined for
Stack ADT when the ADT is represented using an array and hence is
implementation dependent.
Top: This returns the element at the top of stack without removing it from the stack
Push: This adds an element to the top of stack
Pop: This deletes the element at the top of stack
DisplayStack: This prints all the data in the stack
Now let us see in detail how the above operations are carried out when the stack is
represented using an array Stack.
Figure 8.2 shows an array Stack [0,1,…..MaxStack-1]
where MaxStack is the maximum size of the stack. The array Stack contains k+1 items
of type StackItem Type. The value of Top or index to access the stack in this case is k.
3
UNIT-3:STACKS
pop(): It removes an element from the top of the stack. It takes O(1) time, as the top
always points to the newly inserted node.
peek(): It returns the top element of the stack.
size(): It returns the size of the stack, i.e., the total number of items in a stack.
isEmpty(): Returns a boolean value. It returns true if the stack is empty. Else, it
returns false.
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.
Algorithem for PUSH():
1.Checks if the stack is full.
2. If the stack is full, produces an error and exit.
3. If the stack is not full, increments top to point next
empty space.
4. Adds data element to the stack location, where top
is pointing.
5. Returns success.
4
UNIT-3:STACKS
5
UNIT-3:STACKS
6
UNIT-3:STACKS
7
UNIT-3:STACKS
8
UNIT-3:STACKS
Infix Notation:
Each operator is positioned between the operands in an expression written using the infix
notation. Depending on the requirements of the task, infix expressions may be
parenthesized or not.
Example: A + B, (C – D) etc.
Because the operator appears between the operands, all of these expressions are written in
infix notation.
Prefix Notation:
The operator is listed before the operands in the prefix notation. Since the Polish
mathematician invented this system, it is frequently referred to as polish notation.
Example: + A B, -CD etc.
Because the operator occurs before the operands in all of these expressions, prefix notation
is used.
Postfix Notation:
The operator is listed after the operands in postfix notation. Polish notation is simply
reversed in this notation, which is also referred to as Reverse Polish notation.
Example: AB +, CD+, etc.
All these expressions are in postfix notation because the operator comes after the operands.
Evaluation of Arithmetic Expressions:
Evaluation of Arithmetic Expression requires two steps:
1. Put the provided expression first in special notation.
2. In this new notation, evaluate the expression.
The normal precedence rules for arithmetic expressions must be understood in order to
evaluate the expressions. The following are the five fundamental arithmetic operators’
precedence rules:
Highest followed by
^ exponentiation Right to left *Multiplication and
/division
9
UNIT-3:STACKS
Highest followed by +
*Multiplication,
Left to right addition and –
/division
subtraction
+ addition, –
Left to right lowest
subtraction
2. Backtracking
Another use for Stack is backtracking. The optimization problem is solved using a recursive
technique.
3. Delimiter Checking
Delimiter checking, or parsing, which entails analysing a source program
syntactically, is the most prevalent application of Stack in data structures.
Additionally known as parenthesis checking. When a source program written in a
programming language, such as C or C++, is translated into machine language, the
compiler separates the program into several components, such as variable names,
keywords, etc.
by moving left to right while scanning The mismatched delimiters are the main issue
when translating.
We employ a variety of delimiters, such as the parenthesis checks (,), curly braces (,),
square brackets (,), and the widely used / and / delimiters.
Each opening delimiter must be followed by a corresponding closing delimiter, i.e.,
each opening parenthesis must be followed by a corresponding closing parenthesis.
Also, the delimiter can be nested.
The opening delimiter that occurs later in the source program should be closed before
those occurring earlier.
4. Reverse a Data(or)String reversal:
We must reorder the data in such a way that the first and final elements are switched, the
second and second-last elements are switched, and so on for all subsequent elements if we
want to reverse a given collection of data.
Example: If we were to reverse the string Welcome, we would get Emoclew.
There are different reversing applications:
Reversing a string
10
UNIT-3:STACKS
In the example above, we get seven as a quotient and one as the reminder when we divide
14 by 2, and these two values are pushed onto the stack. When we divide seven by two once
more, we get three as the quotient and one as the reminder, which is once more added to the
11
UNIT-3:STACKS
Stack. The supplied number is lowered in this manner until it does not reach zero. The
comparable binary number 1110 is what we receive after we totally pop off the stack.
5.Memory management:
Memory management in data structures is crucial for efficient use of resources and optimal
performance. Here are some key concepts:
1. Dynamic Memory Allocation: This involves allocating memory during runtime
using functions like malloc, calloc, realloc, and free in languages like C and C++. It
allows for flexible memory usage, enabling the creation of data structures of varying
sizes1.
2. Static memory management refers to the allocation of memory at compile-time,
meaning the size and location of memory are determined before the program runs.
Here are some key points about static memory management:
Fixed Size: The memory allocated is fixed and cannot be changed during
runtime. This means you need to know the exact memory requirements
beforehand1.
Compiler-Controlled: Allocation and deallocation of memory are handled by
the compiler, not the programmer1.
Stack Usage: Static memory allocation typically uses the stack data
structure. Variables are stored in the stack, and their memory is automatically
managed when functions are called and return1.
Efficiency: Execution is generally faster compared to dynamic memory
allocation because memory is allocated at compile-time.
Predictability: Since the memory allocation is fixed, it is easier to predict and
manage memory usage2.
12
UNIT-3:STACKS
Function A’s processing won’t be finished until function B’s execution and return have
been completed. This is because function A contains a call to function B. The same is true
of functions B and C. As a result, we see that function A can only be finished after function
B, and function B can only be finished after function C. As a result, function A should be
begun first and finished last. In conclusion, utilising Stack makes it simple to handle the
function activity described above, which follows the last in first out behaviour.
Consider the addresses of the statements to which control is transferred following the
completion of functions A, B, and C as addrA, addrB, and addrC, respectively.
In the Stack, return addresses are displayed in the order that the functions were called, as
can be seen in the image above. Each function is finished, followed by the pop operation,
which starts execution at the position where the Stack was removed. The stack data
structure may thus handle the program that calls multiple functions one after another in the
best possible way. Each function receives control in the proper location, which is the calling
sequence’s reversal.
TOWERS OF HANNOI:
A skeletal recursive procedure (Outline) for the solution of the problem for N number of
disks is as follows:
1. Move the top N-1 disks from peg A to peg B (using C as an auxiliarypeg)
2. Move the bottom disk from peg A to peg C
13
UNIT-3:STACKS
3. Move N-1 disks from Peg B to Peg C (using Peg A as an auxiliary peg)
The pictorial representation of the skeletal recursive procedure for N=4 disks is shown
in Figure 2.
Algorithm
TOH( n, Sour, Aux , Des)
If(n=1)
Write ("Move Disk “, n ," from ", Sour ," to ",Des)
Else
TOH(n-1,Sour,Des,Aux);
Write ("Move Disk “, n ," from ", Sour ," to ",Des)
TOH(n-1,Aux,Sour,Des);
END
14
UNIT-3:STACKS
RECURSION:
Recursion, in the context of algorithms, is a powerful programming concept where a
function calls itself during its execution. It involves breaking down a complex problem into
simpler, more manageable sub-problems and solving each of them. This repetitive self-
calling characteristic distinguishes recursive approaches from traditional iterative methods.
Significance of Recursion in Algorithms
Recursion plays a fundamental and pivotal role in the landscape of algorithms, offering a
unique perspective on problem-solving. Its significance lies in the following aspects:
1. Fundamental Role in Problem-Solving
Recursion provides a natural and elegant way to tackle complex problems by expressing
them in terms of simpler sub-problems. This decomposition allows programmers to build
solutions incrementally, starting from the simplest cases and gradually addressing more
intricate scenarios.
2. Versatility in Handling Complex Tasks
Recursion enables algorithms to embrace a "divide-and-conquer" strategy. By breaking
down a problem into smaller components and solving them independently, recursive
solutions often lead to clearer and more maintainable code. This versatility is particularly
advantageous in scenarios where a problem can be naturally divided into smaller, solvable
sub-problems.
15
UNIT-3:STACKS
Types of Recursion
A. Linear Recursion
Linear recursion represents the basic form of recursion, where a function calls itself exactly
once during its execution. This straightforward approach forms the foundation for more
complex recursive strategies.
B. Tail Recursion
Tail recursion is a specific form of recursion where the recursive call is the last operation
performed in the function. This unique characteristic allows for optimization in certain
programming environments.
Recursion vs. Iteration
A. Contrasting Recursion with Iterative Approaches
Understanding recursion necessitates a comparison with its iterative counterpart. Both
recursion and iteration are mechanisms for solving problems, but they have distinct
characteristics. Let's delve into the differences between these two approaches:
1. Examining the Differences Between Recursive and Iterative Solutions
Recursion:
Function Calls Itself: Recursive solutions involve a function calling itself to solve
smaller instances of a problem.
Elegant, Concise Code: Recursive code can be more expressive and concise,
particularly for problems naturally suited to divide-and-conquer strategies.
Stack Usage: Each recursive call adds a frame to the call stack, which can lead to
stack overflow for deeply nested calls.
Iteration:
Looping Constructs: Iterative solutions use looping constructs
(e.g., for or while loops) to repeatedly execute a set of statements.
Explicit Control: The flow of control is explicit and follows a linear sequence,
making it easier to trace and understand.
Memory Usage: Iterative solutions typically use less memory as they don't rely on
the call stack.
2. When to Choose Recursion over Iteration and Vice Versa
16
UNIT-3:STACKS
***********************
17