Unit 4 Stacks: Structure Page Nos
Unit 4 Stacks: Structure Page Nos
Unit 4 Stacks: Structure Page Nos
UNIT 4 STACKS
Structure Page Nos.
4.0 Introduction 5
4.1 Objectives 6
4.2 Abstract Data Type-Stack 7
4.3 Implementation of Stack 7
4.3.1 Implementation of Stack Using Arrays
4.3.2 Implementation of Stack Using Linked Lists
4.4 Algorithmic Implementation of Multiple Stacks 13
4.5 Applications 14
4.6 Summary 14
4.7 Solutions / Answers 15
4.8 Further Readings 15
4.0 INTRODUCTION
One of the most useful concepts in computer science is stack. In this unit, we shall
examine this simple data structure and see why it plays such a prominent role in the
area of programming. There are certain situations when we can insert or remove an
item only at the beginning or the end of the list.
A stack is a linear structure in which items may be inserted or removed only at one
end called the top of the stack. A stack may be seen in our daily life, for example,
Figure 4.1 depicts a stack of dishes. We can observe that any dish may
be added or removed only from the top of the stack. It concludes that the item added
last will be the item removed first. Therefore, stacks are also called LIFO (Last In
First Out) or FILO (First In Last Out) lists. We also call these lists as “piles” or
“push-down list”.
Generally, two operations are associated with the stacks named Push & Pop.
Example 4.1
Now we see the effects of push and pop operations on to an empty stack. Figure
4.2(a) shows (i) an empty stack; (ii) a list of the elements to be inserted on to stack;
5
Stacks, Queues and (iii) a variable top which helps us keep track of the location at which insertion or
and Trees removal of the item would occur.
top[3] C 3
top[2] B 2 B 2
top[1] A 1 A 1 A 1
top[2] B 2
A 1 top[1] A top[0]
Initially in Figure 4.2(a), top contains 0, implies that the stack is empty. The list
contains three elements, A, B &C. In Figure 4.2(b), we remove an element A from
the list of elements, push it on to stack. The value of top becomes 1, pointing to the
location of the stack at which A is stored.
Similarly, we remove the elements B & C from the list one by one and push them on
to the stack. Accordingly, the value of the top is incremented. Figure 4.2(a) explains
the pushing of B and C on to stack. The top now contains value 3 and pointing to the
location of the last inserted element C.
On the other hand, Figure 4.2(b) explains the working of pop operation. Since, only
the top element can be removed from the stack, in Figure 4.2(b), we remove the top
element C (we have no other choice). C goes to the list of elements and the value of
the top is decremented by 1. The top now contains value 2, pointing to B (the top
element of the stack). Similarly, in Figure 4.2(b), we remove the elements B and A
from the stack one by one and add them to the list of elements. The value of top is
decremented accordingly.
There is no upper limit on the number of items that may be kept in a stack. However,
if a stack contains a single item and the stack is popped, the resulting stack is called
empty stack. The pop operation cannot be applied to such stacks as there is no
element to pop, whereas the push operation can be applied to any stack.
4.1 OBJECTIVES
6
• understand the concept of stack; Stacks
• implement the stack using arrays;
• implement the stack using linked lists;
• implement multiple stacks, and
• give some applications of stack.
Before programming a problem solution that uses a stack, we must decide how to
represent a stack using the data structures that exist in our programming language.
Stacks may be represented in the computer in various ways, usually by means of a
one-way list or a linear array. Each approach has its advantages and disadvantages.
A stack is generally implemented with two basic operations – push and pop. Push
means to insert an item on to stack. The push algorithm is illustrated in Figure 4.3(a).
Here, tos is a pointer which denotes the position of top most item in the stack. Stack
is represented by the array arr and MAXSTACK represents the maximum possible
number of elements in the stack. The pop algorithm is illustrated in Figure 4.3(b).
7
Stacks, Queues
and Trees
The pop operation removes the topmost item from the stack. After removal of top
most value tos is decremented by 1.
A Stack contains an ordered list of elements and an array is also used to store ordered
list of elements. Hence, it would be very easy to manage a stack using an array.
However, the problem with an array is that we are required to declare the size of the
array before using it in a program. Therefore, the size of stack would be fixed.
Though an array and a stack are totally different data structures, an array can be used
to store the elements of a stack. We can declare the array with a maximum size large
enough to manage a stack. Program 4.1 implements a stack using an array.
#include<stdio.h>
void menu();
void push();
void pop();
void showelements();
void main()
{ choice=element=1;
top=0;
menu();
}
void menu()
{
printf("Enter one of the following options:\n");
printf("PUSH 1\n POP 2\n SHOW ELEMENTS 3\n EXIT 4\n");
scanf("%d", &choice);
if (choice==1)
{
push(); menu();
}
if (choice==2)
{
pop();menu();
8
} Stacks
if (choice==3)
{
showelements(); menu();
}
}
void push()
{
if (top<=9)
{
printf("Enter the element to be pushed to stack:\n");
scanf("%d", &element);
stack[top]=element;
++top;
}
else
{
printf("Stack is full\n");
}
return;
}
void pop()
{
if (top>0)
{
--top;
element = stack[top];
printf("Popped element:%d\n", element);
}
else
{
printf("Stack is empty\n");
}
return;
void showelements()
{
if (top<=0)
printf("Stack is empty\n");
else
for(int i=0; i<top; ++i)
printf("%d\n", stack[i]);
}
Program 4.1: Implementation of stack using arrays
Explanation
9
Stacks, Queues The size of the stack was declared as 10. So, stack cannot hold more than 10 elements.
and Trees The main operations that can be performed on a stack are push and pop. How ever, in
a program, we need to provide two more options , namely, showelements and exit.
showelements will display the elements of the stack. In case, the user is not interested
to perform any operation on the stack and would like to get out of the program, then
s/he will select exit option. It will log the user out of the program. choice is a variable
which will enable the user to select the option from the push, pop, showelements and
exit operations. top points to the index of the free location in the stack to where the
next element can be pushed. element is the variable which accepts the integer that has
to be pushed to the stack or will hold the top element of the stack that has to be
popped from the stack. The array stack can hold at most 10 elements. push and pop
will perform the operations of pushing the element to the stack and popping the
element from the stack respectively.
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
/* Definition of the structure node */
typedef struct node
{
int data;
struct node *next;
};
/* Definition of push function */
void push(node **tos,int item)
{
node *temp;
temp=(node*)malloc(sizeof(node)); /* create a new node dynamically */
if(temp==NULL) /* If sufficient amount of memory is */
{ /* not available, the function malloc will */
printf("\nError: Insufficient Memory Space"); /* return NULL to temp */
getch();
return;
}
else /* otherwise*/
{
temp->data=item; /* put the item in the data portion of node*/
10
} Stacks
{
node *temp=tos;
if(temp==NULL) /* Check whether the stack is empty*/
{
printf("\nStack is empty");
return;
}
else
{
while(temp!=NULL)
{
printf("\n%d",temp->data); /* display all the values of the stack*/
temp=temp->next; /* from the front node to the last node*/
}
}
{
int item, ch;
char choice=‘y’;
node *p=NULL;
do
{
clrscr();
printf("\t\t\t\t*****MENU*****");
11
Stacks, Queues printf("\n\t\t\t1. To PUSH an element");
and Trees printf("\n\t\t\t2. To POP an element");
printf("\n\t\t\t3. To DISPLAY the elements of stack");
printf("\n\t\t\t4. Exit");
printf("\n\n\n\t\t\tEnter your choice:-");
scanf("%d",&ch);
switch(ch)
{
case 1:
printf("\n Enter an element which you want to push ");
scanf("%d",&item);
push(&p,item);
break;
case 2:
item=pop(&p);
if(item!=NULL);
printf("\n Detected item is%d",item);
break;
case 3:
printf(“\nThe elements of stack are”);
display(p);
break;
case 4:
exit(0);
} /*switch closed */
printf("\n\n\t\t Do you want to run it again y/n”);
scanf(“%c”,&choice);
} while(choice==’y’);
}
/*end of function main*/
Similarly, as we did in the implementation of stack using arrays, to know the working
of this program, we executed it thrice and pushed 3 elements (10, 20, 30). Then we
call the function display in the next run to see the elements in the stack.
Explanation
Initially, we defined a structure called node. Each node contains two portions, data
and a pointer that keeps the address of the next node in the list. The Push function will
insert a node at the front of the linked list, whereas pop function will delete the node
from the front of the linked list. There is no need to declare the size of the stack in
advance as we have done in the program where in we implemented the stack using
arrays since we create nodes dynamically as well as delete them dynamically. The
function display will print the elements of the stack.
12
Stacks
2) Comment on the following.
(a) Why is the linked list representation of the stack better than the array
representation of the stack?
(b) Discuss the underflow and overflow problem in stacks.
So far, now we have been concerned only with the representation of a single stack.
What happens when a data representation is needed for several stacks? Let us see an
array X whose dimension is m. For convenience, we shall assume that the indexes of
the array commence from 1 and end at m. If we have only 2 stacks to implement in the
same array X, then the solution is simple.
Suppose A and B are two stacks. We can define an array stack A with n1 elements and
an array stack B with n2 elements. Overflow may occur when either stack A contains
more than n1 elements or stack B contains more than n2 elements.
Suppose, instead of that, we define a single array stack with n = n1 + n2 elements for
stack A and B together. See the Figure 4.4 below. Let the stack A “grow” to the right,
and stack B “grow” to the left. In this case, overflow will occur only when A and B
together have more than n = n1 + n2 elements. It does not matter how many elements
individually are there in each stack.
Stack A Stack B
But, in the case of more than 2 stacks, we cannot represent these in the same way
because a one-dimensional array has only two fixed points X(1) and X(m) and each
stack requires a fixed point for its bottom most element. When more than two stacks,
say n, are to be represented sequentially, we can initially divide the available memory
X(1:m) into n segments. If the sizes of the stacks are known, then, we can allocate the
segments to them in proportion to the expected sizes of the various stacks. If the sizes
of the stacks are not known, then, X(1:m) may be divided into equal segments. For
each stack i, we shall use BM (i) to represent a position one less than the position in X
for the bottom most element of that stack. TM(i), 1 < i < n will point to the topmost
element of stack i. We shall use the boundary condition BM (i) = TM (i) iff the ith
stack is empty (refer to Figure 4.5). If we grow the ith stack in lower memory indexes
than the i+1st stack, then, with roughly equal initial segments we have
BM (i) = TM (i) = m/n (i – 1), 1 < i < n, as the initial values of BM (i) and TM (i).
X 1 2 m/n 2 m/n m
13
Stacks, Queues
and Trees
All stacks are empty and memory is divided into roughly equal segments.
Figure 4.6 depicts an algorithm to add an element to the ith stack. Figure 4.7 depicts
an algorithm to delete an element from the ith stack.
ADD(i,e)
Step1: if TM (i)=BM (i+1)
Print “Stack is full” and exit
Step2: [Increment the pointer value by one]
TM (i)Å TM (i)+1
X(TM (i))Å e
Step3: Exit
DELETE(i,e)
Step1: if TM (i)=BM (i)
Print “Stack is empty” and exit
Step2: [remove the topmost item]
eÅ X(TM (i))
TM (i)ÅTM(i)-1
Step3: Exit
4.5 APPLICATIONS
14
Stacks
4.6 SUMMARY
In this unit, we have studied how the stacks are implemented using arrays and using
liked list. Also, the advantages and disadvantages of using these two schemes were
discussed. For example, when a stack is implemented using arrays, it suffers from the
basic limitations of an array (fixed memory). To overcome this problem, stacks are
implemented using linked lists. This unit also introduced learners to the concepts of
multiple stacks. The problems associated with the implementation of multiple stacks
are also covered.
1) Arrays or Pointers
2) Postfix expressions
3) Recursive
Reference Websites
http://www.cs.queensu.ca
15
Stacks, Queues
and Trees UNIT 5 QUEUES
Structure Page Nos.
5.0 Introduction 16
5.1 Objectives 16
5.2 Abstract Data Type-Queue 16
5.3 Implementation of Queue 17
5.3.1 Array implementation of a queue
5.3.2 Linked List implementation of a queue
5.4 Implementation of Multiple Queues 21
5.5 Implementation of Circular Queues 22
5.5.1 Array Implementation of a circular queue
5.5.2 Linked List Implementation of a circular queue
5.6 Implementation of DEQUEUE 25
5.6.1 Array Implementation of a dequeue
5.6.2 Linked List Implementation of a dequeue
5.7 Summary 30
5.8 Solutions / Answers 30
5.9 Further Readings 30
5.0 INTRODUCTION
Queue is a linear data structure used in various applications of computer science. Like
people stand in a queue to get a particular service, various processes will wait in a
queue for their turn to avail a service. In computer science, it is also called a FIFO
(first in first out) list. In this chapter, we will study about various types of queues.
5.1 OBJECTIVES
An important aspect of Abstract Data Types is that they describe the properties of a
data structure without specifying the details of its implementation. The properties can
be implemented independent of any implementation in any programming language.
Queue is a collection of elements, or items, for which the following operations are
defined:
createQueue(Q) : creates an empty queue Q;
isEmpty(Q): is a boolean type predicate that returns ``true'' if Q exists and is
empty, and returns ``false'' otherwise;
addQueue(Q,item) adds the given item to the queue Q; and
deleteQueue (Q, item) : delete an item from the queue Q;
next(Q) removes the least recently added item that remains in the queue Q,
and returns it as the value of the function;
16
isEmpty (createQueue(Q)) : is always true, and Queues
deleteQueue(createQueue(Q)) : error
The primitive isEmpty(Q) is required to know whether the queue is empty or not,
because calling next on an empty queue should cause an error. Like stack, the
situation may be such when the queue is “full” in the case of a finite queue. But we
avoid defining this here as it would depend on the actual length of the Queue defined
in a specific problem.
The word “queue” is like the queue of customers at a counter for any service, in which
customers are dealt with in the order in which they arrive i.e. first in first out (FIFO)
order. In most cases, the first customer in the queue is the first to be served.
As pointed out earlier, Abstract Data Types describe the properties of a structure
without specifying an implementation in any way. Thus, an algorithm which works
with a “queue” data structure will work wherever it is implemented. Different
implementations are usually of different efficiencies.
An example of the queue in computer science is print jobs scheduled for printers.
These jobs are maintained in a queue. The job fired for the printer first gets printed
first. Same is the scenario for job scheduling in the CPU of computer.
Like a stack, a queue also (usually) holds data elements of the same type. We usually
graphically display a queue horizontally. Figure 5.1 depicts a queue of 5 characters.
-------------------------------------
|a|b|c| d| e| f|
-----------------------------------
front rear
The rule followed in a queue is that elements are added at the rear and come off of the
front of the queue. After the addition of an element to the above queue, the position of
rear pointer changes as shown below. Now the rear is pointing to the new element ‘g’
added at the rear of the queue(refer to Figure 5.2).
-------------------------------------
|a|b|c| d| e| f|g|
-----------------------------------
front rear
17
Stacks, Queues After the removal of element ‘a’ from the front, the queue changes to the following
and Trees with the front pointer pointing to ‘b’ (refer to Figure 5.3).
-------------------------------------
|b|c| d| e| f|g|
-----------------------------------
front rear
Step 1: Check for Queue empty condition. If empty, then go to step 2, else go to step 3
Step 2: Message “Queue Empty”
Step 3: Delete the element from the front of the queue. If it is the last element in the
queue, then perform step a else step b
a) make front and rear point to null
b) shift the front pointer ahead to point to the next element in the queue
As the stack is a list of elements, the queue is also a list of elements. The stack and the
queue differ only in the position where the elements can be added or deleted. Like
other liner data structures, queues can also be implemented using arrays. Program 5.1
lists the implementation of a queue using arrays.
#include “stdio.h”
#define QUEUE_LENGTH 50
struct queue
{ int element[QUEUE_LENGTH];
int front, rear, choice,x,y;
}
struct queue q;
main()
{
int choice,x;
printf (“enter 1 for add and 2 to remove element front the queue”)
printf(“Enter your choice”)
scanf(“%d”,&choice);
switch (choice)
case 1 :
printf (“Enter element to be added :”);
18
scanf(“%d”,&x); Queues
add(&q,x);
break;
case 2 :
delete();
break;
}
add(y)
{
++q.rear;
if (q.rear < QUEUE_LENGTH)
q.element[q.rear] = y;
else
printf(“Queue overflow”)
}
delete()
{
The basic element of a linked list is a “record” structure of at least two fields. The
object that holds the data and refers to the next element in the list is called a node
(refer to Figure 5.4).
Data Ptrnext
The data component may contain data of any type. Ptrnext is a reference to the next
element in the queue structure. Figure 5.5 depicts the linked list representation of a
queue.
19
Stacks, Queues Program 5.2 gives the program segment for the addition of an element to the queue.
and Trees
Program 5.3 gives the program segment for the deletion of an element from the queue.
add(int value)
{
struct queue *new;
new = (struct queue*)malloc(sizeof(queue));
new->value = value;
new->next = NULL;
if (front == NULL)
{
queueptr = new;
front = rear = queueptr
}
else
{
rear->next = new;
rear=new;
}
}
delete()
{
int delvalue = 0;
if (front == NULL) printf(“Queue Empty”);
{
delvalue = front->value;
if (front->next==NULL)
{
free(front);
queueptr=front=rear=NULL;
}
else
{
front=front->next;
free(queueptr);
queueptr=front;
}
}
}
Program 5.3: Program segment for deletion of an element from the queue
3) Compare the array and linked list representations of a queue. Explain your
answer.
20
Queues
5.4 IMPLEMENTATION OF MULTIPLE QUEUES
So far, we have seen the representation of a single queue, but many practical
applications in computer science require several queues. Multiqueue is a data structure
where multiple queues are maintained. This type of data structures are used for
process scheduling. We may use one dimensional array or multidimensional array to
represent a multiple queue.
Program 5.4 gives the program segment using arrays for the addition of an element to
a queue in the multiqueue.
Program 5.4: Program segment for the addition of an element to the queue
Program 5.5 gives the program segment for the deletion of an element from the queue.
Program 5.5: Program segment for the deletion of an element from the queue
21
Stacks, Queues
and Trees 5.5 IMPLEMENTATION OF CIRCULAR QUEUES
One of the major problems with the linear queue is the lack of proper utilisation of
space. Suppose that the queue can store 100 elements and the entire queue is full. So,
it means that the queue is holding 100 elements. In case, some of the elements at the
front are deleted, the element at the last position in the queue continues to be at the
same position and there is no efficient way to find out that the queue is not full. In this
way, space utilisation in the case of linear queues is not efficient. This problem is
arising due to the representation of the queue.
In a circular queue, front will point to one position less to the first element anti-clock
wise. So, if the first element is at position 4 in the array, then the front will point to
position 3. When the circular queue is created, then both front and rear point to index
1. Also, we can conclude that the circular queue is empty in case both front and rear
point to the same index. Figure 5.7 depicts a circular queue.
[4]
20
[3]
15
[n-4]
50
[2]
10 [n-3]
[1]
[n-2]
[0]
[n-1]
22
Algorithm for Addition of an element to the circular queue: Queues
Step-1: If “rear” of the queue is pointing to the last position then go to step-2 or else
Step-3
Step-2: make the “rear” value as 0
Step-3: increment the “rear” value by one
Step-4: a. if the “front” points where “rear” is pointing and the queue holds a not
NULL value for it, then its a “queue overflow” state, so quit; else go to
step-b
b. add the new value for the queue position pointed by the "rear"
Step-1: If the queue is empty then say “queue is empty” and quit; else continue
Step-2: Delete the “front” element
Step-3: If the “front” is pointing to the last position of the queue then go to step-4 else
go to step-5
Step-4: Make the “front” point to the first position in the queue and quit
Step-5: Increment the “front” position by one
A circular queue can be implemented using arrays or linked lists. Program 5.6 gives
the array implementation of a circular queue.
#include "stdio.h"
void add(int);
void deleteelement(void);
int max=10; /*the maximum limit for queue has been set*/
static int queue[10];
int front=0, rear=-1; /*queue is initially empty*/
void main()
{
int choice,x;
printf ("enter 1 for addition and 2 to remove element front the queue and 3 for exit");
printf("Enter your choice");
scanf("%d",&choice);
switch (choice)
{
case 1 :
printf ("Enter the element to be added :");
scanf("%d",&x);
add(x);
break;
case 2 :
deleteelement();
break;
}
}
void add(int y)
{
if(rear == max-1)
rear = 0;
else
rear = rear + 1;
if( front == rear && queue[front] != NULL)
printf("Queue Overflow");
else
23
Stacks, Queues queue[rear] = y;
and Trees }
void deleteelement()
{
int deleted_front = 0;
if (front == NULL)
printf("Error - Queue empty");
else
{
deleted_front = queue[front];
queue[front] = NULL;
if (front == max-1)
front = 0;
else
front = front + 1;
}
}
Link list representation of a circular queue is more efficient as it uses space more
efficiently, of course with the extra cost of storing the pointers. Program 5.7 gives the
linked list representation of a circular queue.
#include “stdio.h”
struct cq
{ int value;
int *next;
};
main()
{
int choice,x;
printf (“Enter 1 for addition and 2 to delete element from the queue”)
printf(“Enter your choice”)
scanf(“%d”,&choice);
switch (choice)
case 1 :
printf (“Enter the element to be added :”);
scanf(“%d”,&x);
add(&q,x);
break;
case 2 :
delete();
24
break; Queues
}
}
add(int value)
{
struct cq *new;
new = (struct cq*)malloc(sizeof(queue));
new->value = value
new->next = NULL;
if (front == NULL)
{
cqptr = new;
front = rear = queueptr;
}
else
{
rear->next = new;
rear=new;
}
}
}
}
}
Dequeue (a double ended queue) is an abstract data type similar to queue, where
addition and deletion of elements are allowed at both the ends. Like a linear queue and
a circular queue, a dequeue can also be implemented using arrays or linked lists.
25
Stacks, Queues 5.6.1 Array implementation of a dequeue
and Trees
If a Dequeue is implemented using arrays, then it will suffer with the same problems
that a linear queue had suffered. Program 5.8 gives the array implementation of a
Dequeue.
#include “stdio.h”
#define QUEUE_LENGTH 10;
int dq[QUEUE_LENGTH];
int front, rear, choice,x,y;
main()
{
int choice,x;
front = rear = -1; /* initialize the front and rear to null i.e empty queue */
printf (“enter 1 for addition and 2 to remove element from the front of the queue");
printf (“enter 3 for addition and 4 to remove element from the rear of the queue");
printf(“Enter your choice”);
scanf(“%d”,&choice);
switch (choice)
{
case 1:
printf (“Enter element to be added :”);
scanf(“%d”,&x);
add_front(x);
break;
case 2:
delete_front();
break;
case 3:
printf (“Enter the element to be added :”);
scanf(“%d ”,&x);
add_rear(x);
break;
case 4:
delete_rear();
break;
}
}
/**************** Add at the front ***************/
add_front(int y)
{
if (front == 0)
{
printf(“Element can not be added at the front“);
return;
else
{
front = front - 1;
dq[front] = y;
if (front == -1 ) front = 0;
}
}
/**************** Delete from the front ***************/
delete_front()
{
if front == -1
printf(“Queue empty”);
26
else Queues
return dq[front];
if (front = = rear)
front = rear = -1
else
front = front + 1;
}
/**************** Add at the rear ***************/
add_rear(int y)
if (front == QUEUE_LENGTH -1 )
{
printf(“Element can not be added at the rear “)
return;
else
{
rear = rear + 1;
dq[rear] = y;
if (rear = = -1 )
rear = 0;
}
}
A doubly link list can traverse in both the directions as it has two pointers namely left
and right. The right pointer points to the next node on the right where as the left
pointer points to the previous node on the left. Program 5.9 gives the linked list
implementation of a Dequeue.
# include “stdio.h”
#define NULL 0
struct dq {
int info;
int *left;
int *right;
};
typedef struct dq *dqptr;
dqptr p, tp;
27
Stacks, Queues dqptr head;
and Trees dqptr tail;
main()
{
int choice, I, x;
dqptr n;
dqptr getnode();
printf(“\n Enter 1: Start 2 : Add at Front 3 : Add at Rear 4: Delete at Front 5:
Delete at Back”);
while (1)
{
printf(“\n 1: Start 2 : Add at Front 3 : Add at Back 4: Delete at Front 5: Delete
at Back 6 : exit”);
scanf(“%d”, &choice);
switch (choice)
{
case 1:
create_list();
break;
case 2:
eq_front();
break;
case 3:
eq_back();
break;
case 4:
dq_front();
break;
case 5:
dq_back();
break;
case 6 :
exit(6);
}
}
}
create_list()
{
int I, x;
dqptr t;
p = getnode();
tp = p;
p->left = getnode();
p->info = 10;
p_right = getnode();
return;
}
dqptr getnode()
{
p = (dqptr) malloc(sizeof(struct dq));
return p;
}
dq_empty(dq q)
{
return q->head = = NULL;
28
} Queues
eq_front(dq q, void *info)
{
if (dq_empty(q))
q->head = q->tail = dcons(info, NULL, NULL);
else
{
q-> head -> left =dcons(info, NULL, NULL);
q->head -> left ->right = q->head;
q ->head = q->head ->left;
}
}
dq_back(dq q)
{
if (q!=NULL)
{
dq tp = q-> tail;
*info = tp -> info;
q ->tail = q->tail-> left;
free(tp);
if (q->tail = = NULL)
q -> head = NULL;
else
q -> tail -> right = NULL;
return info;
}
}
29
Stacks, Queues Check Your Progress 2
and Trees
1) _________ allows elements to be added and deleted at the front as well as at the
rear.
2) It is not possible to implement multiple queues in an Array.
(True/False)
3) The index of a circular queue starts at __________.
5.7 SUMMARY
In this unit, we discussed the data structure Queue. It had two ends. One is
front from where the elements can be deleted and the other if rear to where the
elements can be added. A queue can be implemented using Arrays or Linked
lists. Each representation is having it’s own advantages and disadvantages. The
problems with arrays are that they are limited in space. Hence, the queue is
having a limited capacity. If queues are implemented using linked lists, then
this problem is solved. Now, there is no limit on the capacity of the queue. The
only overhead is the memory occupied by the pointers.
There are a number of variants of the queues. Normally, queues mean circular
queues. Apart from linear queues, we also discussed circular queues in this
unit. A special type of queue called Dequeue was also discussed in this unit.
Dequeues permit elements to be added or deleted at either of the rear or front.
We also discussed the array and linked list implementations of Dequeue.
5.8 SOLUTIONS/ANSWERS
1. rear , front
2. First in First out (FIFO) list
1. Dequeue
2. False
3. 0
Reference Books
Reference Websites
http://ciips.ee.uwa.edu.au/~morris/Year2/PLDS210/queues.html
http://www.cs.toronto.edu/~wayne/libwayne/libwayne.html
30
Trees
UNIT 6 TREES
Structure Page Nos.
6.0 Introduction 31
6.1 Objectives 31
6.2 Abstract Data Type-Tree 31
6.3 Implementation of Tree 34
6.4 Tree Traversals 35
6.5 Binary Trees 37
6.6 Implementation of a Binary Tree 38
6.7 Binary Tree Traversals 40
6.7.1 Recursive Implementation of Binary Tree Traversals
6.7.2 Non-Recursive Implementation of Binary Tree Traversals
6.8 Applications 43
6.9 Summary 45
6.10 Solutions/Answers 45
6.11 Further Readings 46
6.0 INTRODUCTION
Have you ever thought how does the operating system manage our files? Why do we
have a hierarchical file system? How do files get saved and deleted under hierarchical
directories? Well, we have answers to all these questions in this section through a
hierarchical data structure called Trees! Although most general form of a tree can be
defined as an acyclic graph, we will consider in this section only rooted tree as
general tree does not have a parent-child relationship.
6.1 OBJECTIVES
After going through this unit, you should be able
• to define a tree as abstract data type (ADT);
• learn the different properties of a Tree and a Binary tree;
• to implement the Tree and Binary tree, and
• give some applications of Tree.
Definition: A set of data values and associated operations that are precisely specified
independent of any particular implementation.
Since the data values and operations are defined with mathematical precision, rather
than as an implementation in a computer language, we may reason about effects of the
31
Stacks, Queues operations, relationship to other abstract data types, whether a programming language
and Trees implements the particular data type, etc.
Structure Tree
Operations:
root
Rules:
left(fork(e, T, T')) = T
left(nil) = error
contents(fork(e, T, T')) = e
contents(nil) = error
Look at the definition of Tree (ADT). A way to think of a binary tree is that it is either
empty (nil) or contains an element and two sub trees which are themselves binary
trees (Refer to Figure 6.1). Fork operation joins two sub tree with a parent node and
32
produces another Binary tree. It may be noted that a tree consisting of a single leaf is Trees
defined to be of height 1.
Root Level 1
Internal node
Level 2
Edge
Leaf node
Level 3
In a more formal way, we can define a tree T as a finite set of one or more nodes such
that there is one designated node r called the root of T, and the remaining nodes in
(T – { r } ) are partitioned into n > 0 disjoint subsets T1, T2, ..., Tk each of which is a
tree, and whose roots r1 , r2 , ..., rk , respectively, are children of r. The general tree is
a generic tree that has one root node, and every node in the tree can have an unlimited
number of child nodes. One popular use of this kind of tree is a Family Tree.
A tree is an instance of a more general category called graph.
33
Stacks, Queues • Breadth defines the number of nodes at a level.
and Trees
• The depth of a node M in a tree is the length of the path from the root of the tree
to M.
• A node in a Binary tree has at most 2 children.
Full Tree : A tree with all the leaves at the same level, and all the non-leaves having
the same degree
Complete Trees
A complete tree is a k-ary position tree in which all levels are filled from left to right.
There are a number of specialized trees.
They are binary trees, binary search trees, AVL-trees, red-black trees, 2-3 trees.
Items of a tree can be partially ordered into a hierarchy via parent-child relationship.
Root node is at the top of the hierarchy and leafs are at the bottom layer of the
hierarchy. Hence, trees can be termed as hierarchical data structures.
The most common way to add nodes to a general tree is to first find the desired parent
of the node you want to insert, then add the node to the parent’s child list. The most
common implementations insert the nodes one at a time, but since each node can be
considered a tree on its own, other implementations build up an entire sub-tree before
adding it to a larger tree. As the nodes are added and deleted dynamically from a tree,
tree are often implemented by link lists. However, it is simpler to write algorithms for
a data representation where the numbers of nodes are fixed. Figure 6.4 depicts the
structure of the node of a general k-ary tree.
34
Trees
1
2 3 4
5 6 7
Figure 6.5 depicts a tree with one data element and three pointers. The number of
pointers required to implement a general tree depend of the maximum degree of nodes
in the tree.
There are three types of tree traversals, namely, Preorder, Postorder and Inorder.
Preorder traversal: Each node is visited before its children are visited; the root is
visited first.
Example of pre order traversal: Reading of a book, as we do not read next chapter
unless we complete all sections of previous chapter and all it’s sections (refer to
Figure 6.6).
book
Preface
Chapter 1
Chapter 8 Summary
Section 1 Section 4
Section 4.1.1
35
Stacks, Queues As each node is traversed only once, the time complexity of preorder traversal is
and Trees T(n) = O(n), where n is number of nodes in the tree.
Postorder traversal: The children of a node are visited before the node itself; the root
is visited last. Every node is visited after its descendents are visited.
Finding the space occupied by files and directories in a file system requires a
postorder traversal as the space occupied by directory requires calculation of space
required by all files in the directory (children in tree structure) (refer to Figure 6.7)
/root
Figure 6.7 : Calculation of space occupied by a file system : A post order traversal
As each node is traversed only once, the time complexity of post order traversal is
T(n) = O(n), where n is number of nodes in the tree.
Inorder traversal: The left sub tree is visited, then the node and then right sub-tree.
─
*
/ 7 3 1
4 2
36
Inorder traversal can be best described by an expression tree, where the operators are Trees
at parent node and operands are at leaf nodes.
Let us consider the above expression tree (refer to Figure 6.8). The preorder,
postorder and inorder traversal are given below:
preorder Traversal : + * / 4 2 7 - 3 1
postorder traversal : 4 2 / 7 * 3 1 - +
inorder traversal : - ((((4 / 2) * 7) + (3 - 1))
There is another tree traversal (of course, not very common) is called level order,
where all the nodes of the same level are travelled first starting from the root (refer to
Figure 6.9).
Recursive Definition: A binary tree is either empty or a node that has left and right
sub-trees that are binary trees. Empty trees are represented as boxes (but we will
almost always omit the boxes).
In a formal way, we can define a binary tree as a finite set of nodes which is either
empty or partitioned in to sets of T0, Tl, Tr , where T0 is the root and Tl and Tr are left
and right binary trees, respectively.
37
Stacks, Queues Properties of a binary tree
and Trees
• If a binary tree contains n nodes, then it contains exactly n – 1 edges;
• A Binary tree of height h has 2h – 1nodes or less.
• If we have a binary tree containing n nodes, then the height of the tree is at most
n and at least ceiling log2(n + 1).
• If a binary tree has n nodes at a level l then, it has at most 2n nodes at a level
l+1
• The total number of nodes in a binary tree with depth d (root has depth zero) is
N = 20 + 21 + 22 + …….+ 2d = 2d+1 - 1
Full Binary Trees: A binary tree of height h which had 2h –1 elements is called a Full
Binary Tree.
Complete Binary Trees: A binary tree whereby if the height is d, and all levels,
except possibly level d, are completely full. If the bottom level is incomplete, then it
has all nodes to the left side. That is the tree has been filled in the level order from left
to right.
Like general tree, binary trees are implemented through linked lists. A typical node in
a Binary tree has a structure as follows (refer to Figure 6.10):
struct NODE
{
struct NODE *leftchild;
int nodevalue; /* this can be of any data type */
struct NODE *rightchild;
};
The binary tree creation follows a very simple principle. For the new element to be
added, compare it with the current element in the tree. If its value is less than the
current element in the tree, then move towards the left side of that element or else to
its right. If there is no sub tree on the left, then make your new element as the left
child of that current element or else compare it with the existing left child and follow
the same rule. Exactly, the same has to done for the case when your new element is
greater than the current element in the tree but this time with the right child. Though
this logic is followed for the creation of a Binary tree, this logic is often suitable to
search for a key value in the binary tree.
Step-1: If value of new element < current element, then go to step-2 or else step -3
Step-2: If the current element does not have a left sub-tree, then make your new
38
element the left child of the current element; else make the existing left child Trees
as your current element and go to step-1
Step-3: If the current element does not have a right sub-tree, then make your new
element the right child of the current element; else make the existing right
child as your current element and go to step-1
Program 6.1 depicts the segment of code for the creation of a binary tree.
struct NODE
{
struct NODE *left;
int value;
struct NODE *right;
};
39
Stacks, Queues First column represents index of node, second column consist of the item stored in the
and Trees node and third and fourth columns indicate the positions of left and right children
(–1 indicates that there is no child to that particular node.)
Let us discuss the inorder binary tree traversal for following binary tree (refer to
Figure 6.12):
We start from the root i.e. * We are supposed to visit its left sub-tree then visit the
node itself and its right sub-tree. Here, root has a left sub-tree rooted at +. So, we
move to + and check for its left sub-tree (we are suppose repaeat this for every node).
Again, + has a left sub-tree rooted at 4. So, we have to check for 4's left sub-tree now,
but 4 doesn't have any left sub-tree and thus we will visit node 4 first (print in our
case) and check for its right sub-tree. As 4 doesn't have any right sub-tree, we'll go
back and visit node +; and check for the right sub-tree of +. It has a right sub-tree
rooted at 5 and so we move to 5. Well, 5 doesn't have any left or right sub-tree. So, we
just visit 5 (print 5)and track back to +. As we have already visited + so we track back
to * . As we are yet to visit the node itself and so we visit * before checking for the
right sub-tree of *, which is 3. As 3 does not have any left or right sub-trees, we visit
3.
So, the inorder traversal results in 4 + 5 * 3
Algorithm: Inorder
* Step-1: For the current node, check whether it has a left
child. If it has, then go to step-2 or
else go to step-3
Step-2: Repeat step-1 for this left child
Step-3: Visit (i.e. printing the node in our case) the
+ 3 current node
Step-4: For the current node check whether it has a right
child. If it has, then go to step-5
4 5 Step-5: Repeat step-1 for this right child
The preoreder and postorder traversals are similar to that of a general binary tree. The
general thing we have seen in all these tree traversals is that the traversal mechanism
is inherently recursive in nature.
There are three classic ways of recursively traversing a binary tree. In each of these,
the left and right sub-trees are visited recursively and the distinguishing feature is
when the element in the root is visited or processed.
Program 6.2, Program 6.3 and Program 6.4 depict the inorder, preorder and postorder
traversals of a Binary tree.
struct NODE
{
40
struct NODE *left; Trees
int value; /* can be of any type */
struct NODE *right;
};
struct NODE
{
struct NODE *left;
int value; /* can be of any type */
struct NODE *right;
};
struct NODE
{
struct NODE *left;
int value; /* can be of any type */
struct NODE *right;
};
printf("%d", curr->value);
In a preorder traversal, the root is visited first (pre) and then the left and right sub-
trees are traversed. In a postorder traversal, the left sub-tree is visited first, followed
by right sub-tree which is then followed by root. In an inorder traversal, the left sub-
tree is visited first, followed by root, followed by right sub-tree.
41
Stacks, Queues 6.7.2 Non-recursive implementation of binary tree traversals
and Trees
As we have seen, as the traversal mechanisms were inherently recursive, the
implementation was also simple through a recursive procedure. However, in the case
of a non-recursive method for traversal, it has to be an iterative procedure; meaning,
all the steps for the traversal of a node have to be under a loop so that the same can be
applied to all the nodes in the tree.
Stack S
push root onto S
repeat until S is empty
{
v = pop S
if v is not NULL
visit v
push v’s right child onto S
push v’s left child onto S
}
Program 6.5 depicts the program segment for the implementation of non-
recursive preorder traversal.
In the worst case, for preorder traversal, the stack will grow to size n/2, where n is
number of nodes in the tree. Another method of traversing binary tree non-recursively
which does not use stack requires pointers to the parent node (called threaded binary
tree).
A threaded binary tree is a binary tree in which every node that does not have a right
child has a THREAD (a third link) to its INORDER successor. By doing this
threading we avoid the recursive method of traversing a tree and use of stack, which
makes use of a lot of memory and time.
The node structure for a threaded binary tree varies a bit and its like this –
struct NODE
{
42
struct NODE *leftchild; Trees
int node_value;
struct NODE *rightchild;
struct NODE *thread; /* third pointer to it’s inorder successor */
}
6.8 APPLICATIONS
Trees are used enormously in computer programming. These can be used for
improving database search times (binary search trees, 2-3 trees, AVL trees, red-black
trees), Game programming (minimax trees, decision trees, pathfinding trees),
3D graphics programming (quadtrees, octrees), Arithmetic Scripting languages
(arithmetic precedence trees), Data compression (Huffman trees), and file systems (B-
trees, sparse indexed trees, tries ). Figure 6.13 depicts a tic-tac-toe game tree showing
various stages of game.
In all of the above scenario except the first one, the player (playing with X) ultimately
looses in subsequent moves.
The General tree (also known as Linked Trees) is a generic tree that has one root
node, and every node in the tree can have an unlimited number of child nodes. One
popular use of this kind of tree is in Family Tree programs. In game programming,
many games use these types of trees for decision-making processes as shown above
for tic-tac-toe. A computer program might need to make a decision based on an event
that happened.
But this is just a simple tree for demonstration. A more complex AI decision tree
would definitely have a lot more options. The interesting thing about using a tree for
decision-making is that the options are cut down for every level of the tree as we go
down, greatly simplifying the subsequent moves and improving the speed at which the
AI program makes a decision.
The big problem with tree based level progressions, however, is that sometimes the
tree can get too large and complex as the number of moves (level in a tree) increases.
Imagine a game offering just two choices for every move to the next level at the end
of each level in a ten level game. This would require a tree of 1023 nodes to be
created.
43
Stacks, Queues Binary trees are used for searching keys. Such trees are called Binary Search
and Trees trees(refer to Figure 6.14).
A Binary Search Tree (BST) is a binary tree with the following properties:
1. The key of a node is always greater than the keys of the nodes in its left sub-tree
2. The key of a node is always smaller than the keys of the nodes in its right sub-tree
root
14
16
10
8 11 15 18
It may be seen that when nodes of a BST are traversed by inorder traversal, the keys
appear in sorted order:
inorder(root)
{
inorder(root.left)
print(root.key)
inorder(root.right)
}
* ─
1 5 8 6
44
Check Your Progress 2 Trees
B C
E
D
G H I J
6.9 SUMMARY
Tree is one of the most widely used data structure employed for representing various
problems. We studied tree as a special case of an acyclic graph. However, rooted trees
are most prominent of all trees. We discussed definition and properties of general
trees with their applications. Various tree traversal methods are also discussed.
Binary tree are the special case of trees which have at most two children. Binary trees
are mostly implemented using link lists. Various tree traversal mechanisms include
inorder, preorder and post order. These tree traversals can be implemented using
recursive procedures and non-recursive procedures. Binary trees have wider
applications in two way decision making problems which use yes/no, true/false etc.
1) If a tree has e edges and n vertices, then e=n – 1. Hence, if a tree has 45 edges,
then it has 46 vertices.
2) A full 4-ary tree with 100 leaves has i=(100 – 1)/(4 – 1)=33 internal vertices.
3) A full 3-ary tree with 100 internal vertices has l = (3 – 1)*100+ 1=201 leaves
45
Stacks, Queues Check Your Progress 2
and Trees
1) Answers
a. G,H,I and J
b. I
c. D
d. 4
e. 4
2) Preorder : ABDCHEIJC Postorder : GHDIJEBCA Inorder : GDHBIEFAC
level-order: ABCDEGHIJ
3) Array representation of the tree in Figure 6.12
Reference websites
http://www.csee.umbc.edu
http://www.cse.ucsc.edu
http://www.webopedia.com
46