UNIT 2 - DataStructures - Reg 21
UNIT 2 - DataStructures - Reg 21
STACK ADT
A stack is linear data structures, a container of elements that are inserted and removed
according to the last-in first-out (LIFO) principle. A stack is an ordered list of elements of
the same data type.for example – a deck of cards or a pile of plates, etc.
What is a Stack?
• Stack is a linear data structure in which the insertion and deletion operations are
performed at only one end.
• In a stack, adding and removing of elements are performed at a single position which is
known as "top". That means, a new element is added at top of the stack and an element
is removed from the top of the stack.
• In stack, the insertion and deletion operations are performed based on LIFO (Last In
First Out) principle.
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 −
1.Stack Overflow
An Attempt to insert an element X when the stack is Full, is said to
be stackoverflow.
For every Push operation, we need to check this condition.
2.Stack Underflow
An Attempt to delete an element when the stack is empty, is said to
be stackunderflow.
For every Pop operation, we need to check this condition.
When data is PUSHed onto 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.
IMPLEMENTATION OF STACK
A stack data structure can be implemented using a one-dimensional array. But stack
implemented using array stores only a fixed number of data values. This implementation is
very simple. Just define a one dimensional array of specific size and insert or delete the
values into that array by using LIFO principle with the help of a variable called 'top'.
Initially, the top is set to -1. Whenever we want to insert a value into the stack, increment
the top value by one and then insert. Whenever we want to delete a value from the stack,
then delete the top value and decrement the top value by one.
As we keep inserting the elements, the Stack gets filled with the elements.
Hence it is necessary to check whether the stack is full or not before inserting a new
element into the stack.
POP OPERATION
If the user needs to know the last element inserted into the stack, then the user can
return the Top element of the stack.
Otherwise, return the element which is pointed by the Top pointer in the Stack.
Routine to return top Element of the stack
int TopElement(Stack S)
{
if(Top==-1)
{
Error(“Empty stack!!No elements”); return 0;
}
else
return S[Top];
}
#include<stdio.h>
int stack[100],choice,n,top,x,i;
void push(void);
void pop(void);
void display(void);
int main()
{
//clrscr();
top=-1;
printf("\n Enter the size of STACK[MAX=100]:");
scanf("%d",&n);
printf("\n\t STACK OPERATIONS USING ARRAY");
printf("\n\t--------------------------------");
printf("\n\t 1.PUSH\n\t 2.POP\n\t 3.DISPLAY\n\t 4.EXIT");
do
{
printf("\n Enter the Choice:");
scanf("%d",&choice);
switch(choice)
{
case 1:
{
push();
break;
}
case 2:
{
pop();
break;
}
case 3:
{
display();
break;
}
case 4:
{
printf("\n\t EXIT POINT ");
break;
}
default:
{
printf ("\n\t Please Enter a Valid Choice(1/2/3/4)");
}
}
}
while(choice!=4);
return 0;
}
void push()
{
if(top>=n-1)
{
printf("\n\tSTACK is over flow");
}
else
{
printf(" Enter a value to be pushed:");
scanf("%d",&x);
top++;
stack[top]=x;
}
}
void pop()
{
if(top<=-1)
{
printf("\n\t Stack is under flow");
}
else
{
printf("\n\t The popped elements is %d",stack[top]);
top--;
}
}
void display()
{
if(top>=0)
{
printf("\n The elements in STACK \n");
for(i=top; i>=0; i--)
printf("\n%d",stack[i]);
printf("\n Press Next Choice");
}
else
{
printf("\n The STACK is empty");
}
The major problem with the stack implemented using an array is, it works only for a fixed
number of data values. That means the amount of data must be specified at the beginning of
the implementation itself. Stack implemented using an array is not suitable, when we don't
know the size of data which we are going to use. A stack data structure can be implemented
by using a linked list data structure. The stack implemented using linked lists can work for
an unlimited number of values. That means, a stack implemented using linked lists works
for the variable size of data. So, there is no need to fix the size at the beginning of the
implementation. The Stack implemented using linked list can organize as many data values
as we want.
In linked list implementation of a stack, every new element is inserted as 'top' element. That
means every newly inserted element is pointed by 'top'. Whenever we want to remove an
element from the stack, simply remove the node which is pointed by 'top' by moving 'top' to
its previous node in the list. The next field of the first element must be always NULL.
Example
In the above example, the last inserted node is 99 and the first inserted node is 25. The
order of elements inserted is 25, 32,50 and 99.
Step 1 - Include all the header files which are used in the program. And declare all the user
defined functions.
Step 2 - Define a 'Node' structure with two members data and next.
Step 4 - Implement the main method by displaying the Menu with a list of operations and
make suitable function calls in the main method.
OUTPUT
APPLICATIONS OF STACK
Syntax Parsing: Many compilers use a stack for parsing the syntax of expressions,
program blocks etc. before translating into low level code.
Backtracking: Suppose we are finding a path for solving maze problem. We choose
a path and after following it we realize that it is wrong. Now we need to go back to
the beginning of the path to start with new path. This can be done with the help of
stack.
Parenthesis Checking: Stack is used to check the proper opening and closing of
parenthesis.
String Reversal: Stack is used to reverse a string. We push the characters of string
one by one into stack and then pop character from stack.
Function Call: Stack is used to keep information about the active functions or
subroutines.
Tower of Hanoi
8 Queen Problem
BALANCING SYMBOLS
Compilers check your programs for syntax errors, but frequently a lack of one
symbol (such as a missing brace or comment starter) will cause the compiler to spill out a
hundred lines of diagnostics without identifying the real error.
Make an empty stack. Read characters until end of file. If the character is an open anything,
push it onto the stack. If it is a close anything, then if the stack is empty report an error.
Otherwise, pop the stack. If the symbol popped is not the corresponding opening symbol,
then report an error. At end of file, if the stack is not empty report an error.
You should be able to convince yourself that this algorithm works. It is clearly linear and
actually makes only one pass through the input. It is thus on-line and quite fast. Extra work
can be done to attempt to decide what to do when an error is reported--such as identifying
the likely cause.
INFIX
The arithmetic operator appears between the two operands to which it is being
applied. If an operator is preceded and succeeded by an operand then such an expression is
termed infix expression.
It follows the scheme of <operand><operator><operand>
E.g., A/B+C
POSTFIX
The arithmetic operator appears directly after the two operands to which it applies.
Also called reverse polish notation.
If an operator is succeeded by both the operand then such an expression is termed
postfix expression.
It follows the scheme of <operand><operand><operator>
E.g., AB+
PREFIX
The arithmetic operator is placed before the two operands to which it applies. Also
called polish notation.
An infix expression is difficult for the machine to know and keep track of
precedence of operators. On the other hand, a postfix expression itself determines the
precedence of operators (as the placement of operators in a postfix expression depends upon
its precedence). Therefore, for the machine it is easier to carry out a postfix expression than
an infix expression.
Let, X is an arithmetic expression written in infix notation. This algorithm finds the
equivalent postfix expression Y.
2. Scan X from left to right and repeat Step 3 to 6 for each element of X until the Stack
is empty.
1. Repeatedly pop from Stack and add to Y each operator (on the top of Stack)
which has the same precedence as or higher precedence than operator.
1. Repeatedly pop from Stack and add to Y each operator (on the top of Stack)
until a left parenthesis is encountered.
7. END.
EXAMPLE 1
Expression: 456*+
EXAMPLE 2
Infix expression:
K + L - M*N + (O^P) * W/U/V * T + Q
The final postfix expression KL+MN*-OP^W*U/V/T*+Q+.
Operand Value
A 2
B 3
C 4
D 4
E 2
FUNCTION CALLS
The algorithm to check balanced symbols suggests a way to implement function calls. The
problem here is that when a call is made to a new function, all the variables local to the
calling routine need to be saved by the system, since otherwise the new function will
overwrite the calling routine's variables. Furthermore, the current location in the routine
must be saved so that the new function knows where to go after it is done. The variables
have generally been assigned by the compiler to machine registers, and there are certain to
be conflicts (usually all procedures get some variables assigned to register #1), especially if
recursion is involved. The reason that this problem is similar to balancing symbols is that a
function call and function return are essentially the same as an open parenthesis and closed
parenthesis, so the same ideas should work.
When there is a function call, all the important information that needs to be saved, such as
register values (corresponding to variable names) and the return address (which can be
obtained from the program counter, which is typically in a register), is saved "on a piece of
paper" in an abstract way and put at the top of a pile. Then the control is transferred to the
new function, which is free to replace the registers with its values. If it makes other function
calls, it follows the same procedure. When the function wants to return, it looks at the
"paper" at the top of the pile and restores all the registers. It then makes the return jump.
Clearly, all of this work can be done using a stack, and that is exactly what happens in
virtually every programming language that implements recursion. The information saved is
called either an activation record or stack frame. The stack in a real computer frequently
grows from the high end of your memory partition downwards, and on many systems there
is no checking for overflow. There is always the possibility that you will run out of stack
space by having too many simultaneously active functions. Needless to say, running out of
stack space is always a fatal error.
In languages and systems that do not check for stack overflow, your program will crash
without an explicit explanation. On these systems, strange things may happen when your
stack gets too big, because your stack will run into part of your program. It could be the
main program, or it could be part of your data, especially if you have a big array. If it runs
into your program, your program will be corrupted; you will have nonsense instructions and
will crash as soon as they are executed. If the stack runs into your data, what is likely to
happen is that when you write something into your data, it will destroy stack information --
probably the return address -- and your program will attempt to return to some weird
address and crash.
In normal events, you should not run out of stack space; doing so is usually an indication of
runaway recursion (forgetting a base case). On the other hand, some perfectly legal and
seemingly innocuous program can cause you to run out of stack space. The routine in
Figure 3.54, which prints out a linked list, is perfectly legal and actually correct. It properly
handles the base case of an empty list, and the recursion is fine. This program can be proven
correct. Unfortunately, if the list contains 20,000 elements, there will be a stack of 20,000
activation records representing the nested calls of line 3. Activation records are typically
large because of all the information they contain, so this program is likely to run out of
stack space. (If 20,000 elements are not enough to make the program crash, replace the
number with a larger one.)
This program is an example of an extremely bad use of recursion known as tail recursion.
Tail recursion refers to a recursive call at the last line. Tail recursion can be mechanically
eliminated by changing the recursive call to a goto preceded by one assignment per function
argument. This simulates the recursive call because nothing needs to be saved -- after the
recursive call finishes, there is really no need to know the saved values. Because of this, we
can just go to the top of the function with the values that would have been used in a
recursive call. The program in Figure 3.55 shows the improved version. Keep in mind that
you should use the more natural while loop construction. The goto is used here to show how
a compiler might automatically remove the recursion.
Removal of tail recursion is so simple that some compilers do it automatically. Even so, it is
best not to find out that yours does not.
void /* Not using a header */
print_list( LIST L )
void
top:
if( L != NULL )
print_element( L->element );
L = L->next;
goto top;
Figure: Printing a list without recursion; a compiler might do this (you should not)
QUEUE ADT
Queue is a linear data structure in which the insertion and deletion operations are performed
at two different ends. In a queue data structure, adding and removing elements are
performed at two different positions. The insertion is performed at one end and deletion is
performed at another end. In a queue data structure, the insertion operation is performed at a
position which is known as 'rear' and the deletion operation is performed at a position which
is known as 'front'. In queue data structure, the insertion and deletion operations are
performed based on FIFO (First In First Out).
Exceptional Conditions
• Overflow: Attempting to insert an element into queue when it is full
• Underflow: Attempting to delete an element from queue when it is empty
Before we implement actual operations, first follow the below steps to create an empty
queue.
● Step 1 - Include all the header files which are used in the program and define a
constant 'SIZE' with specific value.
● Step 2 - Declare all the user defined functions which are used in queue
implementation.
● Step 3 - Create a one dimensional array with above defined SIZE (int queue[SIZE])
● Step 4 - Define two integer variables 'front' and 'rear' and initialize both with '-1'.
(int front = -1, rear = -1)
● Step 5 - Then implement main method by displaying menu of operations list and
make suitable function calls to perform operation selected by the user on queue.
In a queue data structure, enQueue() is a function used to insert a new element into the
queue. In a queue, the new element is always inserted at rear position. The enQueue()
function takes one integer value as a parameter and inserts that value into the queue. We can
use the following steps to insert an element into the queue...
In a queue data structure, deQueue() is a function used to delete an element from the queue.
In a queue, the element is always deleted from front position. The deQueue() function does
not take any value as parameter. We can use the following steps to delete an element from
the queue...
void deQueue(){
if(front == rear)
printf("\nQueue is Empty!!! Deletion is not possible!!!");
else{
printf("\nDeleted : %d", queue[front]);
front++;
if(front == rear)
front = rear = -1;
}}
void display(){
if(rear == -1)
printf("\nQueue is Empty!!!");
else{
int i;
printf("\nQueue elements are:\n");
for(i=front; i<=rear; i++)
printf("%d\t",queue[i]);
}}
an only fixed number of data values. That means, the amount of data must be specified at
the beginning itself. Queue using an array is not suitable when we don't know the size of
data which we are going to use. A queue data structure can be implemented using a linked
list data structure. The queue which is implemented using a linked list can work for an
unlimited number of values. That means, queue using linked list can work for the variable
size of data (No need to fix the size at the beginning of the implementation). The Queue
implemented using linked list can organize as many data values as we want.
Example
In linked list implementation of a queue, the last inserted node is always pointed by 'rear'
and the first node is always pointed by 'front'.
Operations
To implement queue using linked list, we need to set the following things before
implementing actual operations.
● Step 1 - Include all the header files which are used in the program. And declare all
the user defined functions.
● Step 2 - Define a 'Node' structure with two members data and next.
● Step 3 - Define two Node pointers 'front' and 'rear' and set both to NULL.
● Step 4 - Implement the main method by displaying Menu of list of operations and
make suitable function calls in the main method to perform user selected operation.
● Step 1 - Create a newNode with given value and set 'newNode → next' to NULL.
● Step 2 - Check whether queue is Empty (rear == NULL)
● Step 3 - If it is Empty then, set front = newNode and rear = newNode.
● Step 4 - If it is Not Empty then, set rear → next = newNode and rear = newNode.
void dequeue()
{
if(front == NULL)
printf("\nQueue is Empty!!!\n");
else{
struct Node *temp = front;
front = front -> next;
printf("\n Deleted element: %d\n", temp->data);
free(temp);
}
}
CIRCULAR QUEUE
In a normal Queue Data Structure, elements can be inserted until queue becomes full. But
once the queue becomes full, the next element cannot be inserted until all the elements are
deleted from the queue. For example, consider the queue below...
Now consider the following situation after deleting three elements from the queue...
This situation also says that Queue is Full and the new element cannot be inserted because
'rear' is still at last position. In the above situation, even though empty positions are
available in the queue it cannot be used to insert the new element. This is the major problem
in a normal queue data structure. To overcome this problem circular queue data structure is
used.
A circular queue is a linear data structure in which the operations are performed based on
FIFO (First In First Out) principle and the last position is connected back to the first position
to make a circle.
A circular queue solved the limitations of the normal queue. Thus making it a better pick
than the normal queue. It also follows the first come first serve algorithm. Circular Queue is
also called ring Buffer
In Circular Queue, the insertion of a new element is performed at the very first location of
the queue if the last location of the queue is full, in which the first element comes just after
the last element. A circular queue is an abstract data type that contains a collection of data
which allows addition of data at the end of the queue and removal of data at the beginning of
the queue.
Queue items are added at the rear end and the items are deleted at front end of the
circular queue.Here the Queue space is utilized fully by inserting the element at the
Front end if the rear end is full.
Let’s say the MaxSize of your queue is 5, and the rear pointer has already reached
the end of a queue. There is one empty space at the beginning of a queue, which
means that the front pointer is pointing to location 1.
Array implementation starts with the declaration of array and pointer variable
int a[MAX_SIZE];
int front = -1;
It is same as Linear Queue EnQueue Operation (i.e) Inserting the element at the Rear
end
First check for full Queue.
If the circular queue is full, then insertion is not possible
Otherwise check for the rear end.
If the Rear end is full, the elements start getting inserted from the Front end.
else
{
Rear = ( Rear + 1 ) % Arraysize;
CQ[ Rear ] = X;
}
}
Circular Queue DeQueue Operation
It is same as Linear Queue DeQueue operation (i.e) deleting the front element.
First check for Empty Queue
If the Circular Queue is empty, then deletion is not possible
If the Circular Queue has only one element, then the element is deleted and Front
and Rear pointer is initialized to - 1 to represent Empty Queue
Otherwise, Front element is deleted and the Front pointer is made to point to next
element in the Circular Queue
#include<stdio.h>
# define MAX 3
int cqueue_arr[MAX];
int front = -1;
int rear = -1;
void insert(int item)
{
if((front == 0 && rear == MAX-1) || (front == rear+1))
{
printf("Queue Overflow n");
return;
}
if(front == -1)
{
front = 0;
rear = 0;
}
else
{
if(rear == MAX-1)
rear = 0;
else
rear = rear+1;
}
cqueue_arr[rear] = item ;
}
void deletion()
{
if(front == -1)
{
printf("Queue Underflown");
return ;
}
printf("Element deleted from queue is : %dn",cqueue_arr[front]);
if(front == rear)
{
front = -1;
rear=-1;
}
else
{
if(front == MAX-1)
front = 0;
else
front = front+1;
}
}
void display()
{
int front_pos = front,rear_pos = rear;
if(front == -1)
{
printf("Queue is emptyn");
return;
}
printf("Queue elements :n");
if( front_pos <= rear_pos )
while(front_pos <= rear_pos)
{
printf("%d ",cqueue_arr[front_pos]);
front_pos++ ;
}
else
{
while(front_pos <= MAX-1)
{
printf("%d ",cqueue_arr[front_pos]);
front_pos++ ;
}
front_pos = 0;
while(front_pos <= rear_pos)
{
printf("%d ",cqueue_arr[front_pos]);
front_pos++;
}
}
printf("n");
}
int main()
{
int choice,item;
do
{
printf("\n 1.Insertn");
printf("\n 2.Deleten");
printf("\n 3.Displayn");
printf("\n 4.Quitn");
printf("\n Enter your choice : ");
scanf("%d",&choice);
switch(choice)
{
case 1 :
printf("\n Input the element for insertion in queue : ");
scanf("%d", &item);
insert(item);
break;
case 2 :
deletion();
break;
case 3:
display();
break;
case 4:
break;
default:
printf("\n Wrong choicen");
}
}while(choice!=4);
return 0;
}
OUTPUT
1.Insertn
2.Deleten
3.Displayn
4.Quitn
Enter your choice : 1
Input the element for insertion in queue : 100
1.Insertn
2.Deleten
3.Displayn
4.Quitn
Enter your choice : 1
Input the element for insertion in queue : 200
1.Insertn
2.Deleten
3.Displayn
4.Quitn
Enter your choice : 2
Element deleted from queue is : 100n
1.Insertn
2.Deleten
3.Displayn
4.Quitn
Enter your choice : 3
Queue elements :n200 n
1.Insertn
2.Deleten
3.Displayn
4.Quitn
DEQUEUE
DOUBLE ENDED QUEUE
Double ended queue is a more generalized form of queue data structure which allows
insertion and removal of elements from both the ends, i.e , front and back. Double Ended
Queue also called as deque (pronounced as ‘deck’ or ‘dequeue’) is a list in which the
elements can be inserted or deleted at either end in constant time. 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 a sense, this hybrid linear structure provides all the capabilities of stacks and
queues in a single data structure.
Output restricted deque: In this dequeue,deletions can be done only at one of the ends,while
insertions can be done on both ends.
There are four basic operations in usage of Deque
1. Insertion at rear end - Enqueue Right
2. Insertion at front end - Enqueue
Left
3. Deletion at front end - Dequeue Left
4. Deletion at rear end - Dequeue Right
We can perform two more operations on dequeue:
o isFull(): This function returns a true value if the stack is full; otherwise, it returns a
false value.
o isEmpty(): This function returns a true value if the stack is empty; otherwise it
returns a false value.
➔ For input restricted dequeue the operations are Enqueue Right, Dequeue Left, Dequeue
Right.
➔ For output restricted dequeue the operations are Enqueue Left, Enqueue Right, Dequeue
Left.
Memory Representation
The deque can be implemented using two data structures, i.e., circular array, and doubly
linked list.
IMPLEMENTATION OF DEQUE
Time Complexity: Time complexity of all operations like insertfront(), insertlast(),
deletefront(), deletelast()is O(1).
Else we decrement front and insert the element. Since we are using circular array, we
have to keep in mind that if front is equal to 0 then instead of decreasing it by 1 we
make it equal to SIZE-1.
void Dequeue :: push_front(int key)
{
if(full())
{
cout << "OVERFLOW\n";
}
else
{
//If queue is empty
if(front == -1)
front = rear = 0;
else
--front;
arr[front] = key;
}
}
Insert Elements at back
Again we check if the queue is full. If its not full we insert an element at back by following
the given conditions:
If the queue is empty then intialize front and rear to 0. Both will point to the first
element.
Else we increment rear and insert the element. Since we are using circular array, we
have to keep in mind that if rear is equal to SIZE-1 then instead of increasing it by 1
we make it equal to 0
else
++rear;
arr[rear] = key;
}
}
Delete First Element
In order to do this, we first check if the queue is empty. If its not then delete the front
element by following the given conditions :
If only one element is present we once again make front and rear equal to -1.
Else we increment front. But we have to keep in mind that if front is equal to SIZE-1
then instead of increasing it by 1 we make it equal to 0.
else
++front;
}
}
Delete Last Element
Inorder to do this, we again first check if the queue is empty. If its not then we delete the last
element by following the given conditions :
If only one element is present we make front and rear equal to -1.
Else we decrement rear. But we have to keep in mind that if rear is equal to 0 then
instead of decreasing it by 1 we make it equal to SIZE-1.
else
--rear;
}
}
Check if Queue is empty
It can be simply checked by looking where front points to. If front is still intialized with -1,
the queue is empty.
bool Dequeue :: empty()
{
if(front == -1)
return true;
else
return false;
}
Check if Queue is full
Since we are using circular array, we check for following conditions as shown in code to
check if queue is full.
bool Dequeue :: full()
{
if((front == 0 && rear == SIZE-1) ||
(front == rear + 1))
return true;
else
return false;
}
Return First Element
If the queue is not empty then we simply return the value stored in the position which front
points.
int Dequeue :: get_front()
{
if(empty())
{ cout << "f=" <<front << endl;
cout << "UNDERFLOW\n";
return -1;
}
else
{
return arr[front];
}
}
Return Last Element
If the queue is not empty then we simply return the value stored in the position which rear
points.
int Dequeue :: get_back()
{
if(empty())
{
cout << "UNDERFLOW\n";
return -1;
}
else
{
return arr[rear];
}
}.
Implementation of Deque in C
#include<stdio.h>
# define MAX 5
int deque_arr[MAX];
int left = -1;
int right = -1;
/*Begin of insert_right*/
void insert_right()
{
int added_item;
if((left == 0 && right == MAX-1) || (left == right+1))
{ printf("Queue Overflow\n");
return;}
if (left == -1) /* if queue is initially empty */
{ left = 0;
right = 0;}
else
if(right == MAX-1) /*right is at last position of queue */
right = 0;
else
right = right+1;
printf("Input the element for adding in queue : ");
scanf("%d", &added_item);
deque_arr[right] = added_item ;
}
/*End of insert_right*/
/*Begin of insert_left*/
void insert_left()
{ int added_item;
if((left == 0 && right == MAX-1) || (left == right+1))
{ printf("Queue Overflow \n");
return; }
if (left == -1)/*If queue is initially empty*/
{ left = 0;
right = 0; }
else
if(left== 0)
left=MAX-1;
else
left=left-1;
printf("Input the element for adding in queue : ");
scanf("%d", &added_item);
deque_arr[left] = added_item ; }
/*End of insert_left*/
/*Begin of delete_left*/
void delete_left()
{ if (left == -1)
{ printf("Queue Underflow\n");
return ; }
printf("Element deleted from queue is : %d\n",deque_arr[left]);
if(left == right) /*Queue has only one element */
{ left = -1;
right=-1; }
else
if(left == MAX-1)
left = 0;
else
left = left+1;
}
/*End of delete_left*/
/*Begin of delete_right*/
void delete_right()
{if (left == -1)
{printf("Queue Underflow\n");
return ; }
printf("Element deleted from queue is :
%d\n",deque_arr[right]);
if(left == right) /*queue has only one element*/
{ left = -1;
right=-1; }
else
if(right == 0)
right=MAX-1;
else
right=right-1; }
/*End of delete_right*/
/*Begin of input_que*/
void display_queue()
{ int front_pos = left,rear_pos = right;
if(left == -1)
{ printf("Queue is empty\n");
return; }
printf("Queue elements :\n");
if( front_pos <= rear_pos )
{ while(front_pos <= rear_pos)
{ printf("%d ",deque_arr[front_pos]);
front_pos++; } }
else
{ while(front_pos <= MAX-1)
{ printf("%d ",deque_arr[front_pos]);
front_pos++; }
front_pos = 0;
while(front_pos <= rear_pos)
{ printf("%d ",deque_arr[front_pos]);
front_pos++;
}
}
printf("\n");
}
/*End of display_queue*/
/*Begin of input_que*/
void input_que()
{ int choice;
do
{ printf("1.Insert at right\n");
printf("2.Delete from left\n");
printf("3.Delete from right\n");
printf("4.Display\n");
printf("5.Quit\n");
printf("Enter your choice : ");
scanf("%d",&choice);
switch(choice)
{ case 1:
insert_right();
break;
case 2:
delete_left();
break;
case 3:
delete_right();
break;
case 4:
display_queue();
break;
case 5:
break;
default:
printf("Wrong choice\n");
}
}while(choice!=5);
}
/*End of input_que*/
/*Begin of output_que*/
void output_que()
{ int choice;
do
{ printf("1.Insert at right\n");
printf("2.Insert at left\n");
printf("3.Delete from left\n");
printf("4.Display\n");
printf("5.Quit\n");
printf("Enter your choice : ");
scanf("%d",&choice);
switch(choice)
{
case 1:
insert_right();
break;
case 2:
insert_left();
break;
case 3:
delete_left();
break;
case 4:
display_queue();
break;
case 5:
break;
default:
printf("Wrong choice\n");
}
}while(choice!=5);
}
/*End of output_que*/
/*Begin of main*/
main()
{ int choice;
printf("1.Input restricted dequeue\n");
printf("2.Output restricted dequeue\n");
printf("Enter your choice : ");
scanf("%d",&choice);
switch(choice)
{
case 1 :
input_que();
break;
case 2:
output_que();
break;
default:
printf("Wrong choice\n");
}
}
/*End of main*/
OUPUT
1.Input restricted dequeue
2.Output restricted dequeue
Enter your choice : 2
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice : 2
Input the element for adding in queue : 10
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice : 1
Input the element for adding in queue : 20
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice : 4
Queue elements :
10 20
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice : 3
Element deleted from queue is : 10
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice : 4
Queue elements :
20
1.Insert at right
2.Insert at left
3.Delete from left
4.Display
5.Quit
Enter your choice :
APPLICATIONS OF DEQUE
o The deque can be used as a stack and queue; therefore, it can perform both redo and
undo operations.
o Deque data structure supports clockwise and anticlockwise rotations in O(1) time
which can be useful in certain applications.
o Also, the problems where elements need to be removed and or added both ends can
be efficiently solved using Deque.
o An internet browser’s history. Recently visited URLs are supplementary to the front
of the deque, and also the uniform resource locator at the rear of the deque is
removed when some such range of insertions at the front.
o It can be used as a palindrome checker means that if we read the string from both
ends, then the string would be the same.
o It can be used for multiprocessor scheduling. Suppose we have two processors, and
each processor has one process to execute. Each processor is assigned with a process
or a job, and each process contains multiple threads. Each processor maintains a
deque that contains threads that are ready to execute. The processor executes a
process, and if a process creates a child process then that process will be inserted at
the front of the deque of the parent process. Suppose the processor P2 has completed
the execution of all its threads then it steals the thread from the rear end of the
processor P1 and adds to the front end of the processor P2. The processor P2 will take
the thread from the front end; therefore, the deletion takes from both the ends, i.e.,
front and rear end. This is known as the A-steal algorithm for scheduling.
Applications Of Queues Palindrome Checker
A palindrome is a word or sequence which reads the same from any direction i.e. either
backward or forward.
This algorithm is used for implementing the task scheduling for multiple processors often
called multiprocessor scheduling. The processor takes the first element from the deque. A thread
can be accessed from another processor, when one of the processors completes execution of its
own threads. The last element from the deque of another processor is accessed by one of the
processors which then executes it. Undo Redo operations The Deque can be implemented to
achieve the functionality of Undo Redo operations in software applications.
General applications
There are several algorithms that use queues to give efficient running times. When jobs are
submitted to a printer, they are arranged in order of arrival. Thus, essentially, jobs sent to a line
printer are placed on a queue. We say essentially a queue, because jobs can be killed. This
amounts to a deletion from the middle of the queue, which is a violation of the strict definition.
Virtually every real-life line is (supposed to be) a queue. For instance, lines at ticket counters are
queues, because service is first-come first-served. Another example concerns computer networks.
There are many network setups of personal computers in which the disk is attached to one
machine, known as the file server. Users on other machines are given access to files on a first-
come first-served basis, so the data structure is a queue.
Calls to large companies are generally placed on a queue when all operators are busy. In large
universities, where resources are limited, students must sign a waiting list if all terminals are
occupied. The student who has been at a terminal the longest is forced off first, and the student
who has been waiting the longest is the next user to be allowed on.
An example of an easy case would be a phone line with one operator. If the operator is busy,
callers are placed on a waiting line (up to some maximum limit). This problem is important for
businesses, because studies have shown that people are quick to hang up the phone.
In a multiprogramming environment, a single CPU has to serve more than one program
simultaneously. Consider a multiprogramming environment where possible jobs to the CPU are
categorized into three group:
1. Interrupts to be serviced. Variety of devices and terminals are connected with the CPU
and they may interrupt at any moment to get a service from it.
3. Batch jobs to be serviced. These are long term jobs mainly from the non-interactive
users, where all the inputs are fed when jobs are submitted; simulation programs, and jobs to
print documents are of this kind. Here the problem is to schedule all sorts of jobs so that the
required level of performance of the environment will be attained.
One way to implement complex scheduling is to classify the workload according to its
characteristics and to maintain separate process queues. So far the environment is concerned,
This approach is often called multi-level queues scheduling. Process will be assigned to their
respective queues. CPU will then service the processes as per the priority of the queues. In case
of a simple strategy, absolute priority, the process from the highest priority queue (for example,
system processes) are serviced until the queue becomes empty. Then the CPU switches to the
queue of interactive processes which are having medium-priority and so on. A lower- priority
process may, of course. be pre-empted by a higher-priority arrival in one of the upper- level
queues. Multi-level queues strategy is a general discipline but has some drawbacks.
The main drawback is that when the process arrives in higher-priority queues is very high.
The processes in the lower-priority queue may starve for a long time. One way out to solve this
problem is to time slice between the queues. Bach queue gets a certain portion of the CPU time.
Similarly. a process which waits too long in a lower-priority queue may be moved to a
higher-priority queue. For example, consider a multi-level feedback queue strategy with three
queues Q1,Q2 and Q3. A process entering the system is put in queue Q1. A process in Q1 is
given a time quantum τ of 10 ms, say, it does not finish within this time, it is moved to the tail of
queue Q2.
If Q1 is empty, the process at the front of queue Q2 is given a time quantum τ of 20 ms,
say. If it does not complete within this time quantum. it is pre-empted and put into queue Q3.
Processes in queue Q3 are serviced only when queues Q1 and Q2 are empty.
Thus, with this strategy, CPU first executes all processes in queue Q1. Only when Q1 is
empty it will execute all processes in queue Q2. Similarly, processes in queue Q3 will only be
executed if queues Q1 and Q2 are empty. A process which arrives in queue Q1 will pre-empt a
process in queue Q2 or Q3. lt can be observed that, this strategy gives the highest priority to any
process with a CPU burst of 10 ms or less. Processes which need more than 10 ms. but less than
or equal to 20 ms are also served quickly, that is, it gets the next highest priority than the shorter
processes.
Longer processes automatically sink to queue Q3, from Q3. Processes will be served in
First Come First-Serve (FCFS) basis and in case of process waiting for a too long time (as
decided by the scheduler) may be put into the tail of queue Q1.