C and Data Structure
C and Data Structure
Syllabus
UNIT-I: Object Oriented Concepts and C++
C++ Fundamentals - Operators, Expressions and Control Structures: If,If..Else, Switch
- Repetitive Statements- for,while,do..while - Input and Output in C++ - manipulators-
manipulators with parameters. - Pointers and arrays
UNIT-II: Functions and Classes
Functions in C++ - Main Function - Function Prototyping - Parameters Passing in
Functions - Values Return by Functions - inline Functions - Function Overloading.
Classes and Objects; Constructors and Destructors; and Operator Overloading - Type
of Constructors
UNIT – III: Inheritance, Polymorphism & Files
Inheritance : Single Inheritance - Multilevel inheritance - Multiple inheritance -
Hierarchical Inheritance - Hybrid Inheritance - Polymorphism - Working with Files :
Classes for File Stream Operations - Opening and Closing a File - End-of-File
Detection - Updating a File - Error Handling during File Operations .
UNIT-IV: Fundamental Data Structures
Definition of a Data structure - primitive and composite Data Types, Stacks (Array) -
Operations –Linked Stack-Operations- Applications of Stack (Infix to Postfix
Conversion).
Queue (Array)- operations-Linked Queue- Operations- - Singly Linked List -
Operations, Application of List ( Polynomial Addition)-. Doubly Linked List -
Operations.
UNIT-V : Trees and Graphs
Trees: Binary Trees –Binary Search Tree- Operations - Recursive Tree Traversals-
Recursion. Graph - Definition, Types of Graphs, Graph Traversal –Dijkstras shortest
path- DFS and BFS.
Unit-I
Introduction
1. Class
A class is a user defined data type. A class is a logical abstraction. It is a template that defines the form
of an object. A class specifies both code and data. It is not until an object of that class has been created
that a physical representation of that class exists in memory. When you define a class, you declare the
data that it contains and the code that operates on that data. Data is contained in instance variables
defined by the class known as data members, and code is contained in functions known as member
functions. The code and data that constitute a class are called members of the class.
2. Object
An object is an identifiable entity with specific characteristics and behavior. An object is said to be
an instance of a class. Defining an object is similar to defining a variable of any data type. Space is
set aside for it in memory.
3. Encapsulation
Encapsulation is a programming mechanism that binds together code and the data it manipulates, and that
keeps both safe from outside interference and misuse. C++‘s basic unit of encapsulation is the class.
Within a class, code or data or both may be private to that object or public. Private code or data is known
to and accessible by only another part of the object. That is, private code or data cannot be accessed by a
piece of the program that exists outside the object. When code or data is public, other parts of your
program can access it even though it is defined within an object. Typically, the public parts of an object
are used to provide a controlled interface to the private elements of the object. This insulation of the data
from direct access by the program is called data hiding.
4. Data abstraction
In object oriented programming, each object will have external interfaces through which it can be
made use of. There is no need to look into its inner details. The object itself may be made of many
smaller objects again with proper interfaces. The user needs to know the external interfaces only to
make use of an object. The internal details of the objects are hidden which makes them abstract. The
technique of hiding internal details in an object is called data abstraction.
5. Inheritance
Inheritance is the mechanism by which one class can inherit the properties of another. It allows a
hierarchy of classes to be build, moving from the most general to the most specific. When one class
is inherited by another, the class that is inherited is called the base class. The inheriting class is called
the derived class. In general, the process of inheritance begins with the definition of a base class. The
base class defines all qualities that will be common to any derived class. . In OOPs, the concept of
inheritance provides the idea of reusability. In essence, the base class represent the most general
description of a set of traits. The derived class inherits those general traits and adds properties that are
specific to that class.
6. Polymorphism
Polymorphism (from the Greek, meaning ―many forms‖) is a feature that allows one interface to be
used for a general class of actions. The specific action is determined by the exact nature of the
situation. The concept of polymorphism is often expressed by the phrase ―one interface, multiple
methods.‖ This means that it is possible to design a generic interface to a group of related activities.
This helps reduce complexity by allowing the same interface to be used to specify a general class of
action. It is the compiler‘s job to select the specific action as it applies to each situation.
Polymorphism
C++
C++ is an object oriented programming language. It was developed by Bjarne Stroustrup in 1979 at
Bell Laboratories in Murray Hill, New Jersey. He initially called the new language "C with Classes."
However, in 1983 the name was changed to C++.
C++ is a superset of C. Stroustrup built C++ on the foundation of C, including all of C‘s features,
attributes, and benefits. Most of the features that Stroustrup added to C were designed to support
object-oriented programming .These features comprise of classes, inheritance, function overloading
and operator overloading. C++ has many other new features as well, including an improved
approach to input/output (I/O) and a new way to write comments.
C++ is used for developing applications such as editors, databases, personal file systems,
networking utilities, and communication programs. Because C++ shares C‘s efficiency, much high-
performance systems software is constructed using C++.
Lines beginning with a hash sign (#) are directives read and interpreted by what is known as the
preprocessor. They are special lines interpreted before the compilation of the program itself
begins. In this case, the directive #include <iostream.h>, instructs the preprocessor to include a
section of standard C++ code, known as header iostream that allows to perform standard input
and output operations, such as writing the output of this program to the screen.
The function named main is a special function in all C++ programs; it is the function called when the
program is run. The execution of all C++ programs begins with the main function, regardless of
where the function is actually located within the code.
The open brace ({) indicates the beginning of main's function definition, and the closing brace (})
indicates its end.
The statement :
cout<< ―Simple C++ program without using class‖;
causes the string in quotation marks to be displayed on the screen. The identifier cout (pronounced as
c out) denotes an object. It points to the standard output device namely the console monitor. The
operator << is called insertion operator. It directs the string on its right to the object on its left.
The program ends with this statement:
return 0;
This causes zero to be returned to the calling process (which is usually the operating system).
Returning zero indicates that the program terminated normally. Abnormal program termination should
be signaled by returning a nonzero value.
Identifiers
An identifier is a name assigned to a function, variable, or any other user-defined item. Identifiers
can be from one to several characters long.
Data types
Data type defines size and type of values that a variable can store along with the set of operations
that can be performed on that variable. C++ provides built-in data types that correspond to integers,
characters, floating-point values, and Boolean values. There are the seven basic data types in C++
as shown below:
Type Meaning
char(character) holds 8-bit ASCII characters
wchar_t(Wide character) holds characters that are part of large character sets
int(Integer) represent integer numbers having no fractional part
double(Double floating point) Stores real numbers in the range from 1.7x10
–308 to1.7x10308 with a precision of 15 digits.
bool(Boolean) can have only two possible values: true and false.
Void Valueless
C++ allows certain of the basic types to have modifiers preceding them. A modifier alters the
meaning of the base type so that it more precisely fits the needs of various situations. The data
type modifiers are: signed, unsigned, long and short
This figure shows all combinations of the basic data types and modifiers along with their size
and range for a 16-bit word machine
Variable
A variable is a named area in memory used to store values during program execution. Variables
are run time entities. A variable has a symbolic name and can be given a variety of values. When
a variable is given a value, that value is actually placed in the memory space assigned to the
variable. All variables must be declared before they can be used. The general form of a
declaration is:
type variable_list;
Here, type must be a valid data type plus any modifiers, and variable_list may consist of one or
more identifier names separated by commas. Here are some declarations:
int i,j,l;
short int si;
unsigned int ui;
double balance, profit, loss;
Constants
Constants refer to fixed values that the program cannot alter. Constants can be of any of the basic
data types. The way each constant is represented depends upon its type. Constants are also called
literals. We can use keyword const prefix to declare constants with a specific type as follows:
const type variableName = value;
e.g,
const int LENGTH = 10;
Operator
An operator is a symbol that tells the compiler to perform specific mathematical or logical
manipulations. C++ is rich in built-in operators. Generally, there are six type of operators:
Arithmetical operators, Relational operators, Logical operators, Assignment operators, Conditional
operators, Comma operator.
Arithmetical operators
Arithmetical operators +, -, *, /, and % are used to performs an arithmetic (numeric) operation.
Operator Meaning
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
You can use the operators +, -, *, and / with both integral and floating-point data types. Modulus or
remainder % operator is used only with the integral data type.
Relational operators
The relational operators are used to test the relation between two values. All relational operators are
binary operators and therefore require two operands. A relational expression returns zero when the
relation is false and a non-zero when it is true. The following table shows the relational operators.
Logical operators
The logical operators are used to combine one or more relational expression. The logical operators are
Operators Meaning
|| OR
&& AND
! NOT
Assignment operator
The assignment operator '=' is used for assigning a variable to a value. This operator takes the
expression on its right-hand-side and places it into the variable on its left-hand-side. For example:
m = 5;
The operator takes the expression on the right, 5, and stores it in the variable on the left, m.
x = y = z = 32;
This code stores the value 32 in each of the three variables x, y, and z. In addition to standard
assignment operator shown above, C++ also support compound assignment operators.
Pre-decrement: ––variable
Post-decrement: variable––
In Prefix form first variable is first incremented/decremented, then evaluated
In Postfix form first variable is first evaluated, then incremented / decremented.
Conditional operator
The conditional operator ?: is called ternary operator as it requires three operands. The format of the
conditional operator is :
Conditional_ expression ? expression1 : expression2;
If the value of conditional expression is true then the expression1 is evaluated, otherwise expression2
is evaluated.
int a = 5, b = 6;
big = (a > b) ? a : b;
The condition evaluates to false, therefore big gets the value from b and it becomes 6.
Typecasting
Typecasting is the concept of converting the value of one type into another type. For example, you
might have a float that you need to use in a function that requires an integer.
Implicit conversion
Almost every compiler makes use of what is called automatic typecasting. It automatically converts
one type into another type. If the compiler converts a type it will normally give a warning. For
example this warning: conversion from ‗double‘ to ‗int‘, possible loss of data. The problem with this
is, that you get a warning (normally you want to compile without warnings and errors) and you are
not in control. With control we mean, you did not decide to convert to another type, the compiler did.
Also the possible loss of data could be unwanted.
Explicit conversion
The C++ language have ways to give you back control. This can be done with what is called an
explicit conversion.
Type Conversion
The Type Conversion is that which automatically converts the one data type into another but
remember we can store a large data type into the other. For example we can't store a float
into int because a float is greater than int.
When a user can convert the one data type into then it is called as the type
casting. The type Conversion is performed by the compiler but a casting is done by the
user for example converting a float into int. When we use the Type Conversion then it is
called the promotion. In the type casting when we convert a large data type into another
then it is called as the demotion. When we use the type casting then we can loss some data.
Control Structures
Control structures allows to control the flow of program‘s execution based on certain conditions C++
supports following basic control structures:
1) Selection Control structure
2) Loop Control structure
A.1) If Statement:
The syntax of an if statement in C++ is:
if(condition)
{
// statement(s) will execute if the condition is true
}
If the condition evaluates to true, then the block of code inside the if statement will be executed. If it
evaluates to false, then the first set of code after the end of the if statement (after the closing curly
brace) will be executed.
Flowchart showing working of if statement
// A Program to find whether a given number is even or odd using if … else statement
#include<iostream.h>
#include<conio.h>
int main()
{ int n;
cout<<‖enter number‖;
cin>>n;
if(n%2==0)
cout<<‖Even number‖;
else
cout<<‖Odd number‖;
return 0;
If the condition evaluates to true, then the if block of code will be executed, otherwise else block
of code will be executed.
Flowchart
B) Switch
C++ has a built-in multiple-branch selection statement, called switch, which successively tests the
value of an expression against a list of integer or character constants. When a match is found, the
statements associated with that constant are executed. The general form of the switch statement is:
switch (expression) {
case constant1:
statement sequence
break;
case constant2:
statement sequence
break;
case constant3:
statement sequence
break;
.
.
default
statement sequence
}
The expression must evaluate to a character or integer value. Floating-point expressions, for example,
are not allowed. The value of expression is tested, in order, against the values of the constants
specified in the case statements. When a match is found, the statement sequence associated with that
case is executed until the break statement or the end of the switch statement is reached. The default
statement is executed if no matches are found. The default is optional and, if it is not present, no
action takes place if all matches fail.
The break statement is one of C++'s jump statements. You can use it in loops as well as in the switch
statement. When break is encountered in a switch, program execution "jumps" to the line of code
following the switch statement.
Flowchart
while loop
A while loop statement repeatedly executes a target statement as long as a given condition is true. It is
an entry-controlled loop.
The syntax of a while loop in C++ is:
while(condition){
statement(s);
}
Here, statement(s) may be a single statement or a block of statements. The condition may be any
expression, and true is any non-zero value. The loop iterates while the condition is true. After
each execution of the loop, the value of test expression is changed. When the condition becomes
false, program control passes to the line immediately following the loop.
Flowchart
#include<conio.h>
int main(){
int i=1;
while(i<=100){
cout<<i ;
i++;
return 0;
#include<conio.h>
int main( ){
int i=1;
do
{cout<<i ; i++;
} while(i<=100);
return 0;
for Loop
A for loop is a repetition control structure that allows you to efficiently write a loop that needs
to execute a specific number of times. The syntax of a for loop in C++ is:
for ( init; condition; increment ){
statement(s);
}
3. After the body of the for loop executes, the flow of control jumps back up to the increment
statement. This statement allows you to update any C++ loop control variables. This statement can
be left blank, as long as a semicolon appears after the condition.
4. The condition is now evaluated again. If it is true, the loop executes and the process repeats itself
(body of loop, then increment step, and then again condition). After the condition becomes false,
the for loop terminates.
Flow Diagram
#include<conio.h>
int main() {
int i ;
for (i=1;i<=100;i++)
{
cout<<i ;
return 0;
C++ Input/Output
C++ I/O operation is using the stream concept. Stream is the sequence of bytes or
flow of data. It makes the performance fast.
If bytes flow from main memory to device like printer, display screen, or a network
connection, etc, this is called as output operation.
If bytes flow from device like printer, display screen, or a network connection, etc to
main memory, this is called as input operation.
<iostream> It is used to define the cout, cin and cerr objects, which correspond to
standard output stream, standard input stream and standard error
stream, respectively.
<iomanip> It is used to declare services useful for performing formatted I/O, such
as setprecision and setw.
#include <iostream>
using namespace std;
int main( ) {
char ary[] = "Welcome to C++ tutorial";
cout << "Value of ary is: " << ary << endl;
}
#include <iostream>
using namespace std;
int main( ) {
int age;
cout << "Enter your age: ";
cin >> age;
cout << "Your age is: " << age << endl;
}
Output:
#include <iostream>
using namespace std;
int main( ) {
cout << "C++ Tutorial";
cout << " Javatpoint"<<endl;
cout << "End of line"<<endl;
}
Output:
Manipulators
Manipulators are operators used in C++ for formatting output. The data is manipulated by the
programmer‗s choice of display.
There are numerous manipulators available in C++. Some of the more commonly used manipulators
are provided here below:
endl Manipulator:
This manipulator has the same functionality as the ‗\n‗ newline character.
For example:
Exforsys
Training
setw Manipulator:
This manipulator sets the minimum field width on output. The syntax is:
setw(x)
Here setw causes the number or string that follows it to be printed within a field of x characters wide and x
is the argument set in setw manipulator. The header file that must be included while using setw
manipulator is <iomanip.h>
#include <iostream.h>
#include <iomanip.h>
void main( )
{
int x1=12345,x2= 23456, x3=7892;
cout << setw(8) << ‖Exforsys‖ << setw(20) <<
‖Values‖ << endl
1. setw(8) << ―E1234567‖ << setw(20)<< x1 << end
2. setw(8) << ―S1234567‖ << setw(20)<< x2 << end
3. setw(8) << ―A1234567‖ << setw(20)<< x3 << end;
}
The output of the above example is:
setw(8) setw(20)
Exforsys Values
E1234567 12345
S1234567 23456
A1234567 7892
setfill Manipulator:
This is used after setw manipulator. If a value does not entirely fill a field, then the character specified in the
setfill argument of the manipulator is used for filling the fields.
#include <iostream.h>
#include <iomanip.h>
void main( )
{
cout << setw(10) << setfill(‗$‗) << 50 << 33 << endl;
}
$$$$$$$$5033
This is because the setw sets 10 width for the field and the number 50 has only 2 positions in it. So the
remaining 8 positions are filled with $ symbol which is specified in the setfill argument.
setprecision Manipulator:
The setprecision Manipulator is used with floating point numbers. It is used to set the number of digits
printed to the right of the decimal point. This may be used in two forms:
fixed
scientific
These two forms are used when the keywords fixed or scientific are appropriately used before the
setprecision manipulator.
The keyword fixed before the setprecision manipulator prints the floating point number in
fixed notation.
The keyword scientific before the setprecision manipulator prints the floating point number
in scientific notation.
#include <iostream.h>
#include <iomanip.h>
void main( )
{
float x = 0.1;
cout << fixed << setprecision(3) << x << endl;
cout << sceintific << x << endl;
}
0.100
1.000000e-001
The first cout statement contains fixed notation and the setprecision contains argument 3. This means that
three digits after the decimal point and in fixed notation will output the first cout statement as 0.100. The
second cout produces the output in scientific notation. The default value is used since no setprecision
value is provided.
The previous section gave an example of a technique for implementing a manipulator with one parameter:
the technique used to implement the standard manipulators in iostreams. However, this is not the only way
to implement a manipulator with parameters. In this section, we examine other techniques. Although all
explanations are in terms of manipulators with one parameter, it is easy to extend the techniques to
manipulators with several parameters.
Let's start right at the beginning: what is a manipulator with a parameter?
A manipulator with a parameter is an object that can be inserted into or extracted from a stream in an
expression like:
With this inserter defined, the expression std::cout << Manip(x); is equivalent to a call to the shift operator
sketched above; that is, operator<<(std::cout, Manip(x)). Note that the name of the operator<<() does not
need to be qualified with the name of any namespace. Since the operator will typically be defined in the
same namespace as one of its arguments, it is in those namespaces that the operator definition will be sought
to resolve the call.
Assuming that a side effect is created by an associated function fmanipT, the manipulator must call the
associated function with its respective argument(s). Hence it must store the associated function together with
its argument(s) for a call from inside the shift operator.
The associated function fmanipT can be a static member or a namespace-scope function, or even a member
function of type manipT, for example.
In the inserter above, we've assumed that the associated function fmanipT is a static or a non-member
function, and that it takes exactly one argument. Generally, the manipulator type manipT might look like
this:
template <class FctPtr, class Arg1, class Arg2, ...>
class manipT
{
public:
manipT(FctPtr, Arg1, Arg2, ...);
private:
FctPtr fp_;
Arg1 arg1_;
Arg2 arg2_;
};
Note that this is only a suggested manipulator, however. In principle, you can define the manipulator type
in any way that makes the associated side effect function and its arguments available for a call from inside
the respective shift operators for the manipulator type. We show other examples of such manipulator types
later in this chapter; for instance, a manipulator type called smanip mentioned by the C++ standard. It is
an implementation-defined function type returned by the standard manipulators. See the Apache C++
Standard Library Reference Guide for details.
Returning now to the example above, the manipulator object provided as an argument to the overloaded
shift operator is obtained by Manip(x), which has three possible solutions:
1. Manip(x) is a function call. In this case, Manip would be the name of a function that takes an argument
of type x and returns a manipulator object of type manipT; that is, Manip is a function with the
following signature:
manipT Manip (X x);
Manip(x) is a constructor call. In this case, Manip would be the name of a class with a constructor that
takes an argument of type X and constructs a manipulator object of type Manip; that is, Manip and
manipT would be identical:
struct Manip {
Manip(X x);
};
Manip(x) is a call to a function object. In this case, Manip would be an object of a class M, which
defines a function call operator that takes an argument of type x and returns a manipulator object
of type manipT:
struct M {
manipT operator()(X x);
} Manip;
Solutions 1.) and 2.) are semantically different from solution 3.). In solution 1.), Manip is a function and
therefore need not be created by the user. In solution 2.), Manip is a class name and an unnamed temporary
object serves as manipulator object. In solution 3.), however, the manipulator object Manip must be
explicitly created by the user. Hence the user has to write:
manipT Manip;
which is somewhat inconvenient because it forces the user to know an additional name, the manipulator
type manipT, and to create the manipulator object Manip. An alternative could be to provide Manip as a
static or a global object at the user's convenience, but this approach would introduce the well-known order-
of-initialization problems for global
and static objects. For these reasons, solution 3.) is useful if the manipulator has state; that is, if it stores
additional data like a manipulator, let's call it lineno, which provides the next line number each time it is
inserted.
For any of the three solutions just discussed, there is also a choice of associated functions. The
associated function fmanipT can be either:
3) A static or a namespace-scope function;
4) A static member function;
5) A virtual member function.
Among these choices, b.), the use of a static member function, is the preferable in an object-oriented program
because it permits encapsulation of the manipulator together with its associated function. This is particularly
recommended if the manipulator has state, as in solution 3.), where the manipulator is a function object, and
the associated function has to access the manipulator's state. Using c.), a virtual member function, introduces
the overhead of a virtual function call each time the manipulator is inserted or extracted. It is useful if the
manipulator has state, and the state needs to be modified by the associated manipulator function. A static
member function would only be able to access the manipulator's static data; a non-static member function,
however, can access the object-specific data.
Array
In C programming, one of the frequently problem is to handle similar types of data. For example:
if the user wants to store marks of 500 students, this can be done by creating 500 variables
individually but, this is rather tedious and impracticable. These types of problem can be handled
in C programming using arrays.
An array in C Programing can be defined as number of memory locations, each of which can
store the same data type and which can be references through the same variable name. It is a
collective name given to a group of similar quantities. These similar quantities could be marks of
500 students, number of chairs in university, salaries of 300 employees or ages of 250 students.
Thus we can say array is a sequence of data item of homogeneous values (same type). These
values could be all integers, floats or characters etc.
4. One-dimensional arrays.
5. Multidimensional arrays.
data_type array_name[array_size];
Here “data_type” is the type of the array we want to define, “array_name” is the name given to
the array and “array_size” is the size of the array that we want to assign to the array. The array
size is always mentioned inside the “*+”.
For example:
Int age[5];
Initializing Arrays
Initializing of array is very simple in c programming. The initializing values are enclosed within the curly
braces in the declaration and placed following an equal sign after the array name. Here is an example
which declares and initializes an array of five elements of type int. Array can also be initialized after
declaration. Look at the following code, which demonstrate the declaration and initialization of an array.
int age[5]={2,3,4,5,6};
int age[]={2,3,4,5,6};
In this case, the compiler determines the size of array by calculating the number of elements of an array.
For example:
scanf("%d",&age[2]);
//statement to insert value in the third element of array age[]
printf("%d",age[2]);
//statement to print third element of an array.
Arrays can be accessed and updated using its index.An array of n elements, has indices ranging from 0
to n-1. An element can be updated simply by assigning
A[i] = x;
A great care must be taken in dealing with arrays. Unlike in Java, where array index out of bounds
exception is thrown when indices go out of the 0..n-1 range, C arrays may not display any warnings if out
of bounds indices are accessed. Instead,compiler may access the elements out of bounds, thus leading
to critical run time errors.
#include <stdio.h>
int main(){
int i,n;
int marks[n];
int sum=0;
for(i=0;i<n;i++){
printf("Enter marks of student%d: ",i+1);
scanf("%d",&marks[i]); //saving the marks in array
sum+=marks[i];
}
return 0;
Output :
Sum of marks = 50
Multidimensional Arrays:
C programming language allows the user to create arrays of arrays known as multidimensional arrays.
To access a particular element from the array we have to use two subscripts one for row number and
other for column number. The notation is of the form array [i] [j] where i stands for row subscripts and j
stands for column subscripts. The array holds i*j elements. Suppose there is a multidimensional array
array[i][j][k][m]. Then this array can hold i*j*k*m numbers of data. In the same way, the array of any
dimension can be initialized in C programming. For example :
int smarks[3][4];
Here, smarks is an array of two dimension, which is an example of multidimensional array. This array
has 3 rows and 4columns. For better understanding of multidimensional arrays, array elements of above
example can be as below:
Make sure that you remember to put each subscript in its own, correct pair of brackets. All three
examples below are wrong.
This program asks user to enter the size of the matrix (rows and column) then, it asks the user to enter
the elements of two matrices and finally it adds two matrix and displays the result.
int main(){
int r,c;
int a[r][c];
int b[r][c];
int sum[r][c;
for(int i=0;i<r;++i){
for(int j=0;j<c;++j){
printf("Enter element a%d%d: ",i+1,j+1);
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<r;++i)
for(int j=0;j<c;++j)
sum[i][j]=a[i][j]+b[i][j];
return 0;
Program Output:
Enter number of rows (between 1 and 100): 3
Enter number of rows (between 1 and 100): 2
8 -11
6) 5
int A[n];
then A is considered a static array and memory is allocated from the run time stack for A. When A goes
out of scope, the memory is deallocated and A no longer can be referenced. C allows dynamic
declaration of an array as follows:
The above code declares a memory block of size n*sizeof(int) that can be accessed using the pointer A.
For example, A can be initialized as follows:
int i;
for (i=0; i<n; i++)
A[i] = 0;
Note that although A was declared as a pointer, A can be treated as an array. The difference between
int A[10] and int* A = malloc(10*sizeof(int)) is that latter is assigned memory in the dynamic heap (and
hence must be managed by the programmer) and former is assigned memory from the run time stack
(and hence managed by the compiler). Static arrays are used when we know the amount of bytes in array
at compile time while the dynamic array is used where we come to know about the size on run time.
Arrays can also be initialized using the calloc() functions instead of the the malloc(). Calloc function is
used to reserve space for dynamic arrays. Has the following form.
array =(cast-type*)calloc(n,element-size);
Number of elements in the first argument specifies the size in bytes of one element to the second
argument. A successful partitioning, that address is returned, NULL is returned on failure. For example,
an int array of 10 elements can be allocated as follows.
int * array = (int *) calloc (10, sizeof (int));
In C, Arrays can be passed to functions using the array name. Array name is a const pointer to the array.
both one-dimensional and multi-dimensional array can be passed to function as argument. Individual
element is passed to function using pass by value. Original Array elements remain unchanged, as the
actual element is never passed to function. Thus function body cannot modify original value in this case.
If we have declared an array ‘array *5+‘ then instead of passing individual elements, a for loop is useful
in this case to pass all 5 elements of the array.
Output
4
Single element of an array can be passed in similar manner as passing variable to a function.
C program to pass an array containing age of person to a function. This function will return
average age and display the average age in main function.
#include <stdio.h>
float average(float a[]);
int main(){
float avg, c[]={23.4, 55, 22.6, 3, 40.5, 18};
avg=average(c); /* Only name of array is passed as argument. */
printf("Average age=%.2f",avg);
return 0;
}
float average(float a[]){
int i;
float avg, sum=0.0;
for(i=0;i<6;++i){
sum+=a[i];
}
avg =(sum/6);
return avg;
}
Output
Average age=27.08
scanf("%d",&c[i][j]);
}
Function(c); /* passing multi-dimensional array to function */
return 0;
}
void Function(int c[2][2]){
/* Instead to above line, void Function(int c[][2]){ is also valid
*/ int i,j;
printf("Displaying:\n");
for(i=0;i<2;++i)
for(j=0;j<2;++j)
printf("%d\n",c[i][j]);
Output
Enter 4 numbers:
2
3
4
5
Displaying:
2
3
4
5
Pointers
A variable in a program is something with a name, the value of which can vary. The way the compiler
and linker handles this is that it assigns a specific block of memory within the computer to hold the value
of that variable. The size of that block depends on the range over which the variable is allowed to vary.
For example, on 32 bit PC's the size of an integer variable is 4 bytes. On older 16 bit PCs integers were 2
bytes. In C the size of a variable type such as an integer need not be the same on all types of machines.
We have integers, long integers and short integers which you can read up on in any basic text on C. This
document assumes the use of a 32 bit system with 4 byte integers.
When we declare a variable we inform the compiler of two things, the name of the variable and the
type of the variable. For example, we declare a variable of type integer with the name k by writing:
int k;
On seeing the "int" part of this statement the compiler sets aside 4 bytes of memory to hold the value of
the integer. It also sets up a symbol table. In that table it adds the symbol k and the relative address in
memory where those 4 bytes were set aside.
k = 2;
We expect that, at run time when this statement is executed, the value 2 will be placed in that memory
location reserved for the storage of the value of k. In C we refer to a variable such as the integer k as an
"object".
In a sense there are two "values" associated with the object k. One is the value of the integer stored
there and the other the "value" of the memory location, i.e., the address of k. Some texts refer to
these two values with the nomenclature rvalue.
int j, k;
k = 2;
j = 7; <-- line 1
k = j; <-- line 2
Here the compiler interprets the j in line 1 as the address of the variable j and creates code to copy the
value 7 to that address. In line 2, however, the j is interpreted as its rvalue. That is, here the j refers to
the value stored at the memory location set aside for j. So,7 is copied to the address designated to k.
In C when we define a pointer variable we do so by preceding its name with an asterisk. We also give our
pointer a type which, in this case, refers to the type of data stored at the address we will be storing in
our pointer. For example, consider the variable declaration:
int *ptr;
ptr is the name of our variable, the '*' informs the compiler that we want a pointer variable, i.e. to set
aside however many bytes is required to store an address in memory. The int says that we intend to use
our pointer variable to store the address of an integer. Such a pointer is said to "point to" an integer. A
pointer initialized in this manner is called a "null" pointer.
The actual bit pattern used for a null pointer may or may not evaluate to zero since there is no value
assigned to it. Thus, setting the value of a pointer using the NULL macro, as with an assignment
statement such as ptr = NULL, guarantees that the pointer has become a null pointer.
Suppose now that we want to store in ptr the address of our integer variable k. To do this we use the
unary & operator and write:
ptr = &k;
*ptr = 7;
will copy 7 to the address pointed to by ptr. Thus if ptr "points to" (contains the address of) k, the above
statement will set the value of k to 7. That is, when we use the '*' this way we are referring to the value
of that which ptr is pointing to, not the value of the pointer itself.
printf("%d\n",*ptr);
to print to the screen the integer value stored at the address pointed to by ptr.
int A[10];
can be accessed using its pointer representation. The name of the array A is a constant pointer to the
first element of the array. So A can be considered a const int*. Since A is a constant pointer, A = NULL
would be an illegal statement. Arrays and pointers are synonymous in terms of how they use to access
memory. But, the important difference between them is that, a pointer variable can take different
addresses as value whereas, in case of array it is fixed.
Int age[5];
Here ‘age’ points
to the first
element of the
age[0] age[1] age[2] age[3] age[4]
In C , name of the array always points to the first element of an array. Here, address of first element of
an array is &age[0]. Also, age represents the address of the pointer where it is pointing. Hence, &age[0]
is equivalent to age. Note, value inside the address &age[0] and address age are equal. Value in
address &age[0] is age[0] and value in address age is *age. Hence, age[0] is equivalent to *age.
C arrays can be of any type. We define array of ints, chars, doubles etc. We can also define an array of
pointers as follows. Here is the code to define an array of n char pointers or an array of strings.
char* A[n];
each cell in the array A[i] is a char* and so it can point to a character. Now if you would like to assign
a string to each A[i] you can do something like this.
Again this only allocates memory for a string and you still need to copy the characters into this string.
So if you are building a dynamic dictionary (n words) you need to allocate memory for n char*’s and
then allocate just the right amount of memory for each string.
In C, you can declare an array and can use pointer to alter the data of an array. This program declares
the array of six element and the elements of that array are accessed using pointer, and returns the sum.
//Program to find the sum of six numbers with arrays and pointers.
#include <stdio.h>
int main(){
int i,class[6],sum=0;
printf("Enter 6 numbers:\n");
for(i=0;i<6;++i){
scanf("%d",(class+i)); // (class+i) is equivalent to &class[i]
sum += *(class+i); // *(class+i) is equivalent to class[i]
}
printf("Sum=%d",sum);
return 0;
}
Output
Enter 6 numbers:
2
3
4
5
3
4
Sum=21
Pointer Array
A pointer is a place in memory that keeps address An array is a single, pre allocated chunk of
of another place inside contiguous elements (all of the same type), fixed
in size and location.
th
Allows us to indirectly access variables. In other Expression a[4] refers to the 5 element of the
words, we can talk about its address rather than array a.
its value
int num[] = { 2, 4, 5}
Pointer is dynamic in nature. The memory They are static in nature. Once memory is
allocation can be resized or freed later. allocated , it cannot be resized or freed
dynamically
Unit-II
Functions and Classes
C++ Functions
A function groups a number of program statements into a unit and gives it a name. This unit can then
be invoked from other parts of the program. The function‘s code is stored in only one place in
memory, even though the function is executed many times in the course of the program‘s execution.
Functions help to reduce the program size when same set of instructions are to be executed again and
again. A general function consists of three parts, namely, function declaration (or prototype),
function definition and function call.
Function definition
The function definition is the actual body of the function. The function definition consists of two parts
namely, function header and function body.
The general form of a C++ function definition is as follows:
return_type function_name( parameter list )
{ body of the function }
Here, Return Type: A function may return a value. The return_type is the data type of the value
the function returns. Some functions perform the desired operations without returning a value. In
this case, the return_type is the keyword void.
Function Name: This is the actual name of the function.
Parameters: A parameter is like a placeholder. When a function is invoked, you pass a value to
the parameter. This value is referred to as actual parameter or argument. The parameter list refers to
the type, order, and number of the parameters of a function. Parameters are optional; that is, a
function may contain no parameters.
Function Body: The function body contains a collection of statements that define what the function
does.
Calling a Function
To use a function, you will have to call or invoke that function. To call a function, you simply need
to pass the required parameters along with function name, and if function returns a value, then you
can store returned value.
A c++ program calculating factorial of a number using functions
#include<iostream.h>
#include<conio.h>
int main(){
int no, f;
cin>>no;
return 0;
{ int i , fact=1;
for(i=1;i<=n;i++){
fact=fact*i;
return fact;
Inline Functions
An inline function is a function that is expanded inline at the point at which it is invoked, instead of
actually being called. The reason that inline functions are an important addition to C++ is that they
allow you to create very efficient code. Each time a normal function is called, a significant amount of
overhead is generated by the calling and return mechanism. Typically, arguments are pushed onto the
stack and various registers are saved when a function is called, and then restored when the function
returns. The trouble is that these instructions take time. However, when a function is expanded inline,
none of those operations occur. Although expanding function calls in line can produce faster run
times, it can also result in larger code size because of duplicated code. For this reason, it is best to
inline only very small functions. inline is actually just a request, not a command, to the compiler. The
compiler can choose to ignore it. Also, some compilers may not inline all types of functions. If a
function cannot be inlined, it will simply be called as a normal function.
A function can be defined as an inline function by prefixing the keyword inline to the function header
as given below:
inline function header {
function body
}
// A program illustrating inline
function #include<iostream.h>
#include<conio.h>
return x;
else
return y;
int main( ) {
int a,b;
cin>>a>>b;
return 0;
inline functions are similar to macros (because the function code is expanded at the point of the call
at compile time), inline functions are parsed by the compiler, whereas macros are expanded by the
preprocessor. As a result, there are several important differences:
Inline functions follow all the protocols of type safety enforced on normal functions.
as any other function except that they include
Inline functions are specified using the same syntax
the inline keyword in the function declaration.
Expressions passed as arguments to inline functions are evaluated once.
In some cases, expressions passed as arguments to macros can be evaluated more than once.
at pre-compile time, you cannot use them for debugging, but you can use
macros are expanded
inline functions.
Reference variable
A reference variable is an alias, that is, another name for an already existing variable. Once a
reference is initialized with a variable, either the variable name or the reference name may be used to
refer to the variable. To declare a reference variable or parameter, precede the variable's name with
the &.The syntax for declaring a reference variable is:
datatype &Ref = variable name;
Example:
int main(){
return 0;
var2 is a reference variable to var1.Hence, var2 is an alternate name to var1.This code prints the value
of var2 exactly as that of var1.
Call by reference
Arguments can be passed to functions in one of two ways: using call-by-value or call-by-reference.
When using call-by-value, a copy of the argument is passed to the function. Call-by-reference passes
the address of the argument to the function. By default, C++ uses call-by-value.
Provision of the reference variables in c++ permits us to pass parameter to the functions by
reference. When we pass arguments by reference, the formal arguments in the called function
become aliases to the actual arguments in the calling function. This means that when the function is
working with its own arguments, it is actually working on the original data.
Example
#include <iostream.h>
#include<conio.h>
cout<< ―value of a :" << a <<‖ value of b :" << b << endl;
cout<<‖ value of a :" << a<< ―value of b :" << b << endl;
return 0;
void swap(int &x, int &y) { //function definition to swap the values.
int temp;
temp = x;
x = y;
y = temp;
Output:
Function Overloading
Function overloading is the process of using the same name for two or more functions. Each
redefinition of the function must use either different types of parameters or a different number of
parameters. It is only through these differences that the compiler knows which function to call in any
given situation. Overloaded functions can help reduce the complexity of a program by allowing
related operations to be referred to by the same name. To overload a function, simply declare and
define all required versions. The compiler will automatically select the correct version based upon
the number and/or type of the arguments used to call the function. Two functions differing only in
their return types cannot be overloaded.
Example
#include<iostream.h>
#include<conio.h>
int main(){
return 0;
return(a+b+c);
return(l+m);
return(p+q);
Default arguments
C++ allows a function to assign a parameter a default value when no argument corresponding to that
parameter is specified in a call to that function. The default value is specified in a manner
syntactically similar to a variable initialization. All default parameters must be to the right of any
parameters that don't have defaults. We cannot provide a default value to a particular argument in the
middle of an argument list. When you create a function that has one or more default arguments, those
arguments must be specified only once: either in the function's prototype or in the function's definition
if the definition precedes the function's first use.
Default arguments are useful if you don‘t want to go to the trouble of writing arguments that, for
example, almost always have the same value. They are also useful in cases where, after a program is
written, the programmer decides to increase the capability of a function by adding another
argument. Using default arguments means that the existing function calls can continue to use the old
number of arguments, while new function calls can use more.
Example
#include <iostream.h>
#include<conio.h>
return( a + b);
return 0;
Class
A class is a user defined data type. A class is a logical abstraction. It is a template that defines the
form of an object. A class specifies both code and data. It is not until an object of that class has been
created that a physical representation of that class exists in memory. When you define a class, you
declare the data that it contains and the code that operates on that data. Data is contained in instance
variables defined by the class known as data members, and code is contained in functions known as
member functions. The code and data that constitute a class are called members of the class. The
general form of class declaration is:
class class-name {
access-specifier:
data and functions
access-specifier:
data and functions
// ...
access-specifier:
data and functions
} object-list;
The object-list is optional. If present, it declares objects of the class. Here, access-specifier is one of
these three C++ keywords:
public private protected
By default, functions and data declared within a class are private to that class and may be accessed
only by other members of the class. The public access_specifier allows functions or data to be
accessible to other parts of your program. The protected access_specifier is needed only when
inheritance is involved.
Example:
#include<iostream.h>
#include<conio.h>
public:
// public members to
myclass void set_a(intnum);
int get_a( );
};
Object
An object is an identifiable entity with specific characteristics and behavior. An object is said to be
an instance of a class. Defining an object is similar to defining a variable of any data type: Space is
set aside for it in memory. Defining objects in this way means creating them. This is also called
instantiating them. Once a Class has been declared, we can create objects of that Class by using the
class Name like any other built-in type variable as shown:
className objectName
Example
void main( ) {
myclass ob1, ob2; //these are object of type myclass
The private members of a class cannot be accessed directly using the dot operator, but through the
member functions of that class providing data hiding. A member function can call another
member function directly, without using the dot operator.
C++ program to find sum of two numbers using classes
#include<iostream.h>
#include<conio.h>
class A{
int a,b,c;
public:
void sum(){
cin>>a>>b;
c=a+b;
cout<<"sum="<<c;
};
int main(){
A u;
u.sum();
getch();
return(0);
Here the class-name is the name of the class to which the function belongs. The scope resolution
operator (::) tells the compiler that the function func-name belongs to the class class-name. That is,
the scope of the function is restricted to the class-name specified.
Class myclass {
int a;
public:
void set_a(intnum); //member function declaration
int get_a( ); //member function declaration
};
//member function definition outside class using scope resolution operator
void myclass :: set_a(intnum)
{
a=num;
}
int myclass::get_a( ) {
return a;
}
Another use of scope resolution operator is to allow access to the global version of a variable. In many
situation, it happens that the name of global variable and the name of the local variable are same .In
this while accessing the variable, the priority is given to the local variable by the compiler. If we want
to access or use the global variable, then the scope resolution operator (::) is used. The syntax for
accessing a global variable using scope resolution operator is as follows:-
:: Global-variable-name
#include <iostream.h>
#include<conio.h>
class static_type {
public:
int main(){
static_type x;
x.show();
return 0;
Constructor:
A constructor is a special member function whose task is to initialize the objects of its class. It is
special because its name is same as the class name. The constructor is invoked whenever an object
of its associated class is created. It is called constructor because it construct the value data members
of the class. The constructor functions have some special characteristics.
They should be declared in the public section.
They are invoked automatically when the objects are created.
They do not have return types, not even void and therefore, they cannot return values.
They cannot be inherited, though a derived class can call the base class constructor.
Example:
#include< iostream.h>
#include<conio.h>
int a;
public:
void show( );
};
myclass :: myclass( ) {
a=10;
myclass :: show( ) {
cout<< a;
int main( ) {
ob.show( );
return0;
In this simple example the constructor is called when the object is created, and the
constructor initializes the private variable a to10.
Default constructor
The default constructor for any class is the constructor with no arguments. When no arguments are
passed, the constructor will assign the values specifically assigned in the body of the constructor. It
can be zero or any other value. The default constructor can be identified by the name of the class
followed by empty parentheses. Above program uses default constructor. If it is not defined
explicitly, then it is automatically defined implicitly by the system.
Parameterized Constructor
It is possible to pass arguments to constructors. Typically, these arguments help initialize an object
when it is created. To create a parameterized constructor, simply add parameters to it the way you
would to any other function. When you define the constructor's body, use the parameters to initialize
the object.
#include <iostream.h>
#include<conio.h>
class myclass {
int a, b;
public:
{a=i; b=j;}
};
int main() {
ob.show();
return 0;
C++ supports constructor overloading. A constructor is said to be overloaded when the same
constructor with different number of argument and types of arguments initializes an object.
Copy Constructors
The copy constructor is a constructor which creates an object by initializing it with an object of the
same class, which has been created previously. If class definition does not explicitly include copy
constructor, then the system automatically creates one by default. The copy constructor is used to:
Initialize one object from another of the same type.
Copy an object to pass it as an argument to a function.
Copy an object to return it from a function.
}
Here, obj is a reference to an object that is being used to initialize another object. The keyword const
is used because obj should not be changed.
Destructor
A destructor destroys an object after it is no longer in use. The destructor, like constructor, is a member
function with the same name as the class name. But it will be preceded by the character Tilde
(~).A destructor takes no arguments and has no return value. Each class has exactly one destructor. . If
class definition does not explicitly include destructor, then the system automatically creates one by
default. It will be invoked implicitly by the compiler upon exit from the program to clean up storage
that is no longer accessible.
// A Program showing working of constructor and
destructor #include<iostream.h>
#include<conio.h>
class Myclass{
public:
int x;
Myclass(){ //Constructor
x=10; }
~Myclass(){ //Destructor
cout<<‖Destructing….‖ ;
int main(){
cout<<ob1.x<<‖ ―<<ob2.x;
return 0; }
10 10
Destructing……..
Destructing……..
Operator overloading
There is another useful methodology in C++ called operator overloading. The language allows not only
functions to be overloaded, but also most of the operators, such as +, -, *, /, etc. As the name suggests,
here the conventional operators can be programmed to carry out more complex operations. This
overloading concept is fundamentally the same i.e. the same operators can be made to perform
different operations depending on the context. Such operators have to be specifically defined and
appropriate function programmed. When an operator is overloaded, none of its original meaning is
lost. It is simply that a new operation, relative to a specific class, is defined. For example, a class that
defines a linked list might use the + operator to add an object to the list. A class that implements a
stack might use the + to push an object onto the stack.
An operator function defines the operations that the overloaded operator will perform relative to
the class upon which it will work. An operator function is created using the keyword operator. The
general form of an operator function is
type classname::operator#(arg-list) { // operations
}
Here, the operator that you are overloading is substituted for the #, and type is the type of value
returned by the specified operation. Operator functions can be either members or nonmembers of a
class. Nonmember operator functions are often friend functions of the class.
These operators cannot be overloaded:- ., : :, .*, ?
The process of overloading involves the following steps:
Create a class that defines the data type that is to be used in the overloading operation.
Declare the operator function operator op() in the public part of the class.
Define the operator function to implement the required operations.
#include<iostream.h>
#include<conio.h>
class A {
int x,y,z;
public:
x=a;
y=b;
z=c;
void display() {
cout<<"\nx="<<x<<"\ny="<<y<<"\nz="<<z;
x=-x;
y=-y;
z=-z;
};
int main() {
A a;
a.getdata(2,3,4);
a.display();
-a;
a.display();
getch();
return 0;
#include<conio.h>
class A{
int x,y;
public:
void input() {
cin>>x>>y;
void display() {
cout<<"\nx="<<x<<"\ny="<<y<<"\nx+y="<<x+y;
};
A A :: operator+(A p) {
A t;
t.x=x + p.x;
t.y=y + p.y;
return t;
int main(){
a1.input();
a2.input();
a3.display();
getch();
return 0;
Unit-III
Inheritance, Polymorphism & Files
Inheritance
Inheritance is the mechanism by which one class can inherit the properties of another. It allows a
hierarchy of classes to be build, moving from the most general to the most specific. When one class
is inherited by another, the class that is inherited is called the base class. The inheriting class is called
the derived class. In general, the process of inheritance begins with the definition of a base class. The
base class defines all qualities that will be common to any derived class. In essence, the base class
represent the most general description of a set of traits. The derived class inherits those general traits
and adds properties that are specific to that class. When one class inherits another, it uses this general
form:
class derived-class-name : access base-class-name{
// ...
}
Here access is one of the three keywords: public, private, or protected. The access
specifier determines how elements of the base class are inherited by the derived class.
When the access specifier for the inherited base class is public, all public members of the base class
become public members of the derived class. If the access specifier is private, all public members of
the base class become private members of the derived class. In either case, any private members of
the base class remain private to it and are inaccessible by the derived class.
It is important to understand that if the access specifier is private, public members of the base
become private members of the derived class. If the access specifier is not present, it is private
by default.
The protected access specifier is equivalent to the private specifier with the sole exception that
protected members of a base class are accessible to members of any class derived from that base.
Outside the base or derived classes, protected members are not accessible. When a protected member
of a base class is inherited as public by the derived class, it becomes a protected member of the
derived class. If the base class is inherited as private, a protected member of the base becomes a
private member of the derived class. A base class can also be inherited as protected by a derived
class. When this is the case, public and protected members of the base class become protected
members of the derived class (of course, private members of the base remain private to it and are not
accessible by the derived class).
Program to illustrate concept of inheritance
#include<iostream.h>
#include<conio.h>
int x,y;
public:
void show() {
};
int a,b;
public:
void show2() {
};
int main() {
derived d;
getch();
return 0;
Types of Inheritances
Single Inheritance
The process in which a derived class inherits traits from only one base class, is called single
inheritance. In single inheritance, there is only one base class and one derived class. The derived class
inherits the behavior and attributes of the base class. However the vice versa is not true. The derived
class can add its own properties i.e. data members (variables) and functions. It can extend or use
properties of the base class without any modification to the base class. We declare the base class and
derived class as given below:
class base_class {
};
class derived_ class : visibility-mode base_ class {
};
#include<iostream.h>
#include<conio.h>
class base
int x,y;
public:
void show() {
//base class
};
int a,b;
public:
void show2() {
};
int main() {
derived d;
getch();
return 0;
Multiple Inheritance
The process in which a derived class inherits traits from several base classes, is called multiple
inheritance. In Multiple inheritance, there is only one derived class and several base classes.
We declare the base classes and derived class as given below:
class base_class1{
};
class base_class2{
};
class derived_ class : visibility-mode base_ class1 , visibility-mode base_ class2 {
};
Multilevel Inheritance
The process in which a derived class inherits traits from another derived class, is called Multilevel
Inheritance. A derived class with multilevel inheritance is declared as :
class base_class {
};
class derived_ class1 : visibility-mode base_ class {
};
class derived_ class 2: visibility-mode derived_ class1 {
};
Here, derived_ class 2 inherits traits from derived_ class 1 which itself inherits from base_class.
Hierarchical Inheritance
The process in which traits of one class can be inherited by more than one class is known as
Hierarchical inheritance. The base class will include all the features that are common to the
derived classes. A derived class can serve as a base class for lower level classes and so on.
Hybrid Inheritance
The inheritance hierarchy that reflects any legal combination of other types of inheritance is known
as hybrid Inheritance.
Polymorphism
C++ Virtual Function
A virtual function is a member function that is declared within a base class and redefined by a derived
class. In order to make a function virtual, you have to add keyword virtual in front of a function
definition. When a class containing a virtual function is inherited, the derived class redefines the
virtual function relative to the derived class. The virtual function within the base class defines the
form of the interface to that function. Each redefinition of the virtual function by a derived class
implements its operation as it relates specifically to the derived class. That is, the redefinition creates
a specific method. When a virtual function is redefined by a derived class, the keyword virtual is not
needed. A virtual function can be called just like any member function. However, what makes a
virtual function interesting, and capable of supporting run-time polymorphism, is what happens when
a virtual function is called through a pointer. When a base pointer points to a derived object that
contains a virtual function and that virtual function is called through that pointer, C++ determines
which version of that function will be executed based upon the type of object being pointed to by the
pointer. And this determination is made at run time. Therefore, if two or more different classes are
derived from a base class that contains a virtual function, then when different objects are pointed to by
a base pointer, different versions of the virtual function are executed.
// A simple example using a virtual function.
#include<iostream.h> #include<conio.h>
class base
{ public:
};
voidfunc( ) {
};
voidfunc( ) {
};
int main( ) {
base *p;
base ob;
derived1 d_ob1;
derived2 d_ob2;
p = &ob;
p = &d_ob1;
p = &d_ob2;
return 0;
The key part of this declaration is the setting of the function equal to 0. This tells the compiler that no
body exists for this function relative to the base class. When a virtual function is made pure, it forces
any derived class to override it. If a derived class does not, a compile-time error results. Thus,
making a virtual function pure is a way to guaranty that a derived class will provide its own
redefinition.
Abstract class
When a class contains atleast one pure virtual function, it is referred to as an abstract class. Since, an
abstract class contains atleast one function for which no body exists, it is, technically, an incomplete
type, and no objects of that class can be created. Thus, abstract classes exist only to be inherited. It is
important to understand, however, that you can still create a pointer to an abstract class, since it is
through the use of base class pointers that run-time polymorphism is achieved. (It is also possible to
have a reference to an abstract class.) .
C++ provides both the formatted and unformatted IO functions. In formatted or high-level IO, bytes
are grouped and converted to types such as int, double, string or user-defined types. In unformatted
or low-level IO, bytes are treated as raw bytes and unconverted. Formatted IO operations are
supported via overloading the stream insertion (<<) and stream extraction (>>) operators, which
presents a consistent public IO interface.
C++ provides supports for its I/O system in the header file< iostream>. Just as there are different
kinds of I/O (for example, input, output, and file access), there are different classes depending on the
type of I/O. The following are the most important stream classes:
Class istream :- Defines input streams that can be used to carry out formatted and unformatted input
operations. It contains the overloaded extraction (>>) operator functions. Declares input functions
such get(), getline() and read().
Class ostream :- Defines output streams that can be used to write data. Declares output functions
put and write().The ostream class contains the overloaded insertion (<<) operator function
When a C++ program begins, these four streams are automatically opened:
File Handling
File. The information / data stored under a specific name on a storage
device, is called a file.
Stream. It refers to a sequence of bytes.
Text file. It is a file that stores information in ASCII characters. In text
files, each line of text is terminated with a special character known as
EOL (End of Line) character or delimiter character. When this EOL
character is read or written, certain internal translations take place.
Binary file. It is a file that contains information in the same format as it is
held in memory. In binary files, no delimiters are used for a line and no
translations occur here.
Classes for file stream operation
ofstream: Stream class to write on files
ifstream: Stream class to read from files
fstream: Stream class to both read and write from/to files.
Opening a file
OPENING FILE USING
CONSTRUCTOR ofstream
fout(“results”); //output only
ifstream fin(“data”); //input only
OPENING FILE USING open()
Stream-object.open(“filename”,
mode)
ofstream ofile;
ofile.open(“data1”);
ifstream ifile;
ifile.open(“data2”);
File mode parameter Meaning
ios::app Append to end of file
ios::ate go to end of file on opening
ios::binary file open in binary mode
ios::in open file for reading only
ios::out open file for writing only
ios::nocreate open fails if the file does not exist
ios::noreplace open fails if the file already exist
ios::trunc delete the contents of the file if it exist
All these flags can be combined using the bitwise operator OR (|). For
example, if we want to open the file example.bin in binary mode to add data
we could do it by the following call to member function open():
fstream file;
file.open ("example.bin", ios::out | ios::app | ios::binary);
Closing File
fout.close();
fin.close();
INPUT AND OUTPUT OPERATION
put() and get() function
the function put() writes a single character to the associated stream. Similarly,
the function get() reads a single character form the associated stream.
example :
file.get(ch);
file.put(ch);
write() and read() function
write() and read() functions write and read blocks of binary data.
example:
file.read((char *)&obj, sizeof(obj));
file.write((char *)&obj, sizeof(obj));
ERROR HANDLING FUNCTION
FUNCTION RETURN VALUE AND MEANING
returns true (non zero) if end of file is encountered while reading;
eof() otherwise return false(zero)
fail() return true when an input or output operation has failed
returns true if an invalid operation is attempted or any
bad() unrecoverable error has occurred.
good() returns true if no error has occurred.
getch();
return 0;
}
Program to count number of characters.
#include<fstream.h>
#include<conio.h>
int main()
{
ifstream fin;
fin.open("out.txt");
clrscr();
char ch; int count=0;
while(!fin.eof())
{
fin.get(ch);
count++;
}
cout<<"Number of characters in file is "<<count;
fin.close();
getch();
return 0;
}
Program to count number of words
#include<fstream.h>
#include<conio.h>
int main()
{
ifstream fin;
fin.open("out.txt");
char word[30]; int count=0;
while(!fin.eof())
{
fin>>word;
count++;
}
cout<<"Number of words in file is "<<count;
fin.close();
getch();
return 0;
}
Program to count number of lines
#include<fstream.h>
#include<conio.h>
int main()
{
ifstream fin;
fin.open("out.txt");
char str[80]; int count=0;
while(!fin.eof())
{
fin.getline(str,80);
count++;
}
cout<<"Number of lines in file is "<<count;
fin.close();
getch();
return 0;
}
Program to copy contents of file to another file.
#include<fstream.h>
int main()
{
ifstream fin;
fin.open("out.txt");
ofstream fout;
fout.open("sample.txt");
char ch;
while(!fin.eof())
{
fin.get(ch);
fout<<ch;
}
fin.close();
return 0;
}
Basic Operation On Binary File In C++
class student
{
int admno;
char name[20];
public:
void getdata()
{
cout<<"\nEnter The admission no. ";
cin>>admno;
cout<<"\n\nEnter The Name of The Student
"; gets(name);
}
void showdata()
{
cout<<"\nAdmission no. : "<<admno;
cout<<"\nStudent Name : ";
puts(name);
}
int retadmno()
{
return admno;
}
};
fp2.write((char*)&obj,sizeof(obj));
fp2.close();
}
function to display records of file
void display()
{
student obj;
ifstream fp1;
fp1.open("student.dat",ios::binary);
while(fp1.read((char*)&obj,sizeof(obj)))
{
obj.showdata();
}
}
fp.close();
}
Function to search and display from binary file
void search (int n)
{
student obj;
ifstream fp1;
fp1.open("student.dat",ios::binary);
while(fp1.read((char*)&obj,sizeof(obj)))
{
if(obj.retadmno()==n)
obj.showdata();
}
fp1.close();
}
Function to delete a record
void deleterecord(int n)
{
student obj;
ifstream fp1;
fp1.open("student.dat",ios::binary);
ofstream fp2;
fp2.open("Temp.dat",ios::out|ios::binary);
while(fp1.read((char*)&obj,sizeof(obj)))
{
if(obj.retadmno!=n)
fp2.write((char*)&obj,sizeof(obj));
}
fp1.close();
fp2.close();
remove("student.dat");
rename("Temp.dat","student.dat");
}
Function to modify a record
void modifyrecord(int n)
{
fstream fp;
student obj;
int found=0;
fp.open("student.dat",ios::in|ios::out);
while(fp.read((char*)&obj,sizeof(obj)) && found==0)
{
if(obj.retadmno()==n)
{
obj.showdata();
cout<<"\nEnter The New Details of
student"; obj.getdata();
int pos=-1*sizeof(obj);
fp.seekp(pos,ios::cur);
fp.write((char*)&obj,sizeof(obj));
found=1;
}
}
fp.close();
}
UNIT-IV
Fundamental Data Structures
Introduction to Data Structure
Computer is an electronic machine which is used for data processing and manipulation.
When programmer collects such type of data for processing, he would require to store all of
them in computer’s main memory.
In order to make computer work we need to know o Representation of data in computer.
o Accessing of data.
o How to solve problem step by step.
For doing this task we use data structure.
DATA
STRUCTURE
PRIMITIVE NON
PRIMITIVE
Data types
A particular kind of data item, as defined by the values it can take, the programming
language used, or the operations that can be performed on it.
Stack: Stack is a data structure in which insertion and deletion operations are performed at one
end only. o The insertion operation is referred to as ‘PUSH’ and deletion operation is referred
to as ‘POP’ operation. o Stack is also called as Last in First out (LIFO) data structure.
Queue: The data structure which permits the insertion at one end and Deletion at another end,
known as Queue.
o End at which deletion is occurs is known as FRONT end and another end at which insertion
occurs is known as REAR end.
o Queue is also called as First in First out (FIFO) data structure.
Stacks Operations:
A stack is a container of objects that are inserted and removed according to the last-in first-out
(LIFO) principle. In the pushdown stacks only two operations are allowed: push the item into the
stack, and pop the item out of the stack. A stack is a limited access data structure - elements can be
added and removed from the stack only at the top. Push adds an item to the top of the stack, pop
removes the item from the top. A helpful analogy is to think of a stack of books; you can remove
only the top book, also you can add a new book on the top.
A stack may be implemented to have a bounded capacity. If the stack is full and does not contain
enough space to accept an entity to be pushed, the stack is then considered to be in an overflow state.
The pop operation removes an item from the top of the stack. A pop either reveals previously
concealed items or results in an empty stack, but, if the stack is empty, it goes into underflow state,
which means no items are present in stack to be removed.
Stack is an Abstract data structure (ADT) works on the principle Last In First Out (LIFO). The last
element add to the stack is the first element to be delete. Insertion and deletion can be takes place at
one end called TOP. It looks like one side closed tube.
The add operation of the stack is called push operation
The delete operation is called as pop operation.
Push operation on a full stack causes stack overflow.
Pop operation on an empty stack causes stack underflow.
SP is a pointer, which is used to access the top element of the stack.
If you push elements that are added at the top of the stack;
In the same way when we pop the elements, the element at the top of the stack is deleted.
Operations of stack:
There are two operations applied on stack they are
1. push
2. pop.
While performing push & pop operations the following test must be conducted on the stack.
1) Stack is empty or not
2) Stack is full or not
Push:
Push operation is used to add new elements in to the stack. At the time of addition first check the stack is
full or not. If the stack is full it generates an error message "stack overflow". Pop:
Pop operation is used to delete elements from the stack. At the time of deletion first check the stack is
empty or not. If the stack is empty it generates an error message "stack underflow".
Representation of a Stack using Arrays:
Let us consider a stack with 6 elements capacity. This is called as the size of the stack. The number of
elements to be added should not exceed the maximum size of the stack. If we attempt to add new
element beyond the maximum size, we will encounter a stack overflow condition. Similarly, you cannot
remove elements beyond the base of the stack. If such is the case, we will reach a stack underflow
condition.
When an element is taken off from the stack, the operation is performed by pop().
STACK: Stack is a linear data structure which works under the principle of last in first out. Basic
operations: push, pop, display.
1. PUSH: if (top==MAX), display Stack overflow else reading the data and making stack [top]
=data and incrementing the top value by doing top++.
2. POP: if (top==0), display Stack underflow else printing the element at the top of the stack and
decrementing the top value by doing the top.
3. DISPLAY: IF (TOP==0), display Stack is empty else printing the elements in the stack from
stack [0] to stack [top].
return stack.pop()
self.next = None
class Stack:
def isEmpty(self):
return True if self.root is None else False
def pop(self):
if (self.isEmpty()):
return float("-inf")
temp = self.root
self.root = self.root.next
popped = temp.data
return popped
def peek(self):
if self.isEmpty():
return float("-inf")
return self.root.data
4. In recursion, all intermediate arguments and return values are stored on the processor‟s stack.
5. During a function call the return address and arguments are pushed onto a stack and on return
they are popped off.
6. Depth first search uses a stack data structure to find an element from a graph.
Procedure:
2. a)If the scanned symbol is left parenthesis, push it onto the stack.
b) If the scanned symbol is an operand, then place directly in the postfix expression
(output).
c) If the symbol scanned is a right parenthesis, then go on popping all the items from
the stack and place them in the postfix expression till we get the matching left
parenthesis.
d) If the scanned symbol is an operator, then go on removing all the operators from the
stack and place them in the postfix expression, if and only if the precedence of the
operator which is on the top of the stack is greater than (or equal) to the precedence
of the scanned operator and push the scanned operator onto the stack otherwise,
push the scanned operator onto the stack.
Convert the following infix expression A + B * C – D / E * H into its equivalent postfix expression.
A A
+ A +
B AB +
* AB +*
C ABC -
- ABC*+ -
D ABC*+D -
/ ABC*+D -/
E ABC*+DE -/
* ABC*+DE/ -*
H ABC*+DE/H -*
End of The input is now empty. Pop the output symbols from the
string ABC*+DE/H*- stack until it is empty.
Source Code:
# Python program to convert infix expression to postfix
# An operator is encountered
else:
while(not self.isEmpty() and self.notGreater(i)):
self.output.append(self.pop())
self.push(i)
result= "".join(self.output)
print(result)
# Driver program to test above function
exp = "a+b*(c^d-e)^(f+g*h)-i"
obj = Conversion(len(exp))
obj.infixToPostfix(exp)
Procedure:
The postfix expression is evaluated easily by the use of a stack. When a number is seen, it is pushed onto
the stack; when an operator is seen, the operator is applied to the two numbers that are popped from the
stack and the result is pushed onto the stack.
6 6
5 6, 5
2 6, 5, 2
8 2 3 5 6, 5, 5, 8 Next 8 is pushed
Source Code:
self.capacity = capacity
# This array is used a
stack self.array = []
return int(self.pop())
Queue Operations:
A queue is a data structure that is best described as "first in, first out". A queue is another special kind of
list, where items are inserted at one end called the rear and deleted at the other end called the front. A
real world example of a queue is people waiting in line at the bank. As each person enters the bank, he or
she is "enqueued" at the back of the line. When a teller becomes available, they are "dequeued" at the
front of the line.
Let us consider a queue, which can hold maximum of five elements. Initially the queue is empty.
0 1 2 3 4
Q u e u e E mp t y
F RO NT = RE A R = 0
F R
0 1 2 3 4
11
REA R = REA R + 1 = 1
F RO NT = 0
F R
0 1 2 3 4
11 22
RE A R = RE A R + 1 = 2
F RO NT = 0
F R
Again insert another element 33 to the queue. The status of the queue is:
0 1 2 3 4
RE A R = RE A R + 1 = 3
11 22 33
F RO NT = 0
F R
Now, delete an element. The element deleted is the element at the front of the queue. So the status of the
queue is:
0 1 2 3 4
RE A R = 3
22 33
F RO NT = F R O NT + 1 = 1
F R
Again, delete an element. The element to be deleted is always pointed to by the FRONT pointer. So, 22
is deleted. The queue status is as follows:
0 1 2 3 4
RE A R = 3
33
F RO NT = F R O NT + 1 = 2
F R
Now, insert new elements 44 and 55 into the queue. The queue status is:
0 1 2 3 4
RE A R = 5
33 44 55
F RO NT = 2
F R
Next insert another element, say 66 to the queue. We cannot insert 66 to the queue as the rear crossed the
maximum size of the queue (i.e., 5). There will be queue full signal. The queue status is as follows:
0 1 2 3 4
RE A R = 5
33 44 55
F RO NT = 2
F R
Now it is not possible to insert an element 66 even though there are two vacant positions in the linear
queue. To over come this problem the elements of the queue are to be shifted towards the beginning of
the queue so that it creates vacant position at the rear end. Then the FRONT and REAR are to be
adjusted properly. The element 66 can be inserted at the rear end. After this operation, the queue status is
as follows:
0 1 2 3 4
RE A R = 4
33 44 55 66
F RO NT = 0
F R
This difficulty can overcome if we treat queue position with index 0 as a position that comes after
position with index 4 i.e., we treat the queue as a circular queue.
In order to create a queue we require a one dimensional array Q(1:n) and two variables front and rear.
The conventions we shall adopt for these two variables are that front is always 1 less than the actual
front of the queue and rear always points to the last element in the queue. Thus, front = rear if and only if
there are no elements in the queue. The initial condition then is front = rear = 0.
The various queue operations to perform creation, deletion and display the elements in a queue are as
follows:
Linked List Implementation of Queue: We can represent a queue as a linked list. In a queue data is
deleted from the front end and inserted at the rear end. We can perform similar operations on the two
ends of a list. We use two pointers front and rear for our linked queue implementation.
Source Code:
front = 0
rear = 0
mymax = 3
0 def createQueue():
queue = []
return queue
def isEmpty(queue):
return len(queue) == 0
1 def enqueue(queue,item):
queue.append(item)
1 def dequeue(queue):
if (isEmpty(queue)): return
"Queue is empty"
item=queue[0]
del queue[0]
return item
Enqueue") print("2
Dequeue")
print("3 Display")
print("4 Quit")
ch=int(input("Enter choice"))
if(ch==1):
item=input("enter item")
enqueue(queue, item)
rear = rear + 1
else:
print("Queue is full")
elif(ch==2):
print(dequeue(queue))
elif(ch==3):
print(queue)
else:
break
Applications of Queues:
2. When multiple users send print jobs to a printer, each printing job is kept in the printing queue.
Then the printer prints those jobs according to first in first out (FIFO) basis.
3. Breadth first search uses a queue data structure to find an element from a graph.
Time consuming: linear time to be spent in shifting the elements to the beginning of the queue.
Signaling queue full: even if the queue is having vacant position.
The round-robin (RR) scheduling algorithm is designed especially for time-sharing systems. It is similar
to FCFS scheduling, but pre-emption is added to switch between processes. A small unit of time, called
a time quantum or time slices, is defined. A time quantum is generally from 10 to 100 milliseconds. The
ready queue is treated as a circular queue. To implement RR scheduling
The CPU scheduler picks the first process from the ready queue, sets a timer to interrupt after 1
time quantum, and dispatches the process.
The process may have a CPU burst of less than 1 time quantum.
o In this case, the process itself will release the CPU voluntarily.
o The scheduler will then proceed to the next process in the ready queue.
Otherwise, if the CPU burst of the currently running process is longer than 1 time quantum,
o The timer will go off and will cause an interrupt to the OS.
o A context switch will be executed, and the process will be put at the tail of the ready
queue.
o The CPU scheduler will then select the next process in the ready queue.
The average waiting time under the RR policy is often long. Consider the following set of processes that
arrive at time 0, with the length of the CPU burst given in milliseconds: (a time quantum of 4
milliseconds)
24 6 30
3 4 7
3 7 10
Using round-robin scheduling, we would schedule these processes according to the following chart:
A double-ended queue (dequeue, often abbreviated to deque, pronounced deck) generalizes a queue,
for which elements can be added to or removed from either the front (head) or back (tail).It is also often
called a head-tail linked list. Like an ordinary queue, a double-ended queue is a data structure it
supports the following operations: enq_front, enq_back, deq_front, deq_back, and empty. Dequeue can
be behave like a queue by using only enq_front and deq_front , and behaves like a stack by using only
enq_front and deq_rear.
The output restricted DEQUE allows deletions from only one end and input restricted DEQUE allow
insertions at only one end. The DEQUE can be constructed in two ways they are
1) Using array
Operations in DEQUE
Applications of DEQUE:
1. The A-Steal algorithm implements task scheduling for several processors (multiprocessor
scheduling).
3. When one of the processor completes execution of its own threads it can steal a thread from
another processor.
4. It gets the last element from the deque of another processor and executes it.
Circular Queue:
Circular queue is a linear data structure. It follows FIFO principle. In circular queue the last node is
connected back to the first node to make a circle.
3. Using arrays
Let us consider a circular queue, which can hold maximum (MAX) of six elements. Initially the queue is
empty.
F R
5 0
1 Q u e u e E mp t y
4 MAX = 6
F RO NT = RE A R = 0
C O U NT = 0
2
3
C irc u lar Q u e u e
Now, insert 11 to the circular queue. Then circular queue status will be:
5 0
R
11
1 F RO NT = 0
4 RE A R = ( RE A R + 1) % 6 = 1
C O U NT = 1
2
3
C irc u lar Q u e u e
Insert new elements 22, 33, 44 and 55 into the circular queue. The circular queue status is:
F
R
0
5
11
22 1 F RO NT = 0, RE A R = 5
4 55 RE A R = RE A R % 6 = 5
C O U NT = 5
44 33
2
3
C irc u lar Q u e u e
Now, delete an element. The element deleted is the element at the front of the circular queue. So, 11 is
deleted. The circular queue status is as follows:
R
0
5
F
22 1 F RO NT = (F R O NT + 1) % 6 = 1 RE A R
4 55 =5
C O U NT = C O U NT - 1 = 4
44 33
2
3
C irc u lar Q u e u e
Again, delete an element. The element to be deleted is always pointed to by the FRONT pointer. So, 22
is deleted. The circular queue status is as follows:
0
5
1 F RO NT = (F R O NT + 1) % 6 = 2
4 55 RE A R = 5
C O U NT = C O U NT - 1 = 3
44 33
F
3 2
C irc u lar Q u e u e
Again, insert another element 66 to the circular queue. The status of the circular queue is:
0
5
66
1
4 55 F RO NT = 2
RE A R = ( RE A R + 1) % 6 = 0
C O U NT = C O U NT + 1 = 4
44 33
3 2 F
C irc u lar Q u e u e
Now, insert new elements 77 and 88 into the circular queue. The circular queue status is:
0
5
66 77
88 1
4 55 F RO NT = 2, RE A R = 2
RE A R = RE A R % 6 = 2
C O U NT = 6
44 33
R
3 2 F
C irc u lar Q u e u e
Now, if we insert an element to the circular queue, as COUNT = MAX we cannot add the element to
circular queue. So, the circular queue is full.
LINKED LISTS
Linked lists and arrays are similar since they both store collections of data. Array is the most common
data structure used to store collections of elements. Arrays are convenient to declare and provide the easy
syntax to access any element by its index number. Once the array is set up, access to any element is
convenient and fast.
The disadvantages of arrays are:
• The size of the array is fixed. Most often this size is specified at compile time. This makes the
programmers to allocate arrays, which seems "large enough" than required.
• Inserting new elements at the front is potentially expensive because existing elements need to be
shifted over to make room.
• Deleting an element from an array is not possible. Linked lists have their own strengths and
weaknesses, but they happen to be strong where arrays are weak.
Generally array's allocates the memory for all its elements in one block whereas linked lists use
an entirely different strategy. Linked lists allocate memory for each element separately and only
when necessary.
Linked List Concepts:
A linked list is a non-sequential collection of data items. It is a dynamic data structure. For every data
item in a linked list, there is an associated pointer that would give the memory location of the next data
item in the linked list. The data items in the linked list are not in consecutive memory locations. They
may be anywhere, but the accessing of these data items is easier as each data item contains the address of
the next data item.
Advantages of linked lists:
Linked lists have many advantages. Some of the very important advantages are:
1. Linked lists are dynamic data structures. i.e., they can grow or shrink during the execution of a
program.
2. Linked lists have efficient memory utilization. Here, memory is not preallocated. Memory is
allocated whenever it is required and it is de-allocated (removed) when it is no longer needed.
3. Insertion and Deletions are easier and efficient. Linked lists provide flexibility in inserting a data
item at a specified position and deletion of the data item from the given position.
4. Many complex applications can be easily carried out with linked lists.
Insertion of a Node:
One of the most primitive operations that can be done in a singly linked list is the insertion of a node.
Memory is to be allocated for the new node (in a similar way that is done while creating a list) before
reading the data. The new node will contain empty data field and empty next field. The data field of the
new node is then stored with the information read from the user. The next field of the new node is
assigned to NULL. The new node can then be inserted at three different places namely:
• Inserting a node at the beginning.
• Inserting a node at the end.
• Inserting a node at intermediate position.
Inserting a node into the single linked list at a specified intermediate position other than
beginning and end.
Deletion of a node:
Another primitive operation that can be done in a singly linked list is the deletion of a node. Memory is
to be released for the node to be deleted. A node can be deleted from the list from three different places
namely.
• Deleting a node at the beginning.
• Deleting a node at the end.
• Deleting a node at intermediate position.
Deleting a node at the beginning:
class SingleLikedList:
'''Single Linked List Implementation'''
def __init__(self):
self._start = None
self._count = 0
return None
if self._start == None:
self._start = item
else:
cursor = self.getItemAtIndex(pos)
item.next = cursor.next
item.next = cursor.next
cursor.next = item
self._count += 1
def display(self):
cursor = self._start
while cursor != None:
print(cursor._content, end=' ')
cursor = cursor.next
l = SingleLikedList()
l.insert(10)
l.insert(20)
l.insert(30)
l.insert(40)
l.insert(50, 3)
l.display()
Source Code for creating , inserting ,deleting the Implementation of Single Linked List:
class Node(object):
def __init__(self, data=None, next_node=None):
self.data = data
self.next_node = next_node
def get_data(self):
return self.data
def get_next(self):
return self.next_node
def set_next(self, new_next):
self.next_node = new_next
class LinkedList(object):
def __init__(self, head=None):
self.head = head
def insert(self, data):
new_node = Node(data)
new_node.set_next(self.head)
self.head = new_node
def size(self):
current = self.head
count = 0
while current:
count += 1
current = current.get_next()
return count
def search(self, data):
current = self.head
found = False
Source Code for creating , inserting ,deleting the Implementation of Single Linked List:
import sys
import os.path
sys.path.append(os.path.join(os.path.abspath(os.pardir), "/home/satya/PycharmProjects/DataStractures"))
from LinkedList2 import LinkedList
import unittest
class TestLinkedList(unittest.TestCase):
def setUp(self):
self.list = LinkedList()
def tearDown(self):
self.list = None
def test_insert(self):
self.list.insert("David")
self.assertTrue(self.list.head.get_data() == "David")
self.assertTrue(self.list.head.get_next() is None)
def test_insert_two(self):
self.list.insert("David")
self.list.insert("Thomas")
self.assertTrue(self.list.head.get_data() == "Thomas")
head_next = self.list.head.get_next()
self.assertTrue(head_next.get_data() == "David")
def test_nextNode(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
self.assertTrue(self.list.head.get_data() == "Rasmus")
head_next = self.list.head.get_next()
self.assertTrue(head_next.get_data() == "Pallymay")
last = head_next.get_next()
self.assertTrue(last.get_data() == "Jacob")
def test_positive_search(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
found = self.list.search("Jacob")
self.assertTrue(found.get_data() == "Jacob")
found = self.list.search("Pallymay")
self.assertTrue(found.get_data() == "Pallymay")
found = self.list.search("Jacob")
self.assertTrue(found.get_data() == "Jacob")
def test_searchNone(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
# make sure reg search works
found = self.list.search("Jacob")
self.assertTrue(found.get_data() == "Jacob")
with self.assertRaises(ValueError):
self.list.search("Vincent")
def test_delete(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
# Delete the list head self.list.delete("Rasmus")
self.assertTrue(self.list.head.get_data() ==
"Pallymay")
# Delete the list tail
self.list.delete("Jacob")
self.assertTrue(self.list.head.get_next() is None)
def test_delete_value_not_in_list(self):
self.list.insert("Jacob")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
with self.assertRaises(ValueError):
self.list.delete("Sunny")
def test_delete_empty_list(self):
with self.assertRaises(ValueError):
self.list.delete("Sunny")
def test_delete_next_reassignment(self):
self.list.insert("Jacob")
self.list.insert("Cid")
self.list.insert("Pallymay")
self.list.insert("Rasmus")
self.list.delete("Pallymay")
self.list.delete("Cid")
self.assertTrue(self.list.head.next_node.get_data() == "Jacob")
Source Code for creating , inserting ,deleting the Implementation of Single Linked List:
class Node(object):
def __init__(self, data, next):
self.data = data
self.next = next
class SingleList(object):
head = None
tail = None
def show(self):
print "Showing list data:"
current_node = self.head
while current_node is not None:
print current_node.data, " -> ",
current_node = current_node.next
print None
def append(self, data):
node = Node(data, None)
if self.head is None:
self.head = self.tail = node
else:
self.tail.next = node
self.tail = node
def remove(self, node_value):
current_node = self.head
previous_node = None
while current_node is not None:
if current_node.data == node_value:
# if this is the first node (head)
if previous_node is not None:
previous_node.next = current_node.next
else:
self.head = current_node.next
s.append(3)
s.append(4)
s.show()
s.remove(31)
s.remove(3)
s.remove(2)
s.show()
Note that if your linked lists do include a header node, there is no need for the special case code given
above for the remove operation; node n can never be the first node in the list, so there is no need to check
for that case. Similarly, having a header node can simplify the code that adds a node before a given node
n.
Note that if you do decide to use a header node, you must remember to initialize an empty list to contain
one (dummy) node, you must remember not to include the header node in the count of "real" nodes in the
list.
It is also useful when information other than that found in each node of the list is needed. For example,
imagine an application in which the number of items in a list is often calculated. In a standard linked list,
the list function to count the number of nodes has to traverse the entire list every time. However, if the
current length is maintained in a header node, that information can be obtained very quickly. 3.5. Array
based linked lists: Another alternative is to allocate the nodes in blocks. In fact, if you know the
maximum size of a list a head of time, you can pre-allocate the nodes in a single array. The result is a
hybrid structure – an array based linked list.
shows an example of null terminated single linked list where all the nodes are allocated contiguously in
an array.
The beginning of the double linked list is stored in a "start" pointer which points to the first node. The
first node‟s left link and last node‟s right link is set to NULL.
Creating a node for Double Linked List:
Creating a double linked list starts with creating a node. Sufficient memory has to be allocated for
creating a node. The information is stored in the memory.
Double Linked List with 3 nodes:
if self.head is None:
self.head = Node(data)
else:
p = Node(data)
p.next = self.head
self.head.previous = p
self.head = p
def remove(self, index):
if self.head is None:
raise ValueError('Removing off an empty list')
if index < 0 or index >= self.size:
raise IndexError("Index is either negative or greater than the list size.")current = self.head
if index == 0:
self.head = self.head.next
self.head.previous = None
else:
for _ in range(index -1):
current = current.next
p = current.next.next
if p is None:
current.next = None
else:
current.next = p
p.previous = current
def __sizeof__(self):
return self.size
def __repr__(self):
res = '[ '
current = self.head
while current is not None:
res += str(current.data)
res += ' '
current = current.next
res += ']'
return res
class Node:
def __init__(self, data):
if data is None:
raise ValueError('Node value cannot be None')
self.data = data
self.previous = None
self.next = None
class DoubleList(object):
head = None
tail = None
def append(self, data):
new_node = Node(data, None, None)
if self.head is None:
self.head = self.tail = new_node
else:
new_node.prev = self.tail
new_node.next = None
self.tail.next = new_node
self.tail = new_node
def remove(self, node_value):
current_node = self.head
# otherwise we have no prev (it's None), head is the next one, and prev becomes
None self.head = current_node.next
current_node.next.prev = None
current_node = current_node.next
def show(self):
print "Show list data:"
current_node = self.head
while current_node is not None:
print current_node.prev.data if hasattr(current_node.prev, "data") else
current_node = current_node.next
print "*"*50
d = DoubleList()
d.append(5)
d.append(6)
d.append(50)
d.append(30)
d.show()
d.remove(50)
d.remove(5)
d.show()
Polynomials:
A polynomial is of the form: i n i ∑ ci Where, ci is the coefficient of the ith term and n is the degree of
the polynomial
Some examples are:
5x2 + 3x + 1
12x3 – 4x
5x4 – 8x3 + 2x2 + 4x1 + 9x0
It is not necessary to write terms of the polynomials in decreasing order of degree. In other words the two
polynomials 1 + x and x + 1 are equivalent.
The computer implementation requires implementing polynomials as a list of pairs of coefficient and
exponent. Each of these pairs will constitute a structure, so a polynomial will be represented as a list of
structures.
A linked list structure that represents polynomials 5x4 – 8x3 + 2x2 + 4x1 + 9x0
Addition of Polynomials:
To add two polynomials we need to scan them once. If we find terms with the same exponent in the two
polynomials, then we add the coefficients; otherwise, we copy the term of larger exponent into the sum
and go on. When we reach at the end of one of the polynomial, then remaining part of the other is copied
into the sum.
To add two polynomials follow the following steps:
• Read two polynomials
• Add them.
• Display the resultant polynomial.
Unit-V
Trees and Graphs
Trees Basic Concepts:
A tree is a non-empty set one element of which is designated the root of the tree while the remaining
elements are partitioned into non-empty sets each of which is a sub-tree of the root.
A tree T is a set of nodes storing elements such that the nodes have a parent-child relationship that
satisfies the following
• If T is not empty, T has a special tree called the root that has no parent.
• Each node v of T different than the root has a unique parent node w; each node with parent w is a
child of w.
Tree nodes have many useful properties. The depth of a node is the length of the path (or the number of
edges) from the root to that node. The height of a node is the longest path from that node to its leaves.
The height of a tree is the height of the root. A leaf node has no children -- its only path is up to its
parent.
Binary Tree:
In a binary tree, each node can have at most two children. A binary tree node called the root together with two
binary trees called the left subtree and the right subtree.
Tree Terminology:
Leaf node
A node with no children is called a leaf (or external node). A node which is not a leaf is called an internal
node.
Path: A sequence of nodes n1, n2, . . ., nk, such that ni is the parent of ni + 1 for i = 1, 2,. . ., k - 1. The
length of a path is 1 less than the number of nodes on the path. Thus there is a path of length zero from a
node to itself.
Ancestor and Descendent If there is a path from node A to node B, then A is called an ancestor of B
and B is called a descendent of A.
Level: The level of the node refers to its distance from the root. The root of the tree has level O, and the
level of any other node in the tree is one more than the level of its parent.
n
The maximum number of nodes at any level is 2 .
Height:The maximum level in a tree determines its height. The height of a node in a tree is the length of a
longest path from the node to a leaf. The term depth is also used to denote height of the tree.
Depth:The depth of a node is the number of nodes along the path from the root to that node.
Assigning level numbers and Numbering of nodes for a binary tree: The nodes of a binary tree can be
numbered in a natural way, level by level, left to right. The nodes of a complete binary tree can be
numbered so that the root is assigned the number 1, a left child is assigned twice the number assigned its
parent, and a right child is assigned one more than twice the number assigned its parent.
If every non-leaf node in a binary tree has nonempty left and right subtrees, the tree is termed a strictly
binary tree. Thus the tree of figure 7.2.3(a) is strictly binary. A strictly binary tree with n leaves
always contains 2n - 1 nodes.
A full binary tree of height h has all its leaves at level h. Alternatively; All non leaf nodes of a full binary
tree have two children, and the leaf nodes have no children.
h+1
A full binary tree with height h has 2 - 1 nodes. A full binary tree of height h is a strictly binary tree
all of whose leaves are at level h.
3+1
For example, a full binary tree of height 3 contains 2 – 1 = 15 nodes.
A binary tree with n nodes is said to be complete if it contains all the first n nodes of the above
numbering scheme.
A complete binary tree of height h looks like a full binary tree down to level h-1, and the level h is
filled from left to right.
A Binary tree is Perfect Binary Tree in which all internal nodes have two children and all leaves are at
same level.
Following are examples of Perfect Binary Trees.
18
/ \
15 30
/ \ / \
40 50 100 40
18
/ \
15 30
h
A Perfect Binary Tree of height h (where height is number of nodes on path from root to leaf) has 2 – 1
node.
Example of Perfect binary tree is ancestors in family. Keep a person at root, parents as children, parents
of parents as their children.
Tree Traversals:
Traversal of a binary tree means to visit each node in the tree exactly once. The tree traversal is used in all
t it.
In a linear list nodes are visited from first to last, but a tree being a non linear one we need definite rules.
Th ways to traverse a tree. All of them differ only in the order in which they visit the nodes.
In all of them we do not require to do anything to traverse an empty tree. All the traversal methods arebase
functions since a binary tree is itself recursive as every child of a node in a binary tree is itself a binary
tree.
Inorder Traversal:
To traverse a non empty tree in inorder the following steps are followed recursively.
Preorder Traversal:
Algorithm Pre-order(tree)
Post-order Traversal:
Algorithm Post-order(tree)
if root:
if root:
if root:
# Driver code
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print "Preorder traversal of binary tree is"
printPreorder(root)
All keys in n's left subtree are less than the key in n, and
All keys in n's right subtree are greater than the key in n.
In other words, binary search trees are binary trees in which all values in the node‘s left subtree are less
than node value all values in the node‘s right subtree are greater than node value.
Here are some BSTs in which each node just stores an integer key:
In the left one 5 is not greater than 6. In the right one 6 is not greater than 7.
The reason binary-search trees are important is that the following operations can be
implemented efficiently using a BST:
Inserting a node
A naïve algorithm for inserting a node into a BST is that, we start from the root node, if the node to insert
is less than the root, we go to left child, and otherwise we go to the right child of the root. We continue
this process (each node is a root for some sub tree) until we find a null pointer (or leaf node) where we
cannot go any further. We then insert the node as a left or right child of the leaf node based on node is less
or greater than the leaf node. We note that a new node is always inserted as a leaf node. A recursive
algorithm for inserting a node into a BST is as follows. Assume we insert a node N to tree T. if the tree is
empty, the we return new node N as the tree. Otherwise, the problem of inserting is reduced to inserting
the node N to left of right sub trees of T, depending on N is less or greater than T. A definition is as
follows.
Insert(N, T) = N if T is empty
= insert(N, T.left) if N < T
= insert(N, T.right) if N > T
A BST is a connected structure. That is, all nodes in a tree are connected to some other node. For
example, each node has a parent, unless node is the root. Therefore deleting a node could affect all sub
trees of that node. For example, deleting node 5 from the tree could result in losing sub trees that are
rooted at 1 and 9.
Hence we need to be careful about deleting nodes from a tree. The best way to deal with deletion seems to
be considering special cases. What if the node to delete is a leaf node? What if the node is a node with
just one child? What if the node is an internal node (with two children). The latter case is the hardest to
resolve. But we will find a way to handle this situation as well.
Case 1 : The node to delete is a leaf node
This is a very easy case. Just delete the node 46. We are done
Next problem is finding a replacement leaf node for the node to be deleted. We can easily find this as
follows. If the node to be deleted is N, the find the largest node in the left sub tree of N or the smallest
node in the right sub tree of N. These are two candidates that can replace the node to be deleted without
losing the order property. For example, consider the following tree and suppose we need to delete the root
38.
Then we find the largest node in the left sub tree (15) or smallest node in the right sub tree (45) and
replace the root with that node and then delete that node. The following set of images demonstrates this
process.
Recursive Algorithms:
GCD Design: Given two integers a and b, the greatest common divisor is recursively found using the
formula
gcd(a,b) = a if b=0
Base case
b if a=0
gcd(b, a mod b) General case
Fibonacci Design: To start a fibonacci series, we need to know the first two numbers.
// A function is said to be recursive if it calls itself again and again within its body whereas iterative
functions are loop based imperative functions.
// Recursion uses more memory than iteration as its concept is based on stacks.
// Recursion is comparatively slower than iteration due to overhead condition of maintaining stacks.
// Iteration terminates when the loop-continuation condition fails whereas recursion terminates when
a base case is recognized.
// While using recursion multiple activation records are created on stack for each call where as in
iteration everything is done in one activation record.
// Infinite recursion can crash the system whereas infinite looping uses CPU cycles repeatedly.
Types of Recursion:
Recursion is of two types depending on whether a function calls itself from within itself or whether two
functions call one another mutually. The former is called direct recursion and the later is called
Direct Recursion
Indirect Recursion
Linear Recursion
Binary Recursion
Multiple Recursion
Linear Recursion:
It is the most common type of Recursion in which function calls itself repeatedly until base condition
[termination case] is reached. Once the base case is reached the results are return to the caller function.
If a recursive function is called only once then it is called a linear recursion.
Binary Recursion:
Some recursive functions don't just have one call to themselves; they have two (or more). Functions
with two recursive calls are referred to as binary recursive functions.
Example1: The Fibonacci function fib provides a classic example of binary recursion. The Fibonacci
numbers can be defined by the rule:
fib(n) = 0 if n is 0,
// 1 if n is 1,
Fib(0) = 0
Fib(1) = 1
B Program to display the Fibonacci sequence up to n-th term where n is provided by the user
nterms = 10
n1 = 0
n2 = 1
count = 0
print("Fibonacci sequence
upto",nterms,":") print(n1)
else:
print(n1,end=' , ')
nth = n1 + n2
update
values n1 = n2
n2 = nth
count += 1
Tail Recursion:
Tail recursion is a form of linear recursion. In tail recursion, the recursive call is the last thing the
function does. Often, the value of the recursive call is returned. As such, tail recursive functions can
often be easily implemented in an iterative manner; by taking out the recursive call and replacing it with
a loop, the same effect can generally be achieved. In fact, a good compiler can recognize tail recursion
and convert it to iteration in order to optimize the performance of the code.
A good example of a tail recursive function is a function to compute the GCD, or Greatest Common
Denominator, of two numbers:
def factorial(n):
if n == 0: return 1
else: return factorial(n-1) * n
Factorial(n)
Input: integer n ≥ 0
Output: n!
GCD(m, n)
n) Time-Complexity: O(ln n)
Fibonacci(n)
Input: integer n ≥ 0
if n=1 or n=2
then Fibonacci(n)=1
Towers of Hanoi
Input: The aim of the tower of Hanoi problem is to move the initial n different sized disks from needle
A to needle C using a temporary needle B. The rule is that no larger disk is to be placed above the
smaller disk in any of the needle while moving or at any time, and only the top of the disk is to be
moved at a time from any needle to any needle.
Output:
if n == 1:
return
n=4
Graph
A graph is a pictorial representation of a set of objects where some pairs of
objects are connected by links. The interconnected objects are represented
by points termed as vertices, and the links that connect the vertices are
called edges.
Formally, a graph is a pair of sets (V, E), where V is the set of vertices and
E is the set of edges, connecting the pairs of vertices. Take a look at the
following graph −
V = {a, b, c, d, e}
Basic Operations
Following are basic primary operations of a Graph −
Add Edge − Adds an edge between the two vertices of the graph.
To know more about Graph, please read Graph Theory Tutorial . We shall
learn about traversing a graph in the coming chapters.
Graph Traversal:
Graphs are one of the unifying themes of computer science – an abstract representation
which describes the organization of transportation systems, electrical circuits, human
interactions, and telecommunication networks. That so many different structures can be
modeled using a single formalism is a source of great power to the educated programmer.
In this chapter, we focus on problems which require only an elementary knowledge of
graph algorithms, specifically the appropriate use of graph data structures and traversal
algorithms. In Chapter 10, we will present problems relying on more advanced graph
algorithms that find minimum spanning trees, shortest paths, and network flows.
Breadth-first search is appropriate if (1) we don‘t care which order we visit the vertices and
edges of the graph, so any order is appropriate or (2) we are interested in shortest paths on
unweighted graphs.
Breadth-First Search:
You are given the maze figure below and asked to find a way to the exit with a minimum
number of decisions to make (a decision is required whenever there is at least one available
direction to go). We have pointed out these critical positions and given them numbers.
On the basis of the above diagram we will draw a graph with the following rules :
Also the circles colored in cyan are the start (1) and the finish (10).
It is easy to see now that the minimum length path is 1, 3, 14, 15, 10 with 4 decisions to make (the
number of edges connecting the vertices). This is because we have an overview of the maze, we
know every detail about it in advance. But what if we do not ? We would never be aware of the
consequences of our current decision until we make it. What would be our strategy then ?
One possibility is to gather an infinite number of people (this is for instructional purposes only) and
put them in the start position of the maze.
Then every time we are to make a decision, leave one single person in the current position and split
the group into N smaller ones, where N is the number of the current possible decisions to make.
These groups each go a different way and so on, until someone reaches the finish. It is clear that the
path found by this group of people is of minimum length, since every group has to make only one
decision at a time (the length of each step is the same for everyone) and this group made it first to the
finish. Note that passing through a point that already has one person standing there is not permitted.
To reconstruct the minimum length path, let us assume that every person that is left in a certain
point knows exactly the position where the group came from. For example the person in point 2
knows that the group came from the 1st position before it left him there. Now if we start with the
last position of the winning group (the finish) we may go backwards as we know its previous
position, and so on until we reach the start. We have now constructed the exact minimum path (in
terms of decisions to make) to arrive at the finish of the maze, but in reversed order. The last
operation is to reverse the path in order to find the correct one.
This is a version of Lee's algorithm for finding the shortest path between two particular vertices in an
undirected graph. But let us now concentrate on the method of traversal, which in terms of graph
theory is known as Breadth First Search ( BFS in short ). I assume you already know the algorithm,
it is all in the way the groups of people move from one point to another - at each step the groups
separate into smaller ones, then go to the next positions and also leave a person behind. Remember
it is not allowed to pass through a point with one person already standing there. This separation
process continues until there are no more available positions to visit.
For a better understanding let us try and traverse the next graph starting from vertex 3 using this
method. The steps are already described graphically below but let us make a few comments.
First there is a group of 5 people (which is enough for this situation) positioned in vertex 3. Then one
person goes to vertex 1, one to vertex 5, two persons go to vertex 2 and one stays in 3. At the next
phase of the process we clearly see that the person in vertex 1 has nowhere to go, since both its next
possible positions (2 and 3) are occupied. Because 2 is smaller than 5 (considering the integer
numbers order) we will search first for its available positions. The single one is vertex 4, so we leave
one person in 2 and send one to 4. This seems like our last move - no person can move from its
current position anymore since all are occupied.
The BFS method shows the vertices that are visited through each step of the traversal process.
3, 1, 2, 5, 4
To implement this method in C++ we will use a queue to store the current position of each group of
people and search for their available directions to go. Also we will use an additional Boolean array
to store information about each vertex (whether it is occupied or not) :
The Breadth First Search pseudocode looks like this (assuming x is the first node to start the
traversal) :
#include <iostream>
struct node
{
int info;
node *next;
};
class Queue
{
public:
Queue();
~Queue();
bool isEmpty();
void add(int);
int get();
private:
node *first, *last;
};
class Graph
{
public:
Graph(int size = 2);
~Graph();
bool isConnected(int, int);
// adds the (x, y) pair to the edge set
void addEdge(int x, int y);
// performs a Breadth First Search starting with node x
void BFS(int x);
// searches for the minimum length path
// between the start and target vertices
void minPath(int start, int target);
private :
int n;
int **A;
};
Queue::Queue()
{
first = new node;
first->next = NULL;
last = first;
}
Queue::~Queue()
{
delete first;
}
bool Queue::isEmpty()
{
return (first->next == NULL);
}
void Queue::add(int x)
{
node *aux = new node;
aux->info = x;
aux->next = NULL;
last->next = aux;
last = aux;
}
int Queue::get()
{
node *aux = first->next;
int value = aux->info;
first->next = aux->next;
if (last == aux) last = first;
delete aux;
return value;
}
Graph::Graph(int size)
{
int i, j;
if (size < 2)
n = 2;
else
n = size;
A = new int*[n];
for (i = 0; i < n; ++i)
Graph::~Graph()
{
for (int i = 0; i < n; ++i)
delete [] A[i];
delete [] A;
}
void Graph::BFS(int x)
{
Queue Q;
bool *visited = new bool[n+1];
int i;
Q.add(x);
visited[x] = true;
cout << "Breadth First Search starting from vertex ";
cout << x << " : " << endl;
while (!Q.isEmpty())
{
int k = Q.get();
cout << k << " ";
for (i = 1; i <= n; ++i)
if (isConnected(k, i) && !visited[i])
{
Q.add(i);
visited[i] = true;
}
}
Q.add(start);
visited[start] = true;
found = false;
p = q = 0;
X[0].current = start;
X[0].prev = 0;
if (i == target)
found = true;
}
++p;
}
cout << "The minimum length path from " << start;
cout << " to " << target << " is : " << endl; p = 0;
while (q)
{
Y[p] = X[q].current;
q = X[q].prev;
++p;
}
Y[p] = X[0].current;
for (q = 0; q <= p/2; ++q)
{
int temp = Y[q];
Y[q] = Y[p-q];
Y[p-q] = temp;
}
delete [] visited;
delete [] X;
delete [] Y;
}
void Traversal()
{
Graph g(5);
g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4);
g.addEdge(3, 5); g.addEdge(4, 5); g.addEdge(2, 3);
g.BFS(3);
}
void Maze()
{
Graph f(15);
f.addEdge(1, 2); f.addEdge(1, 3); f.addEdge(2, 4);
f.addEdge(3, 14); f.addEdge(4, 5); f.addEdge(4, 6);
f.addEdge(5, 7); f.addEdge(6, 13); f.addEdge(7, 8);
f.addEdge(7, 9); f.addEdge(8, 11); f.addEdge(9, 10);
f.addEdge(10, 12); f.addEdge(10, 15); f.addEdge(11, 12);
f.addEdge(13, 14); f.addEdge(14, 15); f.minPath(1, 10);
void main()
{
Traversal();
cout << endl;
Ma ze();
}
a depth-first search starting at A, assuming that the left edges in the shown graph are chosen before
right edges, and assuming the search remembers previously-visited nodes and will not repeat them
(since this is a small graph), will visit the nodes in the following order: A, B, D, F, E, C, G.
#include<iostream>
class graph
{
private:
int n;
graph* next;
public:
graph* read_graph(graph*);
void dfs(int); //dfs for a single node
void dfs(); //dfs of the entire graph
void ftraverse(graph*);
};
graph *g[100];
int visit[100];
int dfs_span_tree[100][100];
graph* graph::read_graph(graph*head)
{
int x;
graph* last;
head=last=NULL;
if(head==NULL)
head=NEW;
else
last->next=NEW;
last=NEW;
return head;
}
void graph::ftraverse(graph*h)
{
while(h!=NULL)
{
cout<<h->n<<"->";
h=h->next;
}
cout<<"NULL"<<endl;
}
void graph::dfs(int x)
{
cout<<"node "<<x<<" is visited\n";
visit[x]=1;
graph *p;
p=g[x];
while(p!=NULL)
{
int x1=p->n;
if(visit[x1]==0)
{
cout<<"from node "<<x<<' ';
//Add the edge to the dfs spanning tree
dfs_span_tree[x][x1]=1;
dfs(x1);
}
p=p->next;
}
}
void graph::dfs()
{
int i;
cout<<"*****************************************************\n";
cout<<"This program is to implement dfs for an unweighted graph \n";
cout<<"*****************************************************\n";
g[i]=NULL;
for(i=1;i<=n;i++)
{
cout<<"Enter the adjacent nodes to node no. "<<i<<endl;
cout<<"***************************************\n";
g[i]=read_graph(g[i]);
}
for(i=1;i<=n;i++)
visit[i]=0; //mark all nodes as unvisited
for(i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dfs_span_tree[i][j]=0;
for(i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cout<<dfs_span_tree[i][j]<<' ';
cout<<endl;
}
}
int main()
{
graph obj;
obj.dfs();
return 0;
}
Dijkstra’s Algorithm
[Note: this chapter covers material of about 1 lecture.]
Earlier on, we looked at the problem of finding shortest paths in graphs, and used Breadth First Search
(BFS) to solve this problem. At the time, our graphs did not have any edge costs or weights. The task gets a
little more interesting when there are edge costs: then, the equivalent algorithm is called Dijkstra’s
1
Algorithm .
Dijkstra’s algorithm is the right choice when all we are given is the graph with its edge costs/weights.
But often, there is more information, such as geography, which can be used to guide a shortest-path
search to speed it up. The improved version of Dijkstra’s Algorithm taking advantage of extra information
is called A∗ search, and is particularly important in the field of Artificial Intelligence.
Dijkstra’s Algorithm
As we said, we now assume that each edge e = (v, w) of our graph has a cost (or length) c e = c[v][w] ≥
2
0. First, we might just try to run BFS. But that’s clearly not correct, as we can see from Figure 27.1.
v3
1
v2
1 1
v1 6
1
10.
u w
Figure 27.1: An example with edge costs in which BFS does not find the shortest path from u to w.
When we process u (the start node), we add v1 and w to the queue. We next process v1, and add v2.
But when we process w, we are done with it, and never revisit its distance label. So we think that it is at
distance 10 from u, when it really is at distance 4.
The mistake we are making here is that processing w is premature. The node v2 is a much better
candidate to explore next, because it is closer to u; the fact that it is closer means that it may lie on an
actual shortest path from u to w (as it does here), so we shouldn’t conclude anything about the distance
from u to w until we’ve made sure that there isn’t another, shorter, path.
What this suggests is that nodes shouldn’t just be stored in a queue. When a newly added node —
like v2 in our example — is closer to u, then it should be allowed to jump ahead of other nodes and be
processed before them. But we’ve already seen a data structure built exactly for this purpose: a Priority
Queue. So Dijkstra’s Algorithm is obtained pretty much exactly by replacing the Queue in the BFS
algorithm with a Priority Queue. Beyond that change, we just need some small modifications, mostly to
take care of the fact that the estimated distance of a node may be updated multiple times. For instance, in
our example in Figure 27.1, when we explore v2, we update our distance estimate for w to 8 instead of
10, but we later update it to 4, which is the final value. The more formal algorithm we get is as follows:
int d[n]; // will store distances from u
int p[n]; // will store predecessors of nodes on shortest paths from u
int c[n][n]; // contains the costs of edges
Notice the strong similarity to the BFS algorithm. The main change is the we use a priority queue instead
of a queue, and that we have an additional condition that allows us to process a node w: not only when
the node hasn’t been visited at all, but also when we found a strictly shorter distance to get to w. In the
latter case, the node will already be in the priority queue, so instead of adding it, we need to update the
associated value.
When we first learned about Priority Queues (in Chapter 22), we didn’t discuss how to update an entry after
its priority has changed. It’s pretty clear that when its priority gets higher (i.e., its distance gets smaller), we
should trickle the element up. And when the priority gets smaller, we should trickle it down. The not-so-obvious
thing is how we find it in the heap in the first place, so we can trickle it up or down. A linear search through all
elements would take Ω(n), and thus completely defeat the purpose of using a heap in the first place. The most
obvious way to solve this problem is to store somewhere (e.g., in the struct for a node of the graph) an
integer index describing where in the heap the element is located. Then, any time
two elements swap positions in the heap, we can update their entries, at an additional
constant cost. If we don’t like the idea of storing heap information in a node’s struct (since
it’s really not part of the node’s description, but part of the algorithm), we could instead have
another array inside the Priority Queue: that array tells us for each of the nodes (in their
original ordering in the graph) where it is located in the heap.
Either way, it is easy to implement a lookup operation so that we only incur a constant times
as much cost for each step.
The correctness proof for Dijkstra’s Algorithm would use induction over the iterations of
the while loop. It requires a bit more care in the formulation of a loop invariant, and you may
see it in CSCI 270 (or CSCI 570 or 670, if you take it). We won’t go over it here, even though
it’s not all that difficult.
The running time analysis gets a little more interesting than for BFS. The remove() and
add() operations now both take Θ(log n) time (while peek() is still Θ(1)). So the content of
the for loop takes time Θ(log n), and since it executes Θ(out-degree(v)) times, the total time
of the for loop is Θ(out-degree(v) log n). This in turn gives us that the stuff inside the while
loop takes time Θ((1 + out-degree(v)) · log n), which we now have to sum up over all nodes
v, as usual:
Θ((1 + out-degree(v)) · log n) = Θ(log X
X out-degree(v))) = Θ(log n · (n
n·( + m)).
v V
For Dijkstra’s Algorithm, even though in principle, we could have the case m < n just like we could
for BFS, people usually ignore the n part of n + m, and simply describe the running time as Θ(m
log n). Not quite linear time, but it’s still quite fast, and a pretty cool application of priority queues.
In fact, Dijkstra’s Algorithm and A∗ search are really the main reason computer scientists need to
care about priority queues.