Introduction To Data Structures
Introduction To Data Structures
Linked List
Tree
Graph
Stack, Queue etc.
All these data structures allow us to perform different operations on data. We select these
data structures based on which type of operation is required. We will look into these data
structures in more details in our later lessons.
Arrays:
The simplest type of data structure is a linear array. It means a list of finite number n of
elements referenced respectively by a set of n consecutive numbers, usually 1,2,3,…,n. If we
choose the name A for the array then the elements of A are denoted by subscript notation
as a1, a2, a3,…, an , or by parenthesis notation A(1), A(2), A(3),…, A(n) or by bracket notation
A[1], A[2], A[3],… ,A[n]. For example, considering the following array named STUDENTS
containing 6 student names,
Linked List:
In computer science, a linked list is a data structure that consists of a sequence of data
records such that in each record there isa field that contains a reference (that is, a link) to
the next record in the sequence. In other words, A simple linear datastructure, each of
whose nodes includes pointers to the previous and subsequent nodes in the list, enabling
traversal of thestructure from any starting point. For example, considering following two
lists.
These are list of patients and doctors indicating which patient met which doctors and which
doctors saw which patient.
Trees:
Data frequently contain a hierarchical relationship between various elements. The data
structure which reflects this relationship is called a rooted tree graph or simply a tree. This is
sometimes like the properties of an object. For example, properties to store of an employee
in the office directory can be name, age, sex, salary etc. Again the properties “name” can
have sub-properties like first name, middle name, last name etc. This can be showed as
follows.
Stack:
A stack, also called a Last-In-First-Out (LIFO) system, where insertion and deletion can take
place only at one end called the top. This structure is similar in its operation to stack of
dishes in a spring system as in the following figure.
Queue:
A queue, also called a First-In-First-Out (FIFO) system is a linear list where deletions will take
place only at the front end and insertions will take place only at the rear end. In the
following figure we can see a pipe with two ends, left end is the rear end and right end is the
front end. We are inserting some balls and those balls can only be inserted from the rear
and they can come out (deletions) from the front end.
Graph
A graph data structure consists of a finite (and possibly mutable) set of vertices or nodes or
points, together with a set of unordered pairs of these vertices for an undirected graph or a
set of ordered pairs for a directed graph.
Example of Algorithm
Let us consider the following problem for finding the largest value in a given list of values.
Problem Statement : Find the largest number in the given list of numbers?
Input : A list of positive integer numbers. (List must contain at least one number).
Output : The largest number in the given list of positive integer numbers.
Consider the given list of numbers as 'L' (input), and the largest number as 'max' (Output).
Algorithm
Pseudo code is a simple way of writing programming code in English. Pseudo code is not
actual programming language. It uses short phrases to write code for programs before you
actually create it in a specific language.
1.3.1. RULES FOR PSEUDOCODE
1.3.1.1.Pseudocode Notations
Pseudocode is a precise description of a solution as compared to a flowchart. To get a
complete description of the solution with respect to problem definition, pre–post conditions
and return value details are to be included in the algorithm header. In addition,
information about the variables used and the purpose are to be viewed clearly. To help
anyone get all this information at a glance, the pseudocode uses various notations such
as header, purpose, pre–post conditions, return, variables, statement numbers, and
subalgorithms.
1.3.1.3 Purpose
The purpose is a brief description about what the algorithm does.
Pre Nothing
If there are several input parameters, then the pre condition should be shown for each.
For example, a simple array search Algorithm 1.2 has the following header:
algorithm search (val list<array>, val argument<integer>)
Search array for specific item and return index location.
Pre list containing data array to be searched, argument containing data to be located
in the list
Post None
Return Location if found else return −1 indicating that the element is not found
1.3.1.6 Variables
Variables are needed in algorithms. We need not define every variable used in the
algorithm.
The use of meaningful variable names is appreciated as the context of the data is
indicated by its name.
1. sequence
2. decision
3. repetition
The use of only these constructs makes an algorithm easy to understand, debug, and
modify.
Sequence
An algorithm is a sequence of instructions, which can be a simple instruction (input, output,
or assignment) or either of the other two constructs. Figure 1.3 shows an example of
such a sequence construct. Algorithm 1.3 computes the area of a circle.
Sequence construct
do action 1
do action 2
.
.
.
do action n
Algorithm 1.3
Pre None
Post None
Return None
1. Read Radius
2. AreaOfCircle = 2 * 3.142 * Radius * Radius
3. Print AreaOfCircle
4. Stop
Decision
Some problems cannot be solved with the help of just a sequence of simple instructions.
Sometimes, we need to test the conditions. If the result of the testing is true, we follow a
sequence of instructions; if it is false, we follow a different sequence of instructions. This
is called decision or selection construct .
Series of actions
If a condition is true,
Then
Else
example 1.1 Compare two numbers to print the maximum among them.
Solution The algorithm for comparing two numbers is listed in Algorithm 1.4.
algorithm 1.4
Pre None
Post None
Return None
1. Read two numbers Num1 and Num2
2. If Num1 > Num2
Then Print Num1
Else Print Num2
3. Stop
Repetition
In some problems, we need to repeat a set of instructions. We can use repetition construct
for this purpose. Figure 1.5 shows a repetition construct and an example of computing
the sum of first N numbers, N (Algorithm 1.5).
1.3.1.8 Subalgorithms
We studied three constructs — sequence, decision, and iteration — for developing
an algorithm for solvable problems. A solvable problem is a problem that has a solution
that can be described in the form of an algorithm.
Algorithm 1.5
Pre None
Post None
Return SUM
1. Read N
2. Let SUM = 0 and Index = 1
3. while Index <= N do
SUM = SUM + Index
Index = Index + 1
end while
4. Return SUM
Repetition construct
4. Print Result
5. Stop
Here FACT is the subalgorithm to compute the factorial of a number as
n! = n × (n - 1) × (n - 2) × ... × 1
subalgorithm FACT
1. Read n
2. Let Result = 1
3. while(n not equal to 1) do
Result = Result × n
n=n–1
end while
4. Return Result
Pseudocode Advantages
Can be done easily on a word processor
Easily modified
Implements structured concepts well
Pseudocode Disadvantages
It’s not visual
There is no accepted standard, so it varies widely from company to company
An algorithm is a well defined sequence of steps that provides a solution for a given
problem,
while a pseudocode is one of the methods that can be used to represent an
algorithm.
While algorithms can be written in natural language
pseudocode is written in a format that is closely related to high level programming
language structures.
pseudocode does not use specific programming language syntax and therefore could
be understood by programmers who are familiar with different programming
languages.
Transforming an algorithm presented in pseudocode to programming code could be
much easier than converting an algorithm written in natural language.
There is an intimate relationship between the structuring of data and analysis of algorithms.
In fact, a data structure and an algorithm should be thought of as one single unit;
neither one making sense without the other.
Let us consider the example of searching for a person’s phone number in a directory. The
procedure we follow to search a person and get his/her phone number critically depends on
how the phone number and names are arranged in the directory. Let us consider two ways
of organizing the data (phone numbers and names) in the directory.
1. The data is organized randomly. Then to search a person by name, one has to
linearly start from the first name till the last name in the directory. There is no other
option.
2. If the data is organized by sorting the names (alphabetically sorted in ascending
order), then the search is much easier. Instead of linearly searching through all
records, one may search in a particular area for particular alphabets, similar to using
a dictionary.
As the data is in sorted order, both the binary search and a typical directory search methods
work. Hence our ideas for algorithms become possible when we realize that we can
organize the data as we wish.
A data structure is an aggregation of atomic and composite data types into a set with the
relationship among them defined.
Hence, implementation of data structures can be viewed in terms of two phases:
specification and implementation. Such a division of tasks is useful as it helps to control the
complexity of the entire process.
Phase I: Specification At the first stage, a data structure should be designed so that we
know what it does and not necessarily how it will do it.
Phase II: Implementation At this stage, we define all functions with respect to
the description of how to manipulate data. This can be done with algorithms so that
the details of the operation can be understood easily, and the reader can implement
them easily and effectively with the help of any programming language. Either of the
design tools, that is, an algorithm or a flowchart, can be used at this phase.
We shall learn about a priori algorithm analysis. Algorithm analysis deals with the
execution or running time of various operations involved. The running time of an operation
can be defined as the number of computer instructions executed per operation.
Time Factor − Time is measured by counting the number of key operations such as
comparisons in the sorting algorithm.
The complexity of an algorithm f(n) gives the running time and/or the storage space
required by the algorithm in terms of n as the size of input data.
A fixed part that is a space required to store certain data and variables, that are
independent of the size of the problem. For example, simple variables and
constants used, program size, etc.
A variable part is a space required by variables, whose size depends on the size of
the problem. For example, dynamic memory allocation, recursion stack space, etc.
Space complexity S(P) of any algorithm P is S(P) = C + SP(I), where C is the fixed part and S(I)
is the variable part of the algorithm, which depends on instance characteristic I. Following
is a simple example that tries to explain the concept −
Algorithm: SUM(A, B)
Step 1 - START
Step 2 - C ← A + B + 10
Step 3 - Stop
Here we have three variables A, B, and C and one constant. Hence S(P) = 1 + 3. Now, space
depends on data types of given variables and constant types and it will be multiplied
accordingly.
For example, addition of two n-bit integers takes n steps. Consequently, the total
computational time is T(n) = c ∗ n, where c is the time taken for the addition of two bits.
Here, we observe that T(n) grows linearly as the input size increases.
Majorly, we use THREE types of Asymptotic Notations and those are as follows...
1. Big - Oh (O)
Big - Oh notation is used to define the upper bound of an algorithm in terms of Time
Complexity.
That means Big - Oh notation always indicates the maximum time required by an algorithm
for all input values. That means Big - Oh notation describes the worst case of an algorithm
time complexity.
Consider function f(n) the time complexity of an algorithm and g(n) is the most significant
term. If f(n) <= C g(n) for all n >= n0, C > 0 and n0 >= 1. Then we can
represent f(n) as O(g(n)).
f(n) = O(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-
Axis and time required is on Y-Axis
In above graph after a particular input value n0, always C g(n) is greater than f(n) which
indicates the algorithm's upper bound.
Example
Big - Omega notation is used to define the lower bound of an algorithm in terms of Time
Complexity.
That means Big - Omega notation always indicates the minimum time required by an
algorithm for all input values. That means Big - Omega notation describes the best case of
an algorithm time complexity.
Consider function f(n) the time complexity of an algorithm and g(n) is the most significant
term. If f(n) >= C x g(n) for all n >= n0,C > 0 and n0 >= 1. Then we can
represent f(n) as Ω(g(n)).
f(n) = Ω(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-
Axis and time required is on Y-Axis
In above graph after a particular input value n0, always C x g(n) is less than f(n) which
indicates the algorithm's lower bound.
Example
Big - Theta notation is used to define the average bound of an algorithm in terms of Time
Complexity.
That means Big - Theta notation always indicates the average time required by an algorithm
for all input values. That means Big - Theta notation describes the average case of an
algorithm time complexity.
Consider function f(n) the time complexity of an algorithm and g(n) is the most significant
term. If C1 g(n) <= f(n) >= C2 g(n) for all n >= n0, C1, C2 > 0 and n0 >= 1. Then we can
represent f(n) as Θ(g(n)).
f(n) = Θ(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-Axis and
time required is on Y-Axis
In above graph after a particular input value n0, always C1 g(n) is less than f(n) and C2 g(n) is
greater than f(n) which indicates the algorithm's average bound.
Example
Stacks and queues are the two data structures where insert and delete operations are
applied at specifi c ends only. These are special cases of ordered lists and are also called
controlled linear lists. There is a wide variety of software applications where we need
these restricted data structure operations. The following are some examples where stacks
and queues are generally used:
1. Queues are widely used in applications that maintain a list of printing jobs waiting at a
network printer. Here, one queue that can hold all print requests from different users is
kept.
2. Handling function calls in programs very often restricts access at one end to keep track of
the returning position. In such implementation, we need to use stacks. We can keep track of
the return address to earlier function after furnishing/fi nishing a function call using stacks.
1.11.1. Stack
A stack is an Abstract Data Type (ADT), commonly used in most programming languages. It is
named stack as it behaves like a real-world stack, for example – a deck of cards or a pile of
plates, etc.
Stack Representation
The following diagram depicts a stack and its operations −
A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack
can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are going
to implement stack using arrays, which makes it a fixed size stack implementation.
We know that Stack can be represented using an array. Stack is open at one end and
operations can be performed on single end. We can have different primitive operations on
Stack Data Structure.
1. Create
2. Push
3. Pop
4. Empty
A. Creating Stack :
We have declared data array in the above declaration. Whenever we add any element in the
‘data’ array then it will be called as “Pushing Data on the Stack”.
Suppose “top” is a pointer to the top element in a stack. After every push operation, the
value of “top” is incremented by one.
Step 1 − Checks if the stack is full.
Step 3 − If the stack is not full, increments top to point next empty space.
Step 4 − Adds data element to the stack location, where top is pointing.
Step 5 − Returns success.\ Algorithm for PUSH Operation
A simple algorithm for Push operation can be derived as follows −
Whenever we try to remove element from the stack then the operation is called as POP
Operation on Stack.
A Pop operation may involve the following steps −
Step 3 − If the stack is not empty, accesses the data element at whichtop is pointing.
Concept Definition
A stack data structure can be implemented using one dimensional array. But stack
implemented using array, can store only 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 'top'. Initially 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.
Before implementing actual operations, first follow the below steps to create an empty
stack.
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 functions used in stack implementation.
Step 3: Create a one dimensional array with fixed size (int stack[SIZE])
Step 4: Define a integer variable 'top' and initialize with '-1'. (int top = -1)
Step 5: In main method display menu with list of operations and make suitable
function calls to perform operation selected by the user on the stack.
In a stack, push() is a function used to insert an element into the stack. In a stack, the new
element is always inserted at top position. Push function takes one integer value as
parameter and inserts that value into the stack. We can use the following steps to push an
element on to the stack...
In a stack, pop() is a function used to delete an element from the stack. In a stack, the
element is always deleted from top position. Pop function does not take any value as
parameter. We can use the following steps to pop an element from the stack...
class StackUnderFlowException
{
public:
StackUnderFlowException()
{
cout << "Stack underflow" << endl;
}
};
class ArrayStack
{
private:
int data[MAX_SIZE];
int top;
public:
ArrayStack()
{
top = -1;
}
int Pop()
{
if ( top == -1 )
{
throw new StackUnderFlowException();
}
return data[top--];
}
int Top()
{
return data[top];
}
int Size()
{
return top + 1;
}
bool isEmpty()
{
return ( top == -1 ) ? true : false;
}
};
int main()
{
ArrayStack s;
try {
if ( s.isEmpty() )
{
cout << "Stack is empty" << endl;
}
// Push elements
s.Push(100);
s.Push(200);
// Size of stack
cout << "Size of stack = " << s.Size() << endl;
// Top element
cout << s.Top() << endl;
// Pop element
cout << s.Pop() << endl;
// Pop element
cout << s.Pop() << endl;
// Pop element
cout << s.Pop() << endl;
}
catch (...) {
cout << "Some exception occured" << endl;
}
}
OUTPUT:-
Stack is empty
Size of stack = 2
200
200
100
Stack underflow
Some exception occured
1.11.4. Multiple Stacks
Often, data is represented using several stacks. The contiguous stack (stack using an
array) uses separate arrays for more than one stack, if needed. The use of a contiguous
stack when more than one stack is needed is not a space-effi cient approach, because many
locations in the stacks are often left unused. An effi cient solution to this problem is to use
a single array to store more than one stack.
Output:
Popped element from stack1 is 11
Popped element from stack2 is 40
The stack data structure is used in a wide range of applications. A few of them are the
following:
1. Converting infix expression to postfix and prefix expressions
2. Evaluating the postfix expression
3. Checking well-formed (nested) parenthesis
4. Reversing a string
5. Processing function calls
6. Parsing (analyse the structure) of computer programs
7. Simulating recursion
8. In computations such as decimal to binary conversion
9. In backtracking algorithms (often used in optimizations and in games)
1.12. EXPRESSION EVALUATI ON AND CONVERSION
Based on the operator position, expressions are divided into THREE types. They are as
follows...
1. Infix Expression
2. Postfix Expression
3. Prefix Expression
1.12.1.1. Infix Expression
Example
In postfix expression, operator is used after operands. We can say that "Operator follows the
Operands".
Example
In prefix expression, operator is used before operands. We can say that "Operands follows the
Operator".
The general structure of Prefix expression is as follows...
Operator Operand1 Operand2
Operators are written after their operands
The order of evaluation of operators is always from left to right
brackets cannot be used to change the order of evaluation.
Example
(a) AB+, (b) XYZ+*
Example
Any expression can be represented using the above three different types of expressions.
And we can convert an expression from one form to another form like Infix to Postfix, Infix
to Prefix, Prefix to Postfix and vice versa.
Any expression can be represented using three types of expressions (Infix, Postfix and
Prefix). We can also convert one type of expression to another type of expression like Infix
to Postfix, Infix to Prefix, Postfix to Prefix and vice versa.
Conversion To Postfix
EXAMPLE: A+(B*C-(D/E-F)*G)*H
Empty A+(B*C-(D/E-F)*G)*H -
Empty +(B*C-(D/E-F)*G)*H A
+ (B*C-(D/E-F)*G)*H A
+( B*C-(D/E-F)*G)*H A
+( *C-(D/E-F)*G)*H AB
+(* C-(D/E-F)*G)*H AB
+ *H ABC*DE/F-G*-
+* H ABC*DE/F-G*-
+* End ABC*DE/F-G*-H
#include<iostream>
#include<cstring>
#include<stack>
switch (ch) {
case '/':
default : return 0;
stack<char> s;
int weight;
int i = 0;
int k = 0;
char ch;
ch = infix[i];
if (ch == '(') {
s.push(ch);
i++;
continue;
if (ch == ')') {
// a opening parenthesis
s.pop();
if (!s.empty()) {
s.pop();
i++;
continue;
weight = getWeight(ch);
if (weight == 0) {
// we saw an operand
postfix[k++] = ch;
else {
// we saw an operator
if (s.empty()) {
// stack is empty
s.push(ch);
else {
postfix[k++] = s.top();
s.pop();
s.push(ch);
i++;
while (!s.empty()) {
postfix[k++] = s.top();
s.pop();
// main
int main() {
char postfix[size];
infix2postfix(infix,postfix,size);
return 0;
Example
Expression = (A+B^C)*D+E^5
Step 1. Reverse the infix expression.
5^E+D*)C^B+A(
Step 2. Make Every '(' as ')' and every ')' as '('
5^E+D*(C^B+A)
Step 3. Convert expression to postfix form.
A+(B*C-(D/E-F)*G)*H
E+D*(C^B+A) ^ 5 Push
+D*(C^B+A) ^ 5E Push
#include <iostream.h>
#include <string.h>
#include <ctype.h>
const int MAX = 50 ;
class infix
{
private :
char target[MAX], stack[MAX] ;
char *s, *t ;
int top, l ;
public :
infix( ) ;
void setexpr ( char *str ) ;
void push ( char c ) ;
char pop( ) ;
void convert( ) ;
int priority ( char c ) ;
void show( ) ;
};
infix :: infix( )
{
top = -1 ;
strcpy ( target, "" ) ;
strcpy ( stack, "" ) ;
l=0;
}
void infix :: setexpr ( char *str )
{
s = str ;
strrev ( s ) ;
l = strlen ( s ) ;
* ( target + l ) = '\0' ;
t = target + ( l - 1 ) ;
}
void infix :: push ( char c )
{
if ( top == MAX - 1 )
cout << "\nStack is full\n" ;
else
{
top++ ;
stack[top] = c ;
}
}
char infix :: pop( )
{
if ( top == -1 )
{
cout << "Stack is empty\n" ;
return -1 ;
}
else
{
char item = stack[top] ;
top-- ;
return item ;
}
}
void infix :: convert( )
{
char opr ;
while ( *s )
{
if ( *s == ' ' || *s == '\t' )
{
s++ ;
continue ;
}
if ( isdigit ( *s ) || isalpha ( *s ) )
{
while ( isdigit ( *s ) || isalpha ( *s ) )
{
*t = *s ;
s++ ;
t-- ;
}
}
if ( *s == ')' )
{
push ( *s ) ;
s++ ;
}
if ( *s == '(' )
{
opr = pop( ) ;
while ( ( opr ) != ')' )
{
*t = opr ;
t-- ;
opr = pop ( ) ;
}
s++ ;
}
}
while ( top != -1 )
{
opr = pop( ) ;
*t = opr ;
t-- ;
}
t++ ;
}
int infix :: priority ( char c )
{
if ( c == '$' )
return 3 ;
if ( c == '*' || c == '/' || c == '%' )
return 2 ;
else
{
if ( c == '+' || c == '-' )
return 1 ;
else
return 0 ;
}
}
void infix :: show( )
{
while ( *t )
{
cout << " " << *t ;
t++ ;
}
}
void main( )
{
char expr[MAX] ;
infix q ;
q.setexpr( expr ) ;
q.convert( ) ;
Expression Stack
abc-+de-fg-h+/* NuLL
bc-+de-fg-h+/* "a"
c-+de-fg-h+/* "b"
"a"
"c"
-+de-fg-h+/*
"b"
"a"
e-fg-h+/* "d"
"a+b-c"
"e"
-fg-h+/*
"d"
"a+b-c"
"f"
g-h+/*
"d - e"
"a+b-c"
"g"
-h+/* "f"
"d - e"
"a+b-c"
"f-g"
h+/*
"d - e"
"a+b-c"
"h"
+/* "f-g"
"d - e"
"a+b-c"
"f-g+h"
/*
"d - e"
"a+b-c"
* "(d-e)/(f-g-h)"
"a+b-c"
Null
"(a+b-c)*(d-e)/(f-g+h)"
Ans = (a+b-c)*(d-e)/(f-g+h)
Expression Stack
*+a-bc/-de+-fgh NuLL
*+a-bc/-de+-fg
"h"
*+a-bc/-de+-f "g"
"h"
*+a-bc/-de+- "f"
"g"
"h"
*+a-bc/-d "e"
"f-g+h"
"d"
*+a-bc/-
"e"
"f-g+h"
*+a-b "c"
"(d-e)/(f-g+h)"
"b"
*+a-
"c"
"(d-e)/(f-g+h)"
*+a "b-c"
"(d-e)/(f-g+h)"
"a"
*+
"b-c"
"(d-e)/(f-g+h)"
* "a+b-c"
"(d-e)/(f-g+h)"
End
"(a+b-c)*(d-e)/(f-g+h)"
Ans = (a+b-c)*(d-e)/(f-g+h)
is the stack.
to the output, until the stack is empty. Table 3.13 illustrates this algorithm:
off the stack, and they will come out in the reverse order.
#include<iostream>
#include<string.h>
int main()
Reverse(string);
printf(" %s",string);
return 0;
stack<char> S;
/* Pushing to stack */
S.push(p[i]);
p[i] = S.top();
S.pop();
1. 14. RECURSION
In C++, a function can call itself, that is, one of the statements of the function is a call
to itself. Such functions are called recursive functions and can be used to implement
recursive problems in an elegant manner.
To solve a recursive problem using functions, the problem must have an end condition
that can be stated in non-recursive terms. For example, in the case of factorials, we know
that 1! =1. If no such condition exists, then the recursive calls will indefinitely continue
until the computer runs or the program is terminated by the operating system.
Consider the recursive implementation of factorial given that
1! = 1 and n! = n (n +1)!
The recursive function in C++is given by the following statement:
long int factorial (unsigned int n)
{
if(n <= 1)
return(1);
else
return(n * factorial(n − 1));
}
As we can see, the C++ function represents the recursive mathematical definition of
n!. To see how it works, consider the computation of 5!.
The function calls will proceed as follows:
factorial(5) = 5 * factorial(4)
= 5 * (4* factorial (3))
= 5 * (4* (3 * factorial (2)))
= 5 * (4 *(3 * (2 * factorial (1))))
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
As the starting number is not 1, the function calls itself with the value 5 1, that is, 4.
Therefore, the original function call is kept incomplete and pending, and a second call
is made to the factorial with value 4. This process continues until the fifth call is made,
with the value 1. In this call, the function terminates without any further recursion and
returns the desired value of 1!, which is 1. Subsequently, each of the pending function
calls is completed upto the original factorial (5) function call, which returns the
computed value as 120. In the preceding piece of code, parentheses have been used to
show how the recursive calls proceed from left to right and the computations are made
from right to left.
Let us imagine we have a program that manipulates cells each consisting of a pair of fields;
each field can be a pointer to a cell or can hold an "atom." The situation, of course, is like
that of a program written in Lisp, but the program may be written in almost any
programming language, even Pascal, if we define cells to be of a variant record type. Empty
cells available for incorporation into a data structure are kept on an available space list, and
each program variable is represented by a pointer to a cell. The cell pointed to may be one
cell of a large data structure.
Let us assume that as the program runs, new cells may on occasion be seized from the
available space list. For example, we might wish to have the null pointer in the cell with
atom c in Fig. 12.3 replaced by a pointer to a new cell that holds atom i and a null pointer.
This cell will be removed from the top of the available space list. It is also possible that from
time to time, pointers will change in such a way that cells become detached from the
program variables, as the cells holding g and h in Fig. 12.3 have been. For example, the cell
holding c may, at one time, have pointed to the cell holding g. As another example, the
value of variable B may at some time change, which would, if nothing else has changed,
detach the cell now pointed to by B in Fig. 12.3 and also detach the cell holding d and e (but
not the cell holding c, since it would still be reached from A). We call cells not reachable
from any variable and not on the available space list inaccessible.
When cells are detached, and therefore are no longer needed by the program, it would
be nice if they found their way back onto the available space list, so they could be reused. If
we don't reclaim such cells, we shall eventually reach the unacceptable situation where the
program is not using all the cells, yet it wants a cell from available space, and the available
space list is empty. It is then that a time-consuming garbage collection must be performed.
This garbage collection step is "implicit," in the sense that it was not explicitly called for by
the request for space.
Let us now give an algorithm for finding which of a collection of cells of the types suggested
in Fig. 1.15.1 are accessible from the program variables. We shall define the setting for the
problem precisely by defining a cell type in Pascal that is a variant record type; the four
variants, which we call PP, PA, AP, and AA, are determined by which of the two data fields
are pointers and which are atoms. For example, PA means the left field is a pointer and the
right field an atom. An additional boolean field in cells, called mark, indicates whether the
cell has been found accessible. That is, by setting mark to true when garbage collecting, we
"mark" the cell, indicating it is accessible. The important type definitions are shown in Fig.
1.15.2.
type
atomtype = { some appropriate type;
preferably of the same size as pointers }
patterns = (PP, PA, AP, AA);
celltype = record
mark: boolean;
case pattern: patterns of
PP: (left: celltype; right: celltype);
PA: (left: celltype; right: atomtype);
AP: (left: atomtype; right: celltype);
AA: (left: atomtype; right: atomtype);
end;
Fig. 1.15.2. Definition of the type for cells.
We assume there is an array of cells, taking up most of the memory, and some collection
of variables, that are pointers to cells. For simplicity, we assume there is only one variable,
called source, pointing to a cell, but the extension to many variables is not hard. † That is,
we declare
var
source: celltype;
memory: array [1..memorysize ] of celltype;
To mark the cells accessible from source, we first "unmark" all cells, accessible or not, by
running down the array memory and setting the mark field to false. Then we perform a
depth-first search of the graph emanating from source, marking all cells visited. The cells
visited are exactly those that are accessible. We then traverse the array memory and add to
the available space list all unmarked cells. Figure 12.5 shows a procedure dfs to perform the
depth-first search; dfs is called by the procedure collect that unmarks all cells, and then
marks accessible cells by calling dfs. We do not show the code linking the available space list
because of the peculiarities of Pascal. For example, while we could link available cells using
either all left or all right cells, since pointers and atoms are assumed the same size, we are
not permitted to replace atoms by pointers in cells of variant type AA.
UNIT 2
Recursion in C++
2.0. Introduction
When method is call within same method is called Recursion. The method which call
same method is called recursive method. In other words when a method call itself then
that method is called Recursive method.
Recursive method are very useful to solve many mathematical problems like to calculate
factorial of a number, generating Fibonacci series, etc.
Disadvantage of Recursion
It is a very slow process due to stack overlapping.
Recursive programs can create stack overflow.
Recursive method can create as loops.
Implementation of Recursion
#include<iostream.h>
#include<conio.h>
void main()
{
int fact(int);
int i,f,num;
clrscr();
cout<<"Enter any number: ";
cin>>num;
f=fact(num);
cout<<"Factorial: "<<f;
getch();
}
int fact(int n)
{
if(a<0)
return(-1);
if(a==0)
return(1);
else
{
return(n*fact(n-1));
}
}
Output
Enter any number: 5
Factorial: 120
Example
#include<iostream.h>
#include<conio.h>
void main()
{
int table(int,int);
int n,i; // local variable
clrscr();
cout<<"Enter any num : ";
cin>>n;
for(i=1;i< =10;i++)
{
cout<<n<<"*"<<i<<= <<table(n,i)<<endl;
}
getch();
}
int table(n,i)
{
int t;
if(i==1)
{
return(n);
}
else
{
t=(table(n,i-1)+n);
return(t);
//return(table(n,i-1)+n);
}
}
Output
Enter any number: 5
5*1= 5
5*2= 10
5*3= 15
5*4= 20
5*5= 25
5*6= 30
5*7= 35
5*8= 40
5*9= 45
5*10= 50
2.2. Using of stacks in Recursion
1. Linear Recursion
2. Binary Recursion
3. Tail Recursion
4. Mutual Recursion
5. Nested Recursion
1. Linear Recursion: This recursion is the most commonly used. In this recursion a function
call itself in a simple manner and by termination condition it terminates. This process called
'Winding' and when it returns to caller that is called 'Un-Winding'. Termination condition
also known as Base condition.
Run-Time Version
int Fact(long n)
{
if(0>n)
return -1;
if(0 == n)
return 1;
else
{
return ( n* Fact(n-1));
}
}
Winding Process:
Fact(6) 6*Fact(5)
Fact(5) 5*Fact(4)
Fact(4) 4*Fact(3)
Fact(3) 3* Fact(2)
Fact(2) 2* Fact(1)
Fact(1) 1* Fact(0)
Terminating Point
Fact(0) 1
Unwinding Process
Fact(1) 1*1
Fact(2) 2*1
Fact(3) 3*2*1
Fact(4) 4*3*2*1
Fact(5) 5*4*3*2*1
Fact(6) 6*5*4*3*2*1
2.2.1 Binary Recursion: Binary Recursion is a process where function is called twice at a time
inplace of once at a time. Mostly it's using in data structure like operations for tree as
traversal, finding height, merging, etc.
int FibNum(int n)
{
// Base conditions
if (n < 1)
return -1;
if (1 == n || 2 == n)
return 1;
4. Mutual Recursion: Functions calling each other. Let's say FunA calling FunB and FunB
calling FunA recursively. This is not actually not recursive but it's doing same as recursive. So
you can say Programming languages which are not supporting recursive calls, mutual
recursion can be applied there to fulfill the requirement of recursion. Base condition can be
applied to any into one or more than one or all functions.
view source
print?
01 bool IsOddNumber(int n)
02 {
04 if (0 == n)
05 return 0;
06 else
07 // Recursive call by Mutual Method
09 }
10
11 bool IsEvenNumber(int n)
12 {
14 if (0 == n)
15 return 1;
16 else
19 }
3. Nested Recursion: It's very different than all recursions. All recursion can be converted to
iterative (loop) except nested recursion. You can understand this recursion by example of
Ackermann function.
Recursive Functions
A recursive function (DEF) is a function which either calls itself or is in a potential cycle of
function calls. As the definition specifies, there are two types of recursive
functions. Consider a function which calls itself: we call this type of recursion immediate
recursion.
void A() {
A();
return;
}
The second part of the defintion refers to a cycle (or potential cycle if we use conditional
statements) which involves other functions.
A ---------> B
^ |
| |
| |
|---- C <----|
void C() {
A();
return;
}
void B() {
C();
return;
}
void A() {
B();
return;
}
Recursive functions are an inefficient means of solving problems in terms of run times but
are interesting to study nonetheless. For our purposes we will only consider immediate
recursion since this will cause enough difficulty.
if a simple case, return the simple value // base case / stopping condition
For a recursive function to stop calling itself we require some type of stopping condition. If
it is not the base case, then we simplify our computation using the general formula.
Example: n!
Compute n! Recursively.
n! = n * (n - 1)!
0! = 1
1! = 1 * 0! = 1 * 1 = 1
2! = 2 * 1! = 2 * 1 = 2
3! = 3 * 2! = 3 * 2 = 6
4! = 4 * (3 * 2 * 1) = 4 * 3! = 24
Now let us write the C++ function which will calculate the factorial of a given integer. Apply
the simple case and the general factorial function to the general recursive function
established previously.
int Factorial(int n) {
if (n == 0) return 1; // Simple case: 0! = 1
return (n * Factorial(n - 1)); // General function: n! = n * (n - 1)!
}
Here, 0! = 1 is the base case and our general recursive function forms the simplification of
the original problem.
Consider the following program which contains a recursive function. What is the complete
output?
#include <iostream.h>
int Fibonacci(int x) {
if (x == 0) return 0; // Stopping conditions
if (x == 1) return 1;
return Fibonacci(x - 1) + Fibonacci(x - 2);
}
int main() {
int num;
return 0;
}
Fibonacci(5)
/ \
/ \
/ \
/ \
/ \
F(4) + F(3)
/ \ / \
/ \ / \
/ \ / \
/ \ / \
/ \ / \
F(3) + F(2) F(2) + F(1)
/\ /\ | \ \
/ \ / \ | \ \
/ \ / \ | \ \
/ \ / \ | \ \
F(2) + F(1) F(1) + F(0) F(1) + F(0) 1
/\ | | | | |
/ \ | | | | |
/ \ | | | | |
/ \ | | | | |
F(1) + F(0) 1 1 0 1 0
| |
| |
| |
| |
1 0
Thus, Fibonacci(5) = 5.
Input:
5
Output:
5
You may have noticed the name of the function and recognized it as a well-known
mathematical function: the fibonacci sequance.
Working backward, we could have formulated the function given the mathematical
equations:
General form:
Fibonacci(n) = Fibonacci(n - 1) + Fibonacci(n - 2)
Stopping conditions:
Fibonacci(0) = 1
Fibonacci(1) = 1
Example 2: F()
#include <iostream.h>
//F(0) = 1; F(1) = 2
//F(n) = F(n-1) * F(n-2)
int Recursive(int n) {
cout << n << endl;
if (n == 0) return 1;
if (n == 1) return 2;
return (Recursive(n - 1) * Recursive(n - 2));
}
int main()
{
int Result, num;
return 0;
}
Inputting: num = 3.
Output:
3
2
1
0
1
Result: 4
If we evaluate these recursive calls we have
((2 * 1) * 2) = 4
Thus, Recursive(3) = 4.
Recursive(3)
/ \
/ \
/ \
/ \
/ \
R(2) * R(1)
/ \ \
/ \ \
/ \ \
/ \ \
R(1) * R(0) 2
| |
| |
| |
| |
2 1
5
4
3
2
1
0
1
2
1
0
3
2
1
0
1
Result: 32
One thing you may have noticed from Recursive(5) is that one of the subtrees is
Recursive(3).
If a recursive method is called with a base case, the method returns a result. If a method is
called with a more complex problem, the method divides the problem into two or more
conceptual pieces: a piece that the method knows how to do and a slightly smaller version
of the original problem. Because this new problem looks like the original problem, the
method launches a recursive call to work on the smaller problem.
For recursion to terminate, each time the recursion method calls itself with a slightly simpler
version of the original problem, the sequence of smaller and smaller problems must
converge on the base case. When the method recognizes the base case, the result is
returned to the previous method call and a sequence of returns ensures all the way up the
line until the original call of the method eventually returns the final result.
Both iteration and recursion are based on a control structure: Iteration uses a repetition
structure; recursion uses a selection structure.
Both iteration and recursion involve repetition: Iteration explicitly uses a repetition
structure; recursion achieves repetition through repeated method calls.
Iteration and recursion each involve a termination test: Iteration terminates when the loop-
continuation condition fails; recursion terminates when a base case is recognized.
Iteration and recursion can occur infinitely: An infinite loop occurs with iteration if the loop-
continuation test never becomes false; infinite recursion occurs if the recursion step does
not reduce the problem in a manner that converges on the base case.
Recursion repeatedly invokes the mechanism, and consequently the overhead, of method
calls. This can be expensive in both processor time and memory space.
1. Like Stack, Queue is also an ordered list of elements of similar data types.
2. Queue is a FIFO( First in First Out ) structure.
3. Once a new element is inserted into the Queue, all the elements inserted before the
new element in the queue must be removed, to remove the new element.
4. peek( ) function is oftenly used to return the value of first element without dequeuing it.
Basic Operations
Queue operations may involve initializing or defining the queue, utilizing it, and then
completely erasing it from the memory. Here we shall try to understand the basic
operations associated with queues −
Few more functions are required to make the above-mentioned queue operation efficient.
These are −
peek() − Gets the element at the front of the queue without removing it.
In queue, we always dequeue (or access) data, pointed by front pointer and while
enqueing (or storing) data in the queue we take help of rear pointer.
peek()
This function helps to see the data at the front of the queue. The algorithm of peek()
function is as follows −
Algorithm
return queue[front]
end procedure
Implementation of peek() function in C programming language −
Example
int peek() {
return queue[front];
}
isfull()
As we are using single dimension array to implement queue, we just check for the rear
pointer to reach at MAXSIZE to determine that the queue is full. In case we maintain the
queue in a circular linked-list, the algorithm will differ. Algorithm of isfull() function −
Algorithm
Example
bool isfull() {
if(rear == MAXSIZE - 1)
return true;
else
return false;
}
isempty()
Algorithm of isempty() function −
Algorithm
bool isempty() {
if(front < 0 || front > rear)
return true;
else
return false;
}
2.4.2. Enqueue Operation
Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks.
The following steps should be taken to enqueue (insert) data into a queue −
Step 3 − If the queue is not full, increment rear pointer to point the next empty
space.
Step 4 − Add data element to the queue location, where the rear is pointing.
Sometimes, we also check to see if a queue is initialized or not, to handle any unforeseen
situations.
procedure enqueue(data)
if queue is full
return overflow
endif
rear ← rear + 1
queue[rear] ← data
return true
end procedure
Example
Step 3 − If the queue is not empty, access the data where front is pointing.
Step 3 − Increment front pointer to point to the next available data element.
procedure dequeue
if queue is empty
return underflow
end if
data = queue[front]
front ← front + 1
return true
end procedure
Implementation of dequeue() in C programming language −
Example
int dequeue() {
if(isempty())
return 0;
int data = queue[front];
front = front + 1;
return data;
}
Applications of Queue
Queue, as the name suggests is used whenever we need to have any group of objects in an
order in which the first one coming in, also gets out first while the others wait for there
turn, like in the following scenarios :
1. Serving requests on a single shared resource, like a printer, CPU task scheduling etc.
2. In real life, Call Center phone systems will use Queues, to hold people calling them in an
order, until a service representative is free.
3. Handling of interrupts in real-time systems. The interrupts are handled in the same
order as they arrive, First come first served.
Queue() creates a new queue that is empty. It needs no parameters and returns an empty
queue.
enqueue(item) adds a new item to the rear of the queue. It needs the item and returns
nothing.
dequeue() removes the front item from the queue. It needs no parameters and returns the
item. The queue is modified.
isEmpty() tests to see whether the queue is empty. It needs no parameters and returns a
boolean value.
size() returns the number of items in the queue. It needs no parameters and returns an
integer.
first(): Return object at front without removing; error if empty.
As an example, if we assume that q is a queue that has been created and is currently empty,
A queue data structure can be implemented using one dimensional array. But, queue
implemented using array can store only fixed number of data values. The implementation of
queue data structure using array is very simple, just define a one dimensional array of
specific size and insert or delete the values into that array by using FIFO (First In First Out)
principle with the help of variables 'front' and 'rear'. Initially both 'front' and 'rear' are set
to -1. Whenever, we want to insert a new value into the queue, increment 'rear' value by
one and then insert at that position. Whenever we want to delete a value from the queue,
then increment 'front' value by one and then display the value at 'front' position as deleted
element.
Before we implement actual operations, first follow the below steps to create an empty
queue.
1. Step 1: Include all the header files which are used in the program and define a
constant 'SIZE' with specific value.
2. Step 2: Declare all the user defined functions which are used in queue
implementation.
3. Step 3: Create a one dimensional array with above defined SIZE (int queue[SIZE])
4. Step 4: Define two integer variables 'front' and 'rear' and initialize both with '-1'. (int
front = -1, rear = -1)
5. 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 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...
Delete ( ):
Description: Here QUEUE is an array with N locations. FRONT and REAR points to the front
and rear of
the QUEUE.
If (FRONT == 0) Then [Check for underflow]
Print: Underflow
Else
ITEM = QUEUE[FRONT]
If (FRONT == REAR) Then [Check if only one element is left]
Set FRONT = 0
Set REAR = 0
Else
Set FRONT = FRONT + 1 [Increment FRONT by 1]
[End of Step 5 If]
Print: ITEM deleted
[End of Step 1 If]
Exit
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
#include<stdlib.h>
struct Queue
{
int capacity;
int size;
int front;
int rear;
int *array;
};
typedef struct Queue *PtrToNode;
typedef PtrToNode QUEUE;
QUEUE CreateQueue(int max)
{
QUEUE Q;
Q=(struct Queue*)malloc(sizeof(struct Queue));
if(Q==NULL)
printf("\nFatal error");
else
{
Q->size=0;
Q->capacity=max;
Q->front=0;
Q->rear=-1;
Q->array=(int*)malloc(sizeof(int)*max);
if(Q->array==NULL)
printf("\nFatal error");
else
printf("\nQueue is created successfully");
}
return Q;
}
void MakeEmpty(QUEUE Q)
{
Q->size=0;
Q->front=0;
Q->rear=-1;
}
int IsEmpty(QUEUE Q)
{
return Q->size==0;
}
int IsFull(QUEUE Q)
{
return Q->size==Q->capacity;
}
void EnQueue(int x,QUEUE Q)
{
if(IsFull(Q))
printf("\nQueue is Full");
else
{
Q->rear++;
Q->array[Q->rear]=x;
Q->size++;
}
}
void DeQueue(QUEUE Q)
{
if(IsEmpty(Q))
printf("\nQueue is empty");
else
{
Q->front++;
Q->size--;
}
}
int Front(QUEUE Q)
{
return Q->array[Q->front];
}
QUEUE DisposeQueue(QUEUE Q)
{
MakeEmpty(Q);
free(Q->array);
free(Q);
Q=NULL;
return Q;
}
void Display(QUEUE Q)
{
int i;
for(i=Q->front;i<=Q->rear;i++)
printf("\n%d",Q->array[i]);
}
“qarray.c” File:
#include<stdio.h>
#include"qarray.h"
void main()
{
QUEUE Q=NULL;
int a,size,ch;
printf("\n\n1.CreateQueue\n 2.Enqueue\n 3.Dequeue\n 4.Front\n 5.MakeEmpty\n
6.IsEmpty\n 7.IsFull\n 8.DisposeQueue\n 9.Display\n 10.Exit\n");
X:
printf("\nEnter ur choice:\t");
scanf("%d",&ch);
switch(ch)
{
case 1:
if(Q==NULL)
{
printf("\nEnter the size of queue");
scanf("%d",&size);
Q=CreateQueue(size);
}
else
printf("\nQueue is already created");
break;
case 2:
if(Q==NULL)
printf("\nQueue is not yet created");
else
{
printf("\nEnter the element to insert");
scanf("%d",&a);
EnQueue(a,Q);
}
break;
case 3:
if(Q==NULL)
printf("\nQueue is not yet created");
else
DeQueue(Q);
break;
case 4:
if(Q==NULL)
printf("\nQueue is not yet created");
else
{
a=Front(Q);
printf("\n Front element present in the queue is:\t%d",a);
}
break;
case 5:
if(Q==NULL)
printf("\n Queue is not yet created");
else
{
MakeEmpty(Q);
printf("\n Now Queue becomes empty");
}
break;
case 6:
if(Q==NULL)
printf("\n Queue is not yet created");
else if(IsEmpty(Q))
printf("\n Queue is empty");
else
printf("\n Queue contains some element");
break;
case 7:
if(Q==NULL)
printf("\n Queue is not yet created");
else if(IsFull(Q))
printf("\n Queue is full");
else
printf("\n Queue is not full");
break;
case 8:
if(Q==NULL)
printf("\n Queue is not yet created");
else
{
Q=DisposeQueue(Q);
printf("\n Queue is disposed");
}
break;
case 9:
if(Q==NULL)
printf("\n Queue is not yet created");
else
{
printf("\n The elements in the Queue are:");
Display(Q);
}
break;
case 10:
exit(0);
default:
printf("\n*******WRONG CHOICE********");
}
goto X;
}
OUTPUT:
1.CreateQueue
2.Enqueue
3.Dequeue
4.Front
5.MakeEmpty
6.IsEmpty
7.ISFull
8.DisposeQueue
9.Display
10.Exit
Enter ur choice: 1
Enter the size of queue: 3
Queue is created successfully
Enter ur choice: 2
Enter the element to insert: 100
Enter ur choice: 2
Enter the element to insert: 200
Enter ur choice: 2
Enter the element to insert: 300
Enter ur choice: 2
Enter the element to insert: 400
Queue is Full
Enter ur choice: 6
Queue contains some element
Enter ur choice: 4
Front element present in the queue is: 100
Enter ur choice: 7
Queue is Full
Enter ur choice: 9
The Elements in the queue are:
100
200
300
Enter ur choice: 3
Enter ur choice: 9
The Elements in the queue are:
200
300
Enter ur choice: 7
Queue is not Full
Enter ur choice: 4
Front element present in the queue is: 200
Enter ur choice: 5
Now Queue becomes empty
Enter ur choice: 8
Queue is Disposed
Enter ur choice: 12
***********WRONG ENTRY********
Enter ur choice: 10
2.5. Circular Queue
In a normal Queue Data Structure, we can insert elements until queue becomes full. But
once if queue becomes full, we can not insert the next element 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 we can not insert the new element because,
'rear' is still at last position. In above situation, even though we have empty positions in the
queue we can not make use of them to insert new element. This is the major problem in
normal queue data structure. To overcome this problem we use circular queue data
structure.
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.
To implement a circular queue data structure using array, we first perform the following
steps before we implement actual operations.
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 user defined functions used in circular queue implementation.
Step 3: Create a one dimensional array with above defined SIZE (int cQueue[SIZE])
Step 4: Define two integer variables 'front' and 'rear' and initialize both with '-1'. (int
front = -1, rear = -1)
Step 5: Implement main method by displaying menu of operations list and make
suitable function calls to perform operation selected by the user on circular queue.
In a circular queue, enQueue() is a function which is used to insert an element into the
circular queue. In a circular queue, the new element is always inserted at rear position. The
enQueue() function takes one integer value as parameter and inserts that value into the
circular queue. We can use the following steps to insert an element into the circular queue...
Step 1: Check whether queue is FULL. ((rear == SIZE-1 && front == 0) || (front ==
rear+1))
Step 2: If it is FULL, then display "Queue is FULL!!! Insertion is not possible!!!" and
terminate the function.
Step 3: If it is NOT FULL, then check rear == SIZE - 1 && front != 0 if it is TRUE, then
set rear = -1.
Step 4: Increment rear value by one (rear++), set queue[rear] = value and check
'front == -1' if it is TRUE, then set front = 0.
In a circular queue, deQueue() is a function used to delete an element from the circular
queue. In a circular queue, the element is always deleted from front position. The
deQueue() function doesn't take any value as parameter. We can use the following steps to
delete an element from the circular queue...
We can use the following steps to display the elements of a circular queue...
See the logical circularity of the queue. Addition causes the increment in REAR. It means
that when REAR reaches MAX-1 position then Increment in REAR causes REAR to reach at
first position that is 0.
The drawback of circular queue is , difficult to distinguished the full and empty cases. It is
also known as boundary case problem.
Use count variable to hold the current position ( in case of insertion or deletion).
1. Initialize Operation.
2. Is_Full check.
3. Is_Empty check.
4. Addition or Insertion operation.
5. Deletion operation.
Algorithms
1. INIT(QUEUE,FRONT,REAR,COUNT)
2. INSERT-ITEM(QUEUE, FRONT, REAR, MAX, COUNT, ITEM)
3. REMOVE-ITEM(QUEUE, FRONT, REAR, COUNT, ITEM)
4. FULL-CHECK(QUEUE,FRONT,REAR,MAX,COUNT,FULL)
5. EMPTY-CHECK(QUEUE,FRONT,REAR,MAX,COUNT,EMPTY)
INIT(QUEUE,FORNT,REAR,COUNT)
This algorithm is used to initialize circular queue.
1. FRONT := 1;
2. REAR := 0;
3. COUNT := 0;
4. Return;
INSERT-ITEM( QUEUE, FRONT, REAR, MAX, COUNT, ITEM)
This algorithm is used to insert or add item into circular queue.
1. If ( COUNT = MAX ) then
a. Display “Queue overflow”;
b. Return;
2. Otherwise
a. If ( REAR = MAX ) then
i. REAR := 1;
b. Otherwise
i. REAR := REAR + 1;
c. QUEUE(REAR) := ITEM;
d. COUNT := COUNT + 1;
3. Return;
REMOVE-ITEM( QUEUE, FRONT, REAR, COUNT, ITEM)
This algorithm is used to remove or delete item from circular queue.
1. If ( COUNT = 0 ) then
a. Display “Queue underflow”;
b. Return;
2. Otherwise
a. ITEM := QUEUE(FRONT)l
b. If ( FRONT =MAX ) then
i. FRONT := 1;
c. Otherwise
i. FRONT := FRONT + 1;
d. COUNT := COUNT + 1;
3. Return;
EMPTY-CHECK(QUEUE,FRONT,REAR,MAX,COUNT,EMPTY)
This is used to check queue is empty or not.
1. If( COUNT = 0 ) then
a. EMPTY := true;
2. Otherwise
a. EMPTY := false;
3. Return ;
FULL-CHECK(QUEUE,FRONT,REAR,MAX,COUNT,FULL)
This algorithm is used to check queue is full or not.
1. If ( COUNT = MAX ) then
a. FULL := true;
2. Otherwise
a. FULL := false;
3. Return ;
class CirQueue
private:
int front ;
int rear ;
int count ;
int ele[MAX] ;
public:
CirQueue();
int isFull();
int isEmpty();
};
#include <iostream>
#define MAX 5
class CirQueue
private:
int front ;
int rear ;
int count ;
int ele[MAX] ;
public:
CirQueue();
int isFull();
int isEmpty();
};
CirQueue:: CirQueue()
front = 0;
rear = -1;
count = 0;
int full=0;
full = 1;
return full;
int empty=0;
if( count == 0 )
empty = 1;
return empty;
if( isFull() )
cout<<"\nQueue Overflow";
return;
ele[rear] = item;
count++;
if( isEmpty() )
cout<<"\nQueue Underflow";
return -1;
*item = ele[front];
count--;
return 0;
int main()
int item;
CirQueue q = CirQueue();
q.insertQueue(10);
q.insertQueue(20);
q.insertQueue(30);
q.insertQueue(40);
q.insertQueue(50);
q.insertQueue(60);
if( q.deleteQueue(&item) == 0 )
if( q.deleteQueue(&item) == 0 )
if( q.deleteQueue(&item) == 0 )
if( q.deleteQueue(&item) == 0 )
}
if( q.deleteQueue(&item) == 0 )
q.insertQueue(60);
if( q.deleteQueue(&item) == 0 )
if( q.deleteQueue(&item) == 0 )
cout<<"\n";
return 0;
Inserted item : 10
Inserted item : 20
Inserted item : 30
Inserted item : 40
Inserted item : 50
Queue Overflow
Deleted item:10
Deleted item:20
Deleted item:30
Deleted item:40
Deleted item:50
Inserted item : 60
Deleted item:60
Queue Underflow
2.6.Multi-queues
If more number of queues is required to be implemented, then an efficient data structure
to handle multiple queues is required. It is possible to utilize all the available spaces in
a single array. When more than two queues, say n, are represented sequentially, we can
divide the available memory A[size] into n segments and allocate these segments to n
queues, one to each. For each queue i we shall use Front[i] and Rear[i]. We shall use
the condition Front[i] = Rear[i] if and only if the ith queue is empty, and the condition
Rear[i] = Front[i] if and only if the ith queue is full.
If we want five queues, then we can divide the array A[100] into equal parts of 20
and initialize front and rear for each queue, that is, Front[0] = Rear[0] = 0 and
Front[1] = Rear[1] = 20, and so on for other queues (Fig. 5.3).
Double Ended Queue is also a Queue data structure in which the insertion and deletion
operations are performed at both the ends (frontand rear). That means, we can insert at
both front and rear positions and can delete from both front and rear positions.
Double Ended Queue can be represented in TWO ways, those are as follows...
In input restricted double ended queue, the insertion operation is performed at only one
end and deletion operation is performed at both the ends.
In output restricted double ended queue, the deletion operation is performed at only one
end and insertion operation is performed at both the ends.
if(rear==MAX)
Print("Queue is Overflow”);
return;
else
rear=rear+1;
q[rear]=no;
if rear=0
rear=1;
if front=0
front=1;
Step-3: return
if(front<=1)
return;
else
front=front-1;
q[front]=no;
Step-3 : Return
if front=0
return;
Step-2 [Perform deletion]
else
no=q[front];
if front=rear
front=0;
rear=0;
else
front=front+1;
Step-3 : Return
if rear=0
return;
else
no=q[rear];
if front= rear
front=0;
rear=0;
else
rear=rear-1;
Step-3 : Return
Declaration of DeQueue
#define MAX 5
typedef struct DQ
int front ;
int rear ;
int count ;
int ele[MAX];
};
#include <iostream>
#define MAX 5
//Declaration of DQueue
class DQueue
private:
int front ;
int rear ;
int count ;
int ele[MAX] ;
public:
DQueue();
int isFull();
int isEmpty();
};
//Initalize DQueue
DQueue:: DQueue()
front = 0;
rear = -1;
count = 0;
int full=0;
full = 1;
return full;
int empty=0;
if( count == 0 )
empty = 1;
return empty;
if( isFull() )
cout<<"\nQueue Overflow";
return;
ele[rear] = item;
count++;
if( isEmpty() )
cout<<"\nQueue Underflow";
return -1;
*item = ele[front];
return 0;
if( isFull() )
cout<<"\nQueue Overflow";
return;
if ( front == 0)
front = MAX - 1;
else
front = front - 1;
ele[front] = item;
count++;
if( isEmpty() )
cout<<"\nQueue Underflow";
return -1;
}
*item = ele[rear];
if(rear == 0)
rear = MAX - 1;
else
rear = rear - 1;
count--;
return 0;
int main()
int item;
DQueue q = DQueue();
q.insertDQueueAtRear(10);
q.insertDQueueAtRear(20);
q.insertDQueueAtRear(30);
q.insertDQueueAtFront(40);
q.insertDQueueAtFront(50);
q.insertDQueueAtFront(60);
if( q.deleteDQueueAtFont(&item) == 0 )
if( q.deleteDQueueAtFont(&item) == 0 )
if( q.deleteDQueueAtFont(&item) == 0 )
{
if( q.deleteDQueueAtRear(&item) == 0 )
if( q.deleteDQueueAtRear(&item) == 0 )
if( q.deleteDQueueAtRear(&item) == 0 )
cout<<"\n";
return 0;
Inserted item : 10
Inserted item : 20
Inserted item : 30
Inserted item : 40
After Insert At Front FRONT :3, REAR : 2
Inserted item : 50
Queue Overflow
Deleted item:50
Deleted item:40
Deleted item:10
Deleted item:30
Deleted item:20
Queue Underflow
In normal queue data structure, insertion is performed at the end of the queue and deletion
is performed based on the FIFO principle. This queue implementation may not be suitable
for all situations.
Consider a networking application where server has to respond for requests from multiple
clients using queue data structure. Assume four requests arrived to the queue in the order
of R1 requires 20 units of time, R2 requires 2 units of time, R3 requires 10 units of time and
R4 requires 5 units of time. Queue is as follows...
That means, if we use a normal queue data structure to serve these requests the average
waiting time for each request is 27 units of time.
Now, consider another way of serving these requests. If we serve according to their
required amount of time. That means, first we serve R2 which has minimum time required
(2) then serve R4 which has second minimum time required (5) then serve R3 which has
third minimum time required (10) and finnaly R1 which has maximum time required (20).
1. R2 : 2 units of time
2. R4 : 7 units of time (R4 must wait till R2 complete 2 units and R4 itself requeres 5
units. Total 7 units)
3. R3 : 17 units of time (R3 must wait till R4 complete 7 units and R3 itself requeres 10
units. Total 17 units)
4. R1 : 37 units of time (R1 must wait till R3 complete 17 units and R1 itself requeres 20
units. Total 37 units)
Here, average waiting time for all requests (R1, R2, R3 and R4) is (2+7+17+37)/4 ≈ 15 units of
time.
From above two situations, it is very clear that, by using second method server can
complete all four requests with very less time compared to the first method. This is what
exactly done by the priority queue.
Priority queue is a variant of queue data structure in which insertion is performed in the
order of arrival and deletion is performed based on the priority.
In max priority queue, elements are inserted in the order in which they arrive the queue and
always maximum value is removed first from the queue. For example assume that we insert
in order 8, 3, 2, 5 and they are removed in the order 8, 5, 3, 2.
2. Using an Unordered Array (Dynamic Array) with the index of the maximum value
6. Using Unordered Linked List with reference to node with the maximum value
In this representation elements are inserted according to their arrival order and maximum
element is deleted first from max priority queue.
For example, assume that elements are inserted in the order of 8, 2, 3 and 5. And they are
removed in the order 8, 5, 3 and 2.
insert() - New element is added at the end of the queue. This operation requires O(1) time
complexity that means constant time.
findMax() - To find maximum element in the queue, we need to compare with all the
elements in the queue. This operation requires O(n)time complexity.
remove() - To remove an element from the queue first we need to perform findMax() which
requires O(n) and removal of particular element requires constant time O(1). This operation
requires O(n) time complexity.
#2. Using an Unordered Array (Dynamic Array) with the index of the maximum value
In this representation elements are inserted according to their arrival order and maximum
element is deleted first from max priority queue.
For example, assume that elements are inserted in the order of 8, 2, 3 and 5. And they are
removed in the order 8, 5, 3 and 2.
isEmpty() - If 'front == -1' queue is Empty. This operation requires O(1) time complexity that
means constant time.
insert() - New element is added at the end of the queue with O(1) and for each insertion we
need to update maxIndex with O(1). This operation requires O(1) time complexity that
means constant time.
findMax() - To find maximum element in the queue is very simple as maxIndex has
maximum element index. This operation requires O(1)time complexity.
remove() - To remove an element from the queue first we need to perform findMax() which
requires O(1) , removal of particular element requires constant time O(1) and update
maxIndex value which requires O(n). This operation requires O(n) time complexity.
In this representation elements are inserted according to their value in decreasing order and
maximum element is deleted first from max priority queue.
For example, assume that elements are inserted in the order of 8, 5, 3 and 2. And they are
removed in the order 8, 5, 3 and 2.
isEmpty() - If 'front == -1' queue is Empty. This operation requires O(1) time complexity that
means constant time.
insert() - New element is added at a particular position in the decreasing order into the
queue with O(n), because we need to shift existing elements inorder to insert new element
in decreasing order. This operation requires O(n) time complexity.
findMax() - To find maximum element in the queue is very simple as maximum element is at
the beginning of the queue. This operation requires O(1) time complexity.
remove() - To remove an element from the queue first we need to perform findMax() which
requires O(1), removal of particular element requires constant time O(1) and rearrange
remaining elements which requires O(n). This operation requires O(n) time complexity.
In this representation elements are inserted according to their value in increasing order and
maximum element is deleted first from max priority queue.
For example, assume that elements are inserted in the order of 2, 3, 5 and 8. And they are
removed in the order 8, 5, 3 and 2.
isEmpty() - If 'front == -1' queue is Empty. This operation requires O(1) time complexity that
means constant time.
insert() - New element is added at a particular position in the increasing order into the
queue with O(n), because we need to shift existing elements inorder to insert new element
in increasing order. This operation requires O(n) time complexity.
findMax() - To find maximum element in the queue is very simple as maximum element is at
the end of the queue. This operation requires O(1) time complexity.
remove() - To remove an element from the queue first we need to perform findMax() which
requires O(1), removal of particular element requires constant time O(1) and rearrange
remaining elements which requires O(n). This operation requires O(n) time complexity.
In this representation, we use a single linked list to represent max priority queue. In this
representation elements are inserted according to their value in increasing order and node
with maximum value is deleted first from max priority queue.
For example, assume that elements are inserted in the order of 2, 3, 5 and 8. And they are
removed in the order 8, 5, 3 and 2.
isEmpty() - If 'head == NULL' queue is Empty. This operation requires O(1) time complexity
that means constant time.
insert() - New element is added at a particular position in the increasing order into the
queue with O(n), because we need to the position where new element has to be inserted.
This operation requires O(n) time complexity.
findMax() - To find maximum element in the queue is very simple as maximum element is at
the end of the queue. This operation requires O(1) time complexity.
remove() - To remove an element from the queue is simply removing the last node in the
queue which requires O(1). This operation requires O(1) time complexity.
#6. Using Unordered Linked List with reference to node with the maximum value
In this representation, we use a single linked list to represent max priority queue. Always we
maitain a reference (maxValue) to the node with maximum value. In this representation
elements are inserted according to their arrival and node with maximum value is deleted
first from max priority queue.
For example, assume that elements are inserted in the order of 2, 8, 3 and 5. And they are
removed in the order 8, 5, 3 and 2.
Now, let us analyse each operation according to this representation...
isEmpty() - If 'head == NULL' queue is Empty. This operation requires O(1) time complexity
that means constant time.
insert() - New element is added at end the queue with O(1) and update maxValue reference
with O(1). This operation requires O(1) time complexity.
findMax() - To find maximum element in the queue is very simple as maxValue is referenced
to the node with maximum value in the queue. This operation requires O(1) time
complexity.
remove() - To remove an element from the queue is deleting the node which referenced by
maxValue which requires O(1) and update maxValue reference to new node with maximum
value in the queue which requires O(n) time complexity. This operation requires O(n)time
complexity.
Min Priority Queue is similar to max priority queue except removing maximum element first,
we remove minimum element first in min priority queue.
Min priority queue is also has same representations as Max priority queue with minimum
value removal.
#include <iostream>
#define MAX 5
class PQueue
private:
int ele[MAX][MAX];
int count;
public:
PQueue();
PQueue:: PQueue()
int i=0;
count = 0;
ele[i][1] = -1 ;
int i = 0;
return;
}
for ( i = 0; i < MAX; i++ )
break;
ele[i][0] = item;
ele[i][1] = priority;
count++;
int i = 0,max,pos=0;
if( count == 0 )
return -1;
max = ele[0][1];
{
pos = i;
max = ele[i][1];
*item = ele[pos][0];
ele[pos][1] = -1;
count--;
return 0;
int i = 0,max,pos=0;
if( count == 0 )
return -1;
max = ele[0][1];
pos = i;
max = ele[i][1];
}
}
*item = ele[pos][0];
return 0;
int main()
int item;
PQueue q = PQueue();
q.insertWithPriority( 1, 10 );
q.insertWithPriority( 2, 20 );
q.insertWithPriority( 3, 30 );
q.insertWithPriority( 4, 40 );
q.insertWithPriority( 5, 50 );
q.insertWithPriority( 6, 60 );
cout<< endl;
return 0;
OUTPUT
Inserted item is : 10
Inserted item is : 20
Inserted item is : 30
Inserted item is : 40
Inserted item is : 50
Peek Item : 50
Item : 50
Peek Item : 40
Item : 40
Item : 30
Peek Item : 20
Item : 20
Item : 10
They start counting in clockwise direction from the child designated as 1. The counting
proceeds until the mth child is identified. mth child is eliminated from the queue.
Counting for the next round begins from the child next to the eliminated one and proceeds
until the mth child is identified. This child is then eliminated and the process continues.
After few rounds of counting only one child is left and this child is declared as winner.
algorithm
1. Let n be the number of members
2. Get the first member
3. Add all members to the queue
4. while (there is more than one member in the queue)
begin
count through n − 1 members in the queue;
print the name of the nth member;
Remove the nth member from the queue;
end
5. Print the name of the only member in the list.
In the job-scheduling problem, we are given a list of n jobs. Every job i is associated with
an integer deadline di ≥0 and a profit pi ≥0. For any job i, profit is earned if and only if
the job is completed within its deadline. A feasible solution with the maximum sum of
profits is to be obtained.
To find the optimal solution and feasibility of jobs, we are required to find a subset J
such that each job of this subset can be completed by its deadline. The value of a feasible
solution J is the sum of profits of all the jobs in J.
The steps in finding the subset J are as follows:
1. pi X i J is the objective function chosen for the optimization measure.
2. Using this measure, the next job to be included should be the one that increases . pi X i
J.
3. Begin with J = , pi =0, and i J.
4. Add a job to J, which has the largest profit.
5. Add another job to J bearing in mind the following conditions:
(a) Search for the job that has the next maximum profit.
(b) See if this job in union with J is feasible.
(c) If yes, go to step 5 and continue; else go to (d).
(d) Search for the job with the next maximum profit and go to step 2.
6. Terminate when addition of no more jobs is feasible.
Example 1 shows a job scheduling algorithm that works to yield an optimized high
profit solution.
example 1 Consider five jobs with profits (p1, p2, p3, p4, p5) =(20, 15, 10, 5, 1) and
maximum delay allowed (d1, d2, d3, d4, d5) =(2, 2, 1, 3, 3).
Here, the maximum number of jobs that can be completed is
Min(n, maxdelay(di)) =Min(5, 3)=3
Hence, there is a possibility of doing 3 jobs, and there are 3 units of time, as shown
in Table 5.2.
In the first unit of time, job 1 is done and a profit of 20 is gained; in the second unit, job 2 is
done and a profit of 15 is obtained. However, in the third unit of time, job 3 is not available,
so job 4 is done with a gain of 5. Further, the deadline of job 5 has also passed; hence three
jobs 1, 2, and 4 are completed with a total profit of 40.
They are a dynamic in nature which allocates the memory when required.
Insertion and deletion operations can be easily implemented.
Stacks and queues can be easily executed.
Linked List reduces the access time.
2 . Memory Structure :
Stack is stored in contiguous Memory Locations , i.e Suppose first element is Stored
at 2000 then Second Integer element will be stored at 2002 .
But It is not necessary to store next element at the Consecutive memory Location .
Element is stored at any available Location , but the Pointer to that memory location
is stored in Previous Node.
3 . Insertion / Deletion
4 . Memory Allocation :
Now, the next node at the left should point to the new node.
This will put the new node in the middle of the two. The new list should look like this −
Similar steps should be taken if the node is being inserted at the beginning of the list. While
inserting it at the end, the second last node of the list should point to the new node and
the new node will point to NULL.
The left (previous) node of the target node now should point to the next node of the target
node −
This will remove the link that was pointing to the target node. Now, using the following
code, we will remove what the target node is pointing at.
We need to use the deleted node. We can keep that in memory otherwise we can simply
deallocate memory and wipe off the target node completely.
We have to make sure that the last node is not the lost node. So we'll have some temp
node, which looks like the head node pointing to the last node. Now, we shall make all left
side nodes point to their previous nodes one by one.
Except the node (first node) pointed by the head node, all nodes should point to their
predecessor, making them their new successor. The first node will point to NULL.
We'll make the head node point to the new first node by using the temp node.
Although a linked list can be implemented in a variety of ways, the most fl exible
implementation is by using pointers.
Doubly Linked List : In a doubly linked list, each node contains two links the first link
points to the previous node and the next link points to the next node in the sequence.
Circular Linked List : In the circular linked list the last node of the list contains the
address of the first node and forms a circular chain.
Simply a list is a sequence of data, and linked list is a sequence of data linked with each
other.
Single linked list is a sequence of elements in which every element has link to its next
element in the sequence.
In any single linked list, the individual element is called as "Node". Every "Node" contains
two fields, data and next. The data field is used to store actual value of that node and next
field is used to store the address of the next node in the sequence.
Operations
1. Insertion
2. Deletion
3. Display
Before we implement actual operations, first we need to setup empty list. First perform the
following steps before implementing actual operations.
Step 1: Include all the header files which are used in the program.
Step 2: Declare all the user defined functions.
Step 3: Define a Node structure with two members data and next
Step 4: Define a Node pointer 'head' and set it to NULL.
Step 4: Implement the main method by displaying operations menu and make
suitable function calls in the main method to perform user selected operation.
Insertion
In a single linked list, the insertion operation can be performed in three ways. They are as
follows...
We can use the following steps to insert a new node at beginning of the single linked list...
Step 1: Create a newNode with given value.
Step 2: Check whether list is Empty (head == NULL)
Step 3: If it is Empty then, set newNode→next = NULL and head = newNode.
Step 4: If it is Not Empty then, set newNode→next = head and head = newNode.
We can use the following steps to insert a new node at end of the single linked list...
Step 1: Create a newNode with given value and newNode → next as NULL.
Step 2: Check whether list is Empty (head == NULL).
Step 3: If it is Empty then, set head = newNode.
Step 4: If it is Not Empty then, define a node pointer temp and initialize with head.
Step 5: Keep moving the temp to its next node until it reaches to the last node in the
list (until temp → next is equal to NULL).
Step 6: Set temp → next = newNode.
We can use the following steps to insert a new node after a node in the single linked list...
Deletion
In a single linked list, the deletion operation can be performed in three ways. They are as
follows...
We can use the following steps to delete a node from beginning of the single linked list...
Step 1: Check whether list is Empty (head == NULL)
Step 2: If it is Empty then, display 'List is Empty!!! Deletion is not possible' and
terminate the function.
Step 3: If it is Not Empty then, define a Node pointer 'temp' and initialize with head.
Step 4: Check whether list is having only one node (temp → next == NULL)
Step 5: If it is TRUE then set head = NULL and delete temp (Setting Empty list
conditions)
Step 6: If it is FALSE then set head = temp → next, and delete temp.
We can use the following steps to delete a node from end of the single linked list...
We can use the following steps to delete a specific node from the single linked list...
We can use the following steps to display the elements of a single linked list...
/*
* C++ Program to Implement Singly Linked List
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
/*
* Node Declaration
*/
struct node
{
int info;
struct node *next;
}*start;
/*
* Class Declaration
*/
class single_llist
{
public:
node* create_node(int);
void insert_begin();
void insert_pos();
void insert_last();
void delete_pos();
void sort();
void search();
void update();
void reverse();
void display();
single_llist()
{
start = NULL;
}
};
/*
* Main :contains menu
*/
main()
{
int choice, nodes, element, position, i;
single_llist sl;
start = NULL;
while (1)
{
cout<<endl<<"---------------------------------"<<endl;
cout<<endl<<"Operations on singly linked list"<<endl;
cout<<endl<<"---------------------------------"<<endl;
cout<<"1.Insert Node at beginning"<<endl;
cout<<"2.Insert node at last"<<endl;
cout<<"3.Insert node at position"<<endl;
cout<<"4.Sort Link List"<<endl;
cout<<"5.Delete a Particular Node"<<endl;
cout<<"6.Update Node Value"<<endl;
cout<<"7.Search Element"<<endl;
cout<<"8.Display Linked List"<<endl;
cout<<"9.Reverse Linked List "<<endl;
cout<<"10.Exit "<<endl;
cout<<"Enter your choice : ";
cin>>choice;
switch(choice)
{
case 1:
cout<<"Inserting Node at Beginning: "<<endl;
sl.insert_begin();
cout<<endl;
break;
case 2:
cout<<"Inserting Node at Last: "<<endl;
sl.insert_last();
cout<<endl;
break;
case 3:
cout<<"Inserting Node at a given position:"<<endl;
sl.insert_pos();
cout<<endl;
break;
case 4:
cout<<"Sort Link List: "<<endl;
sl.sort();
cout<<endl;
break;
case 5:
cout<<"Delete a particular node: "<<endl;
sl.delete_pos();
break;
case 6:
cout<<"Update Node Value:"<<endl;
sl.update();
cout<<endl;
break;
case 7:
cout<<"Search element in Link List: "<<endl;
sl.search();
cout<<endl;
break;
case 8:
cout<<"Display elements of link list"<<endl;
sl.display();
cout<<endl;
break;
case 9:
cout<<"Reverse elements of Link List"<<endl;
sl.reverse();
cout<<endl;
break;
case 10:
cout<<"Exiting..."<<endl;
exit(1);
break;
default:
cout<<"Wrong choice"<<endl;
}
}
}
/*
* Creating Node
*/
node *single_llist::create_node(int value)
{
struct node *temp, *s;
temp = new(struct node);
if (temp == NULL)
{
cout<<"Memory not allocated "<<endl;
return 0;
}
else
{
temp->info = value;
temp->next = NULL;
return temp;
}
}
/*
* Inserting element in beginning
*/
void single_llist::insert_begin()
{
int value;
cout<<"Enter the value to be inserted: ";
cin>>value;
struct node *temp, *p;
temp = create_node(value);
if (start == NULL)
{
start = temp;
start->next = NULL;
}
else
{
p = start;
start = temp;
start->next = p;
}
cout<<"Element Inserted at beginning"<<endl;
}
/*
* Inserting Node at last
*/
void single_llist::insert_last()
{
int value;
cout<<"Enter the value to be inserted: ";
cin>>value;
struct node *temp, *s;
temp = create_node(value);
s = start;
while (s->next != NULL)
{
s = s->next;
}
temp->next = NULL;
s->next = temp;
cout<<"Element Inserted at last"<<endl;
}
/*
* Insertion of node at a given position
*/
void single_llist::insert_pos()
{
int value, pos, counter = 0;
cout<<"Enter the value to be inserted: ";
cin>>value;
struct node *temp, *s, *ptr;
temp = create_node(value);
cout<<"Enter the postion at which node to be inserted: ";
cin>>pos;
int i;
s = start;
while (s != NULL)
{
s = s->next;
counter++;
}
if (pos == 1)
{
if (start == NULL)
{
start = temp;
start->next = NULL;
}
else
{
ptr = start;
start = temp;
start->next = ptr;
}
}
else if (pos > 1 && pos <= counter)
{
s = start;
for (i = 1; i < pos; i++)
{
ptr = s;
s = s->next;
}
ptr->next = temp;
temp->next = s;
}
else
{
cout<<"Positon out of range"<<endl;
}
}
/*
* Sorting Link List
*/
void single_llist::sort()
{
struct node *ptr, *s;
int value;
if (start == NULL)
{
cout<<"The List is empty"<<endl;
return;
}
ptr = start;
while (ptr != NULL)
{
for (s = ptr->next;s !=NULL;s = s->next)
{
if (ptr->info > s->info)
{
value = ptr->info;
ptr->info = s->info;
s->info = value;
}
}
ptr = ptr->next;
}
}
/*
* Delete element at a given position
*/
void single_llist::delete_pos()
{
int pos, i, counter = 0;
if (start == NULL)
{
cout<<"List is empty"<<endl;
return;
}
cout<<"Enter the position of value to be deleted: ";
cin>>pos;
struct node *s, *ptr;
s = start;
if (pos == 1)
{
start = s->next;
}
else
{
while (s != NULL)
{
s = s->next;
counter++;
}
if (pos > 0 && pos <= counter)
{
s = start;
for (i = 1;i < pos;i++)
{
ptr = s;
s = s->next;
}
ptr->next = s->next;
}
else
{
cout<<"Position out of range"<<endl;
}
free(s);
cout<<"Element Deleted"<<endl;
}
}
/*
* Update a given Node
*/
void single_llist::update()
{
int value, pos, i;
if (start == NULL)
{
cout<<"List is empty"<<endl;
return;
}
cout<<"Enter the node postion to be updated: ";
cin>>pos;
cout<<"Enter the new value: ";
cin>>value;
struct node *s, *ptr;
s = start;
if (pos == 1)
{
start->info = value;
}
else
{
for (i = 0;i < pos - 1;i++)
{
if (s == NULL)
{
cout<<"There are less than "<<pos<<" elements";
return;
}
s = s->next;
}
s->info = value;
}
cout<<"Node Updated"<<endl;
}
/*
* Searching an element
*/
void single_llist::search()
{
int value, pos = 0;
bool flag = false;
if (start == NULL)
{
cout<<"List is empty"<<endl;
return;
}
cout<<"Enter the value to be searched: ";
cin>>value;
struct node *s;
s = start;
while (s != NULL)
{
pos++;
if (s->info == value)
{
flag = true;
cout<<"Element "<<value<<" is found at position "<<pos<<endl;
}
s = s->next;
}
if (!flag)
cout<<"Element "<<value<<" not found in the list"<<endl;
}
/*
* Reverse Link List
*/
void single_llist::reverse()
{
struct node *ptr1, *ptr2, *ptr3;
if (start == NULL)
{
cout<<"List is empty"<<endl;
return;
}
if (start->next == NULL)
{
return;
}
ptr1 = start;
ptr2 = ptr1->next;
ptr3 = ptr2->next;
ptr1->next = NULL;
ptr2->next = ptr1;
while (ptr3 != NULL)
{
ptr1 = ptr2;
ptr2 = ptr3;
ptr3 = ptr3->next;
ptr2->next = ptr1;
}
start = ptr2;
}
/*
* Display Elements of a link list
*/
void single_llist::display()
{
struct node *temp;
if (start == NULL)
{
cout<<"The List is Empty"<<endl;
return;
}
temp = start;
cout<<"Elements of list are: "<<endl;
while (temp != NULL)
{
cout<<temp->info<<"->";
temp = temp->next;
}
cout<<"NULL"<<endl;
}
OUTPUT
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 8
Display elements of link list
The List is Empty.
---------------------------------
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 5
Delete a particular node:
List is empty
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 6
Update Node Value:
List is empty
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 7
Search element in Link List:
List is empty
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 3
Inserting Node at a given position:
Enter the value to be inserted: 1010
Enter the postion at which node to be inserted: 5
Positon out of range
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 1
Inserting Node at Beginning:
Enter the value to be inserted: 100
Element Inserted at beginning
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 1
Inserting Node at Beginning:
Enter the value to be inserted: 200
Element Inserted at beginning
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 8
Display elements of link list
Elements of list are:
200->100->NULL
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 2
Inserting node at last:
Enter the value to be inserted: 50
Element Inserted at last
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 2
Inserting node at last:
Enter the value to be inserted: 150
Element Inserted at last
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 8
Display elements of link list
Elements of list are:
200->100->50->150->NULL
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 3
Inserting node at a given position:
Enter the value to be inserted: 1111
Enter the position at which node to be inserted: 4
---------------------------------
Operations on singly linked list
---------------------------------
1.Insert Node at beginning
2.Insert node at last
3.Insert node at position
4.Sort Link List
5.Delete a Particular Node
6.Update Node Value
7.Search Element
8.Display Linked List
9.Reverse Linked List
10.Exit
Enter your choice : 8
Display elements of link list
Elements of list are:
200->100->50->1111->150->NULL
In a single linked list, every node has link to its next node in the sequence. So, we can
traverse from one node to other node only in one direction and we can not traverse back.
We can solve this kind of problem by using double linked list. Double linked list can be
defined as follows...
Double linked list is a sequence of elements in which every element has links to its
previous element and next element in the sequence.
In double linked list, every node has link to its previous node and next node. So, we can
traverse forward by using next field and can traverse backward by using previous field.
Every node in a double linked list contains three fields and they are shown in the following
figure...
Here, 'link1' field is used to store the address of the previous node in the
sequence, 'link2' field is used to store the address of the next node in the sequence
and 'data' field is used to store the actual value of that node.
Example
In double linked list, the first node must be always pointed by head.
Always the previous field of the first node must be NULL.
Always the next field of the last node must be NULL.
Operations
1. Insertion
2. Deletion
3. Display
Insertion
In a double linked list, the insertion operation can be performed in three ways as follows...
We can use the following steps to insert a new node at beginning of the double linked list...
Step 1: Create a newNode with given value and newNode → previous as NULL.
Step 2: Check whether list is Empty (head == NULL)
Step 3: If it is Empty then, assign NULL to newNode → next and newNode to head.
Step 4: If it is not Empty then, assign head to newNode →
next and newNode to head.
We can use the following steps to insert a new node at end of the double linked list...
Step 1: Create a newNode with given value and newNode → next as NULL.
Step 2: Check whether list is Empty (head == NULL)
Step 3: If it is Empty, then assign NULL to newNode →
previous and newNode to head.
Step 4: If it is not Empty, then, define a node pointer temp and initialize with head.
Step 5: Keep moving the temp to its next node until it reaches to the last node in the
list (until temp → next is equal to NULL).
Step 6: Assign newNode to temp → next and temp to newNode → previous.
We can use the following steps to insert a new node after a node in the double linked list...
Deletion
In a double linked list, the deletion operation can be performed in three ways as follows...
We can use the following steps to delete a node from beginning of the double linked list...
We can use the following steps to delete a node from end of the double linked list...
We can use the following steps to delete a specific node from the double linked list...
We can use the following steps to display the elements of a double linked list...
/*
* C++ Program to Implement Doubly Linked List
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
/*
* Node Declaration
*/
using namespace std;
struct node
{
int info;
struct node *next;
struct node *prev;
}*start;
/*
Class Declaration
*/
class double_llist
{
public:
void create_list(int value);
void add_begin(int value);
void add_after(int value, int position);
void delete_element(int value);
void search_element(int value);
void display_dlist();
void count();
void reverse();
double_llist()
{
start = NULL;
}
};
/*
* Main: Conatins Menu
*/
int main()
{
int choice, element, position;
double_llist dl;
while (1)
{
cout<<endl<<"----------------------------"<<endl;
cout<<endl<<"Operations on Doubly linked list"<<endl;
cout<<endl<<"----------------------------"<<endl;
cout<<"1.Create Node"<<endl;
cout<<"2.Add at begining"<<endl;
cout<<"3.Add after position"<<endl;
cout<<"4.Delete"<<endl;
cout<<"5.Display"<<endl;
cout<<"6.Count"<<endl;
cout<<"7.Reverse"<<endl;
cout<<"8.Quit"<<endl;
cout<<"Enter your choice : ";
cin>>choice;
switch ( choice )
{
case 1:
cout<<"Enter the element: ";
cin>>element;
dl.create_list(element);
cout<<endl;
break;
case 2:
cout<<"Enter the element: ";
cin>>element;
dl.add_begin(element);
cout<<endl;
break;
case 3:
cout<<"Enter the element: ";
cin>>element;
cout<<"Insert Element after postion: ";
cin>>position;
dl.add_after(element, position);
cout<<endl;
break;
case 4:
if (start == NULL)
{
cout<<"List empty,nothing to delete"<<endl;
break;
}
cout<<"Enter the element for deletion: ";
cin>>element;
dl.delete_element(element);
cout<<endl;
break;
case 5:
dl.display_dlist();
cout<<endl;
break;
case 6:
dl.count();
break;
case 7:
if (start == NULL)
{
cout<<"List empty,nothing to reverse"<<endl;
break;
}
dl.reverse();
cout<<endl;
break;
case 8:
exit(1);
default:
cout<<"Wrong choice"<<endl;
}
}
return 0;
}
/*
* Create Double Link List
*/
void double_llist::create_list(int value)
{
struct node *s, *temp;
temp = new(struct node);
temp->info = value;
temp->next = NULL;
if (start == NULL)
{
temp->prev = NULL;
start = temp;
}
else
{
s = start;
while (s->next != NULL)
s = s->next;
s->next = temp;
temp->prev = s;
}
}
/*
* Insertion at the beginning
*/
void double_llist::add_begin(int value)
{
if (start == NULL)
{
cout<<"First Create the list."<<endl;
return;
}
struct node *temp;
temp = new(struct node);
temp->prev = NULL;
temp->info = value;
temp->next = start;
start->prev = temp;
start = temp;
cout<<"Element Inserted"<<endl;
}
/*
* Insertion of element at a particular position
*/
void double_llist::add_after(int value, int pos)
{
if (start == NULL)
{
cout<<"First Create the list."<<endl;
return;
}
struct node *tmp, *q;
int i;
q = start;
for (i = 0;i < pos - 1;i++)
{
q = q->next;
if (q == NULL)
{
cout<<"There are less than ";
cout<<pos<<" elements."<<endl;
return;
}
}
tmp = new(struct node);
tmp->info = value;
if (q->next == NULL)
{
q->next = tmp;
tmp->next = NULL;
tmp->prev = q;
}
else
{
tmp->next = q->next;
tmp->next->prev = tmp;
q->next = tmp;
tmp->prev = q;
}
cout<<"Element Inserted"<<endl;
}
/*
* Deletion of element from the list
*/
void double_llist::delete_element(int value)
{
struct node *tmp, *q;
/*first element deletion*/
if (start->info == value)
{
tmp = start;
start = start->next;
start->prev = NULL;
cout<<"Element Deleted"<<endl;
free(tmp);
return;
}
q = start;
while (q->next->next != NULL)
{
/*Element deleted in between*/
if (q->next->info == value)
{
tmp = q->next;
q->next = tmp->next;
tmp->next->prev = q;
cout<<"Element Deleted"<<endl;
free(tmp);
return;
}
q = q->next;
}
/*last element deleted*/
if (q->next->info == value)
{
tmp = q->next;
free(tmp);
q->next = NULL;
cout<<"Element Deleted"<<endl;
return;
}
cout<<"Element "<<value<<" not found"<<endl;
}
/*
* Display elements of Doubly Link List
*/
void double_llist::display_dlist()
{
struct node *q;
if (start == NULL)
{
cout<<"List empty,nothing to display"<<endl;
return;
}
q = start;
cout<<"The Doubly Link List is :"<<endl;
while (q != NULL)
{
cout<<q->info<<" <-> ";
q = q->next;
}
cout<<"NULL"<<endl;
}
/*
* Number of elements in Doubly Link List
*/
void double_llist::count()
{
struct node *q = start;
int cnt = 0;
while (q != NULL)
{
q = q->next;
cnt++;
}
cout<<"Number of elements are: "<<cnt<<endl;
}
/*
* Reverse Doubly Link List
*/
void double_llist::reverse()
{
struct node *p1, *p2;
p1 = start;
p2 = p1->next;
p1->next = NULL;
p1->prev = p2;
while (p2 != NULL)
{
p2->prev = p2->next;
p2->next = p1;
p1 = p2;
p2 = p2->prev;
}
start = p1;
cout<<"List Reversed"<<endl;
}
OUTPUT
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 2
Enter the element: 100
First Create the list.
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 3
Enter the element: 200
Insert Element after postion: 1
First Create the list.
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 4
List empty,nothing to delete
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 5
List empty,nothing to display
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 6
Number of elements are: 0
---------------------------------
Operations on Doubly linked list
---------------------------------
1.Create Node
2.Add at begining
3.Add after
4.Delete
5.Display
6.Count
7.Reverse
8.Quit
Enter your choice : 7
List empty,nothing to reverse
In single linked list, every node points to its next node in the sequence and the last node
points NULL. But in circular linked list, every node points to its next node in the sequence
but the last node points to the first node in the list.
Circular linked list is a sequence of elements in which every element has link to its next
element in the sequence and the last element has a link to the first element in the
sequence.
That means circular linked list is similar to the single linked list except that the last node
points to the first node in the list
Example
Operations
1. Insertion
2. Deletion
3. Display
Before we implement actual operations, first we need to setup empty list. First perform the
following steps before implementing actual operations.
Step 1: Include all the header files which are used in the program.
Step 2: Declare all the user defined functions.
Step 3: Define a Node structure with two members data and next
Step 4: Define a Node pointer 'head' and set it to NULL.
Step 4: Implement the main method by displaying operations menu and make
suitable function calls in the main method to perform user selected operation.
Insertion
In a circular linked list, the insertion operation can be performed in three ways. They are as
follows...
We can use the following steps to insert a new node at beginning of the circular linked list...
We can use the following steps to insert a new node at end of the circular linked list...
We can use the following steps to insert a new node after a node in the circular linked list...
Deletion
In a circular linked list, the deletion operation can be performed in three ways those are as
follows...
We can use the following steps to delete a node from beginning of the circular linked list...
We can use the following steps to delete a node from end of the circular linked list...
We can use the following steps to delete a specific node from the circular linked list...
We can use the following steps to display the elements of a circular linked list...
/*
* C++ Program to Implement Circular Linked List
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
/*
* Node Declaration
*/
struct node
{
int info;
struct node *next;
}*last;
/*
* Class Declaration
*/
class circular_llist
{
public:
void create_node(int value);
void add_begin(int value);
void add_after(int value, int position);
void delete_element(int value);
void search_element(int value);
void display_list();
void update();
void sort();
circular_llist()
{
last = NULL;
}
};
/*
* Main :contains menu
*/
int main()
{
int choice, element, position;
circular_llist cl;
while (1)
{
cout<<endl<<"---------------------------"<<endl;
cout<<endl<<"Circular singly linked list"<<endl;
cout<<endl<<"---------------------------"<<endl;
cout<<"1.Create Node"<<endl;
cout<<"2.Add at beginning"<<endl;
cout<<"3.Add after"<<endl;
cout<<"4.Delete"<<endl;
cout<<"5.Search"<<endl;
cout<<"6.Display"<<endl;
cout<<"7.Update"<<endl;
cout<<"8.Sort"<<endl;
cout<<"9.Quit"<<endl;
cout<<"Enter your choice : ";
cin>>choice;
switch(choice)
{
case 1:
cout<<"Enter the element: ";
cin>>element;
cl.create_node(element);
cout<<endl;
break;
case 2:
cout<<"Enter the element: ";
cin>>element;
cl.add_begin(element);
cout<<endl;
break;
case 3:
cout<<"Enter the element: ";
cin>>element;
cout<<"Insert element after position: ";
cin>>position;
cl.add_after(element, position);
cout<<endl;
break;
case 4:
if (last == NULL)
{
cout<<"List is empty, nothing to delete"<<endl;
break;
}
cout<<"Enter the element for deletion: ";
cin>>element;
cl.delete_element(element);
cout<<endl;
break;
case 5:
if (last == NULL)
{
cout<<"List Empty!! Can't search"<<endl;
break;
}
cout<<"Enter the element to be searched: ";
cin>>element;
cl.search_element(element);
cout<<endl;
break;
case 6:
cl.display_list();
break;
case 7:
cl.update();
break;
case 8:
cl.sort();
break;
case 9:
exit(1);
break;
default:
cout<<"Wrong choice"<<endl;
}
}
return 0;
}
/*
* Create Circular Link List
*/
void circular_llist::create_node(int value)
{
struct node *temp;
temp = new(struct node);
temp->info = value;
if (last == NULL)
{
last = temp;
temp->next = last;
}
else
{
temp->next = last->next;
last->next = temp;
last = temp;
}
}
/*
* Insertion of element at beginning
*/
void circular_llist::add_begin(int value)
{
if (last == NULL)
{
cout<<"First Create the list."<<endl;
return;
}
struct node *temp;
temp = new(struct node);
temp->info = value;
temp->next = last->next;
last->next = temp;
}
/*
* Insertion of element at a particular place
*/
void circular_llist::add_after(int value, int pos)
{
if (last == NULL)
{
cout<<"First Create the list."<<endl;
return;
}
struct node *temp, *s;
s = last->next;
for (int i = 0;i < pos-1;i++)
{
s = s->next;
if (s == last->next)
{
cout<<"There are less than ";
cout<<pos<<" in the list"<<endl;
return;
}
}
temp = new(struct node);
temp->next = s->next;
temp->info = value;
s->next = temp;
/*Element inserted at the end*/
if (s == last)
{
last=temp;
}
}
/*
* Deletion of element from the list
*/
void circular_llist::delete_element(int value)
{
struct node *temp, *s;
s = last->next;
/* If List has only one element*/
if (last->next == last && last->info == value)
{
temp = last;
last = NULL;
free(temp);
return;
}
if (s->info == value) /*First Element Deletion*/
{
temp = s;
last->next = s->next;
free(temp);
return;
}
while (s->next != last)
{
/*Deletion of Element in between*/
if (s->next->info == value)
{
temp = s->next;
s->next = temp->next;
free(temp);
cout<<"Element "<<value;
cout<<" deleted from the list"<<endl;
return;
}
s = s->next;
}
/*Deletion of last element*/
if (s->next->info == value)
{
temp = s->next;
s->next = last->next;
free(temp);
last = s;
return;
}
cout<<"Element "<<value<<" not found in the list"<<endl;
}
/*
* Search element in the list
*/
void circular_llist::search_element(int value)
{
struct node *s;
int counter = 0;
s = last->next;
while (s != last)
{
counter++;
if (s->info == value)
{
cout<<"Element "<<value;
cout<<" found at position "<<counter<<endl;
return;
}
s = s->next;
}
if (s->info == value)
{
counter++;
cout<<"Element "<<value;
cout<<" found at position "<<counter<<endl;
return;
}
cout<<"Element "<<value<<" not found in the list"<<endl;
}
/*
* Display Circular Link List
*/
void circular_llist::display_list()
{
struct node *s;
if (last == NULL)
{
cout<<"List is empty, nothing to display"<<endl;
return;
}
s = last->next;
cout<<"Circular Link List: "<<endl;
while (s != last)
{
cout<<s->info<<"->";
s = s->next;
}
cout<<s->info<<endl;
}
/*
* Update Circular Link List
*/
void circular_llist::update()
{
int value, pos, i;
if (last == NULL)
{
cout<<"List is empty, nothing to update"<<endl;
return;
}
cout<<"Enter the node position to be updated: ";
cin>>pos;
cout<<"Enter the new value: ";
cin>>value;
struct node *s;
s = last->next;
for (i = 0;i < pos - 1;i++)
{
if (s == last)
{
cout<<"There are less than "<<pos<<" elements.";
cout<<endl;
return;
}
s = s->next;
}
s->info = value;
cout<<"Node Updated"<<endl;
}
/*
* Sort Circular Link List
*/
void circular_llist::sort()
{
struct node *s, *ptr;
int temp;
if (last == NULL)
{
cout<<"List is empty, nothing to sort"<<endl;
return;
}
s = last->next;
while (s != last)
{
ptr = s->next;
while (ptr != last->next)
{
if (ptr != last->next)
{
if (s->info > ptr->info)
{
temp = s->info;
s->info = ptr->info;
ptr->info = temp;
}
}
else
{
break;
}
ptr = ptr->next;
}
s = s->next;
}
}
OUTPUT
---------------------------------
Operations on Circular singly linked list
---------------------------------
1.Create Node
2.Add at beginning
3.Add after
4.Delete
5.Search
6.Display
7.Update
8.Sort
9.Quit
Enter your choice : 1
Enter the element: 100
---------------------------------
Operations on Circular singly linked list
---------------------------------
1.Create Node
2.Add at beginning
3.Add after
4.Delete
5.Search
6.Display
7.Update
8.Sort
9.Quit
Enter your choice : 2
Enter the element: 200
---------------------------------
Operations on Circular singly linked list
---------------------------------
1.Create Node
2.Add at beginning
3.Add after
4.Delete
5.Search
6.Display
7.Update
8.Sort
9.Quit
Enter your choice : 6
Circular Link List:
200->100
2.15.Sparse Matrix
In computer programming, a matrix can be defined with a 2-dimensional array. Any array
with 'm' columns and 'n' rows represents a mXn matrix. There may be a situation in which a
matrix contains more number of ZERO values than NON-ZERO values. Such matrix is known
as sparse matrix.
When a sparse matrix is represented with 2-dimensional array, we waste lot of space to
represent that matrix. For example, consider a matrix of size 100 X 100 containing only 10
non-zero elements. In this matrix, only 10 spaces are filled with non-zero values and
remaining spaces of matrix are filled with zero. That means, totally we allocate 100 X 100 X
2 = 20000 bytes of space to store this integer matrix. And to access these 10 non-zero
elements we have to make scanning for 10000 times.
A sparse matrix can be represented by using TWO representations, those are as follows...
1. Triplet Representation
2. Linked Representation
Triplet Representation
In this representation, we consider only non-zero values along with their row and column
index values. In this representation, the 0th row stores total rows, total columns and total
non-zero values in the matrix.
For example, consider a matrix of size 5 X 6 containing 6 number of non-zero values. This
matrix can be represented as shown in the image...
In above example matrix, there are only 6 non-zero elements ( those are 9, 8, 4, 2, 5 & 2)
and matrix size is 5 X 6. We represent this matrix as shown in the above image. Here the
first row in the right side table is filled with values 5, 6 & 6 which indicates that it is a sparse
matrix with 5 rows, 6 columns & 6 non-zero values. Second row is filled with 0, 4, & 9 which
indicates the value in the matrix at 0th row, 4th column is 9. In the same way the remaining
non-zero values also follows the similar pattern.
In linked representation, we use linked list data structure to represent a sparse matrix. In
this linked list, we use two different nodes namely header node and element node. Header
node consists of three fields and element node consists of five fields as shown in the
image...
Consider the above same sparse matrix used in the Triplet representation. This sparse
matrix can be represented using linked representation as shown in the below image...
In above representation, H0, H1,...,H5 indicates the header nodes which are used to
represent indexes. Remaining nodes are used to represent non-zero elements in the matrix,
except the very first node which is used to represent abstract information of the sparse
matrix (i.e., It is a matrix of 5 X 6 with 6 non-zero elements).
In this representation, in each row and column, the last node right field points to it's
respective header node.
/*
* Class List Declaration
*/
class List
{
private:
int index;
int value;
List *nextindex;
public:
List(int index)
{
this->index = index;
nextindex = NULL;
value = NULL;
}
List()
{
index = -1;
value = NULL;
nextindex = NULL;
}
void store(int index, int value)
{
List *current = this;
List *previous = NULL;
List *node = new List(index);
node->value = value;
while (current != NULL && current->index < index)
{
previous = current;
current = current->nextindex;
}
if (current == NULL)
{
previous->nextindex = node;
}
else
{
if (current->index == index)
{
cout<<"DUPLICATE INDEX"<<endl;
return;
}
previous->nextindex = node;
node->nextindex = current;
}
return;
}
int elementCount()
{
int elementCount = 0;
List *current = this->nextindex;
for ( ; (current != NULL); current = current->nextindex)
{
elementCount++;
}
return elementCount;
}
};
/*
* Class SpareArray Declaration
*/
class SparseArray
{
private:
List *start;
int index;
public:
SparseArray(int index)
{
start = new List();
this->index = index;
}
void store(int index, int value)
{
if (index >= 0 && index < this->index)
{
if (value != NULL)
start->store(index, value);
}
else
{
cout<<"INDEX OUT OF BOUNDS"<<endl;
}
}
int fetch(int index)
{
if (index >= 0 && index < this->index)
return start->fetch(index);
else
{
cout<<"INDEX OUT OF BOUNDS"<<endl;
return NULL;
}
}
int elementCount()
{
return start->elementCount();
}
};
/*
* Class SparseMatrix Declaration
*/
class SparseMatrix
{
private:
int N;
SparseArray **sparsearray;
public:
SparseMatrix(int N)
{
this->N = N;
sparsearray = new SparseArray* [N];
for (int index = 0; index < N; index++)
{
sparsearray[index] = new SparseArray(N);
}
}
void store(int rowindex, int colindex, int value)
{
if (rowindex < 0 || rowindex > N)
{
cout<<"row index out of bounds"<<endl;
return;
}
if (colindex < 0 || colindex > N)
{
cout<<"col index out of bounds"<<endl;
return;
}
sparsearray[rowindex]->store(colindex, value);
}
OUTPUT
the sparse Matrix is:
1 NULL 2
NULL 3 NULL
4 6 NULL
The Size of Sparse Matrix is 5
The major problem with the stack implemented using array is, it works only for fixed
number of data values. That means the amount of data must be specified at the beginning
of the implementation itself. Stack implemented using 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 linked list data structure. The stack implemented using linked list can work for
unlimited number of values. That means, stack implemented using linked list works for
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 next node in the list. The next field of the first element must be always NULL.
Example
In 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.
Operations
To implement stack 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 a Node pointer 'top' and set it to NULL.
Step 4: Implement the main method by displaying Menu with list of operations and
make suitable function calls in the main method.
push(value) - Inserting an element into the Stack
We can use the following steps to insert a new node into the stack...
We can use the following steps to delete a node from the stack...
We can use the following steps to display the elements (nodes) of a stack...
#include<iostream>
struct node{
int data;
node* next;
};
node*tail;
if (head==NULL)
n->data=data;
n->next=NULL;
head=n;
tail=n;
else if (head!=NULL)
n->data=data;
n->next=head;
head=n;
temp=head;
if (temp==NULL)
}
else
while(temp!=NULL)
cout<<temp->data<<endl;
temp=temp->next;
if (head==NULL)
else if (head==tail)
delete head;
head=NULL;
tail=NULL;
else
delptr=head;
head=head->next;
cout<<"The value "<<delptr->data<<" was popped"<<endl;
delete delptr;
temp=head;
if (temp==NULL)
else
temp=head;
int count=0;
if (temp==NULL)
}
else
while(temp!=NULL)
count++;
temp=temp->next;
//Destroy stack
if (head==NULL)
else
while(head!=NULL)
delptr=head;
head=head->next;
delete delptr;
}
}
//Menu function
char menu()
char choice;
cout<<"Menu: \n";
cout<<"7. Exit."<<endl;
cin>>choice;
return choice;
//Main function
int main()
node* head;
node* tail;
head=NULL;
tail=NULL;
char choice;
do{
cout<<"-----------------------------------------------------"<<endl;
choice=menu();
cout<<"-----------------------------------------------------"<<endl;
switch(choice)
case '1':
int data;
cin>>data;
push(head,tail,data);
break;
case '2':
pop(head,tail);
break;
case '3':
showdata(head);
break;
case '4':
top(head);
break;
case '5':
sizeofstack(head);
break;
case '6':
destroystack(head);
break;
default:
cout<<"Exiting Menu";
}
}while(choice!='7');
return 0;
The major problem with the queue implemented using array is, It will work for only fixed
number of data. That means, the amount of data must be specified in the beginning itself.
Queue using 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 linked list data structure. The
queue which is implemented using linked list can work for unlimited number of values. That
means, queue using linked list can work for variable size of data (No need to fix the size at
beginning of the implementation). The Queue implemented using linked list can organize as
many data values as we want.
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'.
Example
In above example, the last inserted node is 50 and it is pointed by 'rear' and the first
inserted node is 10 and it is pointed by 'front'. The order of elements inserted is 10, 15, 22
and 50.
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.
We can use the following steps to insert a new node into the queue...
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.
We can use the following steps to delete a node from the queue...
We can use the following steps to display the elements (nodes) of a queue...
A generalized linked list contains structures or elements with every one containing its own
pointer. It's generalized if the list can have any deletions, insertions, and similar inserted
effectively into it.
In example 3, the list C has three components: the first component is list B, the second
component is again list B, and the third component is list A. This can be pictorially viewed
as in Fig. 6.54.
void LengthTest()
{
Llist myList;
mylist.Create();
int len = mylist.Length();
}
6.14.3 Reversing singly Linked List Without temporary storage
The procedure for reversing a singly linked list without temporary storage is illustrated
UNIT III
In linear data structure, data is organized in sequential order and in non-linear data
structure, data is organized in random order. Tree is a very popular data structure used in
wide range of applications. A tree data structure can be defined as follows...
Tree is a non-linear data structure which organizes data in hierarchical structure and this
is a recursive definition.
In tree data structure, every individual element is called as Node. Node in a tree data
structure, stores the actual data of that particular element and link to next element in
hierarchical structure.
In a tree data structure, if we have N number of nodes then we can have a maximum of N-
1 number of links.
Example
3.1. Terminology
In a tree data structure, the first node is called as Root Node. Every tree must have root
node. We can say that root node is the origin of tree data structure. In any tree, there must
be only one root node. We never have multiple root nodes in a tree.
2. Edge
In a tree data structure, the connecting link between any two nodes is called as EDGE. In a
tree with 'N' number of nodes there will be a maximum of 'N-1' number of edges.
3. Parent
In a tree data structure, the node which is predecessor of any node is called as PARENT
NODE. In simple words, the node which has branch from it to any other node is called as
parent node. Parent node can also be defined as "The node which has child / children".
4. Child
In a tree data structure, the node which is descendant of any node is called as CHILD Node.
In simple words, the node which has a link from its parent node is called as child node. In a
tree, any parent node can have any number of child nodes. In a tree, all the nodes except
root are child nodes.
5. Siblings
In a tree data structure, nodes which belong to same Parent are called as SIBLINGS. In
simple words, the nodes with same parent are called as Sibling nodes.
6. Leaf
In a tree data structure, the node which does not have a child is called as LEAF Node. In
simple words, a leaf is a node with no child.
In a tree data structure, the leaf nodes are also called as External Nodes. External node is
also a node with no child. In a tree, leaf node is also called as 'Terminal' node.
7. Internal Nodes
In a tree data structure, the node which has atleast one child is called as INTERNAL Node. In
simple words, an internal node is a node with atleast one child.
In a tree data structure, nodes other than leaf nodes are called as Internal Nodes. The root
node is also said to be Internal Node if the tree has more than one node. Internal nodes are
also called as 'Non-Terminal' nodes.
8. Degree
In a tree data structure, the total number of children of a node is called as DEGREE of that
Node. In simple words, the Degree of a node is total number of children it has. The highest
degree of a node among all the nodes in a tree is called as 'Degree of Tree'
9. Level
In a tree data structure, the root node is said to be at Level 0 and the children of root node
are at Level 1 and the children of the nodes which are at Level 1 will be at Level 2 and so
on... In simple words, in a tree each step from top to bottom is called as a Level and the
Level count starts with '0' and incremented by one at each level (Step).
10. Height
In a tree data structure, the total number of egdes from leaf node to a particular node in the
longest path is called as HEIGHT of that Node. In a tree, height of the root node is said to
be height of the tree. In a tree, height of all leaf nodes is '0'.
11. Depth
In a tree data structure, the total number of egdes from root node to a particular node is
called as DEPTH of that Node. In a tree, the total number of edges from root node to a leaf
node in the longest path is said to be Depth of the tree. In simple words, the highest depth
of any leaf node in a tree is said to be depth of that tree. In a tree, depth of the root node is
'0'.
12. Path
In a tree data structure, the sequence of Nodes and Edges from one node to another node is
called as PATH between that two Nodes. Length of a Path is total number of nodes in that
path. In below example the path A - B - E - J has length 4.
In a tree data structure, each child from a node forms a subtree recursively. Every child node
will form a subtree on its parent node.
A tree data structure can be represented in two methods. Those methods are as follows...
1. List Representation
In this representation, we use two types of nodes one for representing the node with data
and another for representing only references. We start with a node with data from root
node in the tree. Then it is linked to an internal node through a reference node and is linked
to any other node directly. This process repeats for all the nodes in the tree.
The above tree example can be represented using List representation as follows...
In this representation, we use list with one type of node which consists of three fields
namely Data field, Left child reference field and Right sibling reference field. Data field
stores the actual value of a node, left reference field stores the address of the left child and
right reference field stores the address of the right sibling node. Graphical representation of
that node is as follows...
In this representation, every node's data field stores the actual value of that node. If that
node has left child, then left reference field stores the address of that left child node
otherwise that field stores NULL. If that node has right sibling then right reference field
stores the address of right sibling node otherwise that field stores NULL.
The above tree example can be represented using Left Child - Right Sibling representation as
follows...
Rooted tree Unlike free tree, a rooted tree is a directed graph where one node is designated
as root, whose incoming degree is zero, whereas for all other nodes, the incoming
degree is one.
Ordered tree In many applications, the relative order of the nodes at any particular level
assumes some significance. It is easy to impose an order on the nodes at a level by referring
to a particular node as the first node, to another node as the second, and so on. Such
ordering can be done from left to right . Just like nodes at each level, we can prescribe order
to edges. If in a directed tree, an ordering of a node at each level is prescribed, then such a
tree is called an ordered tree.
Regular tree A tree where each branch node vertex has the same outdegree is called a
regular tree. If in a directed tree, the outdegree of every node is less than or equal to m,
then the tree is called an m-ary tree. If the outdegree of every node is exactly equal to m
(the branch nodes) or zero (the leaf nodes), then the tree is called a regular m-ary tree.
Binary tree A binary tree is a special form of an m-ary tree. Since a binary tree is
important, it is frequently used in various applications of computer science.
We have defined an m-ary tree (general tree). A binary tree is an m-ary position tree
when m =2. In a binary tree, no node has more than two children.
Complete tree A tree with n nodes and of depth k is complete if and only if its nodes
correspond to the nodes that are numbered from 1 to n in the full tree of depth k.
A binary tree of height h is complete if and only if one of the following holds good:
1. It is empty.
2. Its left subtree is complete of height h 1 and its right subtree is completely full of
height h =2.
3. Its left subtree is completely full of height h 1 and its right subtree is complete of
height h =1.
A binary tree is completely full if it is of height h and has (2h+1 -1) nodes.
Full binary tree A binary tree is a full binary tree if it contains the maximum possible
number of nodes in all levels. Figure 7.15 shows a full binary tree of height 2.
In a full binary tree, each node has two children or no child at all. The total number of nodes
in a full binary tree of heighth is 2h+1-1 considering the root at level 0.
It can be calculated by adding the number of nodes of each level as in the following
equation: 20 <21 <22 <... <2h <2h+1 -1
Complete binary tree A binary tree is said to be a complete binary tree if all its levels
except the last level have the maximum number of possible nodes, and all the nodes of the
last level appear as far left as possible. In a complete binary tree, all the leaf nodes are at
the last and the second last level, and the levels are filled from left to right.
Left skewed binary tree If the right subtree is missing in every node of a tree, we call it
a left skewed tree . If the left subtree is missing in every node of a tree, we call it as right
subtree.
Strictly binary tree If every non-terminal node in a binary tree consists of non-empty left
and right subtrees, then such a tree is called a strictly binary tree.
In Fig. 7.19, the non-empty nodes D and E have left and right subtrees. Such expression
trees are known as strictly binary trees.
Extended binary tree A binary tree T with each node having zero or two children is called
an extended binary tree. The nodes with two children are called internal nodes, and those
with zero children are called external nodes. Trees can be converted into extended trees by
adding a node.
Position tree A position tree, also known as a suffix tree, is one that represents the suffixes
of a string S and such representation facilitates string operations being performed faster.
Such a tree’s edges are labelled with strings, such that each suffix of S corresponds to
exactly one path from the tree’s root to a leaf node. The space and time requirement is
linear in the length of S. After its construction, several operations can be performed quickly,
such as locating a substring in S, locating a substring if a certain number of mistakes are
allowed, locating matches for a regular expression pattern, and so
In a normal tree, every node can have any number of children. Binary tree is a special type
of tree data structure in which every node can have a maximum of 2 children. One is known
as left child and the other is known as right child.
A tree in which every node can have a maximum of two children is called as Binary Tree.
In a binary tree, every node can have either 0 children or 1 child or 2 children but not more
than 2 children.
Example
In a binary tree, every node can have a maximum of two children. But in strictly binary tree,
every node should have exactly two children or none. That means every internal node must
have exactly two children. A strictly Binary Tree can be defined as follows...
A binary tree in which every node has either two or zero number of children is called
Strictly Binary Tree
Strictly binary tree is also called as Full Binary Tree or Proper Binary Tree or 2-Tree
Strictly binary tree data structure is used to represent mathematical expressions.
Example
In a binary tree, every node can have a maximum of two children. But in strictly binary tree,
every node should have exactly two children or none and in complete binary tree all the
nodes must have exactly two children and at every level of complete binary tree there must
be 2level number of nodes. For example at level 2 there must be 2 2 = 4 nodes and at level 3
there must be 23 = 8 nodes.
A binary tree in which every internal node has exactly two children and all leaf nodes are
at same level is called Complete Binary Tree.
A binary tree can be converted into Full Binary tree by adding dummy nodes to existing
nodes wherever required.
The full binary tree obtained by adding dummy nodes to a binary tree is called as
Extended Binary Tree.
In above figure, a normal binary tree is converted into full binary tree by adding dummy
nodes (In pink colour).
A binary tree consists of nodes with one predecessor and at most two successors (called the
left child and the right child). The only exception is the root node of the tree that does not
have a predecessor. A node can contain any amount and any type of data.
create - A binary tree may be created in several states. The most common include ...
o an empty tree (i.e. no nodes) and hence the constructor will have no parameters
o a tree with one node (i.e. the root) and hence the constructor will have a single
parameter (the data for the root node)
o a tree with a new root whose children are other existing trees and hence the
constructor will have three parameters (the data for the root node and references to
its subtrees)
isEmpty() - Returns true if there are no nodes in the tree, false otherwise.
o isFull() - May be required by some implementations. Returns true if the tree is full,
false otherwise.
clear() - Removes all of the nodes from the tree (essentially reinitializing it to a new
empty tree).
add(value) - Adds a new node to the tree with the given value. The actual
implementation of this method is determined by the purpose for the tree and how the
tree is to be maintained and/or processed. To begin with, we will assume no particular
purpose or order and therefore add new nodes in such a way that the tree will remain
nearly balanced.
remove() - Removes the root node of the tree and returns its data. The actual
implementation of this method and other forms of removal will be determined by the
purpose for the tree and how the tree is to be maintained and/or processed. For
example, you might need a remove() method with a parameter that indicates which
node of the tree is to be removed (based on position, key data, or reference). To begin
with, we will assume no particular purpose or order and therefore remove the root in
such a way that the tree will remain nearly balanced.
height() - Determines the height of the tree. An empty tree will have height 0.
size() - Determines the number of nodes in the tree.
getRootData() - Returns the data (primative data) or reference to the data (objects) of
the tree's root.
getLeftSubtree() - Returns a reference to the left subtree of this tree.
getRightSubtree() - Returns a reference to the right subtree of this tree.
A binary tree data structure is represented using two methods. Those methods are as
follows...
1. Array Representation
1. Array Representation
In array representation of binary tree, we use a one dimensional array (1-D Array) to
represent a binary tree.
Consider the above example of binary tree and it is represented as follows...
To represent a binary tree of depth 'n' using array representation, we need one dimensional
array with a maximum size of 2n+1 - 1.
Advantages The various merits of representing binary trees using arrays are as follows:
1. Any node can be accessed from any other node by calculating the index.
2. Here, the data is stored without any pointers to its successor or predecessor.
3. In the programming languages, where dynamic memory allocation is not possible
(such as BASIC, fortran ), array representation is the only means to store a tree.
Disadvantages The various demerits when representing binary trees using arrays are
as follows:
1. Other than full binary trees, majority of the array entries may be empty.
2. It allows only static representation. The array size cannot be changed during the
execution.
3. Inserting a new node to it or deleting a node from it is inefficient with this representation,
because it requires considerable data movement up and down the array, which demand
excessive amount of processing time.
We use double linked list to represent a binary tree. In a double linked list, every node
consists of three fields. First field for storing left child address, second for storing actual data
and third for storing right child address.
In this linked list representation, a node has the following structure...
The above example of binary tree represented using Linked list representation is shown as
follows...
// C++ program to create a Complete Binary tree from its Linked List
// Representation
#include <iostream>
#include <string>
#include <queue>
using namespace std;
// Linked list node
struct ListNode
{
int data;
ListNode* next;
};
// method to create a new binary tree node from the given data
BinaryTreeNode* newBinaryTreeNode(int data)
{
BinaryTreeNode *temp = new BinaryTreeNode;
temp->data = data;
temp->left = temp->right = NULL;
return temp;
}
// converts a given linked list representing a complete binary tree into the
// linked representation of binary tree.
void convertList2Binary(ListNode *head, BinaryTreeNode* &root)
{
// queue to store the parent nodes
queue<BinaryTreeNode *> q;
// Base Case
if (head == NULL)
{
root = NULL; // Note that root is passed by reference
return;
}
// 1.) The first node is always the root node, and add it to the queue
root = newBinaryTreeNode(head->data);
q.push(root);
// 2.c) take next two nodes from the linked list. We will add
// them as children of the current parent node in step 2.b. Push them
// into the queue so that they will be parents to the future nodes
BinaryTreeNode *leftChild = NULL, *rightChild = NULL;
leftChild = newBinaryTreeNode(head->data);
q.push(leftChild);
head = head->next;
if (head)
{
rightChild = newBinaryTreeNode(head->data);
q.push(rightChild);
head = head->next;
}
BinaryTreeNode *root;
convertList2Binary(head, root);
cout << "Inorder Traversal of the constructed Binary Tree is: \n";
inorderTraversal(root);
return 0;
}
Run on IDE
Output:
Inorder Traversal of the constructed Binary Tree is:
25 12 30 10 36 15
3.7. Insert ion of a node in Binary tree
The insert() operation inserts a new node at any position in a binary tree. The node to
be inserted could be a branch node or a leaf node. The branch node insertion is generally
based on some criteria that are usually in the context of a special form of a binary tree.
Let us study a commonly used case of inserting a node as a leaf node.
The insertion procedure is a two-step process.
1. Search for the node whose child node is to be inserted. This is a node at some level i,
and a node is to be inserted at the level i 1 as either its left child or right child. This
is the node after which the insertion is to be made.
2. Link a new node to the node that becomes its parent node, that is, either the Lchild or
the Rchild.
3.8.Binary Tree Traversals
When we wanted to display a binary tree, we need to follow some order in which all the
nodes of that binary tree must be displayed. In any binary tree displaying order of nodes
depends on the traversal method.
Displaying (or) visiting order of nodes in a binary tree is called as Binary Tree Traversal.
1. In - Order Traversal
In In-Order traversal, the root node is visited between left child and right child. In this
traversal, the left child node is visited first, then the root node is visited and later we go for
visiting right child node. This in-order traversal is applicable for every root node of all
subtrees in the tree. This is performed recursively for all nodes in the tree.
In the above example of binary tree, first we try to visit left child of root node 'A', but A's left
child is a root node for left subtree. so we try to visit its (B's) left child 'D' and again D is a
root for subtree with nodes D, I and J. So we try to visit its left child 'I' and it is the left most
child. So first we visit 'I' then go for its root node 'D' and later we visit D's right child 'J'. With
this we have completed the left part of node B. Then visit 'B' and next B's right child 'F' is
visited. With this we have completed left part of node A. Then visit root node 'A'. With this
we have completed left and root parts of node A. Then we go for right part of the node A. In
right of A again there is a subtree with root C. So go for left child of C and again it is a
subtree with root G. But G does not have left part so we visit 'G' and then visit G's right child
K. With this we have completed the left part of node C. Then visit root node 'C' and next visit
C's right child 'H' which is the right most child in the tree so we stop the process.
That means here we have visited in the order of I - D - J - B - F - A - G - K - C - H using In-
Order Traversal.
Inorder Traversal:
Algorithm Inorder(tree)
1. Traverse the left subtree, i.e., call Inorder(left-subtree)
2. Visit the root.
3. Traverse the right subtree, i.e., call Inorder(right-subtree)
In-Order Traversal for above example of binary tree is
I-D-J-B-F-A-G-K-C-H
In Pre-Order traversal, the root node is visited before left child and right child nodes. In this
traversal, the root node is visited first, then its left child and later its right child. This pre-
order traversal is applicable for every root node of all subtrees in the tree.
In the above example of binary tree, first we visit root node 'A' then visit its left
child 'B' which is a root for D and F. So we visit B's left child 'D' and again D is a root for I and
J. So we visit D's left child 'I' which is the left most child. So next we go for visiting D's right
child 'J'. With this we have completed root, left and right parts of node D and root, left parts
of node B. Next visit B's right child 'F'. With this we have completed root and left parts of
node A. So we go for A's right child 'C' which is a root node for G and H. After visiting C, we
go for its left child 'G' which is a root for node K. So next we visit left of G, but it does not
have left child so we go for G's right child 'K'. With this we have completed node C's root
and left parts. Next visit C's right child 'H' which is the right most child in the tree. So we
stop the process.
That means here we have visited in the order of A-B-D-I-J-F-C-G-K-H using Pre-Order
Traversal.
Algorithm Preorder(tree)
1. Visit the root.
2. Traverse the left subtree, i.e., call Preorder(left-subtree)
3. Traverse the right subtree, i.e., call Preorder(right-subtree)
Pre-Order Traversal for above example of binary tree is
A-B-D-I-J-F-C-G-K-H
3.8.3. Post - Order Traversal ( leftChild - rightChild - root )
In Post-Order traversal, the root node is visited after left child and right child. In this
traversal, left child node is visited first, then its right child and then its root node. This is
recursively performed until the right most node is visited.
Postorder Traversal:
Algorithm Postorder(tree)
1. Traverse the left subtree, i.e., call Postorder(left-subtree)
2. Traverse the right subtree, i.e., call Postorder(right-subtree)
3. Visit the root.
Post-Order Traversal for above example of binary tree is
I-J-D-F-B-K-G-H-C-A
Depth First Search (DFS) algorithm traverses a graph in a depthward motion and uses a
stack to remember to get the next vertex to start a search, when a dead end occurs in any
iteration.
As in the example given above, DFS algorithm traverses from A to B to C to D first then to E,
then to F and lastly to G. It employs the following rules.
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Push it in a
stack.
Rule 2 − If no adjacent vertex is found, pop up a vertex from the stack. (It will pop up
all the vertices from the stack, which do not have adjacent vertices.)
Rule 3 − Repeat Rule 1 and Rule 2 until the stack is empty.
1.
7.
As C does not have any unvisited adjacent node so we keep popping the stack until we find
a node that has an unvisited adjacent node. In this case, there's none and we keep popping
until the stack is empty.
#include<iostream>
#include<conio.h>
#include<stdlib.h>
using namespace std;
int cost[10][10],i,j,k,n,stk[10],top,v,visit[10],visited[10];
main()
{
int m;
cout <<"enterno of vertices";
cin >> n;
cout <<"ente no of edges";
cin >> m;
cout <<"\nEDGES \n";
for(k=1;k<=m;k++)
{
cin >>i>>j;
cost[i][j]=1;
}
OUTPUT
enterno of vertices9
ente no of edges9
EDGES
12
23
26
15
14
47
57
78
89
enter initial vertex1
ORDER OF VISITED VERTICES1 2 3 6 4 7 8 9 5
Breadth First Search (BFS) algorithm traverses a graph in a breadthward motion and uses a
queue to remember to get the next vertex to start a search, when a dead end occurs in any
iteration.
As in the example given above, BFS algorithm traverses from A to B to E to F first then to C
and G lastly to D. It employs the following rules.
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Insert it in a
queue.
Rule 2 − If no adjacent vertex is found, remove the first vertex from the queue.
1.
2.
4.
5.
6.
7.
At this stage, we are left with no unmarked (unvisited) nodes. But as per the algorithm we
keep on dequeuing in order to get all unvisited nodes. When the queue gets emptied, the
program is over.
#include<iostream>
#include<conio.h>
#include<stdlib.h>
using namespace std;
int cost[10][10],i,j,k,n,qu[10],front,rare,v,visit[10],visited[10];
main()
{
int m;
cout <<"enterno of vertices";
cin >> n;
cout <<"ente no of edges";
cin >> m;
cout <<"\nEDGES \n";
for(k=1;k<=m;k++)
{
cin >>i>>j;
cost[i][j]=1;
}
OUTPUT
enterno of vertices9
ente no of edges9
EDGES
12
23
15
14
47
78
89
26
57
enter initial vertex1
Visited vertices
12 4 5 3 6 7 8 9
3.11. Other Tree Operations
Using traversal as a basic operation, many other operations can be performed on a tree,
such as fi nding the height of the tree, computing the total number of nodes, leaf nodes,
and so on. Let us study a few of such operations.
The left sub-tree of a node has a key less than or equal to its parent node's key.
The right sub-tree of a node has a key greater than or equal to its parent node's key.
Thus, BST divides all its sub-trees into two segments; the left sub-tree and the right sub-
tree and can be defined as −
3.12.1.Representation
BST is a collection of nodes arranged in a way where they maintain BST properties. Each
node has a key and an associated value. While searching, the desired key is compared to
the keys in BST and if found, the associated value is retrieved.
We observe that the root node key (27) has all less-valued keys on the left sub-tree and the
higher valued keys on the right sub-tree.
In a binary search tree, all the nodes in left subtree of any node contains smaller values and
all the nodes in right subtree of that contains larger values as shown in following figure...
Example
The following tree is a Binary Search Tree. In this tree, left subtree of every node contains
nodes with smaller values and right subtree of every node contains larger values.
Every Binary Search Tree is a binary tree but all the Binary Trees need not to be binary
search trees.
1. Search
2. Insertion
3. Deletion
In a binary search tree, the search operation is performed with O(log n) time complexity.
The search operation is performed as follows...
Algorithm
struct node* search(int data){
struct node *current = root;
printf("Visiting elements: ");
while(current->data != data){
if(current != NULL) {
printf("%d ",current->data);
//go to left tree
if(current->data > data){
current = current->leftChild;
}//else go to right tree
else {
current = current->rightChild;
}
//not found
if(current == NULL){
return NULL;
}
}
}
return current;
}
3.12.2.2. Insertion Operation in BST
In a binary search tree, the insertion operation is performed with O(log n) time complexity.
In binary search tree, new node is always inserted as a leaf node. The insertion operation is
performed as follows...
Step 1: Create a newNode with given value and set its left and right to NULL.
Step 2: Check whether tree is Empty.
Step 3: If the tree is Empty, then set set root to newNode.
Step 4: If the tree is Not Empty, then check whether value of newNode
is smaller or larger than the node (here it is root node).
Step 5: If newNode is smaller than or equal to the node, then move to its left child. If
newNode is larger than the node, then move to its right child.
Step 6: Repeat the above step until we reach to a leaf node (e.i., reach to NULL).
Step 7: After reaching a leaf node, then isert the newNode as left child if newNode
is smaller or equal to that leaf else insert it as right child.
void insert(int data) {
struct node *tempNode = (struct node*) malloc(sizeof(struct node));
struct node *current;
struct node *parent;
tempNode->data = data;
tempNode->leftChild = NULL;
tempNode->rightChild = NULL;
while(1) {
parent = current;
if(current == NULL) {
parent->leftChild = tempNode;
return;
}
}//go to right of the tree
else {
current = current->rightChild;
//insert to the right
if(current == NULL) {
parent->rightChild = tempNode;
return;
}
}
}
}
}
In a binary search tree, the deletion operation is performed with O(log n) time complexity.
Deleting a node from Binary search tree has follwing three cases...
We use the following steps to delete a node with one child from BST...
We use the following steps to delete a node with two children from BST...
Example
10,12,5,4,20,8,7,15 and 13
/*
* C++ Program To Implement BST
*/
# include <iostream>
# include <cstdlib>
using namespace std;
/*
* Node Declaration
*/
struct node
{
int info;
struct node *left;
struct node *right;
}*root;
/*
* Class Declaration
*/
class BST
{
public:
void find(int, node **, node **);
void insert(int);
void del(int);
void case_a(node *,node *);
void case_b(node *,node *);
void case_c(node *,node *);
void preorder(node *);
void inorder(node *);
void postorder(node *);
void display(node *, int);
BST()
{
root = NULL;
}
};
/*
* Main Contains Menu
*/
int main()
{
int choice, num;
BST bst;
node *temp;
while (1)
{
cout<<"-----------------"<<endl;
cout<<"Operations on BST"<<endl;
cout<<"-----------------"<<endl;
cout<<"1.Insert Element "<<endl;
cout<<"2.Delete Element "<<endl;
cout<<"3.Inorder Traversal"<<endl;
cout<<"4.Preorder Traversal"<<endl;
cout<<"5.Postorder Traversal"<<endl;
cout<<"6.Display"<<endl;
cout<<"7.Quit"<<endl;
cout<<"Enter your choice : ";
cin>>choice;
switch(choice)
{
case 1:
temp = new node;
cout<<"Enter the number to be inserted : ";
cin>>temp->info;
bst.insert(root, temp);
case 2:
if (root == NULL)
{
cout<<"Tree is empty, nothing to delete"<<endl;
continue;
}
cout<<"Enter the number to be deleted : ";
cin>>num;
bst.del(num);
break;
case 3:
cout<<"Inorder Traversal of BST:"<<endl;
bst.inorder(root);
cout<<endl;
break;
case 4:
cout<<"Preorder Traversal of BST:"<<endl;
bst.preorder(root);
cout<<endl;
break;
case 5:
cout<<"Postorder Traversal of BST:"<<endl;
bst.postorder(root);
cout<<endl;
break;
case 6:
cout<<"Display BST:"<<endl;
bst.display(root,1);
cout<<endl;
break;
case 7:
exit(1);
default:
cout<<"Wrong choice"<<endl;
}
}
}
/*
* Find Element in the Tree
*/
void BST::find(int item, node **par, node **loc)
{
node *ptr, *ptrsave;
if (root == NULL)
{
*loc = NULL;
*par = NULL;
return;
}
if (item == root->info)
{
*loc = root;
*par = NULL;
return;
}
if (item < root->info)
ptr = root->left;
else
ptr = root->right;
ptrsave = root;
while (ptr != NULL)
{
if (item == ptr->info)
{
*loc = ptr;
*par = ptrsave;
return;
}
ptrsave = ptr;
if (item < ptr->info)
ptr = ptr->left;
else
ptr = ptr->right;
}
*loc = NULL;
*par = ptrsave;
}
/*
* Inserting Element into the Tree
*/
void BST::insert(node *tree, node *newnode)
{
if (root == NULL)
{
root = new node;
root->info = newnode->info;
root->left = NULL;
root->right = NULL;
cout<<"Root Node is Added"<<endl;
return;
}
if (tree->info == newnode->info)
{
cout<<"Element already in the tree"<<endl;
return;
}
if (tree->info > newnode->info)
{
if (tree->left != NULL)
{
insert(tree->left, newnode);
}
else
{
tree->left = newnode;
(tree->left)->left = NULL;
(tree->left)->right = NULL;
cout<<"Node Added To Left"<<endl;
return;
}
}
else
{
if (tree->right != NULL)
{
insert(tree->right, newnode);
}
else
{
tree->right = newnode;
(tree->right)->left = NULL;
(tree->right)->right = NULL;
cout<<"Node Added To Right"<<endl;
return;
}
}
}
/*
* Delete Element from the tree
*/
void BST::del(int item)
{
node *parent, *location;
if (root == NULL)
{
cout<<"Tree empty"<<endl;
return;
}
find(item, &parent, &location);
if (location == NULL)
{
cout<<"Item not present in tree"<<endl;
return;
}
if (location->left == NULL && location->right == NULL)
case_a(parent, location);
if (location->left != NULL && location->right == NULL)
case_b(parent, location);
if (location->left == NULL && location->right != NULL)
case_b(parent, location);
if (location->left != NULL && location->right != NULL)
case_c(parent, location);
free(location);
}
/*
* Case A
*/
void BST::case_a(node *par, node *loc )
{
if (par == NULL)
{
root = NULL;
}
else
{
if (loc == par->left)
par->left = NULL;
else
par->right = NULL;
}
}
/*
* Case B
*/
void BST::case_b(node *par, node *loc)
{
node *child;
if (loc->left != NULL)
child = loc->left;
else
child = loc->right;
if (par == NULL)
{
root = child;
}
else
{
if (loc == par->left)
par->left = child;
else
par->right = child;
}
}
/*
* Case C
*/
void BST::case_c(node *par, node *loc)
{
node *ptr, *ptrsave, *suc, *parsuc;
ptrsave = loc;
ptr = loc->right;
while (ptr->left != NULL)
{
ptrsave = ptr;
ptr = ptr->left;
}
suc = ptr;
parsuc = ptrsave;
if (suc->left == NULL && suc->right == NULL)
case_a(parsuc, suc);
else
case_b(parsuc, suc);
if (par == NULL)
{
root = suc;
}
else
{
if (loc == par->left)
par->left = suc;
else
par->right = suc;
}
suc->left = loc->left;
suc->right = loc->right;
}
/*
* Pre Order Traversal
*/
void BST::preorder(node *ptr)
{
if (root == NULL)
{
cout<<"Tree is empty"<<endl;
return;
}
if (ptr != NULL)
{
cout<<ptr->info<<" ";
preorder(ptr->left);
preorder(ptr->right);
}
}
/*
* In Order Traversal
*/
void BST::inorder(node *ptr)
{
if (root == NULL)
{
cout<<"Tree is empty"<<endl;
return;
}
if (ptr != NULL)
{
inorder(ptr->left);
cout<<ptr->info<<" ";
inorder(ptr->right);
}
}
/*
* Postorder Traversal
*/
void BST::postorder(node *ptr)
{
if (root == NULL)
{
cout<<"Tree is empty"<<endl;
return;
}
if (ptr != NULL)
{
postorder(ptr->left);
postorder(ptr->right);
cout<<ptr->info<<" ";
}
}
/*
* Display Tree Structure
*/
void BST::display(node *ptr, int level)
{
int i;
if (ptr != NULL)
{
display(ptr->right, level+1);
cout<<endl;
if (ptr == root)
cout<<"Root->: ";
else
{
for (i = 0;i < level;i++)
cout<<" ";
}
cout<<ptr->info;
display(ptr->left, level+1);
}
}
OUTPUT
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 1
Enter the number to be inserted : 8
Root Node is Added
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 6
Display BST:
Root->: 8
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 1
Enter the number to be inserted : 9
Node Added To Right
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 6
Display BST:
9
Root->: 8
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 1
Enter the number to be inserted : 5
Node Added To Left
-----------------
Operations on BST
-----------------
1.Insert Element
2.Delete Element
3.Inorder Traversal
4.Preorder Traversal
5.Postorder Traversal
6.Display
7.Quit
Enter your choice : 6
Display BST:
9
Root->: 8
5
A binary tree is represented using array representation or linked list representation. When a
binary tree is represented using linked list representation, if any node is not having a child
we use NULL pointer in that position. In any binary tree linked list representation, there are
more number of NULL pointer than actual pointers. Generally, in any binary tree linked list
representation, if there are 2N number of reference fields, then N+1 number of reference
fields are filled with NULL ( N+1 are NULL out of 2N ). This NULL pointer does not play any
role except indicating there is no link (no child).
A. J. Perlis and C. Thornton have proposed new binary tree called "Threaded Binary Tree",
which make use of NULL pointer to improve its traversal processes. In threaded binary tree,
NULL pointers are replaced by references to other nodes in the tree, called threads.
Threaded Binary Tree is also a binary tree in which all left child pointers that are NULL (in
Linked list representation) points to its in-order predecessor, and all right child pointers
that are NULL in Linked list representation) points to its in-order successo
If there is no in-order predecessor or in-order successor, then it point to root node.
To convert above binary tree into threaded binary tree, first find the in-order traversal of
that tree...
H-D-I-B-E-A-F-J-C-G
When we represent above binary tree using linked list representation, nodes H, I, E, F,
J and G left child pointers are NULL. This NULL is replaced by address of its in-order
predecessor, respectively (I to D, E to B, F to A, J to F and G to C), but here the node H does
not have its in-order predecessor, so it points to the root node A. And nodes H, I, E,
J and G right child pointers are NULL. This NULL ponters are replaced by address of its in-
order successor, respectively (H to D, I to B, E to A, and J to C), but here the node G does not
have its in-order successor, so it points to the root node A.
Above example binary tree become as follows after converting into threaded binary tree.
/*
* C++ Program to Implement Threaded Binary Tree
*/
#include <iostream>
#include <cstdlib>
#define MAX_VALUE 65536
using namespace std;
/* Class Node */
class Node
{
public:
int key;
Node *left, *right;
bool leftThread, rightThread;
};
/* Class ThreadedBinarySearchTree */
class ThreadedBinarySearchTree
{
private:
Node *root;
public:
/* Constructor */
ThreadedBinarySearchTree()
{
root = new Node();
root->right = root->left = root;
root->leftThread = true;
root->key = MAX_VALUE;
}
OUTPUT
ThreadedBinarySearchTree Test
ThreadedBinarySearchTree Operations
1. Insert
2. Delete
3. Search
4. Clear
Enter Your Choice: 1
Enter integer element to insert: 28
Tree = 28
ThreadedBinarySearchTree Operations
1. Insert
2. Delete
3. Search
4. Clear
Enter Your Choice: 1
Enter integer element to insert: 5
Tree = 5 28
ThreadedBinarySearchTree Operations
1. Insert
2. Delete
3. Search
4. Clear
Enter Your Choice: 1
Enter integer element to insert: 19
Tree = 5 19 28
Inorder traversal of expression tree produces infix version of given postfix expression (same
with preorder traversal it gives prefix expression)
Evaluating the expression represented by expression tree:
Let t be the expression tree
If t is not null then
If t.value is operand then
Return t.value
A = solve(t.left)
B = solve(t.right)
// calculate applies operator 't.value'
// on A and B, and returns value
Return calculate(A, B, t.value)
Construction of Expression Tree:
Now For constructing expression tree we use a stack. We loop through input expression and
do following for every character.
1) If character is operand push that into stack
2) If character is operator pop two values from stack make them its child and push current
node again.
At the end only element of stack will be root of expression tree.
3.14.2.Decision tree
A decision tree is a flowchart-like structure in which each internal node represents a "test"
on an attribute (e.g. whether a coin flip comes up heads or tails), each branch represents
the outcome of the test and each leaf node represents a class label (decision taken after
computing all attributes). The paths from root to leaf represents classification rules.
In decision analysis a decision tree and the closely related influence diagram are used as a
visual and analytical decision support tool, where the expected values (or expected utility)
of competing alternatives are calculated.
A decision tree consists of 3 types of nodes:
Search is a process of finding a value in a list of values. In other words, searching is the
process of locating given value position in a list of values.
Consider a list of n elements or can represent a file of n records, where each element is a
key / number. The task is to find a particular key in the list in the shortest possible time. If
you know you are going to search for an item in a set, you will need to think carefully about
what type of data structure you will use for that set. At low level, the only searches that get
mentioned are for sorted and unsorted arrays. However, these are not the only data types
that are useful for searching.
searching.
1. Linear search
Start at the beginning of the list and check every element of the list. Very slow [order O(n) ]
but works on an unsorted list.
2. Binary Search
This is used for searching in a sorted array. Test the middle element of the array. If it is too
big. Repeat the process in the left half of the array, and the right half if it’s too small. In this
way, the amount of space that needs to be searched is halved every time, so the time is
O(log n). .
3. Hash Search
Searching a hash table is easy and extremely fast, just find the hash value for the item
you’re looking for then go to that index and start searching the array until you find what you
are looking for or you hit a blank spot. The order is pretty close to o(1), depending on how
full your hash table is.
Search a binary tree is just as easy as searching a hash table, but it is usually slower
(especially if the tree is badly unbalanced). Just start at the root. Then go down the left
subtree if the root is too big and the right subtree if is too small. Repeat until you find what
you want or the sub-tree you want isn’t there. The running time is O(log n) on average and
O(n) in the worst case.
searching.
2. Binary search
3. Fibonacci search
5. Hashed search
3.15.1.1.Linear Search
Linear search algorithm finds given element in a list of elements with O(n) time complexity
where n is total number of elements in the list. This search process starts comparing of
search element with the first element in the list. If both are matching then results with
element found otherwise search element is compared with next element in the list. If both
are matched, then the result is "element found". Otherwise, repeat the same with the next
element in the list until search element is compared with last element in the list, if that last
element also doesn't match, then the result is "Element not found in the list". That means,
the search element is compared with element by element in the list.
Linear search is implemented using following steps...
Example
int main()
{
int a[20],n,x,i,flag=0;
cout<<"How many elements?";
cin>>n;
cout<<"\nEnter elements of the array\n";
for(i=0;i<n;++i)
cin>>a[i];
for(i=0;i<n;++i)
{
if(a[i]==x)
{
flag=1;
break;
}
}
if(flag)
cout<<"\nElement is found at position "<<i+1;
else
cout<<"\nElement not found";
return 0;
}
Output
How many elements?4
Enter elements of the array
5 9 12 4
Enter element to search:9
Element is found at position 2
3.15.1.2. Binary Search Algorithm
Binary search algorithm finds given element in a list of elements with O(log n) time
complexity where n is total number of elements in the list. The binary search algorithm can
be used with only sorted list of element. That means, binary search can be used only with
lkist of element which are already arraged in a order. The binary search can not be used for
list of element which are in random order. This search process starts comparing of the
search element with the middle element in the list. If both are matched, then the result is
"element found". Otherwise, we check whether the search element is smaller or larger than
the middle element in the list. If the search element is smaller, then we repeat the same
process for left sublist of the middle element. If the search element is larger, then we repeat
the same process for right sublist of the middle element. We repeat this process until we
find the search element in the list or until we left with a sublist of only one element. And if
that element also doesn't match with the search element, then the result is "Element not
found in the list".
Example
int main()
{
int search(int [],int,int);
int n,i,a[100],e,res;
cout<<"How Many Elements:";
cin>>n;
cout<<"\nEnter Elements of Array in Ascending order\n";
for(i=0;i<n;++i)
{
cin>>a[i];
}
res=search(a,n,e);
if(res!=0)
cout<<"\nElement found at position "<<res+1;
else
cout<<"\nElement is not found....!!!";
return 0;
}
while(f<=l)
{
m=(f+l)/2;
if(e==a[m])
return(m);
else
if(e>a[m])
f=m+1;
else
l=m-1;
}
return 0;
}
Output
How Many Elements:5
Enter Elements of Array in Ascending order
12 39 40 68 77
Enter element to search:40
Element found at position 3
The Fibonacci search technique is a method of searching a sorted array using a divide and
conquer algorithm that narrows down possible locations with the aid of Fibonacci numbers.
Compared to binary search where the sorted array is divided into two arrays which are both
examined recursively and recombined, Fibonacci search only examines locations whose
addresses have lower dispersion. Therefore, when the elements being searched have non-
uniform access memory storage (i.e., the time needed to access a storage location varies
depending on the location accessed), the Fibonacci search has an advantage over binary
search in slightly reducing the average time needed to access a storage location. Fibonacci
search has a complexity of O(log(n)) (see Big O notation).
Algorithm
Let k be defined as an element in F, the array of Fibonacci numbers. n = Fm is the array size.
If n is not a Fibonacci number, let Fm be the smallest number in F that is greater than n.
The array of Fibonacci numbers is defined where Fk+2 = Fk+1 + Fk, when k ≥ 0, F1 = 1,
and F0 = 0.
To test whether an item is in the list of ordered numbers, follow these steps:
1. Set k = m.
2. If k = 0, stop. There is no match; the item is not in the array.
3. Compare the item against element in Fk−1.
4. If the item matches, stop.
5. If the item is less than entry Fk−1, discard the elements from positions Fk−1 + 1 to n.
Set k = k − 1 and return to step 2.
6. If the item is greater than entry Fk−1, discard the elements from positions 1 to Fk−1.
Renumber the remaining elements from 1 to Fk−2, set k = k − 2, and return to step 2.
Alternative implementation (from "Sorting and Searching" by Knuth):
Given a table of records R1, R2, ..., RN whose keys are in increasing order K1 < K2 < ... < KN, the
algorithm searches for a given argument K. Assume N+1 = Fk+1
Step 1. [Initialize] i ← Fk, p ← Fk-1, q ← Fk-2 (throughout the algorithm, p and q will be
consecutive Fibonacci numbers)
Step 2. [Compare] If K < Ki, go to Step 3; if K > Ki go to Step 4; and if K = Ki, the algorithm
terminates successfully.
Step 3. [Decrease i] If q=0, the algorithm terminates unsuccessfully. Otherwise set (i, p, q) ←
(p, q, p - q) (which moves p and q one position back in the Fibonacci sequence); then return
to Step 2
Step 4. [Increase i] If p=1, the algorithm terminates unsuccessfully. Otherwise set (i,p,q) ← (i
+ q, p - q, 2q - p) (which moves p and q two positions back in the Fibonacci sequence); and
return to Step 2
3.15.1.4. Indexed Sequential Search:
In this searching technique, first of all an index file is created that contains references to a
group of records, once an index is obtained, the partial searching takes less time since it is to
be located in the group/bucket specified by the index. The program given below creates an
index file for the employee records by grouping the records and then locates the required
key by searching the index first and then returns the required record.
3.15.2.Sorting
Internal Sorts:- This method uses only the primary memory during sorting process. All data
items are held in main memory and no secondary memory is required this sorting process. If
all the data that is to be sorted can be accommodated at a time in memory is called internal
sorting. There is a limitation for internal sorts; they can only process relatively small lists due
to memory constraints. There are 3 types of internal sorts.
• Selection sort
• Heap Sort
• Insertion sort
• Shell Sort algorithm
• Bubble Sort
• Quick sort algorithm
External Sorts:- Sorting large amount of data requires external or secondary memory. This
process uses external memory such as HDD, to store the data which is not fit into the main
memory. So, primary memory holds the currently being sorted data only. All external sorts
are based on process of merging. Different parts of data are sorted separately and merged
together. Ex:- Merge Sort
3.15.2.1.Selection sort
Selection sorting is conceptually the most simplest sorting algorithm. This algorithm first
finds the smallest element in the array and exchanges it with the element in the first
position, then find the second smallest element and exchange it with the element in the
second position, and continues in this way until the entire array is sorted.
How Selection Sorting Works
In the first pass, the smallest element found is 1, so it is placed at the first position, then
leaving first element, smallest element is searched from the rest of the elements, 3 is the
smallest, so it is then placed at the second position. Then we leave 1 nad 3, from the rest of
the elements, we search for the smallest and put it at third position and keep doing this,
until array is sorted.
Sorting using Selection Sort Algorithm
void selectionSort(int a[], int size)
{
int i, j, min, temp;
for(i=0; i < size-1; i++ )
{
min = i; //setting min as i
for(j=i+1; j < size; j++)
{
if(a[j] < a[min]) //if element at j is less than element at min position
{
min = j; //then set min as j
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
Complexity Analysis of Selection Sorting
Worst Case Time Complexity : O(n2)
Best Case Time Complexity : O(n2)
Average Time Complexity : O(n2)
Space Complexity : O(1)
int main()
{
int i,j,n,loc,temp,min,a[30];
cout<<"Enter the number of elements:";
cin>>n;
cout<<"\nEnter the elements\n";
for(i=0;i<n;i++)
{
cin>>a[i];
}
for(i=0;i<n-1;i++)
{
min=a[i];
loc=i;
for(j=i+1;j<n;j++)
{
if(min>a[j])
{
min=a[j];
loc=j;
}
}
temp=a[i];
a[i]=a[loc];
a[loc]=temp;
}
return 0;
What is a Heap ?
Heap is a special tree-based data structure, that satisfies the following special heap
properties :
1. Shape Property : Heap data structure is always a Complete Binary Tree, which means all
levels of the tree are fully filled.
2. Heap Property : All nodes are either [greater than or equal to] or [less than or equal
to] each of its children. If the parent nodes are greater than their children, heap is called
a Max-Heap, and if the parent nodes are smalled than their child nodes, heap is
called Min-Heap.
How Heap Sort Works
Initially on receiving an unsorted list, the first step in heap sort is to create a Heap data
structure(Max-Heap or Min-Heap). Once heap is built, the first element of the Heap is either
largest or smallest(depending upon Max-Heap or Min-Heap), so we put the first element of
the heap in our array. Then we again make heap using the remaining elements, to again pick
the first element of the heap and put it into the array. We keep on doing the same
repeatedly untill we have the complete sorted list in our array.
In the below algorithm, initially heapsort() function is called, which calls buildheap() to build
heap, which inturn uses satisfyheap() to build the heap.
Sorting using Heap Sort Algorithm
/* Below program is written in C++ language */
void heapsort(int[], int);
void buildheap(int [], int);
void satisfyheap(int [], int, int);
void main()
{
int a[10], i, size;
cout << "Enter size of list"; // less than 10, because max size of array is 10
cin >> size;
cout << "Enter" << size << "elements";
for( i=0; i < size; i++)
{
cin >> a[i];
}
heapsort(a, size);
getch();
}
Heap sort is not a Stable sort, and requires a constant space for sorting a list.
Heap Sort is very fast and is widely used for sorting.
int main() {
int array[5]= {5,4,3,2,1};
insertionSort(array,5);
return 0;
}
In the list of elements, mentioned in below example, we have taken 25 as pivot. So after the
first pass, the list will be changed like this.
6 8 17 14 25 63 37 52
Hnece after the first pass, pivot will be set at its position, with all the elements smaller to it
on its left and all the elements larger than it on the right. Now 6 8 17 14 and 63 37 52 are
considered as two separate lists, and same logic is applied on them, and we keep doing this
until the complete list is sorted.
How Quick Sorting Works
Sorting using Quick Sort Algorithm
/* a[] is the array, p is starting index, that is 0,
and r is the last index of array. */
void quicksort(int a[], int p, int r)
{
if(p < r)
{
int q;
q = partition(a, p, r);
quicksort(a, p, q);
quicksort(a, q+1, r);
}
}
int partition(int a[], int p, int r)
{
int i, j, pivot, temp;
pivot = a[p];
i = p;
j = r;
while(1)
{
while(a[i] < pivot && a[i] != pivot)
i++;
while(a[j] > pivot && a[j] != pivot)
j--;
if(i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
else
{
return j;
}
}
}
Complexity Analysis of Quick Sort
Worst Case Time Complexity : O(n2)
Best Case Time Complexity : O(n log n)
Average Time Complexity : O(n log n)
Space Complexity : O(n log n)
Space required by quick sort is very less, only O(n log n) additional space is required.
Quick sort is not a stable sorting technique, so it might change the occurence of two
similar elements in the list while sorting.
while(i <= q)
{
b[k++] = a[i++];
}
while(j <= r)
{
b[k++] = a[j++];
}
Time complexity of Merge Sort is O(n Log n) in all 3 cases (worst, average and best) as
merge sort always divides the array in two halves and take linear time to merge two
halves.
It requires equal amount of additional space as the unsorted list. Hence its not at all
recommended for searching large unsorted lists.
It is the best Sorting technique for sorting Linked Lists.
Assume that we have k tapes - then the number of passes will be reduced - logk(N/M)
At a given merge step we merge the first k runs, then the second k runs, etc.
When finished with the first set of runs, do the same with the next set of runs.
Ta1: 17, 3, 29, 56, 24, 18, 4, 9, 10, 6, 45, 36, 11, 43
Assume that we have three tapes (k = 3) and the memory can hold three records.
The first three records are read into memory, sorted and written on Tb1,
the second three records are read into memory, sorted and stored on Tb2,
finally the third three records are read into memory, sorted and stored on
Tb3.
Tb1: 3, 17, 29
Tb2: 18, 24, 56
Tb3: 4, 9, 10
The next portion, which is also the last one, is sorted and stored onto Tb2:
Thus, after the main memory sort, our tapes look like this:
Tb3: 4, 9, 10
B. Merging
a. We build a heap tree in main memory out of the first records in each
tape.
These records are: 3, 18, and 4.
b. We take the smallest of them - 3, using the deleteMin operation,
and store it on tape Ta1.
The record '3' belonged to Tb1, so we read the next record from Tb1 -
17,
and insert it into the heap. Now the heap contains 18, 4, and 17.
The record '4' comes from Tb3, so we read the next record '9' from
Tb3
and insert it into the heap.
Now the heap contains 18, 17 and 9.
d. Proceeding in this way, the first three runs will be stored in sorted
order on Ta1.
Proceeding in this way, the entire file will be sorted on tape Tb1.
Tb1: 3, 4, 6, 9, 10, 11, 17, 18, 24, 29, 36, 43, 45, 56
polyphase merge sort is an algorithm which decreases the number of "runs" at every
iteration of the main loop by merging "runs" in pairs. Typically, a merge sort splits items into
groups then recursively sorts each group. Once the groups are sorted, they are merged into
a final, sorted sequence.
Polyphase merge sorts are ideal for sorting and merging large files. Two pairs of input and
output files are opened as file streams. At the end of each iteration, input files are deleted,
output files are closed and reopened as input files. The use of file streams makes it possible
to sort and merge files which can not be loaded into the computer's main memory.
File 1 just emptied and became the new output file. One run is left on each input tape, and
merging those runs together will make the sorted file.
Stepping back to the previous iteration, we were reading from 1 and 2. One run is merged
from 1 and 2 before file 1 goes empty. Notice that file 2 is not completely consumed -- it has
one run left to match the final merge (above).
Stepping back another iteration, 2 runs are merged from 1 and 3 before file 3 goes empty.
Stepping back another iteration, 3 runs are merged from 2 and 3 before file 2 goes empty.
Stepping back another iteration, 5 runs are merged from 1 and 2 before file 1 goes empty.
In practice, the input file won't happen to have a Fibonacci number of runs it (and the
number of runs won't be known until after the file has been read). The fix is to pad the input
files with dummy runs to make the required Fibonacci sequence.
For comparison, the ordinary merge sort will combine 16 runs in 4 passes using 4 files. The
polyphase merge will combine 13 runs in 5 passes using only 3 files. Alternatively, a
polyphase merge will combine 17 runs in 4 passes using 4 files. (Sequence: 1, 1, 1, 3, 5, 9,
17, 31, 57, ...)
An iteration (or pass) in ordinary merge sort involves reading and writing the entire file. An
iteration in a polyphase sort does not read or write the entire file[4], so a typical polyphase
iteration will take less time than a merge sort iteration.
Selection sort
Selection sort is an exception in our list. This is considered an academic sorting algorithm.
Why? Because the time efficiency is always O(n2) which is not acceptable. There is no real
world usage for selection sort except passing the data structure course exam.
pros
Nothing
cons
Practical usage
Help students to get some credits towards their degree, nothing to be precise
Bubble sort
This is the other exception in the list because bubble sort is too slow to be practical. Unless
the sequence is almost sorted feasibility of bubble sort is zero and the running time is O(n 2).
This is one of the three simple sorting algorithms alongside selection sort and insertion sort
but like selection sort falls short of insertions sort in terms of efficiency even for small
sequences.
pros
Practical usage
Insertion sort
Insertion sort is definitely not the most efficient algorithm out there but its power lies in its
simplicity. Since it is very easy to implement and adequately efficient for small number of
elements, it is useful for small applications or trivial ones. The definition of small is vague
and depends on a lot of things but a safe bet is if under 50, insertion sort is fast enough.
Another situation that insertion sort is useful is when the sequence is almost sorted. Such
sequences may seem like exceptions but in real world applications often you encounter
almost sorted elements. The run time of insertions sort is O(n 2) at worst case scenario. So
far we have another useless alternative for selection sort. But if implemented well the run
time can be reduced to O(n+k). n is the number of elements and k is the number of
inversions (the number of pair of elements out of order). With this new run time in mind
you can see if the sequence is almost sorted (k is small) the run time can be almost linear
which is a huge improvement over the polynomial n2.
pros
Easy to implement
The more the sequence is ordered the closer is run time to linear time O(n)
cons
Practical usage
For small applications when the sequence is small (less than 50 elements)
When the sequence is going to be almost sorted
Heap sort
This is the first general purpose sorting algorithm we are introducing here. Heap sort runs at
O(nlogn) which is optimal for comparison based sorting algorithms. Though heap sort has
the same run time as quick sort and merge sort but it is usually outperformed in real world
scenarios. If you are asking then why should anyone use it, the answer lies in space
efficiency. Nowadays computers come with huge amount of memory, enough for many
applications. Does this mean heap sort is losing its shine? No, still when writing programs for
environments with limited memory, such as embedded systems or space efficiency is much
more important than time efficiency. A rule of thumb is if the sequence is small enough to
easily fit in main memory then heap sort is good choice.
pros
Runs at O(nlogn)
Can be easily implemented to be executed in place
cons
Practical usage
Quick sort
One of the most widely used sorting algorithms in computer industry. Surprisingly quick sort
has a running time of O(n2) that makes it susceptible in real-time applications. Having a
polynomial worst case scenario still quick sort usually outperforms both quick sort and
merge sort (coming next). The reason behind the popularity of quick sort despite the short
comings is both being fast in real world scenarios (not necessarily worst case) and the ability
to be implemented as an in place algorithm.
pros
cons
Polynomial worst case scenario makes it susceptible for time critical applications
Provides non stable sort due to swapping of elements in partitioning step
Practical usage
Merge sort
Having a O(nlogn) worst case scenario run time makes merge sort a powerful sorting
algorithm. The main drawback of this algorithm is its space inefficiency. That is in the
process of sorting lots of temporary arrays have to be created and many copying of
elements is involved. This doesn’t mean merge sort is not useful. When the data to be
sorted is distributed across different locations like cache, main memory etc then copying
data is inevitable. Merge sort mainly owes its popularity to Tim Peters who designed a
variant of it which is in essence a bottom-up merge sort and is known as Tim sort.
pros
Excellent choice when data is fetched from resources other than main memory
Having a worst case scenario run time of O(nlogn) which is optimal
Tim sort variant is really powerful
cons
Lots of overhead in copying data between arrays and making new arrays
Extremely difficult to implement it in place for arrays
Space inefficiency
Practical usage
When data is in different locations like cache, main memory, external memory etc.
A multi-way merge sort variant is used in GNU sorting utility
Tim sort variant is standard sorting algorithm in Python programming language since
2003
Default sorting algorithm of arrays of object type in Java since version 7 onward
cons
Practical usage
When the prerequisites of data types is met then they are the definitive choice
Comparison table2:
worst case time average case time best case time worst case space
The binary search tree (BST) is one of the fundamental data structures extensively used for
searching the target in a set of ordered data. These two cases lead to the following two
kinds of search trees:
1. Static BST—is one that is not allowed to update its structure once it is constructed. In
other words, the static BST is an offl ine algorithm, which is presumably aware of the
access sequence beforehand.
2. Dynamic BST—is one that changes during the access sequence. We assume that the
dynamic BST is an online algorithm, which does not have prior information about the
sequence.
Symbol Table
The symbol table is a kind of a ‘keyed table’ which stores <key, information> pairs with
no additional logical structure.
The operations performed on symbol tables are the following:
1. Inserting the <key, information> pairs into the collection.
2. Removing the <key, information> pairs by specifying the key.
3. Searching for a particular key.
4. Retrieving the information associated with a key.
An optimal binary search tree is a binary search tree for which the nodes are arranged on
levels such that the tree cost is minimum.
For the purpose of a better presentation of optimal binary search trees, we will consider
“extended binary search trees”, which have the keys stored at their internal nodes. Suppose
“n” keys k1, k2, … , k n are stored at the internal nodes of a binary search tree. It is assumed
that the keys are given in sorted order, so that k1< k2 < … < kn.
Algorithm
OBST(i, j) denotes the optimal binary search tree containing the keys ki,
ki+1, …, kj;
Wi, j denotes the weight matrix for OBST(i, j)
Wi, j can be defined using the following formula:
All possible optimal subtrees are not required. Those that are consist of sequences of keys
that are immediate successors of the smallest key in the subtree, successors in the sorted
order for the keys.
The bottom-up approach generates all the smallest required optimal subtrees first, then all
next smallest, and so on until the final solution involving all the weights is found. Since the
algorithm requires access to each subtree’s weighted path length, these weighted path
lengths must also be retained to avoid their recalculation. They will be stored in the weight
matrix ‘W’. Finally, the root of each subtree must also be stored for reference in the root
matrix ‘R’.
ALGORITHMS IN PSEUDOCODE
We have the following procedure for determining R(i, j) and C(i, j) with
0 <= i <= j <= n:
PROCEDURE COMPUTE_ROOT(n, p, q; R, C)
begin
for i = 0 to n do
C (i, i) _ 0
W (i, i) _ q(i)
for m = 0 to n do
for i = 0 to (n – m) do
j_i+m
W (i, j) _ W (i, j – 1) + p (j) + q (j)
*find C (i, j) and R (i, j) which minimize the tree cost
end
FUNCTION CONSTRUCT(R, i, j)
begin
*build a new internal node N labeled (i, j)
k _ R (i, j)
if i = k then
*build a new leaf node N’ labeled (i, i)
else
*N’ _ CONSTRUCT(R, i, k)
*N’ is the left child of node N
if k = (j – 1) then
*build a new leaf node N’’ labeled (j, j)
else
*N’’ _ CONSTRUCT(R, k + 1, j)
*N’’ is the right child of node N
return N
end
Find the optimal binary search tree for N = 6, having keys k1 … k6 and
weights p1 = 10, p2 = 3, p3 = 9, p4 = 2, p5 = 0, p6 = 10; q0 = 5, q1 = 6, q2 = 4, q3
= 4, q4 = 3, q5 = 8, q6 = 0. The following figure shows the arrays as they
would appear after the initialization and their final disposition.
Thus the OBST will have the final structure
AVL tree is a self balanced binary search tree. That means, an AVL tree is also a binary
search tree but it is a balanced tree. A binary tree is said to be balanced, if the difference
between the hieghts of left and right subtrees of every node in the tree is either -1, 0 or +1.
In other words, a binary tree is said to be balanced if for every node, height of its children
differ by at most one. In an AVL tree, every node maintains a extra information known
as balance factor. The AVL tree was introduced in the year of 1962 by G.M. Adelson-Velsky
and E.M. Landis.
An AVL tree is a balanced binary search tree. In an AVL tree, balance factor of every node
is either -1, 0 or +1.
Balance factor of a node is the difference between the heights of left and right subtrees of
that node. The balance factor of a node is calculated either height of left subtree - height of
right subtree (OR) height of right subtree - height of left subtree. In the following
explanation, we are calculating as follows...
Example
The above tree is a binary search tree and every node is satisfying balance factor condition.
So this tree is said to be an AVL tree.
Every AVL Tree is a binary search tree but all the Binary Search Trees need not to be AVL
trees.
In AVL tree, after performing every operation like insertion and deletion we need to check
the balance factor of every node in the tree. If every node satisfies the balance factor
condition then we conclude the operation otherwise we must make it balanced. We
use rotationoperations to make the tree balanced whenever the tree is becoming
imbalanced due to any operation.
Rotation is the process of moving the nodes to either left or right to make tree balanced.
There are four rotations and they are classified into two types.
In LL Rotation every node moves one position to left from the current position. To
understand LL Rotation, let us consider following insertion operations into an AVL Tree...
In RR Rotation every node moves one position to right from the current position. To
understand RR Rotation, let us consider following insertion operations into an AVL Tree...
The LR Rotation is combination of single left rotation followed by single right rotation. In LR
Roration, first every node moves one position to left then one position to right from the
current position. To understand LR Rotation, let us consider following insertion operations
into an AVL Tree...
3.19.1.4. Right Left Rotation (RL Rotation)
The RL Rotation is combination of single right rotation followed by single left rotation. In RL
Roration, first every node moves one position to right then one position to left from the
current position. To understand RL Rotation, let us consider following insertion operations
into an AVL Tree...
1. Search
2. Insertion
3. Deletion
In an AVL tree, the search operation is performed with O(log n) time complexity. The search
operation is performed similar to Binary search tree search operation. We use the following
steps to search an element in AVL tree...
In an AVL tree, the insertion operation is performed with O(log n) time complexity. In AVL
Tree, new node is always inserted as a leaf node. The insertion operation is performed as
follows...
Step 1: Insert the new element into the tree using Binary Search Tree insertion logic.
Step 2: After insertion, check the Balance Factor of every node.
Step 3: If the Balance Factor of every node is 0 or 1 or -1 then go for next operation.
Step 4: If the Balance Factor of any node is other than 0 or 1 or -1 then tree is said to
be imbalanced. Then perform the suitable Rotation to make it balanced. And go for
next operation.
Example: Construct an AVL Tree by inserting numbers from 1 to 8.
3.19.2.3 Deletion Operation in AVL Tree
In an AVL Tree, the deletion operation is similar to deletion operation in BST. But after every
deletion operation we need to check with the Balance Factor condition. If the tree is
balanced after deletion then go for next operation otherwise perform the suitable rotation
to make the tree Balanced.
#include <iostream.h>
#include <stdlib.h>
#include<constream.h>
#define FALSE 0
#define TRUE 1
struct AVLNode
{
int data ;
int balfact ;
AVLNode *left ;
AVLNode *right ;
};
class avltree
{
private :
AVLNode *root ;
public :
avltree( ) ;
AVLNode* insert ( int data, int *h ) ;
static AVLNode* buildtree ( AVLNode *root, int data, int *h ) ;
void display( AVLNode *root ) ;
AVLNode* deldata ( AVLNode* root, int data, int *h ) ;
static AVLNode* del ( AVLNode *node, AVLNode* root, int *h ) ;
static AVLNode* balright ( AVLNode *root, int *h ) ;
static AVLNode* balleft ( AVLNode* root, int *h ) ;
void setroot ( AVLNode *avl ) ;
~avltree( ) ;
static void deltree ( AVLNode *root ) ;
};
avltree :: avltree( )
{
root = NULL ;
}
AVLNode* avltree :: insert ( int data, int *h )
{
root = buildtree ( root, data, h ) ;
return root ;
}
AVLNode* avltree :: buildtree ( AVLNode *root, int data, int *h )
{
AVLNode *node1, *node2 ;
if ( root == NULL )
{
root = new AVLNode ;
root -> data = data ;
root -> left = NULL ;
root -> right = NULL ;
root -> balfact = 0 ;
*h = TRUE ;
return ( root ) ;
}
if ( data < root -> data )
{
root -> left = buildtree ( root -> left, data, h ) ;
case 0 :
root -> balfact = 1 ;
break ;
case -1 :
root -> balfact = 0 ;
*h = FALSE ;
}
}
}
if ( *h )
{
switch ( root -> balfact )
{
case 1 :
root -> balfact = 0 ;
*h = FALSE ;
break ;
case 0 :
root -> balfact = -1 ;
break ;
case -1 :
node1 = root -> right ;
if ( node1 -> balfact == -1 )
{
cout << "\nLeft rotation." ;
root -> right = node1 -> left ;
node1 -> left = root ;
root -> balfact = 0 ;
root = node1 ;
}
else
{
case 0 :
root -> balfact = 1 ;
*h = FALSE ;
break ;
case 1 :
temp1 = root -> left ;
if ( temp1 -> balfact >= 0 )
{
cout << "\nRight rotation." ;
root -> left = temp1 -> right ;
temp1 -> right = root ;
UNIT IV
4.0. Hashing
Hashing is a technique to convert a range of key values into a range of indexes of an array.
We're going to use modulo operator to get a range of key values. Consider an example of
hash table of size 20, and the following items are to be stored. Item are in the (key,value)
format.
1 1 1 % 20 = 1 1
2 2 2 % 20 = 2 2
3 42 42 % 20 = 2 2
4 4 4 % 20 = 4 4
5 12 12 % 20 = 12 12
6 14 14 % 20 = 14 14
7 17 17 % 20 = 17 17
8 13 13 % 20 = 13 13
9 37 37 % 20 = 17 17
• Probe Each action of address calculation and check for success is called as a probe.
• Collision The result of two keys hashing into the same address is called collision.
• Synonym Keys that hash to the same address are called synonyms.
• Overflow The result of many keys hashing to a single address and lack of room in the
bucket is known as an overflow. Collision and overflow are synonymous when the
bucket is of size 1.
• Closed or internal hashing When we use fixed space for storage eventually limiting
the number of records to be stored, it is called as closed or internal hashing.
• Hash function Hash function is an arithmetic function that transforms a key into an
• address which is used for storing and retrieving a record.
• Perfect hash function The hash function that transforms different keys into different
• addresses is called a perfect hash function. The worth of a hash function depends on
how
• well it avoids collision.
• Load density The maximum storage capacity, that is, the maximum number of
records
• that can be accommodated, is called as loading density.
• Full table A full table is one in which all locations are occupied. Owing to the
• characteristics of hash functions, there are always empty locations, rather a hash
function should not allow the table to get filled in more than 75%.
• Load factor Load factor is the number of records stored in a table divided by the
• maximum capacity of the table, expressed in terms of percentage.
• Rehashing Rehashing is with respect to closed hashing. When we try to store the
record with Key1 at the bucket position Hash(Key1) and find that it already holds a
record, it is collision situation. To handle collision, we use a strategy to choose a
sequence of alternative locations Hash1(Key1), Hash2(Key1), and so on within the
bucket table so as to place the record with Key1. This is known as rehashing.
Issues in hashing In case of collision, there are two main issues to be considered:
1. We need a good hashing function that minimizes the number of collisions.
2. We want an efficient collision resolution strategy so as to store or locate synonyms.
The key K is divided by some number m and the remainder is used as the
hash address of K.
h(k)=k mod m
This gives the indexes in the range 0 to m-1 so the hash table should be of
size m
Generally a prime number is a best choice which will spread keys evenly.
A uniform hash function is designed to distribute the keys roughly evenly into
the available positions within the array (or hash table).
For example:- Let us say apply division approach to find hash value for some values
considering number of buckets be 10 as shown below.
4.3.2. The Multiplication Method
The multiplication method for creating a hash function operates in two steps.
ä Step 1. Multiply the key k by a constant A in the range 0 < A < 1,
and extract the fractional part of kA.
ä Step 2. Multiply this value by m and take the floor of the result. Why?
In short, the hash function is
h(k) = bm·(kA mod 1)c,
where (kA mod 1) denotes the fractional part of kA, that is, kA−bkAc.
of which has the same length as the required address with the possible
exception of the last part .
The parts are then added together , ignoring the final carry, to form an
address.
(123456)2=15241383936
If a malicious adversary chooses keys to be hashed, then he can choose n keys that all hash
to the same slot, yielding an average retrieval time of Q(n). Any fixed hash function is
vulnerable to this sort of worst case behavior. The only effective way to improve the
situation is to choose a hash function randomly in a way that is independent of the keys that
are actually going to be stored.
This approach is referred to as universal hashing. In other words, universal hashing is to
select the hash function at random and at run time from a carefully designed collection of
hash functions.
Let H = {h1,h2, . . . ,hl} be a finite collection of hash functions that map a given
universe U of keys into a range {0,1, . . . ,m−1}.
Such a collection is said to be universal if for each pair of distinct keys x, y 2U, the
number of hash functions h 2 H for which h(x) = h(y) is at most
The hash function "uniformly" distributes the data across the entire set of possible
hash values.
The hash function generates very different hash values for similar strings.
•Retrieval of an item,r, with hash address,i, is simply retrieval from the linked list at
positioni.
•Deletion of an item,r, with hash address,i, is simply deletingrfrom the linked list at
positioni.
•Example:Load the keys 23, 13, 21, 14, 7, 8, and 15, in this order, in a hash table of size 7
using separate chaining with the hash function: h(key) = key % 7
4.5.1.2.Open addressing
Open addressing hash tables store the records directly within the array.
A hash collision is resolved by probing, or searching through alternate locations in the array.
• Linear probing
• Quadratic probing
• Random probing
• Double hashing
#define HASHTABLESIZE 51
typedefstruct
{
intkey[HASHTABLESIZE];
intstate[HASHTABLESIZE];
/* -1=lazy delete, 0=empty, 1=occupied */
} hashtable;
/* The hash function */
inthash(intinput)
{
return input % HASHTABLESIZE;
}
In Open addressing, if collision occurs, alternative cells are tried. h0(X), h1(X), h2(X), ...
This is implemented via a linear search for an empty slot, from the point of collision. If the
physical end of table is reached during the linear search, the search will wrap around to the
beginning of the table and continue from there.
If an empty slot is not found before reaching the point of collision, the table is full.
A problem with the linear probe method is that it is possible for blocks of data to form when
collisions are resolved. This is known as primary clustering.
This means that any key that hashes into the cluster will require several attempts to resolve
the collision.
For example, insert the nodes 89, 18, 49, 58, and 69 into a hash table that holds 10 items
using the division method:
To resolve the primary clustering problem, quadratic probing can be used. With quadratic
probing, rather than always moving one spot, move i2 spots from the point of collision,
where i is the number of attempts to resolve the collision.
Limitation: at most half of the table can be used as alternative locations to resolve collisions.
This means that once the table is more than half full, it's difficult to find an empty spot. This
new problem is known as secondary clustering because elements that hash to the same
hash key will always probe the same alternative cells.
Double hashing uses the idea of applying a second hash function to the key when a collision
occurs. The result of the second hash function will be the number of positions form the
point of collision to insert.
A popular second hash function is: Hash2(key) = R - ( key % R ) where R is a prime number
that is smaller than the size of the table.
4.5.1.5. Hashing with Rehashing
Once the hash table gets too full, the running time for operations will start to take too long
and may fail. To solve this problem, a table at least twice the size of the original will be built
and the elements will be transferred to the new table.
This is a very expensive operation! O(N) since there are N elements to rehash and the table
size is roughly 2N. This is ok though since it doesn't happen that often.
An overflow occurs when the home bucket for a new pair (key, element) is full.
We may handle overflows by:
o Search the hash table in some systematic fashion for a bucket that is not full.
Linear probing (linear open addressing).
Quadratic probing.
Random probing.
o Eliminate overflows by permitting each bucket to keep a list of all pairs for
which it is the home bucket.
Array linear list.
Chain
Open addressing ensures that all elements are stored directly into the hash table, thus it
attempts to resolve collisions using various methods.
Linear Probing resolves collisions by placing the data into the next open slot in the
table.
Worst-case find/insert/erase time is Q(n), where n is the number of pairs in the table.
This happens when all pairs are in the same cluster.
Expected Performance
a = loading density = (number of pairs)/b.
o a = 12/17.
Sn = expected number of buckets examined in a successful search when n is large
Un = expected number of buckets examined in a unsuccessful search when n is large
Time to put and remove is governed by Un.
Quadratic Probing
Linear probing searches buckets (H(x)+i2)%b
Quadratic probing uses a quadratic function of i as the increment
Examine buckets H(x), (H(x)+i2)%b, (H(x)-i2)%b, for 1<=i<=(b-1)/2
b is a prime number of the form 4j+3, j is an integer
Random Probing
Random Probing works incorporating with random numbers.
o H(x):= (H’(x) + S[i]) % b
o S[i] is a table with size b-1
S[i] is a random permuation of integers [1,b-1].
Example
Assume that the hash function returns a string of bits. The first i bits of each string will
be used as indices to figure out where they will go in the "directory" (hash table).
Additionally, i is the smallest number such that the index of every item in the table is
unique.
Keys to be used:
Let's assume that for this particular example, the bucket size is 1. The first two keys to
be inserted, k1 and k2, can be distinguished by the most significant bit, and would be
inserted into the table as follows:
Now, if k3 were to be hashed to the table, it wouldn't be enough to distinguish all three keys
by one bit (because both k3 and k1 have 1 as their leftmost bit). Also, because the bucket
size is one, the table would overflow. Because comparing the first two most significant bits
would give each key a unique location, the directory size is doubled as follows:
And so now k1 and k3 have a unique location, being distinguished by the first two leftmost
bits. Because k2 is in the top half of the table, both 00 and 01 point to it because there is no
other key to compare to that begins with a 0.
Now, k4 needs to be inserted, and it has the first two bits as 01..(1110), and using a 2 bit depth in the
directory, this maps from 01 to Bucket A. Bucket A is full (max size 1), so it must be split; because
there is more than one pointer to Bucket A, there is no need to increase the directory size.
1. The key size that maps the directory (the global depth), and
2. The key size that has previously mapped the bucket (the local depth)
In order to distinguish the two action cases:
Now,
is tried again, with 2 bits 01.., and now key 01 points to a new bucket but there is still B K1 in
it (B K1 and also begins with 01). If ADBC had been 000110, with key 00, there would have
been no problem, because 00 would have remained in the new bucket A' and bucket D
would have been empty.
So Bucket D needs to be split, but a check of its local depth, which is 2, is the same as the
global depth, which is 2, so the directory must be split again, in order to hold keys of
sufficient detail, e.g. 3 bits.
5. The contents of the split bucket D, , has been re-keyed with 3 bits, and it ends
up in D.
6. K4 is retried and it ends up in E which has a spare slot.
Now, k2 is in D and E is tried again, with 3 bits 011.., and it points to bucket D which already
contains k2 so is full; D's local depth is 2 but now the global depth is 3 after the directory
doubling, so now D can be split into bucket's D' and E, the contents of D, k2 has its retried
with a new global depth bitmask of 3 and ends up in D', then the new entry is retried
with bitmasked using the new global depth bit count of 3 and this gives 011 which now
points to a new bucket E which is empty. So k1 goes in Bucket E.
4.8. Dictionary
a dictionary (table) is an abstract model of a database like a priority queue, a dictionary
stores key-element pairs. the main operation supported by a dictionary is searching by key
4.8.1.Dictionary ADT
The dictionary ADT models a searchable collection of keyelement items. The main
operations of a dictionary are searching, inserting, and deleting items.
Multiple items with the same key are allowed.
4.8.2. Applications:
address book
credit card authorization
mapping host names (e.g., cs16.net) to internet addresses (e.g., 128.148.34.101)
find(k): if the dictionary has an item with key k, returns the position of this element,
else, returns a null position.
insertItem(k, o): inserts item (k, o) into the dictionary
removeElement(k): if the dictionary has an item with key k, removes it from the
dictionary and returns its element. An error occurs if there is no such element.
size(), isEmpty()
keys(), Elements()
The level assignment of newly inserted pair is done using a random number
generator (0 to RAND_MAX)
int lev = 0
In a regular skip list structure with N pairs, the maximum level is log1/pN - 1
4.10. Heaps
Heap is a special case of balanced binary tree data structure where the root-node key is
compared with its children and arranged accordingly. If α has child node β then −
key(α) ≥ key(β)
As the value of parent is greater than that of child, this property generates Max Heap.
Based on this criteria, a heap can be of two types −
For Input → 35 33 42 10 14 19 27 44 26 31
Min-Heap − Where the value of the root node is less than or equal to either of its children.
Max-Heap − Where the value of the root node is greater than or equal to either of its
children.
Both trees are constructed using the same input and order of arrival.
Applications: A heap has many applications, including the most efficient implementation of
priority queues, which are useful in many applications. In particular, heaps are crucial in
several efficient graph algorithms.
Variants:
2-3 heap
Binary heap
Many many others
Binary heap storage rules -- A heap implemented with a binary tree in which the following
two rules are followed:
The element contained by each node is greater than or equal to the elements of that
node's children.
The tree is a complete binary tree.
Example: We want to insert a node with value 42 to the heap on the left.
The above process is called reheapification upward.
1. Place the new element in the heap in the first available location. This keeps the
structure as a complete binary tree, but it might no longer be a heap since the new
element might have a greater value than its parent.
2. while (the new element has a greater value than its parent) swap the new element
with its parent.
3. Notice that Step 2 will stop when the new element reaches the root or when the
new element's parent has a value greater than or equal to the new element's
value.
The procedure for deleting the root from the heap -- effectively extracting the maximum
element in a max-heap or the minimum element in a min-heap.
1. Copy the element at the root of the heap to the variable used to return a value.
2. Copy the last element in the deepest level to the root and then take this last node
out of the tree. This element is called the "out-of-place" element.
3. while (the out-of-place element has a value that is lower than one of its children)
swap the out-of-place element with its greatest-value child.
4. Return the answer that was saved in Step 1.
5. Notice that Step 3 will stop when the out-of-place element reaches a leaf or it has
a value that is greater or equal to all its children.
Now, think about how to build a heap. Check out the example of inserting 27, 35, 23, 22,
4, 45, 21, 5, 42 and 19 to an empty heap.
A more common approach is to store the heap in an array. Since heap is always a complete
binary tree, it can be stored compactly. No space is required for pointers; instead, the
parent and children of each node can be found by simple arithmetic on array indices.
For each index i, element arr[i] has children at arr[2i + 1] and arr[2i + 2], and the
parent at arr[floor( ( i - 1 )/2 )].
This implementation is particularly useful in the heapsort algorithm, where it allows the
space in the input array to be reused to store the heap (i.e., the algorithm is in-place).
However it requires allocating the array before filling it, which makes this method not that
useful in priority queues implementation, where the number of elements is unknown.
A heap could be built by successive insertions. This approach requires O(n log n) time
for n elements. Why?
As an example, let's build a heap with the following values: 20, 35, 23, 22, 4, 45, 21, 5, 42
and 19. Click here to see the process.
As has been proved here, this optimal method requires O(n) time for n elements.
1) Heap Sort: Heap Sort uses Binary Heap to sort an array in O(nLogn) time.
2) Priority Queue: Priority queues can be efficiently implemented using Binary Heap because
it supports insert(), delete() and extractmax(), decreaseKey() operations in O(logn) time.
Binomoial Heap and Fibonacci Heap are variations of Binary Heap. These variations perform
union also efficiently.
3) Graph Algorithms: The priority queues are especially used in Graph Algorithms
like Dijkstra’s Shortest Pathand Prim’s Minimum Spanning Tree.
4) Many problems can be efficiently solved using Heaps. See following for example.
a) K’th Largest Element in an array.
b) Sort an almost sorted array/
c) Merge K Sorted Arrays.
4.13. Indexing
We know that data is stored in the form of records. Every record has a key field, which
helps it to be recognized uniquely.
Indexing is a data structure technique to efficiently retrieve records from the database files
based on some attributes on which the indexing has been done. Indexing in database
systems is similar to what we see in books.
Indexing is defined based on its indexing attributes. Indexing can be of the following types
−
Primary Index − Primary index is defined on an ordered data file. The data file is
ordered on a key field. The key field is generally the primary key of the relation.
Clustering Index − Clustering index is defined on an ordered data file. The data file is
ordered on a non-key field.
Ordered Indexing is of two types −
Dense Index
Sparse Index
Dense Index
In dense index, there is an index record for every search key value in the database. This
makes searching faster but requires more space to store index records itself. Index records
contain search key value and a pointer to the actual record on the disk.
Sparse Index
In sparse index, index records are not created for every search key. An index record here
contains a search key and an actual pointer to the data on the disk. To search a record, we
first proceed by index record and reach at the actual location of the data. If the data we are
looking for is not where we directly reach by following the index, then the system starts
sequential search until the desired data is found.
Multilevel Index
Index records comprise search-key values and data pointers. Multilevel index is stored on
the disk along with the actual database files. As the size of the database grows, so does the
size of the indices. There is an immense need to keep the index records in the main
memory so as to speed up the search operations. If single-level index is used, then a large
size index cannot be kept in memory which leads to multiple disk accesses.
Multi-level Index helps in breaking down the index into several smaller indices in order to
make the outermost level so small that it can be saved in a single disk block, which can
easily be accommodated anywhere in the main memory.
A multiway tree is a tree that can have more than two children
A multiway tree of order m88 (or an **m-way tree) is one in which a tree can have
m children.
An m-way search tree is a m-way tree in which:
a) Each node has m children and m-1 key fields
b) The keys in each node are in ascending order.
c) The keys in the first i children are smaller than the ithith key
d) The keys in the last m-i children are larger than the ithith key
In a binary search tree, m=2. So it has one value and two sub trees.
The figure above is a m-way search tree of order 3.
M-way search trees give the same advantages to m-way trees that binary search
trees gave to binary trees - they provide fast information retrieval and update.
However, they also have the same problems that binary search trees had - they can
become unbalanced, which means that the construction of the tree becomes of vital
importance.
In m-way search tree, each sub-tree is also a m-way search tree and follows the
same rules.
An extension of a multiway search tree of order m is a B-tree of order m.
This type of tree will be used when the data to be accessed/stored is located on
secondary storage devices because they allow for large amounts of data to be stored
in a node.
4.15. B - Trees
In a binary search tree, AVL Tree, Red-Black tree etc., every node can have only one value
(key) and maximum of two children but there is another type of search tree called B-Tree in
which a node can store more than one value (key) and it can have more than two children.
B-Tree was developed in the year of 1972 by Bayer and McCreight with the name Height
Balanced m-way Search Tree. Later it was named as B-Tree.
B-Tree is a self-balanced search tree with multiple keys in every node and more than two
children for every node.
Here, number of keys in a node and number of children for a node is depend on the order of
the B-Tree. Every B-Tree has order.
For example, B-Tree of Order 4 contains maximum 3 key values in a node and maximum 4
children for a node.
Example
Operations on a B-Tree
1. Search
2. Insertion
3. deletion
In a B-Ttree, the search operation is similar to that of Binary Search Tree. In a Binary search
tree, the search process starts from the root node and every time we make a 2-way decision
(we go to either left subtree or right subtree). In B-Tree also search process starts from the
root node but every time we make n-way decision where n is the total number of children
that node has. In a B-Ttree, the search operation is performed with O(log n) time
complexity. The search operation is performed as follows...
In a B-Tree, the new element must be added only at leaf node. That means, always the new
keyValue is attached to leaf node only. The insertion operation is performed as follows...
Example
Most queries can be executed more quickly if the values are stored in order. But it's not
practical to hope to store all the rows in the table one after another, in sorted order,
because this requires rewriting the entire table with each insertion or deletion of a row.
This leads us to instead imagine storing our rows in a tree structure. Our first instinct would
be a balanced binary search tree like a red-black tree, but this really doesn't make much
sense for a database since it is stored on disk. You see, disks work by reading and writing
whole blocks of data at once — typically 512 bytes or four kilobytes. A node of a binary
search tree uses a small fraction of that, so it makes sense to look for a structure that fits
more neatly into a disk block.
Hence the B+-tree, in which each node stores up to d references to children and up to d − 1
keys. Each reference is considered “between” two of the node's keys; it references the root
of a subtree for which all values are between these two keys.
A B+-tree requires that each leaf be the same distance from the root, as in this picture,
where searching for any of the 11 values (all listed on the bottom level) will involve loading
three nodes from the disk (the root block, a second-level block, and a leaf).
In practice, d will be larger — as large, in fact, as it takes to fill a disk block. Suppose a block
is 4KB, our keys are 4-byte integers, and each reference is a 6-byte file offset. Then we'd
choose d to be the largest value so that 4 (d − 1) + 6 d ≤ 4096; solving this inequality for d,
we end up with d ≤ 410, so we'd use 410 for d. As you can see, d can be large.
In our examples, we'll continue to use 4 for d. Looking at our invariants, this requires that
each leaf have at least two keys, and each internal node to have at least two children (and
thus at least one key).
2. Insertion algorithm
1. If the node has an empty space, insert the key/reference pair into the node.
2. If the node is already full, split it into two nodes, distributing the keys evenly
between the two nodes. If the node is a leaf, take a copy of the minimum value in
the second of these two nodes and repeat this insertion algorithm to insert it into
the parent node. If the node is a non-leaf, exclude the middle value during the split
and repeat this insertion algorithm to insert this excluded value into the parent
node.
Initial:
Insert 20:
Insert 13:
Insert 15:
Insert 10:
Insert 11:
Insert 12:
3. Deletion algorithm
1. Remove the required key and associated reference from the node.
2. If the node still has enough keys and references to satisfy the invariants, stop.
3. If the node has too few keys to satisfy the invariants, but its next oldest or next
youngest sibling at the same level has more than necessary, distribute the keys
between this node and the neighbor. Repair the keys in the level above to represent
that these nodes now have a different “split point” between them; this involves
simply changing a key in the levels above, without deletion or insertion.
4. If the node has too few keys to satisfy the invariant, and the next oldest or next
youngest sibling is at the minimum for the invariant, then merge the node with its
sibling; if the node is a non-leaf, we will need to incorporate the “split key” from the
parent into our merging. In either case, we will need to repeat the removal algorithm
on the parent node to remove the “split key” that previously separated these
merged nodes — unless the parent is the root and we are removing the final key
from the root, in which case the merged node becomes the new root (and the tree
has become one level shorter than before).
Initial:
Delete 13:
Delete 15:
Delete 1:
I. File Activity:
It specifies that percent of actual records proceeds in single run. If a small percent of record
is accessed at any given time, the file should be organized on disk for the direct access in
contrast. If a fare percentage of records affected regularly than storing the file on tape
would be more efficient & less costly.
4.19.File organisation
A file is organised to ensure that records are available for processing. It should be designed
with the activity and volatility information and the nature of storage media, Other
consideration are cost of file media, enquiry, requirements of users and file’s privacy,
integrity, security and confidentiality.
The various file organization methods are:
Sequential access.
Direct or random access.
Index sequential access.
Type of application.
Method of processing.
Size of the file.
File inquiry capabilities.
File volatility.
The response time.
4.19.1. Sequential access method
Sequential access method: Here the records are arranged in the ascending or descending
order or chronological order of a key field which may be numeric or both. Since the records
are ordered by a key field, there is no storage location identification. It is used in
applications like payroll management where the file is to be processed in entirety, i.e. each
record is processed. Here, to have an access to a particular record, each record must be
examined until we get the desired record. Sequential files are normally created and stored
on magnetic tape using batch processing method.
Advantages:
Simple to understand.
Easy to maintain and organize
Loading a record requires only the record key.
Relatively inexpensive I/O media and devices can be used.
Easy to reconstruct the files.
The proportion of file records to be processed is high.
Disadvantages:
Direct access files organization: (Random or relative organization). Files in his type are
stored in direct access storage devices such as magnetic disk, using an identifying key. The
identifying key relates to its actual storage position in the file. The computer can directly
locate the key to find the desired record without having to search through any other record
first. Here the records are stored randomly, hence the name random file. It uses online
system where the response and updation are fast.
Advantages:
Disadvantages:
Data may be accidentally erased or over written unless special precautions are
taken.
Risk of loss of accuracy and breach of security. Special backup and reconstruction
procedures must be established.
Less efficient use of storage space.
Expensive hardware and software are required.
High complexity in programming.
File updation is more difficult when compared to that of sequential method.
Advantages:
Permits efficient and economic use of sequential processing technique when the
activity rate is high.
Permits quick access to records, in a relatively efficient way when this activity is a
fraction of the work load.
Disadvantages: