Module-2 DS Notes
Module-2 DS Notes
Module-2 DS Notes
MODULE-2
Stack
Stack Definition and Examples
Stack is an ordered list in which insertions (also called push) and deletions (also called pops )
are made at one end called the top.
Given a stack S = (a0, ...., an-1), we say that a0 is the bottom element, an-1 is the top
element,and ai is on top of element ai-1, 0 < i < n.
Since the last element inserted into a stack is the first element removed, a stack is also known
as a Last-In-First-Out (LIFO) list.
Implementation of stack
The first, or bottom, element of the stack is stored in stack [0], the second in stack [1], and the
ith in stack [i-1].
Variable top points to the top element in the stack.
Top= -1 to denote an empty stack.
ADTStack is
objects: a finite ordered list with zero or more elements.
}#include<stdio.h>
#define MAX 10
int top= -1,stack[MAX];
int pop()
{
int itemdel;
if (top==-1)
return 0;
else
{
itemdel=stack[top--];
return itemdel;
}
}
void display()
{
int i;
if(top==-1)
printf("Stack Empty\n");
else
{
printf("Elements Are:\n");
for(i=top;i>=0;i--)
printf("%d\n",stack[i]);
}
}
void main()
{
int ch,item,num,itemdel;
while(1)
{
printf("\nEnter the Choice\n1.Push\n2.Pop\n3.Display\n4.Exit\n");
scanf("%d",&ch);
switch(ch)
{
case 1: printf("Enter item to be inserted\n");
scanf("%d",&item);
push(item);
break;
case 2: itemdel=pop();
if(itemdel)
printf("\n Deleted Item is:%d\n",itemdel);
else
printf("Stack Underflow\n");
break;
case 3: display();
break;
case 4: exit(0);
}
}
If we do not know the maximum size of the stack at compile time, space can be allocated for the
elements dynamically at run time and the size of the array can be increases as needed.
Creation of stack: Here the capacity of the stack is taken as 1. The value of the capacity can be altered
specific to the application
StackCreateS() ::=
int *stack
Stack=(int*)malloc(stack, sizeof(int));
int capacity = 1;
int top = -1;
The function push remains the same except that MAX_STACK_SIZE is replaced with capacity
pop()
{/* delete and return the top element from the stack */
if (top == -1)
return stackEmpty(); /* returns an error key */
return stack[top--];
}
Stackfull with Array doubling:The code for stackFull is changed. The new code for stackFull
attempts to increase the capacity of the array stack so that we can add an additional element to the
stack. In array doubling, the capacity of the array is doubled whenever it becomes necessary to increase
the capacity of an array.
void stackFull()
{
stack=(int*)realloc(stack, 2 * capacity * sizeof(int))
capacity =capacity * 2;
}
Analysis
In the worst case, the realloc function needs to allocate 2*capacity *sizeof (*stack) bytes of
memory and copy capacity*sizeof (*stack)) bytes of memory from the old array into the new
one.
Under the assumptions that memory may be allocated in O(1) time and that a stack element
can be copied in O(1) time, the time required by array doubling is O(capacity). The total time
spent in array doubling is of O(n) where n is the total number of push operations.
Hence even with the time spent on array doubling in the total time of push over all n pushes in
O(n). This conclusion is valid even the stack array is resized by a factor c>1.
Application of stack
Conversion of Expression
Evaluation of expression
Recursion
To convert an expression from infix to prefix or postfix we follow the rules of precedence.
Precedence : The order in which different operators are evaluated in an expression is called
precendence
Associativity : The order in which operators of same precedence are evaluated in an expression
is called Associativity.
The operators are listed in the order of higher precedence down to lower precedence
Operator Associativity
--,++ left-to-right
Unary operators ,!,-,+, &, *,sizeof Right to left
*,/,% left-to-right
+,- left-to-right
The operands in the infix and the postfix expression are in the same order. With respect to operators ,
precedence of operators plays an important role in converting an infix expression to postfix expression.
We make use of the stack to insert the operators according to their precedence.
Algorithm Polish(Q,P)
Suppose Q is an arithmetic expression written in infix notation. This algorithm finds the equivalent
Postfix expression P.
char symbol,item;
push('#');
for (i=0;infix[i]!='\0';i++)
{
symbol=infix[i];
switch(symbol)
{
case '(': push(symbol);
break;
case ')':item=pop();
while(item!='(')
{
postfix[p++]=item;
item=pop();
}
break;
case '+':
case '-':
case '*':
case '/':
case '%':while(precd(s[top])>=precd(symbol))
{
item=pop();
postfix[p++]=item;
}
push(symbol);
break;
Analysis: Let n be length of the infix string. (n) time is spent extracting tokens . There are two while
loop where the total time spent is (n) since the number of tokens that get stacked and unstacked is
linear in n . So the complexity of the function is (n)
Each operator in a postfix string refers to the previous two operands in the string. If we are parsing a
string, each time we read operands we push it to the stack and when we read a operator, its operands
will be the two topmost elements in the stack. We can then pop these two operands and perform the
indicated operation and push the result back to the stack so that it can be used by the next operator.
The following function evaluates a postfix expression using a stack and a stack of float elements is
declared globally
float s[25];
int top;
It does not check if the postfix expression is valid or not. If we input erroneous expression it
returns wrong result
We cannot enter negative numbers, as the symbol to indicate negation will be misinterpreted
as subtraction operation
Analysis: Let n be length of the postfix string then the complexity of the function is (n)
Algorithm PostfixEval(P)
This algorithm finds the VALUE of an arithmetic expression P written in postfix notation
Recursion: Recursion is the process of defining an object in terms of a simpler case of itself.
Suppose p is a function containing either a call statement to itself (direct recursion) or a call statement
to a second function that may eventually result in a call statement back to the original function
P(indirect recursion). Then the function P is called a recursive function.
There must be a certain argument called the base value for which the function will not call
itself.
Each time the function refers to itself the argument of the function must be closer to the base
value.
Factorial function: The factorial of a number n is got by finding the product of all the number form
1 to n. ie 1*2*3…*n.. It is represented as n!
Example 4!=4*3*2*1=24
5!=5*4*3*2*1=120
0!=1
From a close observation it can be observed that 5!= 5*4! . Therefore n!=n*(n-1)!
The definition is recursive since the function refers to itself for all value of n>0.
The value of n! is explicitly given as 1 when the value of n=0 which can be taken as the base
value.
This can be implemented by the code
factorial(int n)
{
f=1;
for(i=1;i<=n;i++)
f=f*i;
return(f)
}
This is the iterative implementation of the factorial function
For example
factorial (5)=5*factorial(4)
factorial (4)=4*factorial(3)
factorial (3)=3*factorial(2)
factorial(2)=2*factorial(1)
factorial(int n)
{
if (n==0 )
return(1);
else
return(n*factorial(n-1))
}
Fibonacci numbers in C
Example:
Fibo(4)= fibo(3)+fibo(2)
=fibo(2)+fibo(1)+fibo(2)
=fibo(1)+fibo(0)+fibo(1)+ fibo(2)
= 1+ fibo(0)+fibo(1)+ fibo(2)
=1+0 + fibo(1)+ fibo(2)
Dept. of ISE, SVIT Page 11
18CS32 DS Notes
=1+0+1+fibo(2)
=2+ fibo(1)+ fibo(0)
=2+1+fibo(0)
=2+1+0= 3
GCD of two numbers: The function accepts two numbers as input and returns the gcd of the two
numbers
gcd(int m, int n)
{
if (n==0)
return m;
retrun(gcd(n,m%n))
}
Example:
gcd(2,0)=2
gcd(0,2)= gcd(2,0)=2
gcd(4,2)= gcd(2,0)=2
gcd(7,3)= gcd(3,1)= gcd(1,0)=1
Binary search in C
int binsearch(int *a , int key, int low, int high)
{
If (low>high)
return(-1);
mid=(low+high)/2;
if (key==a[mid])
return(mid)
else
if (key>a[mid])
return(binsearch(a,key,mid+1,high));
else
return(binsearch(a,key,low,mid-1));
}
Write a program to solve the Tower of Hanoi problem using a recursive function
void tower(int n,char source,char temp,char dest)
{
if(n==1)
{
printf("Move disc 1 from %c to %c\n",source,dest);
count++;
return;
}
tower(n-1,source,dest,temp);
printf("Move disc %d from %c to %c\n",n,source,dest);
count++;
tower(n-1,temp,source,dest);
}
Void main()
{
int n,count;
printf("Enter the number of discs\n");
scanf("%d",&n);
tower(n,'A','B','C');
printf("The number of moves=%d\n",count);
}
Note: Ideal number of moves to solve Tower of Hanoi is given as 2n -1 where n is the total number of
disks
Ackermann Function
The Ackermann function is a function with two arguments each of which can be assigned any non
negative integer 0,1,2… .......... This function finds its application in mathematical logic.
This function is defined as follows
a) If m = 0 then A(m,n) = n + 1
b) If m != 0 but n = 0 then A(m,n) = A(m - 1,1)
c) If m != 0 and n != 0 then A(m,n) = A(m - 1, A(m,n - 1))
Example1:
A(1,2) =A(0,A(1,1))
=A(0,A(0,A(1,0)))
= A(0,A(0,A(0,1))
=A(0,A(0,2)
=A(0,3)
4
Example 2:
A(1,3) = A(0,A(1,2))
=A(0,A(0,A(1,1)))
=A(0,A(0,A(0,A(1,0))))
=A(0,A(0,A(0,A(0,1))))
=A(0,A(0,A(0,2)
=A(0,A(0,3)
=A(0,4)
5
Iterative Recursive
Implemented using looping Implemented using recursive calls to
statements functions
Executes faster Takes more time to execute
Memory utilization is Less Memory utilization is more
Lines of code are more Lines of code are lesser
Does not require stack Implementation requires stack
Queues:
A queue is an ordered list in which insertions and deletions take place at different ends. The end at
which new elements are added is called the rear, and that from which old elements are deleted is called
the front. Queues are also known as First-In-First-Out (FIFO) lists.
Example
Initially f =-1 r=-1 queue empty
Element
index [0] [1] [2] [3] [4] [5]
f=-1
Insert 3
Element 3
f=-1
Insert 5
Element 3 5
f=-1
Insert 7
Element 3 5 7
delete
Element 5 7
Deleted item =3
C implementation of queues for an integer array: A queue can be represented by using an array to
hold the elements of the array and to use two variables to hold the position of the first and last element
of the queue.
#define size 10
int q[size];
int front=-1 ,rear=-1;
Dept. of ISE, SVIT Page 16
18CS32 DS Notes
Insert operation
The insert operation first checks for queue overflow. If the queue is not full it inserts one element into
the queue at the rear.
Void insert(int item)
{
If rear==size-1)
Printf(“queue overflow”);
else
{
rear++;
q[rear]=item;
}
}
Delete operation: Delete operation checks for queue underflow condition and if the queue is not
empty it will remove the element at the front.
int delete()
{
int itemdel;
if (front ==rear)
{
Printf(“queue underflow”);
return(0);
}
else
{
front++
itemdel=q[front];
return(itemdel);
}
}
Display operation: The display operation will display the elements of the queue if the queue is not
empty.
void display(s)
{
if (front==rear)
printf(“queue empty”);
else
{
for(i=front+1;i<=rear;i++)
printf(“%d”,q[i]);
}
}
Disadvantage of linear queue: The following example illustrates the disadvantage of linear queue
Even if the queue is empty since the value of rear= size-1 elements cannot be inserted into the queue.
This is the disadvantage of linear queue.
Example:
Initially when front=0 rear=0 i.e when front == rear the queue is empty now after 6 insertions
are made again fron=0 and rear= 0 that is the queue is full. So, we cannot distinguish between
an empty and a full queue.
To avoid the resulting confusion, the value of the rear is incremented before we check for the
condition front == rear for queue overflow.
#define MAX_QUEUE_SIZE 6
int q[size];
int front=0 ,rear=0;
Insert operation: The insert operation first checks for queue overflow. If the queue is not full it inserts
one element into the queue at the rear.
Delete operation: Delete operation checks for queue underflow condition and if the queue is not
empty it will remove the element at the front.
element deleteq()
{
element item;
if (front == rear)
return queueEmpty();
front = (front+1) % MAX_QUEUE_SIZE;
return queue[front];
}
To add an element to a full queue, we must first increase the size of this array using a function
such as realloc.
As with dynamically allocated stacks, we use array doubling. However, it isn't sufficient to
simply double array size using realloc.
Consider the full queue . This figure shows a queue with seven elements in an array whose
capacity is 8. To visualize array doubling when a circular queue is used, the array is flattened
out as shown in the array of Figure (b).
To get a proper circular queue configuration, The number of elements copied can be limited to capacity
- 1 by customizing the array doubling code so as to obtain the configuration as shown below.
/* switch to newQueue */
front= 2 * capacity - 1;
rear = capacity - 2;
capacity *= 2;
free(queue);
queue = newQueue;
}
The function copy(a,b,c) copies elements from locations a through b-1 to locations beginning at c
Deques: A deque (pronounced either as deck or dequeue) is a linear list in which elements
can be added or removed at either end but not in the middle. The term deque is a contraction
of the namedouble ended queue.
Representation: It is represented as a circular array deque with pointers left and right, which point to
the two ends of the queue. It is assumed that the elements extend from the left end to the right end in
the array. The term circular comes from the fact that DEQUE[0] comes after DEQUE[n-1] in the array.
Example1:
Left=2 A B C
Right=4
Example2:
Left=5 A B D E
Right=1
[0] [1] [2] [3] [4] [5] [6]
Priority queue: A priority queue is a collection of elements such that each element has been
assigned a priority such that the order in which the elements are deleted and processed comes
from the following rules.
1. An element of higher priority is processed before any element of lower priority.
2. Two elements with the same priority are processed according to the order in which they were
added to the queue.
Example: Time sharing system: programs of higher priority are processed first and programs with the
same priority form a standard queue.
Representation using multiple queue: Use a separate queue for each level of priority. Each queue
will appear in its own circular array and must have its own pair of pointers, front and rear. If each
queue is allocated the same amount of space, a two dimensional array can be used instead of the linear
arrays.
Example : Consider the queue given below with the jobs and its priorities and its representation. A
job with priority 1 is considered to have the highest priority
J1 1
J2 1
J3 2
J4 4
J5 4
J6 6
Front rear 1 2 3 4 5 6
1 1 2 1 J1 J2
2 2 3 2 J3
3 3
4 4 5 4 J4 J5
5 5
6 6 6 6 J6
Delete operation
Algorithm:
1. Find the smallest k such that front[k]!=rear[k] ie Find the first non empty queue
2. Delete the process at the front of the queue
3. Exit
Insert operation
Algorithm: this algorithm adds an ITEM with priority number P to a priority queue maintained by a
two dimensional array
1. Inset ITEM as the rear element in row P-1 of queue
2. exit
If there is a single stack, the starting point is top=-1 and maximum size is SIZE-1
If there are two stacks to be represented in a single array then we use stack [0] for the bottom
element of the first stack, and stack[MEMORY_SIZE - 1] for the bottom element of the second
stack. The first stack grows toward stack[MEMORY_SIZE - 1] and the second grows toward
stack[0]. With this representation, we can efficiently use all the available space.
Representing more than two stacks within the same array poses problems since we no longer
have an obvious point for the bottom element of each stack. Assuming that we have n stacks,
we can divide the available memory into n segments. This initial division may be done in
proportion to the expected sizes of the various stacks, if this is known. Otherwise, we may
divide the memory into equal segments.
Assume that i refers to the stack number of one of the n stacks.To establish this stack, we must
create indices for both the bottom and top positions of this stack.The convention we use is that
o bottom [i], 0 ≤ i < MAX_STACKS, points to the position immediately to the left of the
bottom element of stack i.
o top[i], 0 ≤ i < MAX_STACKS points to the top element.
o Stack i is empty if bottom[i] = top[i].
To divide the array into roughly equal segments we use the following code:
Stack i can grow from bottom[i] + 1 to bottom [i + 1 ] before it is full. Boundary for the last stack,
boundary [n] is set to MEMORY_SIZE- 1
Initial configuration of the stack is shown below m is the size of the memory
element pop(int i)
{
if (top[i] == bottom[i])
return stackEmpty(i);
return stack[top[i]--];
}
Mazing Problem
Representation: Maze is represented as a two dimensional array in which zeros represent the open
paths and ones the barriers. The location in the maize can be determined by the row number and
column number Figure below shows a simple maze.
If the position is on the border then there are less than eight directions. To avoid checking for border
conditions the maze can be surrounded by a border of ones. Thus an m*p maize will require an
(m+2)*(p+2) array the entrance is at position [1][1] and exit is at [m][p]. The possible direction to
move can be predefined in an array move as shown below where the eight possible directions are
numbered from 0 to 7. For each direction we indicate the vertical and horizontal offset.
Offset move[8];
Table of moves: The array moves is initialized according to the table given below.
As we move through the maze, we may have the choice of several directions of movement. Since we
do not know the best choice we save our current position and arbitrarily pick a possible move. By
saving the current position we can return to it and try another path. A second two dimensional array
can be maintained to keep track of the visited locations. The maze can be implemented by making use
of a stack where the element is defined as follows.