Chapter 1
Chapter 1
Overview of C
CHAPTER OUTLINE
In fact, it is a high level language that not only supports the necessary
data types and data structures needed by a normal programmer but it
can also access the computer hardware through specially designed
declarations and functions and, therefore, it is often called as a
middlelevel language.
It is popular because of the following characteristics:
1.
2.
3.
4.
5.
6.
Small size
Wide use of functions and function calls
Loose typing
Bitwise low level programming support
Structured language
Wide use of pointers to access data structures and physical memory of the
system
Besides the above characteristics, the C programs are small and efficient.
A C program can be compiled on variety of computers.
1.2 CHARACTERS USED IN C
The unsigned int is stored in one word of the memory whereas the
unsigned short is stored in 16 bits and does not depend upon the word
size of the memory.
Examples of invalid integers are:
(i) 9, 24, 173
illegal-comma used
(ii) 5.29
(iii) 79 248
blank used
1.3.2 Character Data Type (char)
A
N
*
7
It may be noted that the character 7 is different from the numeric value
7. In fact, former is of typechar and later of type int. Each character
has a numeric code, i.e., the ASCII code. For instance, the ASCII code for
the character A is 65 and that of * is 42. A table of ASCII codes is given
in Appendix A.
A character data type is referred to as char. It is stored in one byte of
memory. However, the representation varies from computer to
computer. For instance, some computers support signed as well as
unsigned characters. The signed characters can store integer values from
128 to +127 whereas the unsigned characters store ASCII codes from 0
to 255.
1.3.3 The Floating point (float) Data Type
A floating point number is a real number which can be represented in
two forms: decimal and exponent forms. Floating point numbers are
generally used for measuring quantities.
1. Decimal form: The floating point number in this form has a decimal
point. Even if it is an integral value, it must include the decimal point.
Examples of valid decimal form numbers are:
973.24
849.
73.0
82349.24
9.0004
2. Exponent form: The exponent form of floating point number consists of
the following parts:
<integer>.<fraction>e<exponent>
Examples of valid floating point numbers are:
3.45 e7
0.249 e6
A summary of C basic data types is given the Table 1.1. From this table, it
may be observed that character and integer type data can also be
declared as unsigned. Such data types are called unsigned data types.
In this representation, the data is always a positive number with range
starting from 0 to a maximum value. Thus, a number twice as big as a
signed number can be represented through unsigned data types.
Table 1.1 Basic data types in C
1.4 C TOKENS
Identifiers
Keywords
Constants
Variables
1.4.1 Identifiers
Symbolic names can be used in C for various data items. For example, if
a programmer desires to store a value 27, then he can choose any
symbolic name (say, ROLL) and use it as given below:
ROLL=27;
Where ROLL is a memory location and the symbol = is an assignment
operator.
The significance of the above statement is that ROLL is a symbolic name
for a memory location where the value 27 is being stored. A symbolic
name is generally known as an identifier.
The identifier is a sequence of characters taken from C character set. The
number of characters in an identifier is not fixed though most of the C
compilers allow 31 characters. The rules for the formation of an identifier
are:
1. An identifier can consist of alphabets, digits and and/or underscores.
2. It must not start with a digit.
3. C is case sensitive, i.e., upper case and lower case letters are considered
different from each other.
4. An identifier can start with an underscore character. Some special C names
begin with the underscore.
5. Special characters such as blank space, comma, semicolon, colon, period,
slash, etc. are not allowed.
6. The name of an identifier should be so chosen that its usage and meaning
becomes clear. For example, total, salary, roll no, etc. are self
explanatory identifiers.
1.4.2 Keywords
A keyword is a reserved word of C. This cannot be used as an identifier
by the user in his program. The set of C keywords is given in Table 1.2.
Table 1.2 Standard keywords in C
1.4.3 Variables
A variable is the most fundamental aspect of any computer language. It
is a location in the computer memory which can store data and is given a
symbolic name for easy reference. The variables can be used to hold
different values at different times during a program run. To understand
this concept, let us have a look at the following set of statements:
Total = 500.25;
(i)
(ii)
intcount;
inti,j,k;
charch,first;
floatTotal,Net;
longintsal;
doublesalary.
The above declaration means that rate is a constant of type integer having
a fixed value 50. Consider the following declaration:
const int rate;
rate = 50;
The above initialization of constant rate is illegal. This is because of the
reason that a constant cannot be initialized for a value at a place other than
where it is declared. It may be further noted that if a program does not
change or mutates\a constant or constant object then the program is called
as const correct.
2. Floating point constant: It is a constant which can be assigned values of
real or floating point type. For example, if it is desired to have a constant
called Pi containing value 3.1415, then the following declaration can be
used.
constfloatPi=3.1415;
The above declaration means that Pi is a constant of type float having a
fixed value 3.1415. It may be noted here that by default a floating point
constant is of type double.
3. Character constant: A character constant can contain a single character
of information. Thus, data such as Y or N is known as a character
constant. Let us assume that it is desired to have a constant called Akshar
containing the character Q; following declaration can be used to obtain
such a constant.
constcharAkshar=Q;
The above declaration means that Akshar is a constant of type char
having a fixed value Q.
A sequence of characters enclosed within quotes is known as a string
literal. For example, the
character sequence computer is a string literal. When a string literal is
assigned to an identifier declared as a constant, then it is known as
a stringconstant. In fact, a string is an array of characters. Arrays are
discussed later in the chapter.
printf(Theageofstudent=%d,age);
The above statement would display the following data on the screen:
The age of student = 25
Thus, the text between the quotes has been displayed as such but for %d,
the data stored in the variable age has been displayed.
A float can be displayed on the screen by including a format specifier
(%f) within the pair of quotes as shown below:
floatrate=9.5;
printf(Therateofprovidentfund=%f,rate);
The above statements would produce the following display:
The rate of provident fund = 9.5
A char can be displayed by including a format specifier (%c) within the
pair of quotes as shown below:
charch=A
printf(Thefirstalphabetis:%c,ch);
Obviously, the output of the above set of statements would be:
The first alphabet is: A.
The other specifiers which can be used in printf() function are given
in Appendix B.
1.6.2 How to Read Data from Keyboard using scanf()
Similar to printf() function, scanf() function also uses the %d,
%f,%c, and other format specifiers to specify the kind of data to be
read from the keyboard.
However, it expects the programmer to prefix the address operator '&' to
every variable written in the argument list as shown below:
introll;
scanf(%d,&roll);
The above set of statements declares the variable roll of type int, and
reads the value for roll from the keyboard. It may be noted that &, the
address operator, is prefixed to the variable roll. The reason for
specifying & would be discussed later in the book.
Similarly, other format specifiers can be used to input data from the
keyboard.
Example 1: Write an interactive program that reads the marks secured
by a student for four subjectsSub1, Sub2, Sub3, and Sub4, the
maximum marks of a subject being 100. The program shall compute the
percentage of marks obtained by the student.
Solution: The scanf() and prinf() functions would be used to do
the required task. The required program is as follows:
#include<stdio.h>
main()
{
intsub1,sub2,sub3,sub4;
floatpercent;
printf("Enterthemarksforfoursubjects:");
scanf("%d%d%d%d",&sub1,&sub2,&sub3,&sub4);
percent=(sub11sub21sub31sub4)/400.00*100;
printf("Thepercentage5%f",percent);
}
For input data 45 56 76 90, the above program computes the percentage
and displays the following output:
The percentage = 66.750000
Though the output is correct, but it has displayed unnecessary four
trailing zeros. This can be controlled by using field width specifiers
discussed later in the chapter.
1.7 COMMENTS
/*Thisisanillustrationofmultiplelinecomment.
TheCcompilerignorestheselines.Aprogrammercan
usecommentlinesfordocumentationofhisprograms*/
In fact, a comment is a non-executable statement in the program.
1.8 ESCAPE SEQUENCE (BACKSLASH CHARACTER CONSTANTS)
}
The character constant \n has been placed at the beginning of the text
of the second printf()statement and, therefore, the output of the
program would be:
A back slash character constant
prints the output on the next line.
Some other important backslash constants are listed below:
Backslash character constant
Meaning
\t
tab
\b
backspace
\a
bell (alert)
\n
newline character
Note:
The backslash characters are also known as escape sequences; such
characters can be used within apostrophes or within double quotes.
1. \t (tab): This character is called a tab character. Whenever it is inserted
in an output statement, the display moves over to the next present tab stop.
Generally, a tab is of 8 blank spaces on the screen. An example of usage of
character \t is given below:
#include<stdio.h>
main()
{
printf(\nhello\tCompBoy);
}
The output of the above statement would be on the new line with spacing
as shown below:
helloCompBoy
2. \b (Backspace): This character is also called backspace character. It is
equivalent to the back space key symbol ()available on the computer or
typewriter. It moves one column backward and positions the cursor on the
#include<stdio.h>
main()
{
printf(\nThisis\natest.);
}
The output of this statement would be:
Thisis
atest.
1.9 OPERATORS AND EXPRESSIONS
Stands for
Example
addition
x+y
subtraction
xy
multiplication
x*y
division
x/y
modulus or remainder
x%y
decrement
x
x
++
increment
x++
57
2.923
++x
(a*b)
8*((a + b))
For example, if the initial value of j is = then the expression --j will
decrement the contents of j to 4.
The increment and decrement operators can be used both as a prefix and
as a postfix to a variable
as shown below:
++xorx++
yory
As long as the increment or decrement operator is not used as part of an
expression, the prefix and postfix forms of these operators do not make
any difference. For example, ++x and x++ would produce the same
result. However, if such an operator is part of an expression then the
prefix and postfix forms would produce entirely different results.
In the prefix form, the operand is incremented or decremented before
the operand is used in the program. Whereas in the postfix form, the
operand is used first and then incremented or decremented.
1.9.2 Relational and Logical Operators
A relational operator is used to compare two values and the result of
such an operation is always logical, i.e., either true or false. The valid
relational operators supported by C are given in Table 1.4.
Table 1.4 Relational operators supported by C
Symbol
Stands for
Example
>
greater than
x>y
>=
x >= y
<
less than
x<y
<=
x <= y
==
equal to
x==y
!=
not equal to
x!=y
Example 2: Take two variables x and y with initial values 10 and 15,
respectively. Demonstrate the usage of relational operators by
using x and y as operands.
Solution: The usage of relational operators is illustrated below with the
help of a table:
x=10,y=15
Expression
Result
x>y
False
x + 5> = y
True
x<y
True
x<=y
True
x= =y
False
x + 5= =y
True
x! = y
True
Stands for
Example
&&
Logical AND
x&&y
||
Logical OR
x||y
Logical NOT
|x
For initial value of x=5 and y=7, consider the following expression:
(x<6)&&(y>6)
The operand x<6 is true and the operand y>6 is also true. Thus,
the result of above given logical expression is also true. However, the
result of following expression is false because one of the operands is
false:
(x<6)&&(y>7)
Similarly, consider the following expression:
(x<6)||(y>7)
The operand x<6 is true whereas the operand y>7 is false. Since
these operands are connected by logical OR, the result of this expression
is true (Rule 2). However, the result of the following expression becomes
false because both the operands are false.
!(x<6)||(y>7)
Note: The expression on the right hand side of logical operators & and ||
does not get evaluated in case the left hand side determines the outcome.
Consider the expression given below:
x&&y
If x evaluates to false (zero), then the outcome of the above expression is
bound to be false irrespective of y evaluating to any logical value.
Therefore, there is no need to evaluate the term y in the above
expression.
Similarly, in the following expression, if x evaluates to true (non zero)
then the outcome is bound to be true. Thus, y will not be evaluated.
x||y
(x<y)||(x>20)&&!(z)||((x<y)&&(z>5))
For a complex expression such as given above, it becomes difficult to
make out as to in what order the evaluation of sub-expressions would
take place.
In C, the order of evaluation of an expression is carried out according to
the operator precedence given in Table 1.6.
Table 1.6 Operator precedence
where
if:
expression:
statement
sequence:
is a reserved word.
is a boolean expression enclosed within a set of
parentheses (These parentheses are necessary
even if there is a single variable in the
expression).
is either a simple statement or a block. However,
it cannot be a declaration.
1. if(A>B)A=B;
2. if(total<100){
total=total+val;
count=count+1;
}
3. if((Net>7000)&&(l_tex==500)){
:
}
2 The if-else statement: It can be observed from the above examples that
the simple if statement does nothing when the expression is false. An if-
else statement takes care of this aspect. The general form of this construct
is given below:
if(expression)
{
statementsequence1
}
else
{
statementsequence2
}
where
if:
expression:
statement
sequence1:
else:
statement
sequence2:
is a reserved word.
is a reserved word.
can be a simple or a compound statement.
1.
2.
if(A>B)C=A;
elseC=B;
if(x==100)
printf(\nEqualto100);
else
printf(\nNotEqualto100);
It may be noted here that both the if and else parts are terminated by
semicolons.
2 Nested if statements (if-else-if ladder): The statement sequence of if
or else may contain another if statement, i.e., the if-else statements can be
nested within one another as shown below:
if(exp1)
if(exp2)
{
:
}
else
if(exp3)
{
:
}
else
{
:
}
It may be noted here that sometimes the nesting may become complex in
the sense that it becomes difficult to decide which if does the else match.
This is called danglingelseproblem. The C compiler follows the
following rule in this regard:
Rule: Each else matches to its nearest unmatched preceding if.
Consider the following nested if:
if(x<50)if(y>5)Net=x+y;elseNet=xy;
In the above statement, the else part matches the second if (i.e., if (y > 5))
because it is the nearest preceding if. It is suggested that the
nested if(s) should be written with proper indentation. The else(s) should
be lined up with their matching if(s). Nested if(s) written in this
fashion are also called if-else-if ladder. For example, the nested if given
above should be written as:
if(x<50)
if(y>5)
Net=x+y;
else
Net=xy;
However, if one desires to match the else with the first if, then the braces
should be used as shown below:
if(x<50){
if(y>5)
Net=x+y;
}
else
Net=xy;
The evaluation of if-else-if ladder is carried out from top to bottom. Each
conditional expression is tested and if found true, only then its
corresponding statement is executed. The remaining ladder is, therefore, by
passed. In a situation where none of the nested conditions is found true, the
final else part is executed.
3 Switch statement (selection of one of many alternatives): If it is
required in a program to select one of several different courses of action,
then the switch statement of C can be used. In fact, it is a multibranch
selection statement that makes the control to jump to one of the several
statements based on the value of an int variable or expression. The general
form of this statement is given as:
switch(expression)
{
caseconstant1:
statement;
break;
caseconstant2:
statement;
break;
:
default:
statement;
}
where
switch:
is a reserved word.
expression:
case:
is a reserved word.
constant:
statement:
default:
break:
1. The value of the expression is matched with the random case constants of
the switch construct.
2. If a match is found then its corresponding statements are executed and
when break is encountered, the flow of control jumps out of the switch
statement. If break statement is not encountered, then the control continues
across other statement. In fact, switch is the only statement in C which is
error prone. The reason is that if the control is in a particular case and then
it keeps running through all cases in the absence of a proper break
statement. This phenomenon is called fall-through.
3. If no match is found and if a default label is present, then the statement
corresponding to default is executed.
4. The values of the various case constants must be unique.
5. There can be only one default statement in a switch statement.
where
while:
is a reserved word of C.
<cond>:
is a boolean expression.
statement:
statement;
}
while<cond>;
where
do:
is a reserved word.
statement:
while:
is a reserved word.
<cond>:
is a boolean expression.
1.
2.
3.
4.
Thus, it performs a post-test in the sense that the condition is not tested
until the body of the loop is executed at least once.
3. The for loop: It is a count controlled loop in the sense that the program
knows in advance as to how many times the loop is to be executed. The
general form of this construct is given below:
for(initialization;expression;increment)
{
statement
}
where
for:
is a reserved word.
initializatio
n:
expression:
increment:
statement:
The loop is executed with the loop control variable at initial value, final
value and the values in between.
We can increase the power of for-loop with the help of the comma
operator. This operator allows the inclusion of more than one expression in
place of a single expression in the for statement as shown below:
for(exp1a,exp1b;exp2;exp3a,exp3b;)statement;
Consider the following program segment:
:
for(i=1,j=10;i<=10;i++,j)
{
:
}
The variable i and j have been initialized to values 1 and 10, respectively.
Please note that these initialization expressions are separated by a comma.
However, the required semicolon remains as such. During the execution of
the loop, i increases from 1 to 10 whereas j decreases from 10 to 1
simultaneously. Similarly, the increment and decrement operations have
been separated by a comma in the for statement.
Though the power of the loop can be increased by including more than
one initialization and increment expression separated with the comma
operator but there can be only one test expression which can be simple or
complex.
printf(\nErrorininput);
break;
}
:
}
Whenever the value contained, in variable val becomes negative, the
message Error in input would be displayed and because of the break
statement the loop will be terminated.
The continue statement can also be used in any C loop to bypass the
rest of the code segment of the current iteration of the loop. The loop,
however, is not terminated. Thus, the execution of the loop resumes with
the next iteration. For example, the following loop computes the sum of
positive numbers in a list of 50 numbers:
:
sum=0;
for(i=0;i<50;i++)
{
printf(\n%d,val);
if(val<=0)continue;
sum=sum+val;
}
:
It may be noted here that continue statement has no relevance as far as
the switch statement is concerned. Therefore, it cannot be used in a
switch statement.
1.10.4 The exit() Function
In the event of encountering a fatal error, the programmer may desire to
terminate the program itself. For such a situation, C supports a function
called exit() which can be invoked by the programmer to exit from the
program. This function can be called from anywhere inside the body of
the program. For normal termination, the programmer can include an
argument 0 while invoking this library function as shown below:
#include<stdio.h>
#include<process.h>
voidmain()
{
:
if(error)exit(0);
:
}
It may be noted here that the file process.h has to be included as header
file because it contains the function prototype of the library
function exit().
1.10.5 Nested Loops
It is possible to nest one loop construct inside the body of another. The
inner and outer loops need not be of the same construct as shown below.
while<cond>
{
do
{
}while<cond>;
for(init;exp;inc)
{
:
}
:
}
The rules for the formation of nested loops are:
1. An outer for loop and an inner for loop cannot have the same control
variable.
2. The inner loop must be completely nested inside the body of the outer loop.
is a reserved word.
<statement
label>:
statement.
gotorpara;
rpara:
again: C = A + B;
Thus, again is a label attached to the statement C = A + B and the
control of execution can be transferred to this statement by the following
goto statement.
gotoagain;
It may be noted here that the transfer of control out of a control
structure is allowed. However, a goto branch into a control structure is
not allowed.
Some example programs using control structures are given below.
Example 3: Write a program that reads three numbers and prints the
largest of them.
Solution: The ifelse ladder would be used. The required program is
given below:
/*Thisprogramdisplaysthelargestofthreenumbers
*/
#include<stdio.h>
main()
{
intA,B,C;
printf(\nEntertheNumbersA,B,C:);
scanf(%d%d%d,&A,&B,&C);
/*ifelseladder*/
if(A>B)
if(A>C)
printf(\nAislargest);
else
printf(\nCislargest);
else
if(B>C)
printf(\nBislargest);
else
printf(\nCislargest);
/*endofladder*/
}
Most of the programs read and write data through input/output devices
such as screen, keyboard, printer, and disk devices, etc. Obviously, the
programmer has to learn how to perform correct I/O operations so that
efficient programs can be written. We have already used two C
functions printf()and scanf() in the previous examples. In the
Type
Description
(1) getchar()
stream
(2) putchar()
stream
(3) getc()
stream
(4) putc()
stream
(5) gets()
stream
(6) puts()
stream
(7) cgets()
console
(8) getche()
console
(9) putch()
console
(10) cputs()
console
In order to use the above functions, the programmer must include the
stdio.h header file at the beginning of his program. The C or C
environment assumes keyboard as the standard input device and VDU
as standard output device. A consol comprises both keyboard and VDU.
By default, all stream I/Os are buffered. At this point, it is better that we
understand the term buffered I/O before we proceed to discuss the
stream I/O functions.
1.11.1 Buffered I/O
Whenever we read from the keyboard or from a file stored on a device, a
block of data is stored in a buffer called input buffer. The input stream,
therefore, reads from the buffer with the help of a pointer until the input
buffer is empty (see Figure 1.7). As soon as the buffer becomes empty,
the next block of data is transferred into the buffer from the I/O device.
Meaning
stdin
standard input
stdout
standard output
stderr
standard error
stdaux
auxiliary storage
stdprn
printer
In the computer, stdin and stdout refer to the users console, i.e.,
input device as keyboard and output device as VDU (screen). The stream
pointers stdprn and stdaux refer to the printer and auxiliary
storage, respectively. The error messages generated by the stream are
sent to standard error (stderr).
In order to clear the buffers, a function called fflush() can be used.
For example, input buffer of standard input device can be cleared by
invoking the function: fflush(stdin).
1.11.2 Single Character Functions
C supports many input and output functions based on character. These
functions read or write one character at a time. C supports both stream
3.
Note: These functions cannot read special keys from the keyboard such
as function keys.
1.11.3 String-based Functions
String-based functions read or write a string of characters at a time. C
supports both stream and console I/O string-based functions.
1. gets() and puts() functions: These functions are string-based I/O
functions. The functiongets() reads a string from the standard input
device (keyboard). It is also buffered and, therefore, the return or enter key
has to be typed to terminate the input. The fflush() function should also be
used after each gets() to avoid interference. For example, the program
segment given below reads a string in a variable name from keyboard.
:
name=gets();
fflush(stdin);
:
Arrays are very useful for list and table processing. However, the
elements of an array must be of the same data type. In certain situations,
we require a construct that can store data items of mixed data types. C
struct:
is the keyword.
<name>:
member1,2n:
/*Thisprogramillustratescopyingofcomplete
structures*/
#include<stdio.h>
main()
{
structstudent{
charname[20];
introll;
intage;
charclass[6];
};
/*initializeavariableoftypestudent*/
structstudentstud1={SachinKumar,101,16,
CE_IV};
structstudentstud2;
/*assignstud1tostud2*/
stud2=stud1;
/*Displaycontentsofstud2*/
printf(\nThecopiedcontentsare....);
printf(\nName:%s,stud2.name);
printf(\nRoll:%d,stud2.roll);
printf(\nAge:%d,stud2.age);
printf(\nclass:%s,stud2.class);
}
1.13.6 Nested Structures
Nested structures are structures as member of another structure. For
instance, the date of birth (DOB) is a structure within the structure of a
student as shown in figure below. These types of structures are known as
nested structures.
intyy;
};
structstudent
{
charname[20];
introll;
structdatedob;
intmarks;
};
It may be noted here that the member of a nested structure is referenced
from the outermost to inner most with the help of dot operators. Let us
declare a variable stud of type student:
structstudentstud;
The following statement illustrates the assignment of value 10 to the
member mm of this nested structure stud:
stud.dob.mm=10;
The above statement means: store value 10 in mm member of a structure
dob which itself is a member of structure variable called stud.
1.14 USER-DEFINED DATA TYPES
typedef:
is a reserved word.
<type>:
<new_type>:
1, and so on. Once the type color has been defined, then variables of that
type can be defined in the following manner:
enumcolorA,B;
Now, the variable A of type color can use the enumerations (blue, yellow,
etc.) in a program.
Thus, the following operations on variable A are valid:
A=red;
:
if(A==red)
x=x+10;
else
x=x5;
Enumerated data type can be precisely defined as the ordered set of
distinct constant values defined as a data type in a program.
Examples of some valid enumerated data types are:
1. enumfurniture
{
chair,
table,
stool,
desk
};
2. enumcar
{
Maruti,
Santro,
Micra,
Indigo
}
True
True
Table == Desk
False
True
Suppose in a fete, every visitor is assured to win any one of the items
listed below:
1.
2.
3.
4.
Wall clock
Toaster
Electric Iron
DVD player
Obviously, a visitor to the fete must carry a bag large enough to carry the
largest item so that any one of the items won by him can be held in that
bag. With the same philosophy in mind, the designers of C introduced a
special variable called union which may hold objects of different sizes
and types but one at a time. Though a union is a variable and can hold
only one item at a time, it looks like a structure. The general format of a
union is given below:
union<name>{
member1
member2
:
member
};
where
union:
is a keyword.
<name>:
member1,2,n:
1.16 FUNCTIONS
<type>:
<name>:
arguments
:
The program segment enclosed within the opening brace and closing
brace is known as the function body. For example, the main function in C
is written as shown below:
main()
{
}
Let us write a function tri_area()which calculates the area of a
triangle. Two variables base and height will be used to read the values
from the keyboard. The function is given below:
floattri_area()
{
floatbase,height,area;
printf(\nEnterBaseandHeight);
scanf(%f%f,base,height);
area=(0.5)*base*height;
return(area);
}
It may be noted here that the function tri_area() has defined its own
variables. These are known as local variables. The advantage of their
usage is that once the execution of the function is over these variables are
disposed off by the system, freeing the memory for other things.
Let us write another function clear() which clears the screen of the
VDU
/*Thisfunctionclearsthescreenbywritingblank
linesonthescreen*/
voidclear()
{
inti;
for(i=0;i<=30;i++)
printf(\n);
}
Similarly, let us write a function add which receives two parameters x
and y of type integer from the calling function and returns the sum of x
andy.
intadd(intx,inty)
{
inttemp;
temp=x+y;
returntemp;
}
In the above function, we have used a local variable temp to obtain the
sum of x and y. The value contained in temp is returned through return
statement. In fact, variable temp has been used to enhance the
readability of the function only, otherwise it is unnecessary. The function
can be optimized as shown below:
indadd(intx,inty)
{
return(x+y);
}
Thus, a function has following four elements:
1.
2.
3.
4.
A name
A body
A return type
An argument list
The name of the function has to be unique so that the compiler can
identify it. The body of the function contains the C statements which
define the task performed by the function. The return type is the type of
value that the function is able to return to the calling function. The
argument or parameter list contains the list of values of variables from
outside required by the function to perform the given task. For example,
the function given above has a name add. It has a body enclosed between
the curly braces. It can return a value of type int. The argument list is x
and y.
1.16.1 Function prototypes
Similar to variables, all functions must be declared before they are used
in a program. C allows the declaration of a function in the calling
program with the help of a function prototype. The general form of a
function prototype in the calling program is given below:
<type> <name> (arguments);
wher
e
<type>:
<name>:
arguments
:
is a list of parameters.
;:
}
}
floattri_area()
{
floatbase,height,area;
printf(\nEnterBaseandHeight);
scanf(%f%f,&base,&height);
area=(0.5)*base*height;
return(area);
}
/*Thisfunctionclearsthescreenbywritingblank
linesonthescreen*/
voidclear()
{
inti;
for(i=0;i<=30;i++)
printf(\n);
}
It may be noted here that when this program is executed, the first
statement to be executed is the first executable statement of the
function main() of the program (i.e., clear() in this case). A function
will be executed only after it is called from some other function or itself.
The variables base, height, and area are local to function tri_area and
they are not available in themain() function. Similarly, the variable flag
declared in function main() is also not available to
functions tri_area() and clear().
However, the variables declared outside any function can be accessed by
all the functions. Such variables are called global variables.
Let us consider the following program which declares the variables flag
and area outside the main function instead of declaring them inside the
functions main() and tri_area(), respectively.
#include<stdio.h>
/*Functionprototypes*/
voidtri_area();
voidclear();
/*GlobalVariables*/
intflag;
floatarea;
main()
{
charch;/*LocalVariable*/
clear();/*Clearthescreen*/
flag=0;
while(flag==0)
{
tri_area();/*calltri_areatocomputearea*/
printf(\nAreaofTriangle=%5.2f,area);
printf(\nWanttocalculateagain:EnterY/N);
fflush(stdin);
ch=getchar();
if(ch==N||ch==n)
flag=1;
clear();
}
}
/*FunctionDefinitions*/
voidtri_area()
{
floatbase,height;
printf(\nEnterBaseandHeight);
scanf(%f%f,&base,&height);
area=(0.5)*base*height;
}
/*Thisfunctionclearsthescreenbywritingblank
linesonthescreen*/
voidclear()
{
inti;
for(i=0;i<=30;i++)
printf(\n);
}
It may be noted in the above program that the variable area is available
to both the functions tri_area() and main(). On the other hand,
the local variables base and height are available to
functiontri_area only. They are neither available to main() function
nor to the function clear(). Similarly, variable i is also local to
function clear().
The above program seems to be wonderful but it fails down badly because
variables a and b do not get any values back from the function
exchange(). The reason is that although all values of actual parameters
(i.e., a and b) have been passed on to formal parameters (i.e., x and y)
correctly, the changes done to the values in the formal parameters are not
reflected back into the calling function.
Once the above program is executed for input value (say 30 and 45), the
following output is produced:
The exchanged contents are: 30 and 45
The reason for this behaviour is that at the time of function call, the
values of actual parameters a and b get copied into the memory locations of
the formal parameters x and y of the functionexchange().
It may be noted that the variables x and y have entirely different memory
locations from variablesaandb. Therefore any, changes done to the
contents of x and y are not reflected back to the variables a and b. Thus, the
original data remains unaltered. In fact, this type of behaviour is desirable
from a pure function.
Since in this type of parameter passing, only the values are passed from
the calling function to the called function, the technique is called call by
value.
2. Call by reference: If the programmer desires that the changes made to
the formal parameters be reflected back to their corresponding actual
parameters, then he should use call by reference method of parameter
passing. This technique passes the addresses or references of the actual
parameters to the called function. Let us rewrite the
function exchange(). In fact, in the called function, the formal arguments
receive the addresses of the actual arguments by using pointers to them.
After the above program is executed for input values (say 30 and 45), the
following output is produced:
Theexchangedvaluesare:4530
Now, we can see that the program has worked correctly in the sense that
changes made inside the function exchange() have been available to
corresponding actual parameters a and b in the function main(). Thus,
the address operator & has done the required task.
The main advantage of call by reference method is that more than one
value can be received back from the called function.
Note:
1. As far as possible, we should use pass by value technique of parameter
passing. If some value is to be received back from the function, it should be
done through a return statement of the function.
2. A function call can use both the methods, i.e., call by value and call by
reference simultaneously.For example, the following is a valid function
prototype:
voidcalc(inta,float*b,float*c);
3. When actual parameters are passed from the calling function to the called
function by call by value method, the names of the corresponding dummy
parameters could be the same or different. However, it is suggested that
they may be named different in order to make the program more elegant.
4. Array parameters: Arrays can also be passed as parameters to functions.
This is always done through call by reference method. In C, the name of the
array represents the address of the first element of the array and, therefore,
during the function call only the name of the array is passed from the calling
function. For example, if an array xyz of size 100 is to be passed to a
functionsome() then the function call will be as given below:,
some(xyz);
The formal parameters can be declared in the called function in three
ways. The different ways are illustrated with the help of a
function some() that receives an array xyz of size 100 of integer type.
Method I:
In this method, the array is declared without subscript, i.e., of an known
size as shown below:
voidsome(intxyz[])
{
}
The C compiler automatically computes the size of the array.
Method II:
In this method, an array of same size and type is declared as shown
below:
voidsome(intxyz[100])
{
}
Method III:
In this method, the operator * is applied on the name of the array
indicating that it is a pointer.
voidsome(int*xyz)
{
}
All the three methods are equivalent and the function some() can be
called by the following statement:
some(xyz);
Note:
1. The arrays are never passed by value and cannot be used as the return type
of a function.
2. In C, the name of the array represents the address of the first element or the
zeroth element of the array. So when an array is used as an argument to a
function, only the address of the array gets passed and not the copy of the
entire array. Hence, any changes made to the array inside the function are
automatically reflected in the main function. You do not have to
use ampersand(&) with array name even though it is a call by reference
method. The ampersand(&) is implied in the case of array.
reflected back to the calling function. The program given below illustrates
the usage of call by reference method.
This program reads the data of a student in the function main() and passes
the structure by reference to the function called print_data().
/*Thisprogramillustratesthepassingofthe
structuresbyreferencetoafunction*/
#include<stdio.h>
structstudent
{
charname[20];
intage;
introll;
charclass[5];
};
/*Prototypeofthefunction*/
voidprint_data(structstudent&Sob);
main()
{
structstudentstud;
printf(\nEnterthestudentdata);
printf(\nName:);fflush(stdin);
gets(stud.name);
printf(\nAge:);scanf(%d,&stud.age);
printf(\nRoll:);scanf(%d,&stud.roll);
printf(\nClass:);fflush(stdin);
gets(stud.class);
print_data(&stud);/*Thestructureisbeingpassedby
reference*/
}
voidprint_data(structstudent&Sob)
{
structstudent*ptr;
ptr=sob;
printf(\nThestudentdata..);
printf(\nName:);fflush(stdout);
puts(ptr>name);
printf(Age:%d,ptr>age);
printf(\nRoll:%d,ptr>roll);
printf(\nClass:);fflush(stdout);
puts(ptr>class);
}
structstudentstud,newstud;
printf(\nEnterthestudentdata);
printf(\nName:);fflush(stdin);
gets(stud.name);
printf(\nAge:);scanf(%d,&stud.age);
printf(\nRoll:);scanf(%d,&stud.roll);
printf(\nClass:);fflush(stdin);
gets(stud.class);
/*callfunctiontochangedata*/
newstud=change_data(stud);
printf(\nThechangeddata..);
print_data(&newstud);/*Thestructureisbeing
passedbyvalue*/
}
/*Thisfunctionchangesthedataofa
student*/
structstudentchange_data(structstudentSob)
{
inti=0;
while(Sob.name[i]!=\0)
{/*Capitalizetheletters*/
Sob.name[i]=toupper(Sob.name[i]);
i++;
}
return(Sob);
}
voidprint_data(structstudent*ptr)
{
printf(\nThestudentdata..);
printf(\nName:);fflush(stdout);
puts(ptr>name);
printf(Age:%d,ptr>age);
printf(\nRoll:%d,ptr>roll);
printf(\nClass:);fflush(stdout);
puts(ptr>class);
}
1.17 RECURSION
performing computation each time by the same method and the result of
computation is utilized as the source of data in the next repetition of the
loop. For example the factorial of an integer N can be computed by the
following iterative loop:
fact=1;
for(i=1;i<=N;i++)
fact=fact*i;
printf(\nThefactorialof%d%d,N,fact);
We can also use an alternative approach wherein we reduce the problem
into a smaller instance of the same problem. For example, factorial of N
can be reduced to a product of N and the factorial of N 1 as given
below:
Fact ( N )= N Fact (N 1)
In the above statement, a function Fact(N) has been defined in terms of
Fact(N 1), where Fact (N 1) is a smaller problem as compared to
Fact(N). This type of definition is known as recursive
definition.Recursion can be defined as: the ability of a concept being
defined within the definition itself. In programming terms, recursion is
defined as the ability of a function being called from within the function
itself.
Now, by the same definition, the factorial of (N 1) can be defined as
given below :
Fact(N 1) = (N 1) Fact(N 2)
We can continue this process till we end up with Fact(0) which is equal
to 1, and is also the terminating condition for this reduction process. The
terminating condition for a recursive function is also called the basic
solution. For instance, the basic solution for a list of elements is that if
the list contains only one element then this element is largest as well as
smallest element. Similarly, if a list contains only one element then it is
already sorted.
The process described above can be implemented by using a recursive
function Fact() which is defined in terms of itself as shown below:
intfact(intN)
{
if(N==0)
return(1);
else
return(N*fact(N1);
}
It may be further noted that the function Fact is being called by itself but
with parameter N being replaced by N 1. A trace of Fact (N) for N = 4 is
given in Fig.1.11
printf("\nThisprogramcomputesthefactorialofa
number");
printf("\nEnterthenumber:");
scanf("%d",&num);
factorial=fact(num);
printf("\nThefactorialof%dis%d",num,
factorial);
}
longintfact(intn)
{
if(n==0)
return(1);
else
return(n*fact(n1));
}
Recursion can be used to write simple, short, and elegant programs.
However, a recursive algorithm requires a basic condition that must
terminate the recursive calls. The absence of this condition would result
in infinite recursive calls. For example, in function fact the condition N=
= 0 is the required terminating condition.
Note: Since recursion involves overheads such as CPU time and
memory storage, it is suggested that recursion should be used with care.
Example 8: Write a function that computes xy by using recursion.
Solution: We can use the property that xy is simply a product of x and xy1
. For example, 64 = 6 63 . The recursive definition of xy is given below:
Steps
1. Find the larger of the two numbers and store larger in x and smaller in y.
2. Divide the x by y and store the remainder in rem.
3. If rem is equal to zero, then the smaller number (y) is the required GCD and
stop.
printf("\nEntertheintegerswhosegcdistobefound
:");
scanf("%d%d",&x,&y);
if(x>y)
ans=gcd(x,y);
else
ans=gcd(y,x);
printf("\nTheGCDis:%d",ans);
}
//ThisfunctioncomputestheGCD
intgcd(intx,inty)
{
intrem;
rem=x%y;
if(rem==0)
returny;
else
gcd(y,rem);
}
Example 10: Write a program that generates the first n terms of
Fibonacci sequence by recursion. The sequence is 0, 1, 1, 2, 3, 5, 8, .
Solution: In a fibonacci series each term (except the first two) can be
obtained by the sum of its two immediate predecessors. The recursive
definition of this sequence is given below:
Let us now write a program that uses a function Fib() to compute the
first n terms of the series.
/*ThisprogramgeneratesFibonacciseries*/
#include<stdio.h>
intfib(int);
voidmain()
{
intn;
inti,term;
printf("\nEnterthetermstobegenerated:");
scanf("%d",&n);
for(i=1;i<=n;i++)
{
term=fib(i);
printf("%d",term);
}
}
//Functiontoreturnafibonacciterm
intfib(intn)
{
if(n==1)
return0;
else
if(n==2)
return1;
else
return(fib(n1)+fib(n2));
}
From above, we can summarize the characteristics of recursion as:
o There must exist at least a basic solution called terminating condition. This
statement is processed without recursion.
o There must exist a method of reducing a problem into a smaller version of
the problem
o A mechanism to throw back the smaller problem back to the recursive
function
{
if(n==1)
return0;
else
if(n==2)
return1;
else
return(fib(n1)+fib(n2));
}
Another example of binary recursion is binary tree recursive operations,
done for both left child and right child sub trees.
C. Tail Recursion
When a recursive call in a recursive function is the last call without any
pending operation then the recursion is called tail recursion. Thus, the
last result of the recursive call is the final result of the function.
Consider the function fact(), given below:
longintfact(intn)
{
if(n==0)
return(1);
else
return(n*fact(n1));
}
This function is not tail recursive because the last statement is not
recursive call but the multiplication operation: n * fact (n 1).
However, the following modified function fact() is tail recursive.
longfact(intN,intresult)
{
if(N==1)
returnresult;
else
returnfact(N1,N*result);//Tailrecursion
}
It may be noted that the last statement in the above function is a
recursive call without any pending operation. Hence the function is tail
recursive. Since the argument &result& is acting as an accumulator, its
initial value must be set to 1. Therefore, for computing factorial of a
given number (say 5), the above function must be called with the
following statement:
Factorial = fact ( 5, 1);
Example 11: Write a program that computes the factorial of a given
number by using the above given tail recursive function fact().
Solution: The required program is given below:
#include<stdio.h>
longfact(intN,intresult)
{
if(N==1)
returnresult;
else
returnfact(N1,N*result);
}
voidmain()
{
longfactorial;
intnum;
printf("\nEnterthenumber");
scanf("%d",&num);
factorial=fact(num,1);
printf("\nfactorial=%u",factorial);
}
Sample outputs for the program are given below:
else
returnpower(x,y1,x*result);//Tail
recursion
}
Example 13: Write a program that tests the tail recursive function
of Example 12 for given values of x and y.
Solution: The required program is given below:
#include<stdio.h>
longpower(intx,inty,longresult)
{
if(y==0)
returnresult;
else
returnpower(x,y1,x*result);//Tailrecursion
}
voidmain()
{
longpow;
intx,y;
printf("\nEnterthex,y");
scanf("%d%d",&x,&y);
pow=power(x,y,1);
printf("\nx^y=%u",pow);
}
Sample output of the above program is given below:
Sum(6) = 1 + 2 + 3 + 4 + = + 6
Solution: The normal recursive function is given below:
longsum(intN)
{
if(N==1)
return1;
else
returnN+sum(N1);
}
The tail recursive version of function sum() is given below:
longsum(intN,intresult)
{
if(N==1)
returnresult;
else
return(sum(N1,N+result));//Tailrecursion
}
Example 15: Write a complete program that takes the tail recursive
version of the function sum() to compute the first N integers.
The legend predicts that as soon as all the disks are moved to the
destination tower, the world will come to an end, i.e., the priests will
crumble into dust and the world will vanish.
This problem can be solved with the help of recursion. Assume there are
N disks on the source tower. Since on the destination tower, the
bottommost disk needs to be the bottom most of the source tower, it is
necessary that we move N 1 disks to the middle tower and thereafter
Fig. 1.14 Moving N 1 disks to the middle tower and the bottom most
to final tower
Now repeat this exercise with N 1 disks using source tower as the
middle tower, and middle tower playing the role of the source tower.
The above given steps can be written using a recursive algorithm as given
below:
Algoritm towOfHan ( N, source, dest, middle )
{
if(N==1)
moveadiskfromsourcetodest
else
{towOfHan(N1,source,middle,dest)
moveadiskfromsourcetodest
towOfHan(N1,middle,dest,source)
}
}
Example 16: Write a program that simulate the moves of Tower of
Hanoi for a given number of disks, say N.
Solution: The above given algorithm towOfHan() has been used to code
the required program which is given below:
//ThisprogramsimulatesthemovesoftowerofHanoi
#include<stdio.h>
#include<conio.h>
voidmovDisk(intsource,intdest);
voidtowOfHan(intnumDisk,intsource,intdest,int
mid);
voidmain()
{
intnumDisk;
clrscr();
printf("\nHowmanydisksdoyouwanttomove?");
scanf("%d",&numDisk);
printf("\nDiskTomove%ddisksfrom1to2using3
asmiddledisk:",
numDisk);
towOfHan(numDisk,1,2,3);
}
voidmovDisk(intsource,intdest)
{
printf("\nmoveadiskfrom%dto%d",source,dest);
}
voidtowOfHan(intnumDisk,intsource,intdest,int
mid)
{
if(numDisk==1)
movDisk(source,dest);
else
{
towOfHan(numDisk1,source,mid,dest);
movDisk(source,dest);
towOfHan(numDisk1,mid,dest,source);
}
}
A sample output of the program is given below:
printf(Compu);
printf(ter);
}
Ans. The output would be: Computer
2. What would be the output of following program?
#include<stdio.h>
main()
{
printf(Compu\buter);
}
Ans. The output would be: Computer
3. What would be the output of following program?
voidmain()
{
intk=30;
/*
printf(k=%d,k);
*/
printf(valueofk=%d,k);
}
Ans. The output would be: value of k = 30
4. Write four equivalent C statements each of which subtracts 1 from a variable
called Val.
Ans. The required statements are:
1.
2.
3.
4.
Val=Val1;
Val;
Val;
Val=1;
1.
2.
if(Val==0)
if(Val!=0)
1.
2.
if(!Val)
if(Val)
2 Write a single statement that increments the value of a variable Val by 1 and
then adds it to a variable called num.
Ans. The required statement is: num=num+(++Val);
3 What will be the output of the following statements?
1.
2.
ch=A+10;
printf(%c,ch);
inta=5;
printf(%d,a<10);
#include<stdio.h>
intfirst(int,int);
intsecond(int);
voidmain()
{
intx=10,y=5,z;
z=first(x,y);
printf(\nz=%d,z);
}
intfirst(intx,inty)
{
return(x+second(y));
}
intsecond(inty)
{
return++y;
}
Ans. The output would be: 16
EXERCISES
1. Define the terms: token, keyword, identifier, variable, constant and const
correct.
2. What is meant by basic data types? Explain in brief.
3. What would be the appropriate data type for the following?
1. Length of a cloth
2. Age of a student
3. An exclamation mark
2 What is meant by a backslash character? What is the utility of these
characters in a program?
3 What would be the output of the following program?
#include<stdio.h>
{
intk;
printf(k=%d,k);
}
1.
2.
3.
4.
an error
unpredictable
34216
0
Note: Kindly refer to Chapter 5 before reading the text of this section.
2
Data Structures and Algorithms: An Introduction
CHAPTER OUTLINE
2.1 Overview
2.2 Concept of Data Structures
2.3 Design of a Suitable Algorithm
2.4 Algorithm Analysis
2.1 OVERVIEW
It may be noted that elements of list and the procedure represent the
data and program of the problem, respectively.
Now, the efficiency of the program would primarily depend upon the
organization of data. When the data is poorly organized, then the
program cannot efficiently access it. For instance, the time taken to open
the lock of your house depends upon where you have kept the key to it:
in your hand, in front pocket, in the ticket pocket of your pant or the
inner pocket of your briefcase, etc. There may be a variety of motives for
selecting one or the other of these places to keep the key. Key in the hand
or in the front pocket would be quickly accessible as compared to other
and grade. Out of many possibilities, the following two choices of data
structures are worth considering:
Choice I: The various data items related to a student are: name, roll
number, marks, percentage, and grade. The total number of students is
60. Therefore, the simplest choice would be to have as many parallel lists
as there are data items to be represented. The arrangement is
shown Figure 2.4.
temp=roll[i];
roll[i]=roll[j];
roll[j]=temp;
temp=marks[i];
marks[i]=marks[j];
marks[j]=temp;
fTemp=percent[i];
percent[i]=percent[j];
percent[j]=fTemp;
chTemp=grade[i];
grade[i]=grade[j];
grade[j]=chTemp;
Though the above code may work, but it is not only a clumsy way of
representing the data but would require a lot of typing effort also. In fact,
as many as fifteen statements are required to exchange the data of ith
student with that of jth student.
Choice II: From the built-in data structures, the struct can be
selected to represent the data of a student as shown below:
structstudent{
charname[15];
introll;
intmarks;
floatpercent;
chargrade;
};
Similarly, from built-in data structures, an array called studList can
be selected to represent a list. However, in this case each location has to
be of type student which itself is a structure. The required declaration is
given below:
structstudentstudList[60];
It may be noted that the above given array of structures is a user-defined
data structure constructed from two built-in data
structures: struct and array. Now in this case, exchange between data
of ith andjth student would require the following operations:
inti,j;
structstudenttemp;
temp=studList[i];
studList[i]=studList[j];
studList[j]=temp;
Thus, only three statements are required to exchange the data of ith
student with that of jth student.
A comparison of the two choices discussed above establishes that Choice
II is definitely better thanChoice I as far as the selection of data
structures for the given problem is concerned. The number of lines of
code of Choice II is definitely less than the Choice I. Moreover, the code
of Choice II is comparatively easy and elegant.
From the above discussion, it can be concluded that choice of right data
structures is of paramount importance from the point of view of
representing the data belonging to a problem.
2.2.2 Types of Data Structures
A large number of data structure are available to programmers that can
be usefully employed in a program. The data structures can be broadly
classified into two categories: linear and non-linear.
(1) Linear data structures: These data structures represent a
sequence of items. The elements follow a linear ordering. For instance,
one-dimensional arrays and linked lists are linear data structures. These
can be used to model objects like queues, stacks, chains (linked lists),
etc., as shown in Figure 2.5.
5. Constant: It is the memory location that does not change its contents
during the execution of a program.
6. Variable: It is the most fundamental aspect of any computer language and
can be defined as a location in the memory wherein a value can be
manipulated, i.e., stored, accessed, and modified.
2.3 DESIGN OF A SUITABLE ALGORITHM
Thus, we can say that the algorithm tells the computer when to read
data, compute on it, and write the answers.
While solving problems with the help of computers, the following facts
should be kept in mind:
A location in the main memory has the property: Writing-in is
destructive and Reading-out is non-destructive. In simple words, we
can say that if a value is assigned to a memory location, then its previous
contents will be lost. However, if we use the value of a memory location
then the contents of the memory do not change, i.e., it remains intact.
For example, in the following statement, the value of variables B and C
are being used and their sum is stored in variable A
A=B+C
where, the symbol = is an assignment operator.
If previous values of A, B, C were 15, 45, 20, respectively then after
execution of the above statement, the new contents of A, B, C would be
65, 45, and 20, respectively.
Consider the following statement:
Total = Total + 100;
The abnormal looking statement is very much normal as far as computer
memory is concerned. The statement reveals the following fact:
Old value of variable Total is being added to an integer 100 and the
result is being stored as new value in the variable Total, i.e., replacing
its old contents with the new contents.
After the execution of the statement with old contents of Total (say 24),
the new contents of Total will be 124.
List of numbers
Size of list
Type of numbers to be stored in the list
In which order to be sorted
Output specifications
1. In what order and format the outputs values will be produced?
2. What types of values to be produced with what spacing i.e., decimal spacing,
significant digits, etc.
3. What main headings and column headings to be printed on the output page.
The problem at hand is How to print the table. The first version of the
algorithm is given below:
Algorithm Print_TableVer1.
{
Step1.Printtable
}
When we look at the table, we find that the table is nothing but a
collection of rows and we have to print only 10 rows. So, the problem has
been reduced to printing a row 10 times. The second refined version of
the algorithm is given below:
Algorithm Print_TableVer2.
{
Step
1.Row=1
2.Printtherow
3.Row=Row+1
4.IfRow<=10repeatsteps2and3
5.Stop
}
Now our focus is on how to print a row. A closer look at the problem
suggests that a row is nothing but a collection of columns and we have to
print only 10 columns for each row. The third refined version of the
algorithm is given below:
Algorithm Print_TableVer3.
{
Step
1.Row=1
2.Col=1
2.1Printthecol.
2.2Col=Col+1
2.3IfCol<=10repeatsteps2.1and2.2
2.4Gotoanewline.
3.Row=Row+1
4.IfRow<=10repeatsteps2and3
5.Stop.
}
Now our concern is how to print a column. We find that it is very
simple, i.e., we get the value to be printed by multiplying the values
contained in the variables Row and Col The final version of the refined
algorithm is given below:
Algorithm Print_TableVer4.
{
Step
1.Row=1
2.Col=1
2.1ColVal=Row*Col
2.2PrinttheColVal.
2.3Col=Col+1
2.4IfCol<=10repeatsteps2.1to2.3
2.5Gotoanewline.
3.Row=Row+1
4.IfRow<=10repeatsteps2and3
5.Stop.
}
The programmer stops at this stage where he finds that the step(s) is
trivial and can be easily implemented.
Solution:
Input: Three numbers: Num1,Num2, and Num3
Type of numbers: integer.
Output: Display the biggest number and a message in this regard.
Algorithm Biggest()
{
Step
1.ReadNum1,Num2,Num3;
2.If(Num1>Num2)
2.1If(Num1>Num3)
PrintNum1isthelargest
Else
PrintNum3isthelargest
Else
2.2If(Num2>Num3)
PrintNum2isthelargest
Else
PrintNum3isthelargest
3.Stop
}
(3) Iteration: The iteration structure is used to repeatedly execute a
statement or a block of statements. The loop is repeatedly executed until
a certain specified condition is satisfied.
Example 5: Write an algorithm that finds the largest number from a
given list of numbers.
Solution:
Input: List of numbers, size of list, prompt for input.
Type of numbers: integer.
Output: Display the largest number and a message in this regard.
The most basic solution to this problem is that if there is only one
number in the list then the number is the largest. Therefore, the first
number of the list would be taken as largest. The largest would then be
compared with the rest of the numbers in the list. If a number is found
larger than the current largest, then it is stored in the largest. This
process is repeated till the end of the list. For iteration, For loop can be
employed.
The concerned algorithm is given below:
Algorithm Largest
{
Step
1.PromptEnterthesizeofthelist;
2.ReadN;
3.PromptEnteraNumber;
4.ReadNum;
5.Largest=Num;/*Thefirstnumberisthe
largest*/
6.For(I=2toN)
{
6.1PromptEnteraNumber
6.2ReadNum
6.3If(Num>Largest)Largest=Num
}
7.PromptThelargestNumber=
8.PrintLargest;
9.Stop
}
It may be noted here that if a loop terminates after a number of
iterations, then the loop is called afinite loop. On the other hand if a
loop does not terminate at all and keeps on executing endlessly, then it is
called an infinite loop.
(4) Use of accumulators and counters: Accumulator is a variable
that is generally used to compute sum or product of a series. For
example, the sum of the following list of numbers can be computed by
initializing a variable (say Total) to zero.
The 1st number of the list is added to Total and then the 2nd, 3rd and so
on till the end of the list. Finally, the variable Total would get the
accumulated sum of the list given above.
Example 6: Write an algorithm that reads a list of 50 integer values and
prints the total of the list.
Solution: Let us assume that a variable called sum would be used as an
accumulator. A variable called Num would be used to read a number
from the input list one at time. For loop will be employed to do the
iteration.
The Algorithm is given below:
Algorithm SumList
{
Step
1.Sum=0;
2.For(I=1to50)
{
2.1ReadNum;
2.2Sum=Sum+Num;/*Theaccumulationofthe
list*/
}
3.PromptThesumofthelist=;
4.PrintSum;
5.Stop
}
It may be noted that for product of a series, the accumulator is initialized
to 1.
Counter is a variable used to count number of operations/actions
performed in a loop. It may also be used to count number of members
present in a list or a set of items.
The counter variable is generally initialized by 0. On the occurrence of an
event/operation, the variable is incremented by 1. At the end of the
counting process, the variable contains the required count.
Example 7: Write an algorithm that finds the number of zeros present
in a given list of N numbers.
Solution: A counter variable called Num_of_Zeros would be used to
count the number of zeros present in a given list of numbers. A variable
called Num would be used to read a number from the input list one at
time. For loop will be employed to do the iteration.
The Algorithm is given below:
Algorithm Count_zeros()
{
Step
1.Num_of_zeros=0;
2.For(I=1toN)
{
2.1ReadNum;
2.2If(Num==0)
Num_of_zeros=Num_of_zeros+1;
}
3.PromptThenumberofzeroelements=;
4.PrintNum_of_zeros;
5.Stop
}
Example 8: Write a program that computes the sum of first N multiples
of an integer called K, i.e.
sumSeries = 1*K + 2*K + 3*K + N*K
Solution: An accumulator called sumSeries would be used to
compute the required sum of the series. As the number of steps are
known (i.e., N), For loop is the most appropriate for the required
computation. The index of the loop will act as the counter.
Input: N, K
Output: sumSeries
/*Thisprogramcomputesthesumofaseries*/
#include<stdio.h>
voidmain()
{
intsumSeries,i;
intN;/*Numberofterms*/
intK;/*Theintegermultiple*/
printf(\nEnterNumberoftermsandInteger
Multiple);
scanf(%d%d,&N,&K);
sumSeries=0;/*Accumulatorinitialized*/
For(i=1;i<=N;i++)
{
sumSeries=sumSeries+i*K;
}
printf(\nThesumoffirst%dterms=%d,N,
sumSeries);
}
In fact, it can be concluded that irrespective of the programming
paradigms, a general algorithm is designed based on following
constructs:
1. Sequence structure: Statement after statements are written in sequence
in a program.
A critical look at the above discussion indicates that the data structures
and algorithms are related to each other and have to be studied together.
For a given problem, an algorithm is developed to solve it but the
algorithm needs to operate on data. Unless the data is stored in a
suitable data structure, the design of algorithm remains incomplete. So,
both the data structures and algorithm are important for program
development.
In fact, an algorithm requires following two resources:
1. Memory space: Space occupied by program code and the associated data
structures.
2. CPU time: Time spent by the algorithm to solve the problem.
where c>0.
(2.1)
From above relation, we can say that for a large value of n, the function
g provides an upper bound on the growth rate T.
Let us now consider the various constructs used by a programmer to
write an algorithm.
(1) Simple statement: Let us assume that a statement takes a unit
time to execute, i.e., 1. Thus, T(n) = 1 for a simple statement. To satisfy
the Big-Oh condition, the above relation can be rewritten as shown
below:
T(n) <= 1*1
{
Step
1.Readbase,height;
2.Area=0.5*base*height;
3.PrintArea;
4.Stop
}
The above algorithm takes four steps to complete.
Thus, T(n) = 4.
To satisfy the relation 2.1, the above relation can be rewritten as:
T(n) <= 4*1
where c = 4 and n0 = 0.
= 4 (Steps 1, 3, 4, 5)
=1
= 3 (1 comparison, 1 addition, 1
assignment)
where c = 4, n0 >= 4
Hence, the given algorithm has time complexity of the order of N, i.e.,
O(N). This indicates that the term 4 has negligible contribution in the
expression: 3*N + 4.
Note: In case of nested loops, the time complexity depends upon the
counters of both outer and inner loops. Consider the nested loops given
below. This program segment contains S, a sequence of statements
within nested loops I and J.
For(I=1;I<=N;I++)
{
For(J=1;J<=N;J++)
{
S;
}
}
It may be noted that for each iteration of I loop, the J loop executes N
times. Therefore, for N iterations of I loop, the J loop would execute N*N
= N2 times. Accordingly, the statement S would also execute N2times.
Thus, T(n) = N2
To satisfy the relation 2.1, the above relation can be rewritten as shown
below:
T(n) <= 1 * N2
Comparing the above relation with relation 2.1, we get g(n) = N2.
Therefore, the above relation can be expressed as:
T(n) = O(N2)
Hence, the given nested loop has time complexity of the order of N2, i.e.,
O(N2).
Example 9: Compute the time complexity of the following nested loop:
For(I=1;I<=N;I++)
{
For(J=1;J<=I;J++)
{
S;
}
}
Hence, the given algorithm has time complexity of the order of N2, i.e.,
O(N2). This indicates that the term N/2 has negligible contribution in the
expression N2/2 + N/2.
(4) If-then-else structure: In the if-then-else structure, the then and
else parts are considered separately. Consider the if-then-else structure
given in Figure 2.8.
}
Else
{
For(i=1;i<=N,i++)
{
x=x+i;
}
}
Solution: It may be noted that in the above algorithm, the time
complexity of then and else part are O(1) and O(N), respectively. The
maximum of the two is O(N). Therefore, the time complexity of the
above algorithm is O(N).
Example 11: compute the time complexity of the following relations:
1.
2.
3.
4.
T(n) = 2527
T(n) = 8*n + 17
T(n) = 15*N2 + 6
T(n) = 5*N3 + N2 = 2*N
Solution:
1. T(n) = 2527 <= 2527*1, where c = 2527 and n0 = 0.
= O(1)
Ans. The time complexity = O(1)
Note: All the relations, given in example 10 were polynomials. From the
answers, we can say that any polynomial has the time complexity of BigOh of its leading term with coefficient of 1.
It is also understandable that the Big-Oh does not provide the exact
execution time. Rather, it gives only an asymptotic upper bound of the
execution time of an algorithm.
For different types of functions, the growth rates for different values of
input size n have been tabulated in Table 2.1.
It can be observed from Table 2.1 that for a given input, the growth rate
O(lg n) is faster than O(n lg n). For the same input size, the growth rate
O(N2) is the slowest.
Later in the book, for various algorithms, timing estimates will be
included using Big-Oh notation.
Table 2.1 Growth rates of functions
EXERCISES
1.
2.
3.
4.
5.
6.
7.
8.
1.
2.
3.
4.
5.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Data element
Primitive data types
Data object
Constant
Variable
1.
2.
3.
4.
T(n) = 410
T(n) = 4*n -12
T(n) = 13*N2 -7
T(n) = 3*N3 + N214*N
1.
2.
3.
4.
for(inti=0;i<n;i++)
{
for(intj=0;j<n;j++)
{
statement;
}
}
for(inti=0;i<n;i++)
{
for(intj=i+1;j<n;j++)
{
statement;
}
}
for(inti=0;i<n;i++)
{
for(intj=n;j>i;j)
{
statement;
}
}
for(inti=0;i<n;i++)
{
for(intj=0;j<m;j++)
{
statement1;
}
statement2;
}
if(a==b)
statement1;
elseif(a>b)
statement2;
else
statement3;
Chapter 3
Arrays: Searching and Sorting
CHAPTER OUTLINE
3.1 Introduction
3.2 One-dimensional Arrays
3.3 Multi-dimensional Arrays
3.4 Representation of Arrays in Physical Memory
3.5 Applications of Arrays
3.1 INTRODUCTION
In fact, the studList shown in Figure 3.1 can be looked upon by the
following two points of views:
1. It is a linear list of 45 names stored in contiguous locationsan abstract
view of a list having finite number of homogeneous elements (i.e., 45
names).
2. It is a set of 0 to 44 memory locations sharing a common name
called studListit is an array data structure in which 45 names have been
stored.
It may be noted that all the elements in the array are of same type,
i.e., string of char in this case. The individual elements within the array
can be designated by an index. The individual elements are randomly
accessible by integers, called the index.
For instance, the zeroth element (Sagun) in the list can be referred to as
studList [0] and the 43rd element (Ridhi) as studList [43] where 0
and 43 are the indices. An index has to be of type integer.
An index is also called a subscript. Therefore, individual elements of an
array are called subscripted variables. For instance, studList
[0] and studList[43] are subscripted variables.
From the above discussion, we can define an array as a finite
ordered collection of items of same type. It is a set of index,
value pairs.
An array is a built-in data structure in every programming language.
Arrays are designed to have a fixed size. Some languages provide zerobased indexing whereas other languages provide one-basedindexing.
C is an example of zero-based indexing language because the index of
its arrays starts from 0. Pascal is the example of one-based
indexing because the index of its arrays starts from 1.
An array whose elements are specified by a single subscript is known
as one-dimensional array. The array whose elements are specified by
two or more than two subscripts is called as multi-dimensional array.
3.2 ONE-DIMENSIONAL ARRAYS
Solution: As the problem deals with a list of marks, we can select a onedimensional array calledmarksList to represent the list. The grace
marks are to be added to all the elements of the list, for which we must
use the For-loop. The required program is given below:
/*Thisprogramaddsgracemarkstoallelementsofa
listofmarks*/
#include<stdio.h>
voidmain()
{
intmarksList[30];
inti;
intgrace=5;/*gracemarks*/
/*ReadthemarksList*/
printf(\nEnterthelistofmarksonebyone);
for(i=0;i<30;i++)
{
scanf(%d,&marksList[i]);
marksList[i]=marksList[i]+grace;
}
/*PrinttheFinallist*/
printf(\nThefinalListis);
printf(\nS.No.\tMarks);
for(i=0;i<30;i++)
printf(\n%d\t%d,i,marksList[i]);
}
It may be noted that in the above program, a component by component
processing has been done on the all elements of the array
called marksList.
The following operations are defined on array data structure:
o Traversal: An array can be travelled by visiting its elements starting from
the zeroth element to the last in the list.
o Selection: An array allows selection of an element for a given (random)
index.
o Searching: An array can be searched for a particular value.
o Insertion: An element can be inserted in an array at a particular location.
o Deletion: An element can be deleted from a particular location of an array.
o Sorting: The elements of an array can be manipulated to obtain a sorted
array.
{
intList[30];
intN;
inti,numNeg,numZero,numPos;
printf(\nEnterthesizeofthelist);
scanf(%d,&N);
printf(Entertheelementsonebyone);
/*ReadtheList*/
for(i=0;i<N;i++)
{
printf(\nEnterNumber:);
scanf(%d,&List[i]);
}
numNeg=0;/*Initializecounters*/
numZero=0;
numPos=0;
/*TraveltheList*/
for(i=0;i<N;i++)
{
if(List[i]<0)
numNeg=numNeg+1;
else
if(List[i]==0)
numZero=numZero+1;
else
numPos=numPos+1;
}
}
3.2.2 Selection
An array allows selection of an element for a given (random) index.
Therefore, the array is also called random access data structure. The
selection operation is useful in a situation wherein the user wants query
about the contents of a list for a given position.
Example 4: Write a program that stores a merit list in an array
called Merit. The index of the array denotes the merit position, i.e.,
1st, 2nd, 3rd, etc. whereas the contents of various array locations
represent the percentage of marks obtained by the candidates. On users
demand, the program displays the percentage of marks obtained for a
given position in the merit list as per the following format:
Position: 4
Percentage: 93.25
Solution: An array called Merit would be used to store the given merit
list. Two variables pos andpercentage would be used to store the merit
position and the percentage of a candidate, respectively. A do-while loop
would be used to iteratively interact with the user through following
menu displayed to the user:
Menu:
Query----1
Quit------2
Enter your choice
Note: As per the demand of the program, we will leave the zeroth
location as unused and fill the array from index 1.
The required program is given below:
/*Thisprogramillustratestherandomselection
operationallowedbyarraydatastructures*/
#include<stdio.h>
#include<conio.h>
voidmain()
{
floatMerit[30];
intsize;
inti;
intpos;
floatpercentage;
intchoice;
printf(\nEnterthesizeofthelist);
scanf(%d,&size);
printf(\nEnterthemeritlistonebyone);
for(i=1;i<=size;i++)/*ReadMerit*/
{
printf(\nEnterdata:);
scanf(%f,&Merit[i]);
}
/*displaymenuandstartsession*/
do
{
clrscr();
printf(\nMenu);
printf(\nQuery1);
printf(\nQuit2);
printf(\nEnteryourchoice:);
scanf(%d,&choice);
/*useswitchstatement
fordeterminingthechoice*/
switch(choice)
{
case1:printf(\nEnterPosition);
scanf(%d,&pos);
percentage=Merit[pos];/*therandom
selection*/
printf(\nposition=%d,pos);
printf(\npercentage=%4.2f,percentage);
break;
case2:printf(\nQuitting);
}
printf(\npressakeytocontinue);
getch();
}
while(choice!=2);
}
Searching and sorting are very important activities in a data processing
environment and they are discussed in detail in the subsequent section.
3.2.3 Searching
There are many situations where we want to find out whether a
particular item is present in a list or not. For instance, in a given voter
list of a colony a person may search his name to ascertain whether he is a
valid voter or not. For similar reasons, passengers look for their names in
the railway reservation lists.
Note: System programs extensively search symbols, literals,
mnemonics, compiler and assembler directives, etc.
In fact, search is an operation in which a given list is searched for a
particular value. The location of the searched element is
informed. Search can be precisely defined an activity of looking for a
value or item in a list.
A list can be searched sequentially wherein the search for the data item
starts from the beginning and continues till the end of the list. This
simple method of search is also called linear search. It may be noted
that for a list of size 1,000, the worst case is 1,000 comparisons.
Let us consider a situation wherein we are interested in searching a list
of numbers called numListfor an element having value equal to the
contents of a variable called val. It is desired that the location of the
element, if found, be displayed.
The list of numbers can be comfortably stored in an array
called numList of type int. To find the element, the list would be
travelled in such a manner that each visited element would be compared
to the variable val. If the match is found, then the location of the
corresponding position would be stored in a variable called Pos. As the
number of elements in the list is known, For-loop would be used to travel
the array.
Algorithm searchList()
{
step
1.readnumList
2.readVal
3.Pos=1initializePostoanonexisting
position
4.for(i=0;i<N;i++)
{
4.1if(val==numList[i])
Pos=i;
}
5.if(Pos!=1)
printPos.
}
The C implementation of the above algorithm is given below:
/*ThisprogramsearchesagivenvaluecalledValin
alistcallednumList*/
#include<stdio.h>
voidmain()
{
intnumList[20];
intN;/*Sizeofthelist*/
intPos,val,i;
printf(\nEnterthesizeoftheList);
scanf(%d,&N);
printf(\nEntertheelementsonebyone);
for(i=0;i<N;i++)
{
scanf(%d,&numList[i]);
}
printf(\nEnterthevaluetobesearched);
scanf(%d,&val);
/*Searchtheelementanditspositionin
thelist*/
Pos=1;
for(i=0;i<N;i++)
{
if(val==numList[i])
{
Pos=i;
break;
}
/*Theelementisfoundcomeoutof
loop*/
}
if(Pos!=1)
printf(\nTheelementfoundat%dlocation,
Pos);
else
printf(\nSearchFailed);
}
Example 5: Write a program that finds the second largest element in a
given list of N numbers.
Solution: The most basic solution for this problem is that if there are
two elements in the list then the smaller of the two will be the second
largest. In this case, we would set the second largest number to 9999,
a value not possible in the list.
In given problem, the list will be searched linearly for the required
second largest number. Two variables
called firstLarge and secLarge would be employed to store the first
and second largest numbers. The following algorithm will be used:
Algorithm SecLarge()
{
step
1.readnum;
2.firstLarge=num;
3.secLarge=9999;
4.for(i=2;i<5N;i++)
{
4.1readnum;
4.2if(firstLarge<num)
{secLarge=firstLarge;
firstLarge=num;
}
else
if(secLarge<num)
secLarge=num;
}
4.promptSecondLarge5;
5.writesecLarge;
}
The equivalent program is given below:
/*Thisprogramfindsthesecondlargestinagiven
listofnumbers*/
#include<stdio.h>
intNum,firstLarge,secLarge;
intN,i;
voidmain()
{
printf(\nEnterthesizeofthelist);
scanf(%d,&N);
printf(\nEnterthelistonebyone);
scanf(%d,&Num);
firstLarge=Num;
secLarge=9999;
for(i=2;i<5N;i++)
{
scanf(%d,&Num);
if(firstLarge<Num)
{secLarge5firstLarge;
firstLarge5Num;
}
else
if(secLarge<Num)
secLarge=Num;
}
printf(\nSecondLarge=%d,secLarge);
}
The above discussed search through a list, stored in an array, has the
following characteristics:
o The search is linear.
o The search starts from the first element and continues in a sequential
fashion from element to element till the desired entry is found.
o In the worst case, a total number of N steps need to be taken for a list of size
N.
Thus, the linear search is slow and to some extent inefficient. In special
circumstances, faster searches can be applied.
For instance, binary search is a faster method as compared to linear
search. It mimics the process of searching a name in a directory wherein
one opens a page in the middle of the directory and examines the page
for the required name. If it is found, the search stops; otherwise, the
search is applied either to first half of the directory or to the second half.
3.2.3.1 Binary Search
If a list is already sorted, then the search for an entry (sayVal) in the
list can be made faster by using divide and conquer technique. The list
is divided into two halves separated by the middle element as shown
in Figure 3.2.
3. Repeat step 1 and 2 on the selected half until the entry is found otherwise
report failure.
This search is called binary because in each iteration, the given list is
divided into two (i.e., binary) parts. Therefore, in next iteration the
search becomes limited to half the size of the list to be searched. For
instance, in first iteration the size of the list is N which reduces to almost
N/2 in the second iteration and so on.
Let us consider a sorted list stored in an array called Series given
in Figure 3.3.
Fig. 3.3 The Series containing nine elements
Suppose we desire to search the Series for a value (say14) and its
position in it. Binary search begins by looking at the middle value in the
Series. The middle index of the array is approximated by averaging the
first and last indices and truncating the result, i.e., (0 + 8)/2 = 4. Now,
the content of the fourth location in Series happens to be 11 as shown
in Figure 3.4. Since the value we are looking for (i.e., 14) is greater than
11, the middle value, it may be present in the right half (Series [5]
to Series[8]).
Algorithm binSearch()
{
Step
1.First=0;
2.Last=N1;
3.Pos=1;
4.Flag=false;
5.While(First<=LastandFlag==false)
{
5.1Middle=(first+last)div2
if(Series[middle]==Val)
{Pos=middle;
flag=true;
breakfromtheloop;
}
else
if(Series[middle]<Val)First=middle
+1;
elseLast=middle1;
}
6.if(flag==true)
promptThevaluefoundat;
writepos;
elsepromptThevaluenotfound;
}
It may be noted that div operator has been used to indicate that it is
an integer division. The integer division will truncate the results to the
nearest integer. If the desired value is found, then the flag is set to true
and the while loop terminates; otherwise, a stage arrives when first
becomes greater than the last, indicating the failure of the search. Thus,
the variables First and Last keep track of the lower and the upper
bounds of the array, respectively.
Example 6: Write a program that uses binary search to search a given
value called Val in a list of Nnumbers called Series.
Solution: The Algorithm binSearch() discussed above is used to
write the required program.
/*Thisprogramusesbinarysearchtofindagiven
valuecalledvalinalistofNnumbers*/
#include<stdio.h>
#definetrue1
#definefalse0
voidmain()
{
intFirst;
intLast;
intMiddle;
intSeries[20];/*ThelistofNsortednumbers*/
intVal;
intflag;/*Thevaluetobesearched*/
intN,Pos,i;
printf(\nEnterthesizeofthelist);
scanf(%d,&N);
printf(\nEnterthesortedlistonebyone);
for(i=0;i<N;i++)
{
scanf(%d,&Series[i]);
}
printf(\nEnterthenumbertobesearched);
scanf(%d,&Val);
/*BINSEARCHbegins*/
Pos=1;/*Nonexistingposition*/
flag=false;/*Assumesearchfailure*/
First=0;
Last=N1;
while((First<=Last)&&(flag==false))
{
Middle=(First+Last)/2;
if(Series[Middle]==Val)
{Pos=Middle;
flag=true;
break;
}
else
if(Series[Middle]<Val)
First=Middle+1;
else
Last=Middle1;
}
if(flag==true)
printf(\nThevaluefoundat%d,Pos);
else
printf(\nThevaluenotfound);
}
Binary search through a list, stored in an array, has the following
characteristics:
o The list must be sorted, i.e., ordered.
o It is faster as compared to the linear search.
o A list with large number of elements would increase the total execution
time, the reason being that the list must be ordered which requires extra
effort.
Number of steps
(23)
32
(25)
256
(28)
512
(29)
where c = 1 and n0 = 0.
Comparing the above relation with relation 2.1, we get g(n) = log2n.
Therefore, the above relation can be expressed as:
T(n) = O(log2n)
Hence, binary search has time complexity of the order of log2n, i.e.,
O(log2n).
3.2.4 Insertion and Deletion
3.2.4.1 Insertion
Insertion is an operation in which a new element is added at a particular
place in a list. When the list is represented using an array, the element
can be added very easily at the end of the list provided the space is
available. However, insertion of an element at a particular location
within the list is a difficult operation. For instance, if it is desired to
insert an element at ith location of a list then an empty space will have to
be created at the ith location of the list. In fact, all the elements from ith
location are shifted by one place towards right to accommodate the new
element.
voidmain()
{
intList[N];
intKey;
intLoc;
inti,M;
intback;
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&M);
printf(\nEnterthelistonebyone);
for(i50;i<M;i++)
{
scanf(%d,&List[i]);
}
printf(\nEnterthekeyandlocationrelativeto0
index);
scanf(%d%d,&Key,&Loc);
/*InsertthekeyatLocinList*/
if(Loc>M+1)
printf(\nInsertionnotpossible);
else
{
back=M+1;
/*Shiftelementsonestepright*/
while(back>Loc)
{
List[back]=List[back1];
back;
}
/*InsertKey*/
List[back]=Key;
M=M+1;
/*Displaythefinallist*/
printf(\nTheFinalListis);
for(i=0;i<M;i++)
{
printf(%d,List[i]);
}
}
}
3.2.4.2 Deletion
Deletion is the operation that removes an element from a given location
of a list. When the list is represented using an array, the element can be
very easily deleted from the end of the list. However, if it is desired to
delete an element from an ith location of the list, then all elements from
the right of (i+1)th location will have to be shifted one step towards left
to preserve contiguous locations in the array.
For instance, if it is desired to remove an element from 4th location of
the list given in Figure 3.8, then all elements from right of 4th location
would have to be shifted one step towards left.
{
2.1charList[Back]=charList[Back+1];/*Shift
elementsone
stepleft*/
2.2Back=Back+1;
}
3.M=M1;
4.Stop
}
It may be noted that in step 3, the contents of M have been decremented
by 1 to indicate that after deletion the last element in
the charList would be at (M - 1)th location.
Example 8: Write a program that deletes an element from a location
called loc in a list of M numbers called List.
Solution: The algorithm delElement() is used to implement the
required program.
/*Thisprogramdeletesanelementfromlistof
numbers*/
#include<stdio.h>
#defineN20
voidmain()
{
intList[N];
intLoc;
inti,M;
intback;
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&M);
printf(\nEnterthelistonebyone);
for(i=0;i<M;i++)
{
scanf(%d,&List[i]);
}
printf(\nEnterlocationfromwherethedeletionis
required);
scanf(%d,&Loc);
/*InsertthekeyatLocinList*/
if(Loc>M)
printf(\nDeletionnotpossible);
else
{
back=Loc;
/*Shiftelementsonestepleft*/
while(back<M)
{
List[back]=List[back+1];
back++;
}
M=M1;
/*Displaythefinallist*/
printf(\nTheFinalListis.);
for(i=0;i<M;i++)
{
printf(%d,List[i]);
}
}
}
It may be noted that arrays are not suitable data structures for problems
requiring insertions and deletions in a list. The reason is that we need to
shift elements to right or to left for insertion and deletion operations,
respectively. The problem aggravates when the size of the list is very
large as an equally large number of shift operations would be required to
insert and delete the elements. Thus, insertion and deletion are very slow
operations as far as arrays are concerned.
3.2.5 Sorting
It is an operation in which all the elements of a list are arranged in a
predetermined order. The elements can be arranged in a sequence from
smallest to largest such that every element is less than or equal to its
next neighbour in the list. Such an arrangement is called ascending
order. Assuming an array called List containing N elements, the
ascending order can be defined by the following relation:
List[i]<=List[i+1],0<i<N1
Similarly in descending order, the elements are arranged in a sequence
from largest to smallest such that every element is greater than or equal
to its next neighbour in the list. The descending order can be defined by
the following relation:
List[i]>=List[i+1],0<i<N1
It has been estimated that in a data processing environment, 25 per cent
of the time is consumed in sorting of data. Many sorting algorithms have
been developed. Some of the most popular sorting algorithms that can be
applied to arrays are in-place sort algorithms. An in-place algorithm is
generally a comparison-based algorithm that stores the sorted elements
of the list in the same array as occupied by the original one. A detailed
discussion on sorting algorithms is given in subsequent sections.
3.2.5.1 Selection Sort
It is a very simple and natural way of sorting a list. It finds the smallest
element in the list and exchanges it with the element present at the head
of the list as shown in Figure 3.10.
From Figures 3.11 and 3.12, it may be observed that it is a case of nested
loops. The outer loop is required for passes over the list and the inner
loop for searching smallest element within the unsorted part of the list.
In fact, for N number of elements, N 1 passes are made.
An algorithm for selection sort is given below. In this algorithm, the
elements of a list stored in an array called LIST[N] are sorted in
ascending order. Two variables called Small and Pos are used to locate
the smallest element in the unsorted part of the list. Temp is the variable
used to interchange the selected element with the first element of the
unsorted part of the list.
Algorithm SelSort()
{
Step
1.ForI=1toN1/*OuterLoop*/
{
1.1small=List[I];
1.2Pos=I;
1.3ForJ=I+1toN/*InnerLoop*/
{
1.3.1if(List[J]<small)
{
small=List[J];
Pos=J;/*Notethepositionofthesmallest*/
}
}
1.4Temp=List[I];/*Exchangesmallestwiththe
Head*/
1.5List[I]=List[Pos];
1.6List[Pos]=Temp;
}
2.Printthesortedlist
}
/*Thisprogramsortsalistbyusingselectionsort
*/
#include<stdio.h>
main()
{
intlist[10];
intsmall,pos,N,i,j,temp;
printf(\nEnterthesizeofthelist:);
scanf(%d,&N);
printf(\nEnterthelist:);
for(i50;i<N;i++)
{
printf(\nEnterNumber:);
scanf(%d,&list[i]);
}
/*Sortthelist*/
for(i50;i<N1;i++)
{
small=list[i];
pos=i;
/*Findthesmallestoftheunsortedlist*/
for(j=i+1;j<N;j++)
{
if(small>list[j])
{
small=list[j];
pos=j;
}
}
/*Exchangethesmallwiththe
firstelementofunsortedlist*/
temp=list[i];
list[i]=list[pos];
list[pos]=temp;
}
printf(\nThesortedlist);
for(i50;i<N;i++)
printf(%d,list[i]);
}
T(n) = O(N2)
Hence, selection sort has time complexity of the order of N2, i.e., O(N2).
3.2.5.3 Bubble Sort
It is also a very simple sorting algorithm. It proceeds by looking at the
list from left to right. Each adjacent pair of elements is compared.
Whenever a pair is found not to be in order, the elements are exchanged.
Therefore after the first pass, the largest element bubbles up to the right
end of the list. A trace of first pass on a list of numbers is shown
in Figure 3.13.
It may be noted that after the pass is over, the largest element in the list
(i.e., 20) has bubbled up to the end of the list and six exchanges were
made. Now the same process can be repeated for the list for second pass
as shown in Figure 3.14.
We observe that after the second pass is over, the list has become sorted
and only two exchanges were made. Now a point worth noting is as to
how and when it will be decided that the list has become sorted. The
simple criteria would be to check whether or not any exchange(s) has
been made during the current pass. If yes, then the list is not yet sorted
otherwise if it is no, then it can be decided that the list has just become
sorted.
An algorithm for bubble sort is given below. In this algorithm, the
elements of a list, stored in an array called List[N], are sorted in an
ascending order. The algorithm uses two loopsthe outer while loop and
inner For loop. The inner For loop makes a pass on the list. If during the
pass, any exchange(s) is made then it is recorded in a variable
called flag, i.e., flag is set to false. The outer while loop keeps track of
the flag. As soon as the flag informs that no exchange(s) took place
during the current pass indicating that the list is now sorted, the
algorithm stops.
Algorithm bubbleSort()
{
Step
1.Flag=false;
2.While(Flag==false)
{
2.1Flag=true;
2.2Forj=0toN2
{
2.2.1if(List[J]>List[J+1])
{
temp=List[J];
List[J]=List[J+1];
List[J+1]=temp;
Flag=false;
}
}
}
3.Printthesortedlist
4.Stop
}
It may be noted that the algorithm stops as soon as the list becomes
sorted. Thus, bubble sort is a very useful algorithm when the list is
almost sorted i.e., only a very small percentage of elements are out of
order.
Example 10: Given is a list of N randomly ordered numbers. Write a
program that sorts the list in ascending order by using bubble sort.
Solution: The required program is given below:
In this program, the elements of a list are stored in an array
called List. The elements are sorted using above given
algorithm bubbleSort().
/*Thisprogramsortsagivenlistofnumbersin
ascendingorder,usingbubblesort*/
#include<stdio.h>
#defineN20
#definetrue1
#definefalse0
voidmain()
{
intList[N];
intflag;
intsize;
inti,j,temp;
intcount;/*countsthenumberofpasses*/
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&size);
printf(\nEnterthelistonebyone);
for(i=0;i<size;i++)
{
scanf(%d,&List[i]);
}
/*Sortthelistbybubblesort*/
flag=false;
count=0;
while(flag==false)
{
flag=true;/*Assumenoexchangetakesplace*/
count++;
for(j=0;j<size1;j++)
{
if(List[j]>List[j+1])
{/*Exchangethecontents*/
temp=List[j];
List[j]=List[j+1];
List[j+1]=temp;
flag=false;/*Recordtheexchange
operation*/
}
}
}
/*Printthesortedlist*/
printf(\nThesortedlistis.);
for(i=0;i<size;i++)
printf(%d,List[i]);
printf(\nThenumberofpassesmade=%d,
count);
}
It may be noted that the above program has used a variable called count
that counts the number of passes made while sorting the list. The test
runs conducted on the program have established that the program is
very efficient in case of almost sorted list of elements. In fact, it takes
only one scan to establish that the supplied list is already sorted.
Note: Bubble sort is also called a sinking sort meaning that the
elements sink down in the list to their proper position.
3.2.5.4 Analysis of Bubble Sort
In bubble sort, again, there are two major operationscomparison and
exchange. The average number of exchange operations would be difficult
to estimate. Therefore, we will focus on comparison operations.
A closer look reveals that for a list of N numbers, bubble sort also has the
following number of comparisons, i.e., same as the selection sort.
(N 1) + (N 2) + . 2 + 1 = (N 1) N/2 = N2/2 N/2
Therefore, the time complexity of bubble sort is also O(N2).
However if the list is already sorted in the ascending order, no exchange
operations would be required and it becomes the best case. The
algorithm will have only N comparisons, i.e., only a linear running time.
3.2.5.5 Insertion Sort
This algorithm mimics the process of arranging a pack of playing cards.
In the pack of cards, the first two cards are put in correct relative order.
The third card is inserted at correct place relative to the first two. The
fourth card is inserted at the correct place relative to the first three, and
so on.
Given a list of numbers, it divides the list into two partsorted part and
unsorted part. The first element becomes the sorted part and the rest of
the list becomes the unsorted part as shown in Figure 3.15. It picks up
one element from the front of the unsorted part and inserts it at its
proper position in the sorted part of the list. This insertion action is
repeated till the unsorted part is exhausted.
The algorithm for the insertion sort is given below. In this algorithm, the
elements of a list stored in an array called List[N] are sorted in an
ascending order. The algorithm uses two loopsthe outer For loop and
inner while loop. The inner while loop shifts the elements of the sorted
part by one step to right so that proper place for incoming element is
created. The outer For loop inserts the element from unsorted part into
the created place and moves to next element of the unsorted part.
Algorithm insertSort()
{
Step
1.ForI=2toN/*Thefirstelementbecomesthe
sortedpart*/
{
1.1Temp=List[I];/*Savetheelementfrom
unsortedpartintotemp*/
1.2J=I1;
1.3While(Temp<=List[J]ANDJ>=0)
{
List[J+1]=List[J];/*Shiftelements
towardsright*/
J=J1;
}
1.4List[J+1]=Temp;
}
2.Printthelist
3.Stop
}
Example 11: Given is a list of N randomly ordered numbers. Write a
program that sorts the list in ascending order by using insertion sort.
Solution: The required program is given below:
/*Thisprogramsortsagivenlistofnumbersin
ascendingorderusinginsertionsort*/
#include<stdio.h>
#defineN20
voidmain()
{
intList[N];
intsize;
inti,j,temp;
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&size);
printf(\nEnterthelistonebyone);
for(i=0;i<size;i++)
{
scanf(%d,&List[i]);
}
/*SortthelistbyInsertionsort*/
for(i=1;i<size;i++)
{
temp=List[i];/*Pickandsavethefirstelement
oftheunsortedpart*/
j=i1;
while((temp<List[j])&&(j>=0))/*Scanfor
properplace*/
{
List[j+1]=List[j];
j=j1;
}
List[j+1]=temp;/*Inserttheelementatthe
properplace*/
}
/*Printthesortedlist*/
printf(\nThesortedlistis.);
for(i=0;i<size;i++)
{
printf(%d,List[i]);
}
}
3.2.5.6 Analysis of Insertion Sort
A critical look at the algorithm indicates that in worst case, i.e., when the
list is sorted in reverse order, the jth iteration requires (j 1)
comparisons and copy operations. Therefore, the total number of
comparison and copy operations for a list of N numbers would be:
1 + 2 + 3 +.(N 2) + (N 1) = (N 1) N/2 = N2/2 N/2
Therefore, the time complexity of insertion sort is also O(N2).
However if the list is already sorted in the ascending order, no copy
operations would be required. It becomes the best case and the
algorithm will have only N comparisons, i.e., a linear running time.
3.2.5.7 Merge Sort
This method uses following two concepts:
1. If a list is empty or it contains only one element, then the list is already
sorted. A list that contains only one element is also called singleton.
2. It uses the old proven technique of divide and conquer to recursively
divide the list into sub-lists until it is left with either empty or singleton
lists.
In fact, this algorithm divides a given list into two almost equal sub-lists.
Each sub-list, thus obtained, is recursively divided into further two sublists and so on till singletons or empty lists are left as shown in Figure
3.16.
Since the singletons and empty lists are inherently sorted, the only step
left is to merge the singletons into sub-lists containing two elements each
(see Figure 3.17)which are further merged into sub-lists containing four
elements each and so on. This merging operation is recursively carried
out till a final merged list is obtained as shown in Figure 3.17.
the list. The variable Ptr3 is also incremented to point to next vacant
location in mergeList.
This process of comparing, storing and shifting is repeated till both the
lists are merged and stored inmergeList as shown in Figure 3.19.
ptr2++;/*movetonextelementinthelist*/
ptr3++;
}
}
5.while(ptr1<mid)/*copyremainingfirstlist
*/
{
5.1mergeList[ptr3]=List[ptr1];
5.2ptr1++;
5.3ptr3++;
}
6.while(ptr2<=ub)/*copyremainingsecondlist
*/
{
6.1mergeList[ptr3]=List[ptr2];
6.2ptr2++;
6.3ptr3++;
}
7.for(i=lb;i<ptr3;i++)/*copymergedlist
backintooriginallist*/
7.1List[i]5mergeList[i];
8.Stop
}
It may be noted that an extra temporary array called mergeList is
required to store the intermediate merged sub-lists. The contents of
the mergeList are finally copied back into the original list.
The algorithm for the merge sort is given below. In this algorithm, the
elements of a list stored in an array called List[N] are sorted in an
ascending order. The algorithm has two partsmergeSort and merge.
The merge algorithm, given above, merges two given sorted lists into a
third list, which is also sorted. The mergeSort algorithm takes a list and
stores into an array called List[N]. It uses two variables lb and ub to
keep track of lower and upper bounds of list or sub-lists as the case may
be. It recursively divides the list into almost equal parts till singletons or
empty lists are left. The sublistsare recursively merged through
merge algorithm to produce final sorted list.
Algorithm mergeSort(List,lb,ub)
{
Step
1.if(lb<ub)
{
1.1mid=(lb+ub)/2;/*dividethelistinto
twosublists*/
1.2mergeSort(List,lb,mid);/*sorttheleft
sublist*/
1.3mergeSort(List,mid+l,ub);/*sortthe
rightsublist*/
1.4merge(List,lb,mid+l,ub);/*mergethelists
*/
}
2.Stop
}
Example 12: Given is a list of N randomly ordered numbers. Write a
program that sorts the list in ascending order by using merge sort.
Solution: The required program uses both the algorithms
mergeSort() and merge().
/*Thisprogramsortsagivenlistofnumbersin
ascendingorderusingmergesort*/
#include<stdio.h>
#include<conio.h>
#defineN20
voidmergeSort(intList[],intlb,intub);
voidmerge(intList[],intlb,intmid,intub);
voidmain()
{
intList[N];
inti,size;
intmid;
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&size);
printf(\nEnterthelistonebyone);
for(i50;i<size;i++)
{
scanf(%d,&List[i]);
}
/*Sortthelistbymergesort*/
mergeSort(List,0,size1);
printf(\nThesortedlistis.);
for(i=0;i<size;i++)
{
printf(%d,List[i]);
}
}
voidmergeSort(intList[],intlb,intub)
{
intmid;
if(lb<ub)
{
mid=(lb+ub)/2;
mergeSort(List,lb,mid);
mergeSort(List,mid+1,ub);
merge(List,lb,mid+1,ub);
}
}
voidmerge(intList[],intlb,intmid,intub)
{
intmergeList[20];
intptr1,ptr2,ptr3;
inti;
ptr1=lb;
ptr2=mid;
ptr3=lb;
while((ptr1<mid)&&ptr2<=ub)
{
if(List[ptr1]<=List[ptr2])
{mergeList[ptr3]=List[ptr1];
ptr1++;
ptr3++;
}
else
{mergeList[ptr3]=List[ptr2];
ptr2++;
ptr3++;
}
}
while(ptr1<mid)
{mergeList[ptr3]=List[ptr1];
ptr1++;
ptr3++;
}
while(ptr2<=ub)
{mergeList[ptr3]=List[ptr2];
ptr2++;
ptr3++;
}
for(i=lb;i<ptr3;i++)
{
List[i]=mergeList[i];
}
}
3.2.5.8 Analysis of Merge Sort
The merge sort requires the following operations:
1.
2.
3.
4.
For a list of N elements, steps 1 and 2 require two times the number of
comparisons needed for sorting a sub-list of size N/2. The step 3 requires
N steps to merge the two sub-lists.
Therefore, number of comparisons in merge sort can be defined by the
following recursive relation:
NumComp (N) = 0 if N = 1,
= 2*NumComp(N/2) + N for N > 1
Thus, NumComp (1) = 0
NumComp (N) = 2*NumComp(N/2) + N
(3.1)
(3.4)
The merge sort stops when we are left with one element per partition,
i.e., when N= 2k.
Now for N = 2k or k = log N, we can rewrite Eq. 3.4 as:
NumComp
(N)
NNumComp(N/N) + N*log2N
N*log2N
4.lb++;
5.partition(pivot,List,lb,ub);
}
Algorithm partition(pivot,List,lb,ub)
{
Step
1.i=lb;
2.j=ub;
3.while(i<=j)
{
/*travelthelistfromlbtillanelement
greaterthanthepivotisfound*/
3.1while(List[i]<=pivot)i++;
/*travelthelistfromubtillanelement
smallerthanthepivotisfound*/
3.2while(List[j]>pivot)j;
3.3if(i<=j)/*exchangetheelements*/
{
temp=List[i];
List[i]=List[j];
List[j]=temp;
}
}
4.temp=List[j];/*placethepivotatmidofthe
sublists*/
5.List[j]=List[lb1];
6.List[lb1]=temp;
7.if(j>lb)quicksort(List,lb,j1);/*sort
leftsublist*/
8.if(j<ub)quicksort(List,j+1,ub);/*sort
therightsublist*/
}
Example 13: Given is a list of N randomly ordered numbers. Write a
program that sorts the list in ascending order by using quick sort.
Solution: The required program uses both the algorithms
quickSort() and partition(). In this program, a variable
called Key has been used that acts as a pivot.
/*Thisprogramsortsagivenlistofnumbersin
ascendingorder,usingquicksort*/
#include<stdio.h>
#include<conio.h>
#defineN20
voidpartition(intKey,intList[],intlb,intub);
voidquicksort(intList[],intlb,intub);
voidmain()
{
intList[N];
inti,size,Pos,temp;
intlb,ub;
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&size);
printf(\nEnterthelistonebyone);
for(i=0;i<size;i++)
{
scanf(%d,&List[i]);
}
/*Sortthelistbyquicksort*/
quicksort(List,0,size1);
/*Printthesortedlist*/
printf(\nThesortedlistis.);
for(i=0;i<size;i++)
{
printf(%d,List[i]);
}
}
voidquicksort(intList[],intlb,intub)
{
intKey;/*ThePivot*/
Key=List[lb];lb++;
partition(Key,List,lb,ub);
}
voidpartition(intKey,intList[],intlb,intub)
{
inti,j,temp;
i=lb;
j=ub;
while(i<=j)
{
while(List[i]<=Key)i++;
while(List[j]>Key)j;
printf(\ni5%dj5%d,i,j);
getch();
if(i<=j)
{
temp=List[i];
List[i]=List[j];
List[j]=temp;
}
}
temp=List[j];
List[j]=List[lb1];
List[lb1]=temp;
if(j>lb)quicksort(List,lb,j1);
if(j<ub)quicksort(List,j+1,ub);
}
3.2.5.10 Analysis of Quick Sort
The quick sort requires the following operations:
Step
1.
2.
3.
4.
The best case for quick sort algorithm comes when we split the input as
evenly as possible into two sub-lists. Thus in the best case, each sub-list
would be of size n/2. Anyway, the partitioning operation would require N
comparisons to split the list of size N into the required sub-lists.
Now, for a list of size N, the number of comparisons can be defined as
follows:
NumComp(N
)
0 when N = 1
N + 2*NumComp(N/2)
List[j+k]=temp;/*insertthesavedelement
atitsplace*/
s++;/*gotonextset*/
}
}
3.printthesortedlist
4.Stop
}
Example 14: Given is a list of N randomly ordered numbers. Write a
program that sorts the list in ascending order by using shell sort.
Solution: The required program uses the algorithm shellSort().
/*Thisprogramsortsagivenlistofnumbersin
ascendingorderusingShellsort*/
#include<stdio.h>
#defineN20
voidmain()
{
intList[N];
intsize;
inti,j,k,p;
inttemp,s,step;
intdimStep[]={5,3,1};/*thediminishingsteps*/
printf(\nEnterthesizeofthelist(<20));
scanf(%d,&size);
printf(\nEnterthelistonebyone);
for(i=0;i<size;i++)
{
scanf(%d,&List[i]);
}
/*sortthelistbyshellsort*/
for(step=0;step<3;step++)
{
k=dimStep[step];/*setktodiminishingstep*/
s=0;/*startfromthesetofthelist*/
for(i=s+k;i<size;i+=k)
{
temp=List[i];/*savetheelementfromtheset
*/
j=ik;
while((temp<List[j])&&(j>=0))/*findthe
placeforinsertion*/
{
List[j+k]=List[j];
j=jk;
}
List[j+k]=temp;/*insertthesavedelement
atitsplace*/
s++;/*gotonextset*/
}
}/*Printthesortedlist*/
printf(\nThesortedlistis.);
for(i=0;i<size;i++)
{
printf(%d,List[i]);
}
}
The analysis of shell sort is beyond the scope of the book.
3.2.5.12 Radix Sort
It is a non-comparison-based algorithm suitable for integer values to be
sorted. It sorts the elements digit by digit. It starts sorting the elements
according to the least significant digit of the elements. This partially
sorted list is then sorted on the basis of second least significant bit and
so on.
Note: This algorithm is suitable to be implemented through linked lists.
Therefore, discussion on radix sort is currently being postponed and it
would be dealt with later in the book.
3.3 MULTI-DIMENSIONAL ARRAYS
two subscripts. The first subscript denotes rows of the matrix varying
from 0 to 2 whereas the second subscript denotes the columns varying
from 0 to 3.
3.Stop
}
In the above algorithm, nested loops have been used. The outer loop is
employed to travel the rows and the inner to travel the columns within a
given row.
Example 15: Given two matrices A[]mr and B[]rn , compute the
product of two matrices such that:
C[]mn=A[]mr*B[]rn
Solution: The multiplication of two matrices requires that a row of
matrix A be multiplied to a column of matrix B to generate an element of
matrix C as shown in Figure 3.27.
Fig. 3.27 The matrix multiplication operation
A close observation of the computation shown in Figure 3.27 gives the
following relation for an element of matrix C:
C[I][J]5C[I][J]+A[I][K]B[K][J]
0 to r1
We would use the above relation to compute the various elements of the
matrix C. The required program is given below:
/*ThisprogrammultipliestwomatricesA&Band
storestheresultantmatrixinC*/
#include<stdio.h>
main()
{
intA[10][10],B[10][10],C[10][10];
inti,j,k;/*Thearrayindices*/
intm,n,r;/*Orderofmatrices*/
printf(\nEntertheorderofthematricesm,r,
n:);
scanf(%d%d%d,&m,&r,&n);
printf(\nEnterTheelementsofMatrixA:);
for(i=0;i<m;i++)
for(j=0;j<r;j++)
{
printf(\nEnteranelement:);
scanf(%d,&A[i][j]);
}
printf(\nEnterTheelementsofMatrixB:);
for(i=0;i<r;i++)
for(j=0;j<n;j++)
{
printf(\nEnteranelement:);
scanf(%d,&B[i][j]);
}
/*Multiplythematrices*/
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
C[i][j]=0;
for(k=0;k<r;k++)
{
C[i][j]=C[i][j]+A[i][k]*B[k][j];
}
}
}
printf(\nTheresultantmatrixis\n);
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
printf(%d,C[i][j]);
}
printf(\n);
}
}
Example 16: A nasty number is defined as a number which has at least
two pairs of integer factors such that the difference of one pair equals to
sum of the other pair. For instance, the factors of 6 are 1, 2, 3, and 6.
Now the difference of factor pair (6, 1) is equal to sum of factor pair (2,
3), i.e., 6 1 = 2 + 3. Therefore, 6 is a nasty number.
Choose appropriate data structure and write a program that displays all
the nasty numbers present in a list of numbers.
Solution: The following data structures would be used to store the list
of numbers and the list of factorpair:
1. A one-dimensional array called List to store the list of numbers.
2. A two-dimensional array called pair_of_factors to store the list of pairfactors of a given number.
24
12
count=0;
/*Computethefactors*/
pair_of_factors[0][0]=1;
pair_of_factors[0][1]=num;
for(j=2;j<=sqrt(num);j++)
{
if(num%j==0)
{
count++;
pair_of_factors[count][0]=j;
pair_of_factors[count][1]=num/j;
}
}
/*Checkfornastiness*/
for(j=0;j<=count;j++)
{
diff=pair_of_factors[j][1]pair_of_factors[j]
[0];
for(k=0;k<=count;k++)
{
sum=pair_of_factors[k][1]+
pair_of_factors[k][0];
if(diff==sum)
{
printf(\n%disaNastynumber,num);
break;
}
}/*Endofkloop*/
}/*Endofjloop*/
}/*Endofiloop*/
}/*Endofprogram*/
3.4 REPRESENTATION OF ARRAYS IN PHYSICAL MEMORY
computes its physical load time address of physical memory location. For
example, List[5] would be loaded at physical address 5 105, in the
physical memory (see Figure 3.28).
Since physical memory is linear by construction, the address mapping is
quite a simple exercise. The compiler needs to know only the starting
address of the first element in the physical memory. The rest of the
addresses can be generated by a trivial formula. A brief discussion on
address computation is given in the subsequent sections.
100
2nd element
100 + 1
3rd element
100 + 2
100 + 5
_
_
6th element
It is very easy to deduce from above address map that the equivalent
physical address of an Ith element of array = 100 + (I 1). The starting
address of array at physical memory (i.e., 100) is called base address
(Base) of the array. For example, the base address of array called LIST is
100.
The formula for address of Ith location can be written as given below:
Address of List[I] = Base + (I 1)
(3.5)
Eq. 3.5 is a simple case, i.e., it is assumed that every element of List
would occupy only one location of the physical memory. However if an
element of an array occupies S number of memory locations, theEq.
3.5 can be modified as as follows:
Address of List[I] = Base 1 (I 1) * S
(3.6)
where
List: is the name of the array.
Base: is starting or base address of array.
I: is the index of the element.
S: is the number of locations occupied by an element of the array.
3.4.2 Physical Address Computation of Elements of Two-dimensional Arrays
Let us reconsider the two-dimensional array called TAB[2][3] as
shown in Figure 3.29.
The array called TAB has two rows and three columns. It has been
stored in physical memory at a starting address 100. The address map is
given below:
The address of 1st row, 1st col
100
100 + 1
100 + 2
100 + 5
_
_
2nd row, 3rd col
(3.7)
To test the above row major order formula, let us calculate the address of
element represented byTAB[2][1] with Base = 100.
Given: I = 2, J = 1, Base = 100. Putting these values in Eq. 3.7, we get
Address = 100 + (2 1)*3 + 1 1 = 100 + 3 + 0 = 103.
From Figure 3.29, we find that the address value is correct.
Now we can generalize the formula of ? Eq. 3.7 for an array of size [M]
[N] in the following form:
Address of [I][J] the element = BASE + (I 1)*N + (J
1)
(3.8)
The formula given above is a simple case, i.e., it is assumed that every
element of TAB would occupy only one location of the physical memory.
However if an element of an array occupies S number of memory
locations, the row major order formula (3.8) can be modified as given
below:
Address of TAB [I][J] the element = BASE + ((I 1)*N + (J
1))*S
(3.9)
where
TAB: is the name of the array.
Base: is starting or base address of array.
I: is the row index of the element.
J: is the column index of the element.
N: is number of columns present in a row.
S: is the number of locations occupied by an element of the array.
Similarly, the formula for column major order is as follows:
Address of TAB [I][J] the element = BASE + ((J 1)*M + (I
1))*S
(3.10)
where
TAB: is the name of the array.
Base: is starting or base address of array.
I: is the row index of the element.
J: is the column index of the element.
M: is number of rows present in a column.
S: is the number of locations occupied by an element of the array.
Example 17: TAB is a two-dimensional array with five rows and three
columns. Each element occupies one memory location. If TAB[1][1]
begins at address 500, find the location of TAB [4][2] for row major
order of storage.
Solution:
Given
base
Address
500 + (9 + 1)
510.
Example 18: MAT is a two-dimensional array with ten rows and five
columns. Each element is stored in two memory locations. If MAT[1]
[1] begins at address 200, find the location of MAT[3][4] for row
major order of storage.
Solution:
Given
base
Address
226.
Address
454.
3.5 APPLICATIONS OF ARRAYS
The arrays are used data structures which are very widely in numerous
applications. Some of the popular applications are:
3.5.1 Polynomial Representation and Operations
An expression of the form f(x) = anxn + an-1 xn-1+ an-2 xn-2 + ..+ a0 is
called a polynomial
where an 0.
anxn , an-1 xn-1 a1 x1, a0 are called terms.
an, an-1, a1, a0 are called coefficient.
xn, xn-1, xn-2are called exponents,
Now, a polynomial can be considered as a list comprising coefficients
and exponents as shown below:
F(x) = {an, xn , an-1, xn-1, a1, x1, a0, x0
For example, the polynomial 6x5 + 8x2 + 5 can be represented as the list
shown below:
Polynomial 5 {6, 5, 8, 2, 5, 0}
The above list can be very easily implemented using a one-dimensional
array, say Pol[] as shown inFigure 3.30 where every alternate location
contains coefficient and exponent.
The above representation can be modified to incorporate number of
terms present in a polynomial by reserving the zeroth location of the
array for this purpose. The modified representation is shown inFigure
3.31.
{
pol3[k]=pol2[j];
pol3[k1]=pol2[j1];
j=j+2;
k=k+2;
}
else
{
pol3[k1]=pol1[i1]+pol2[j1];
pol3[k]=pol1[i];
i=i+2;
j=j+2;
k=k+2;
}
}
/*copytheremainingPol1*/
while(i<m)
{
pol3[k]=pol1[i];
pol3[k1]=pol1[i1];
i=i+2;
k=k+2;
}
/*copytheremainingPol2*/
while(j<n)
{
pol3[k]=pol2[j];
pol3[k1]=pol2[j1];
j=j+2;
k=k+2;
}
pol3[0]=(k1)/2;
/*Displaythefinalpolynomial*/
printf(\nFinalPol=);
dispPol(pol3);
printf(\b);
}
voidreadPol(intpol[],intterms)
{
inti;
pol[0]=terms;
i=1;
printf(\nEntertheterms(coefexp)indecreasing
orderofdegree);
while(terms>0)
{
scanf(%d%d,&pol[i],&pol[i+1]);
i=i+2;
terms;
}
}
voiddispPol(intpol[])
{
intsize;
inti;
size=pol[0]*2+1;
printf(\n);
for(i=1;i<size;i+52)
{
printf(%d*x^%d+,pol[i],pol[i+1]);
}
}
We can also choose other data structures to represent polynomials. For
instance, a term can be represented by struct construct of C as shown
below:
structterm
{
intcoef;
intexp;
};
and the polynomial called pol of ten terms can be represented by an
array of structures of type term as shown below:
struct term pol1[10];
Now the algorithm merge() can be applied to add two such polynomials
with above chosen data structures. It is left as an exercise for the readers
to write that program.
istherownumber
Col
isthecolumnnumber
:
E:
isthenonzeroentryatthegivenrow
andcol.
Lets us now apply the above representation to MatA to obtain the list of
ordered pairs as given below:
0,4,2
1,1,3
1,6,5
3,3,7
4,7,4
5,0,8
The above representation is also called a condensed matrix. It is using
comparatively very less space which will become even prominent for
large matrices, say of the order of 500 500 or more.
{/*storenonzeroelementintocondensed
matrix*/
matA[k][0]=i;
matA[k][1]=j;
matA[k][2]=element;
k++;
}
}
matA[0][2]=k1;/*recordno.ofnonzero
elements*/
printf(\nThecondensedmatrixis);
for(i=1;i<k;i++)
printf(\n%d%d%d,matA[i][0],matA[i]
[1],matA[i][2]);
}
Note: No space has been provided for the sparse matrix. The elements
have been read one by one and the nonzero elements have been stored
into the condensed matrix.
A sample run of the program is given below:
intmat1[10][3],mat2[10][3],mat3[20][3];
intm,n;
clrscr();
printf(\nEntertheorderofthesparsemat1:m,
n);
scanf(%d%d,&m,&n);
readMat(mat1,m,n);
printMat(mat1);
printf(\nEntertheorderofthesparsemat2:m,
n);
scanf(%d%d,&m,&n);
readMat(mat2,m,n);
printMat(mat2);
addsparsMat(mat1,mat2,mat3);
printf(\nThefinalcondensedMatrix);
printMat(mat3);
}
voidreadMat(intmatA[10][3],intm,intn)
{
inti,j,k;
intelement;
matA[0][0]=m;/*no.ofrows*/
matA[0][1]=n;/*no.ofcols*/
k=1;/*pointto1stpositionofsparsemat*/
printf(\nEntertheelementsofthematrixinrow
majororder);
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
scanf(%d,&element);
if(element!=0)
{
matA[k][0]=i;
matA[k][1]=j;
matA[k][2]=element;
k++;
}
}
matA[0][2]=k1;/*recordno.ofnonzeroelements
*/
}
voidprintMat(intmatA[10][3])
{
inti,size;
size=matA[0][2];
for(i=0;i<=size;i++)
printf(\n%d%d%d,matA[i][0],matA[i]
[1],matA[i][2]);
}
voidaddsparsMat(intmat1[10][3],intmat2[10][3],
intmat3[20][3])
{
inti,j,k;
ints1,s2;/*No.ofelementsinmat1andmat2*/
s1=mat1[0][2];
s2=mat2[0][2];
i=j=k=0;
while(i<=s1&&j<=s2)
{
if(mat1[i][0]<mat2[j][0])/*rowofmat1<mat2*/
{
mat3[k][0]=mat1[i][0];
mat3[k][1]=mat1[i][1];
mat3[k][2]=mat1[i][2];
k++;i++;
}
else
if(mat1[i][0]>mat2[j][0])/*rowofmat1>mat2
*/
{
mat3[k][0]=mat2[j][0];
mat3[k][1]=mat2[j][1];
mat3[k][2]=mat2[j][2];
k++;j++;
}
else
{/*rowofmat1=mat2*/
if(mat1[i][1]<mat2[j][1])/*colofmat1<
mat2*/
{
mat3[k][0]=mat1[i][0];
mat3[k][1]=mat1[i][1];
mat3[k][2]=mat1[i][2];
k++;i++;
}
else
if(mat1[i][1]>mat2[j][1])/*colofmat1>
mat2*/
{
mat3[k][0]=mat2[j][0];
mat3[k][1]=mat2[j][1];
mat3[k][2]=mat2[j][2];
k++;j++;
}
else
{/*colofmat1=mat2*/
mat3[k][0]=mat2[i][0];
mat3[k][1]=mat2[i][1];
mat3[k][2]=mat1[i][2]+mat2[j][2];
k++;i++;j++;
}
}
}
while(i<=s1)/*copyrestoftheelementsof
mat1*/
{
mat3[k][0]=mat1[i][0];
mat3[k][1]=mat1[i][1];
mat3[k][2]=mat1[i][2];
k++;i++;
}
while(j<=s2)/*copyrestoftheelementsofmat2
*/
{
mat3[k][0]=mat2[j][0];
mat3[k][1]=mat2[j][1];
mat3[k][2]=mat1[j][2];
k++;j++;
}
mat3[0][2]=k1;
}
A sample output of the program is given below:
voidprintMat(intmatA[][]);
voidtransMat(intmat[][],inttrans[][]);
voidmain()
{
intmat[10][3],trans[10][3];
intm,n;
clrscr();
printf(\nEntertheorderofthesparsemat:m,
n);
scanf(%d%d,&m,&n);
readMat(mat,m,n);
printMat(mat);
transMat(mat,trans);
printf(\nThetransposedcondensedMatrix);
printMat(trans);
}
voidreadMat(intmatA[10][3],intm,intn)
{
inti,j,k;
intelement;
matA[0][0]=m;/*no.ofrows*/
matA[0][1]=n;/*no.ofcols*/
k=1;/*pointto1stpositionofsparsemat*/
printf(\nEntertheelementsofthematrixinrow
majororder);
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
scanf(%d,&element);
if(element!=0)
{
matA[k][0]=i;
matA[k][1]=j;
matA[k][2]=element;
k++;
}
}
matA[0][2]=k1;/*recordno.ofnonzeroelements
*/
}
voidprintMat(intmatA[10][3])
{
inti,size;
size=matA[0][2];
for(i=0;i<=size;i++)
printf(\n%d%d%d,matA[i][0],matA[i][1],matA[i]
[2]);
}
voidtransMat(intmat[10][3],inttrans[10][3])
{
inti,j,small,size,pos,temp;
size=mat[0][2];
trans[0][0]=mat[0][1];
trans[0][1]=mat[0][0];
trans[0][2]=mat[0][2];
for(i=1;i<=size;i++)
{
trans[i][0]=mat[i][0];
trans[i][1]=mat[i][1];
trans[i][2]=mat[i][2];
}
for(i=1;i<size;i++)
{
small=trans[i][1];pos=i;
for(j=i+1;j<=size;j++)
{
if(small>trans[j][1])
{
small=trans[j][1];
pos=j;
}
else
if(small==trans[j][1]&&trans[i][0]>trans[j]
[0])
{
small=trans[j][1];
pos=j;
}
}
temp=trans[i][0];trans[i][0]=trans[pos]
[0];trans[pos][0]=temp;
temp=trans[i][1];trans[i][1]=trans[pos]
[1];trans[pos][1]=temp;
temp=trans[i][2];trans[i][2]=trans[pos]
[2];trans[pos][2]=temp;
temp=trans[i][0];trans[i][0]=trans[i][1];
trans[i][1]=temp;
}
}
A sample output is given below:
EXERCISES
1. Define the terms: array, subscript, subscripted variables and strings.
2. Write suitable array declaration for the following:
1.
2.
3.
4.
2
3
4
5
6
7
8
10 Write a program that gives the sum of positive elements of an array LIST of
integer type.
11 Write a program that reads two matrices A and B of order m n and
compute the following:
C=A+B
12 What happens to an array if it is assigned with less number of elements at
the time of initialization?
13 Write a program that computes the sum of diagonal elements of a square
matrix.
14 Write a program that takes a sparse matrix A and finds its transpose AT and
displays it.
15 Let A be an N N square matrix array. Write algorithms for the following:
4
Stacks and Queues
CHAPTER OUTLINE
4.1 Stacks
4.2 Applications of Stacks
4.3 Queues
4.1 STACKS
We are familiar with arrays and the operations performed on them. The
various elements can be accessed from any position in a list, stored in an
array. Some practical applications require that the additions or deletions
be done only at one end. For example, the packets of wheat floor received
in a super bazaar are put on top of another in the order of their arrival.
Now, a customer can remove only the packet currently lying on the top,
i.e., the last packet arrived in the bazaar is the first one to leave. The
second customer would get the second last packet and so on. In the mean
time, if more packets arrive then they will be added on the current top as
shown in Figure 4.1.
It may be noted that the variable called Top keeps track of the location
where addition can be done. If there is a bound on the size of
the stack (sayN) then as soon as the Top becomes equal to N,
thestack is said to be full.
Let us now apply pop operation three times in succession and see the
results. The following three items will be removed from
the stack in LIFO fashion:
E D C
The variable top is also decremented accordingly after each Pop
operation as shown in Figure 4.4.
o The Pop ( ) function would return the popped value if the operation is
successful and 9999 otherwise, i.e., the unusual value 9999 indicates that
the stack was empty.
printf(\nThestackfull);
break;
case2:result=pop(stack,&top);
if(result==9999)
printf(\nThestackisempty);
else
printf(\nThepoppedvalue=%d,result);
break;
case3:display(stack,top);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=4);
}
intpush(intstack[],int*top,intval,intsize)
{
if(*top>=size)
return0;/*thestackisFull*/
else
{
*top=*top+1;
stack[*top]=val;
return1;
}
}
intpop(intstack[],int*top)
{intval;
if(*top<0)
return9999;/*thestackisempty*/
else
{
val=stack[*top];
*top=*top1;
returnval;
}
}
voiddisplay(intstack[],inttop)
{
inti;
printf(\nThecontentsofstackare:);
for(i=top;i>=0;i)
printf(%d,stack[i]);
}
4.2 APPLICATIONS OF STACKS
The stacks are used in numerous applications and some of them are as
follows:
o
o
o
o
o
Stands for
Exponentiation
X^8
Division
X/Y
Multiplication
X*Y
Addition
X+Y
Subtraction
XY
Sum + 10
Total 100
Total/Rate*Interest
X^Y B*C
Operator(s)
^, exponentiation
/ *, division, multiplication
+ , addition, subtraction
Note:
o The operators of same priority like (*, /) or (+, ) are performed from left to
right.
o The operators contained within parentheses are evaluated first. When the
parentheses are nested, the innermost is evaluated first and so on.
o The exponential operators are evaluated from right to left. For example, in
the following expression,
A^B^C
The sub-expressions are evaluated from right to left as shown in Figure
4.5.
The arithmetic expression, as discussed above, is called
an infix expression wherein the operator is placed between two
operands. The evaluation takes place according to priorities assigned to
the operators (see Table 4.2). However, to overrule the priority of an
operator, parenthesis is used. For example, the two infix expressions
given below are entirely different because in Expression 2, the priority of
the division operator / has been overruled by the embedded
parenthesis.
+AB
A+B
*+ABC
(A + B)*C
+/A + BC*D EF
A/(B + C) + D*(E F)
AB+
A+B
AB + C*
(A + B)*C
ABC +/DEF *+
A/(B + C) + D*(E F)
It may be noted that in Polish and reverse Polish notations, there are no
parentheses and, therefore, the order of evaluation will be determined by
the positions of the operators and related operands in the expression.
A critical look at the postfix expressions indicates that such
representation is excellent from the execution point of view. The reason
being that while execution, no consideration has to be given to the
priority of the operators rather the placement of operators decides the
order of execution. Moreover, it is free from parentheses. Same is true
for prefix expressions but in this book, only the postfixexpressions
would be considered.
Thus, there is a need for conversion of infix to postfix expression.
The subsequent section discusses two methods to convert
the infix expressions to postfix expressions.
4.2.1.3 Conversion of Infix Expression to Postfix Expression
An infix expression can be converted into postfix by two methods:
parenthesis method and stackmethod.
(1) Parenthesis method The following steps are used to convert
an infix expression to postfixexpression by parenthesis method:
1. Fully parenthesize the infix expression.
Consider the infix expression A/(B + C) + D*(E F). Let us apply the
above steps to convert this expression to postfix notation. The
operations are shown in Figure 4.7.
It may be noted that the left hand parenthesis ( has the highest
incoming priority and least instack priority.
Let us assume that an infix expression is terminated by the character
;. We shall call operators and operands of an expression by a general
name element. The following broader level algorithm convert ( ) can be
used to convert an infix expression to postfix expression.
Algorithm convert( )
{
Step
1.Scantheexpressionfromlefttoright.
2.Iftheelementisanoperand,thenstorethe
elementintothetargetarea.
3.Iftheelementisanoperator,thenpushitonto
astackwithfollowingrules:
3.1Whiletheincomingpriorityislessthanor
equaltoinstackpriority,poptheoperatorsand
storeintothetargetarea.
3.2Iftheelementisrighthandparenthesis),
thenpopalltheelementsfromthestacktillleft
handparenthesis(ispoppedout.Thepopped
elementsarestoredinthetargetarea.
4.Iftheelementis;,thenpopalltheelements
tillthestackisemptyandstorethemintothe
targetarea.
5.Stop
}
Example 2: Write a program that uses a stack to convert
an infix expression to a postfixexpression.
Solution: We would employ the algorithm convert( ), given above.
The following data structures would be used:
The list of allowed binary operators along with their icp and isp is
stored in an array of structures called opTab[]. An array
called stack of type operator A is the main data structure. match ( )
function is being used to find out as to whether a given element is an
operator or an operand depending upon the result (res) of match
operation. The required program is given below:
/*Thisprogramconvertsaninfixexpressionto
apostfixexpression*/
#include<stdio.h>
#include<conio.h>
structoperator{/*operatordeclaration*/
charopName;
intisp;
inticp;
};
intpush(structoperatorstak[],int*top,struct
operatorval,
intsize);
structoperatorpop(structoperatorstak[],int
*top);
voiddisplay(structoperatorstack[],inttop);
intmatch(structoperatoropTab[8],charelement,
struct
operator*op);
voidmain()
{
charinfix[20];
chartarget[20];
structoperatorstack[20];/*thestackdeclaration
*/
inttop=1;/*initiallythestackisempty*/
intres;
charval;
intsize;
intpos;
inti;
structoperatorop,opTemp;
structoperatoropTab[8]={(,0,6,/*The
operatorsinformation*/
),0,0,
^,4,5,
*,3,3,
/,3,3,
+,2,2,
,2,2,
;,0,1,
};
printf(\nEnterthesizeoftheinfixexpression);
scanf(%d,&size);size;
printf(\nEnterthetermsofinfixexpressionone
byone);
for(i=0;i<=size;i++)
{fflush(stdin);/*flushtheinputbuffer*/
scanf(%c,&infix[i]);
}
pos=0;/*positionintargetexpression*/
for(i=0;i<=size;i++)
{
res=match(opTab,infix[i],&op);/*find
whetheroperator/operand*/
if(res==0)
{target[pos]=infix[i];/*storeintotarget
expression*/
pos++;
}
else
{if(top<0)
push(stack,&top,op,size);/*firsttimePush
*/
else
{opTemp.opName=#;/*placeanyvaluetoopTemp
*/
if(op.opName==))
{while(opTemp.opName!=()
{
opTemp=pop(stack,&top);
if(opTemp.opName!=()/*omit(*/
{target[pos]=opTemp.opName;
pos++;
}
}
}
else
{while(stack[top].isp>=op.icp&&top>=0)
{
opTemp=pop(stack,&top);
target[pos]=opTemp.opName;
pos++;
}
push(stack,&top,op,size);
}
}
}
}
/*printthepostfixexpression*/
printf(\nThepostfixexpressionis:);
for(i=0;i<pos;i++)
{
printf(%c,target[i]);
}
}
intpush(structoperatorstack[],int*top,struct
operator
val,intsize)
{
if(*top>=size)
return0;/*thestackisfull*/
else
{
*top=*top+1;
stack[*top]=val;
return1;
}
}
structoperatorpop(structoperatorstack[],int
*top)
{structoperatorval;
if(*top<0)
{val.opName=#;
returnval;/*thestackisempty*/
}
else
{
val=stack[*top];
*top=*top1;
returnval;
}
}
voiddisplay(structoperatorstack[],inttop)
{
inti;
printf(\nThecontentsofstackare:);
for(i=top;i>=0;i)
printf(%c,stack[i].opName);
}
intmatch(structoperatoropTab[8],charelement,
structoperator*op)
{
inti;
for(i=0;i<8;i++)
{
if(opTab[i].opName==element)
{
*op=opTab[i];
return1;
}
}
return0;
}
For following given infix expressions, the above program produces the
correct output as shown below:
Infix expression
Postfix expression
(1) A ^ B ^ C + D;
ABC^^D+
AB CD/* E +
2.if(elementisanoperand)
if(element!=;)
{pushelementonthestack}
else
{
popresultfromthestack;
printtheresult;
exittostep4;
}
else
{
popoperand1fromstack;
popoperand2fromstack;
performtheoperation;
pushresultonthestack;
}
3.repeatsteps1and2
4.stop
}
Example 6: Write a program that uses a stack to evaluate
a postfix expression.
Solution: The algorithm evalPostfix( )and the following data
structures would be used for the required program.
The postfix expression will be stored in an array called postfix[ ] of
following structure type.
structterm/*structuretostoreanelement*/
{
charelement;
floatval;
};
where
element stores an operand or an operator.
val stores the actual value of the operand or 0 for an operator.
For instance, the expression ABC*+; for values A = 10, B = 15, C = 8 will
be stored in postfix[ ] as shown below:
postfix
An array called stack of type float would also be used.
The required program is given below:
/*Thisprogramusesastacktoevaluateapostfix
expression*/
#include<stdio.h>
#include<conio.h>
structterm/*structuretostoreanelement*/
{
charelement;
floatval;
};
/*arraytostorethepostfixexpression*/
intpush(floatstack[],int*top,floatval,int
size);
floatpop(floatstack[],int*top);
voidmain()
{
floatstack[20];
structtermpostFix[20];
inttop=1;
inti;
intsize;
floatresult;
inttemp;
floatop1,op2;/*theoperands*/
printf(\nEnterthenumberofelementsinthe
postfixexpression);
scanf(%d,&size);
printf(\nEntertheexpressionaselementvalue
pair);
printf(\nEnter0foranoperatorincluding;);
for(i=0;i<size;i++)
{
printf(\nEnterelementvalpair);
fflush(stdin);
scanf(%c%f,&postFix[i].element,
&postFix[i].val);
}
for(i=0;i<size;i++)
{
switch(postFix[i].element)
{
case+:op2=pop(stack,&top);
op1=pop(stack,&top);
result=op1+op2;
temp=push(stack,&top,result,size);
if(temp==0)printf(\nstackfull);
break;
case*:op2=pop(stack,&top);
op1=pop(stack,&top);
result=op1*op2;
temp=push(stack,&top,result,size);
if(temp==0)printf(\nstackfull);
break;
case:op2=pop(stack,&top);
op1=pop(stack,&top);
result=op1op2;
temp=push(stack,&top,result,size);
if(temp==0)printf(\nstackfull);
break;
case/:op2=pop(stack,&top);
op1=pop(stack,&top);
result=op1/op2;
temp=push(stack,&top,result,size);
if(temp==0)printf(\nstackfull);
break;
case;:result=pop(stack,&top);
printf(\nTheresult=%8.2f,result);
break;
default:temp=push(stack,&top,
postFix[i].val,size);
if(temp==0)printf(\nstackfull);
}
}
}
intpush(floatstack[],int*top,floatval,int
size)
{
if(*top<size)
{
*top=*top+1;
stack[*top]=val;
return1;
}
else
return0;
}
floatpop(floatstack[],int*top)
{
floatterm;
term=0;
if(*top>=0)
{
term=stack[*top];
*top=*top1;
returnterm;
}
else
returnterm;
}
For following given input:
A8
B6
C 20
*0
0
;0
the program gives the following output:
The Result = 112.00
Example 7: Use a stack to evaluate the following postfix arithmetic
expression. Show the changing status of the stack in tabular form.
A B C * for A = 8, B = 6, C = 20
Solution: We would use the algorithm evalPostfix( ). The
simulation of this algorithm for the above expression is given in Table
4.9.
Table 4.9 Evaluation of A B C *
for X = 1, Y = 5, Z, = 2, P = 3, A = 15, B = 3, C
4.3 QUEUES
A queue is the most common situation in this world where the first
person in the line is the person to be served first; the new comers join at
the end. We find that in this kind of arrangement, all additions are made
at one end and the deletions made at the other. The end where all
additions are made is called the rear end. The other end from where the
deletions are made is called the front end as shown in Figure 4.8.
The algorithms for addition and deletion operations on the queue are
given below. These algorithms use an array called Queue[N] to represent
a queue of size N locations. A variable called Front keeps track of the
front of the queue, i.e., the location from where deletions are made.
Another variable called Rear keeps track of the rear end of the queue,
i.e., the location where additions are made. Another variable called item
is used to store the item to be added or deleted from the queue.
The algorithm for addition is given below:
Algorithm addQ( )
{
Step
1.if(Rear>=N)then{prompt(QueueFull);
exit}
2.Rear=Rear+1;
3.Queue[Rear]=item;
4.stop
}
It may be noted that the item is added to the queue (i.e., addition
operation) provided the queue is not already full, i.e., it is checked at
step1 whether or not the queue has room for another item.
The algorithm for deletion is given below:
Algorithm delQ( )
{
Step
1.if(Front==Rear)thenprompt(QueueEmpty);
else
Front=Front+1
Item=Queue[Front];
2.stop
}
Note that in the above algorithm, before removing an item from the
queue, it is checked at step 1 to see if there is at least one item on the
queue that can be removed.
The performance of the queue data structure for n elements is given
below:
o The space complexity is O(n).
o The time complexity of each operation is O(1).
o The size of the queue, implemented using array, must be defined in
advance, i.e., a priori. Moreover, the size cannot be changed.
printf(\nEnterthesizeofthequeue);
scanf(%d,&size);
size;/*adjustedforindex=0*/
/*createmenu*/
do
{
clrscr();
printf(\nMenuQueueOperations);
printf(\nAdd1);
printf(\ndelete2);
printf(\nDisplayqueue3);
printf(\nQuit4);
printf(\nEnteryourchoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEnterthevaluetobe
added);
scanf(%d,&val);
result=addQ(Queue,&Rear,val,size);
if(result==0)
printf(\nThequeuefull);
break;
case2:result=delQ(Queue,&Front,&Rear);
if(result==9999)
printf(\nThequeueisEmpty);
else
printf(\nThedeletedvalue=%d,result);
break;
case3:display(Queue,Front,Rear);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=4);
}
intaddQ(intQueue[],int*Rear,intval,intsize)
{
if(*Rear>=size)
return0;/*thequeueisFull*/
else
{
*Rear=*Rear+1;
Queue[*Rear]=val;
return1;
}
}
intdelQ(intQueue[],int*Front,int*Rear)
{intval;
if(*Front==*Rear)
return9999;/*thequeueisempty*/
else
{
*Front=*Front+1;
val=Queue[*Front];
returnval;
}
}
voiddisplay(intQueue[],intFront,intRear)
{
inti;
printf(\nThecontentsofqueueare:);
for(i=Front+1;i<=Rear;i++)
printf(%d,Queue[i]);
}
Note:
1. The drawback of a linear queue is that a stage may arrive when both Front
and Rear are equal and point to the last location of the array as shown
in Figure 4.14.
2. The major drawback of linear queue is that at a given time, all the locations
to the left of Front are always vacant and unutilized.
of the array and the item C is deleted from this location pointed by
Front as shown in Figure 4.17.
It may be noted that after more deletions, a stage will come when Front
becomes equal to Rear (Front= Rear), indicating that the queue is
empty.
Rear=(Rear+1)modN
Front=(Front+1)modN
where N is the size of the queue.
The algorithms for addition and deletion operations in a circular queue
are given below. These algorithms use an array called cirQ[N] to
represent a queue of size N locations. A variable called Front keeps track
of the front of the queue, i.e., the location from where deletions are
made. Another variable called Rear keeps track of the rear end of the
queue, i.e., the location where additions are made. Another variable
called item is used to store the item to be added or deleted from the
queue.
The algorithm for addition is given below:
Algorithm addQ()
{
Step
1.if(((Rear+1)%N)==Front)
then{prompt(QueueFull);exit}
2.Rear=(Rear+1)%N;
3.cirQ[Rear]=item;
4.stop
}
It may be noted that the item is added to the queue (i.e., addition
operation) provided the queue is not already full, i.e., at step1 it is
checked whether or not the queue has room for another item.
The algorithm for deletion is given below:
Algorithm delQ()
{
Step
1.if(Front==Rear)thenprompt(QueueEmpty);
else
Front=(Front+1)%N
Item=Queue[Front];
returnItem;
2.stop
}
Note that in the above algorithm, before removing an item from the
queue, it is checked at step 1 to see if there is at least one item on the
queue that can be removed.
Example 10: Write a program in C that uses the following menu to
simulate the circular queue operations on items of int type.
switch(choice)
{
case1:printf(\nEnterthevaluetobe
added);
scanf(%d,&val);
result=addQ(cirQ,&Front,&Rear,val,size);
if(result==0)
printf(\nThequeuefull);
break;
case2:result=delQ(cirQ,&Front,&Rear,size);
if(result==9999)
printf(\nThequeueisEmpty);
else
printf(\nThedeletedvalue=%d,result);
break;
case3:display(cirQ,Front,Rear,size);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=4);
}
intaddQ(intcirQ[],int*Front,int*Rear,intval,
intsize)
{
if(((*Rear+1)%size)==*Front)
return0;/*thequeueisFull*/
else
{
*Rear=(*Rear+1)%size;
cirQ[*Rear]=val;
return1;
}
}
intdelQ(intcirQ[],int*Front,int*Rear,int
size)
{intval;
if(*Front==*Rear)
return9999;/*thequeueisempty*/
else
{
*Front=(*Front+1)%size;
val=cirQ[*Front];
returnval;
}
}
voiddisplay(intcirQ[],intFront,intRear,int
size)
{
inti;
printf(\nThecontentsofqueueare:);
i=Front;
while(i!=Rear)
{
i=(i+1)%size;
printf(%d,cirQ[i]);
}
}
It may be noted that the circular queue has been implemented in a onedimensional array, which is linear by nature. We only view the array to
be a circle in the sense that it is imagined that the last location of the
array is immediately followed by the 0th location as shown in Figure
4.18.
The shaded area shows the queue elements. When the queue is full, then
it satisfies the condition: (Rear + 1) % N == Front. The queue empty
condition is indicated by the condition: Front == Rear. However, at
least one location remains vacant all the time.
priority
P8
P5
P7
P4
P2
Now, the scheduler shall create a priority queue that can store a list of
pairs (Process No., priority). It has two possible choices to store the pairs
as shown in Figure 4.20.
Choice (a): The addition operation simply adds the newly arrived job at
the rear end, i.e., in the order of its arrival. This operation is O(1).
The deletion operation requires the process with higher priority to be
searched and removed, P7 in this case. This operation is O(n) for n
elements in the queue as we have to traverse the queue to find the
element.
Front=Rear=0;/*initiallythequeuesettobe
empty*/
printf(\nEnterthesizeofthequeue);
scanf(%d,&size);
/*createmenu*/
do
{
clrscr();
printf(\nMenuPriorityqueueoperations);
printf(\nAdd1);
printf(\nDelete2);
printf(\nDisplayqueue3);
printf(\nQuit4);
printf(\nEnteryourchoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEntertheprocessnumber
anditsprioritytobeadded);
scanf(%s%d,&val.process,
&val.priority);
result=addPq(pQ,&Rear,val,size);
if(result==0)
{printf(\nThequeuefull);}
break;
case2:result=delPq(pQ,&Front,&Rear,
&val);
if(result==0)
printf(\nThequeueisempty);
else
printf(\nThedeletedproc=%swith
priority%d,val.
process,val.priority);
break;
case3:display(pQ,Front,Rear);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=4);
}
intaddPq(structprocpQ[],int*Rear,structproc
val,intsize)
{
inti;
if((*Rear+1)>=size)
return0;/*thequeueisFull*/
else
{
*Rear=(*Rear+1);
i=*Rear1;
while(val.priority>pQ[i].priority)
{
pQ[i+1]=pQ[i];
i=i1;
}
i++;
pQ[i]=val;
}
return1;
}
intdelPq(structprocpQ[],int*Front,int*Rear,
structproc*val)
{
if(*Front==*Rear)
return0;/*thequeueisempty*/
else
{
*Front=(*Front+1);
*val=pQ[*Front];
return1;
}
}
voiddisplay(structprocpQ[],intFront,intRear)
{
inti;
printf(\nThecontentsofqueueare:);
i=Front;
while(i!=Rear)
{
i=i+1;
printf((%s,%d),pQ[i].process,
pQ[i].priority);
}
}
It may be noted that in order to keep it simple, the priority queue has
been implemented as a linear queue. A sample input is given below:
Enter the process number and its priority to be added P4 7
where P4 is the name of the process and 7 is its priority.
4.3.4 The Deque
A deque is a queue that allows additions and deletions from both ends
(see Figure 4.21). This data structure is suitable for implementing the
redo and undo operations of software such as word processor, paint
program, drawing tools, etc.
A one-dimensional array can be suitably used to implement a deque
wherein the following four operations need to be designed:
1.
2.
3.
4.
intdelFront(intdQ[],int*Front,int*Rear,int
*val);
voiddisplay(intdQ[],intFront,intRear);
voidmain()
{
intdQ[20];/*thequeuedeclaration*/
intFront;
intRear;
intval;
intsize;
intchoice;
intresult;
Front=Rear=0;/*initiallythequeuesettobe
empty*/
printf(\nEnterthesizeofthequeue);
scanf(%d,&size);
size;/*adjustedforindex=0*/
/*createmenu*/
do
{
clrscr();
printf(\nMenudQueOperations);
printf(\nAdditematRear1);
printf(\nAdditematFront2);
printf(\nDeleteitemfromRear3);
printf(\nDeleteitemfromFront4);
printf(\nDisplaydeque5);
printf(\nQuit6);
printf(\nEnteryourchoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEnterthevaluetobeadded);
scanf(%d,&val);
result=addRear(dQ,&Rear,val,size);
if(result==0)
printf(\nThequeueisfull);
break;
case2:printf(\nEnterthevaluetobeadded);
scanf(%d,&val);
result=addFront(dQ,&Front,val);
if(result==0)
printf(\nThequeueisfull);
break;
case3:result=delRear(dQ,&Rear,&Front,&val);
if(result==0)
printf(\nThequeueisempty);
else
printf(\nThedeletedvalue=%d,val);
break;
case4:result=delFront(dQ,&Front,&Rear,&val);
if(result==0)
printf(\nThequeueisempty);
else
printf(\nThedeletedvalue=%d,val);
break;
case5:display(dQ,Front,Rear);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=6);
}
intaddRear(intdQ[],int*Rear,intval,intsize)
{
if(*Rear>=size)
return0;/*thequeueisFull*/
else
{
*Rear=*Rear+1;
dQ[*Rear]=val;
return1;
}
}
intaddFront(intdQ[],int*Front,intval)
{
if(*Front<=0)
return0;/*thequeueisfull*/
else
{
dQ[*Front]=val;
*Front=*Front1;
return1;
}
}
intdelRear(intdQ[],int*Rear,int*Front,int
*val)
{
if(*Rear==*Front)
return0;/*thequeueisempty*/
else
{
*val=dQ[*Rear];
*Rear=*Rear1;
return1;
}
}
intdelFront(intdQ[],int*Front,int*Rear,int
*val)
{
if(*Front==*Rear)
return0;/*thequeueisempty*/
else
{
*Front=*Front+1;
*val=dQ[*Front];
return1;
}
}
voiddisplay(intQueue[],intFront,intRear)
{
inti;
printf(\nThecontentsofqueueare:);
for(i=Front+1;i<=Rear;i++)
printf(%d,Queue[i]);
}
Example 12: The items can be iteratively removed from both ends of a
deque and compared with each other. Therefore, the deque can be easily
used to determine whether a given string is a palindrome or not. Write
a program in C that uses a deque to determine whether a given string is
a palindrome or not.
printf(\nThequeuefull);
i++;
}
size=i1;
flag=1;/*Assumingthatthestringis
while(flag&&result)
{
result=delRear(dQ,&Rear,&Front,&val1);
result=delFront(dQ,&Front,&Rear,&val2);
if(val1!=val2){flag=0;break;}/*stringis
notapalindrome*/
}
if(flag==1)printf(\nThestringispalindrome);
elseprintf(\nThestringisnotapalindrome);
}
intaddRear(chardQ[],int*Rear,charval,intsize)
{
if(*Rear>=size)
return0;/*thequeueisfull*/
else
{
*Rear=*Rear+1;
dQ[*Rear]=val;
return1;
}
}
intaddFront(chardQ[],int*Front,charval)
{
if(*Front<=0)
return0;/*thequeueisfull*/
else
{
dQ[*Front]=val;
*Front=*Front1;
return1;
}
}
intdelRear(chardQ[],int*Rear,int*Front,char
*val)
{
if(*Rear==*Front)
return0;/*thequeueisempty*/
else
{
*val=dQ[*Rear];
/*Ifthesizeofstringisoddthendonot
decrementRear*/
if((*Rear1)>*Front)*Rear=*Rear1;
return1;
}
}
intdelFront(chardQ[],int*Front,int*Rear,char
*val)
{
if(*Front==*Rear)
{
return0;
}/*thequeueisempty*/
else
{
*Front=*Front+1;
*val=dQ[*Front];
return1;
}
}
voiddisplay(charQueue[],intFront,intRear)
{
inti;
printf(\nThecontentsofqueueare:);
for(i=Front+1;i<=Rear;i++)
printf(%c,Queue[i]);
}
Note: The function addFront() of deque system is redundant in this
application. The functiondelRear() has been suitably modified for
strings having odd number of characters.
Example 13: Two stacks can be implemented using a single array.
Write a program in C that allows push and pop operations from the
specified stack. It must also check the conditions stackemptyand
full for a specified stack.
Fig. 4.23 Two stacks that grow towards middle of the array
The required program is given below:
/*Thisprogramsimulatestwostacksoperations*/
#include<stdio.h>
#include<conio.h>
intpushStack1(intstak[],int*top1,int*top2,int
val);
intpushStack2(intstak[],int*top1,int*top2,int
val);
intpopStack1(intstak[],int*top1);
intpopStack2(intstak[],int*top2);
voiddispStack1(intstack[],inttop1);
voiddispStack2(intstack[],inttop2);
voidmain()
{
inttwoStack[10];/*thestackdeclaration*/
inttop1=1;/*initiallythestack1isempty*/
inttop2=10;/*initiallythestack2isempty*/
intval;
intchoice;
intresult;
/*createmenu*/
do
{
clrscr();
printf(\nMenutwoStackOperations);
printf(\nPushStack11);
printf(\nPushStack22);
printf(\nPopStack13);
printf(\nPopStack24);
printf(\nDispStack15);
printf(\nDispStack26);
printf(\nQuit7);
printf(\nEnteryourchoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEnterthevaluetobe
pushed);
scanf(%d,&val);
result=pushStack1
(twoStack,&top1,&top2,val);
if(result==0)
printf(\nThestackisfull);
break;
case2:printf(\nEnterthevaluetobe
pushed);
scanf(%d,&val);
result=pushStack2(twoStack,&top1,&top2,
val);
if(result==0)
printf(\nThestackisfull);
break;
case3:result=popStack1(twoStack,&top1);
if(result==9999)
printf(\nThestackisempty);
else
printf(\nThepoppedvalue=%d,result);
break;
case4:result=popStack2(twoStack,&top2);
if(result==9999)
printf(\nThestackisempty);
else
printf(\nThepoppedvalue=%d,result);
break;
case5:dispStack1(twoStack,top1);
break;
case6:dispStack2(twoStack,top2);
break;
}
printf(\n\nPressanykeytocontinue);
getch();
}
while(choice!=7);
}
intpushStack1(intstack[],int*top1,int*top2,
intval)
{
if(*top1+1==*top2)
return0;/*thestackisfull*/
else
{
*top1=*top1+1;
stack[*top1]=val;
return1;
}
}
intpushStack2(intstack[],int*top1,int*top2,int
val)
{
if(*top1+1==*top2)
return0;/*thestackisfull*/
else
{
*top2=*top21;
stack[*top2]=val;
return1;
}
}
intpopStack1(intstack[],int*top1)
{intval;
if(*top1<0)
return9999;/*thestackisempty*/
else
{
val=stack[*top1];
*top1=*top11;
returnval;
}
}
intpopStack2(intstack[],int*top2)
{intval;
if(*top2>10)
return9999;/*thestackisempty*/
else
{
val=stack[*top2];
*top2=*top2+1;
returnval;
}
}
voiddispStack1(intstack[],inttop1)
{
inti;
printf(\nThecontentsofstack1are:);
for(i=top1;i>=0;i)
printf(%d,stack[i]);
}
voiddispStack2(intstack[],inttop2)
{
inti;
printf(\nThecontentsofstack2are:);
for(i=top2;i<=10;i++)
printf(%d,stack[i]);
}
EXERCISES
1. Give static implementation of stack by writing push and pop routine for it.
2. Explain overflow and underflow conditions of a stack with examples.
3. How can a stack be used in checking the well-formedness of an
expression, i.e., balance of the left and right parenthesis of the expression?
4. Write down an algorithm to implement two stacks using only one array.
Your stack routine should not report an overflow unless every slot in the
array is used.
5. Write a non-recursive program to reverse a string using stack.
6. Explain prefix, infix, and postfix expressions with examples.
1. (x + y z)/(h + k)*s
2. j - k/g^h + (n + m)
3. a*(b c)/d + e*f
2 Write a program that evaluates an expression represented
in postfix form.
3 Evaluate the following postfix expressions for the provided data:
1. ab ^ c * d / e + where a = 5, b = 3, c = d = 2, e = 9
2. abcde + * + where a 5 + , b = 4, c = 7, d = 5, e = 2
3. ab + cd * + e * where a = 2, b = 6, c = 3, d = 5, e = 9
2 What are the applications of a stack? Support your answer with suitable
examples.
3 What is a linear queue? What are the limitations of linear queue?
4 What do you understand by a queue? Write algorithm for inserting and
deleting of a data element from the queue.
5 Write the functions insert() and delete() for a circular queue.
6 Write a menu driven main() program that tests the functions developed in
Exercise 14.
7 Differentiate between linear queue and circular queue. Which one is better
and why?
8 Describe the applications of queues.
9 What are priority queues? Write their applications.
10 What is doubly ended queue? Write a program/algorithm to implement a
doubly ended queue (deque).
5
Pointers
CHAPTER OUTLINE
5.1 Introduction
5.2 Pointer Variables
5.3 Pointers and Arrays
5.4 Array of Pointers
5.5 Pointers and Structures
5.6 Dynamic Allocation
5.1 INTRODUCTION
Amount of storage
character
1 byte
integer
2 byte
float
4 byte
Rong
4 byte
double
8 byte
p2=p1
Makes p2 point to the same location which is being pointed by p1 as
shown in Figure 5.9.
p1=&x;
p2=&y;
/*Exchangethepointers*/
p3=p1;
p1=p2;
p2=p3;
/*Printthecontentsthroughexchangedcontents*/
printf(\nTheexchangedcontentsare);
printf(%d&%d,*p1,*p2);
}
Let us assume that it is desired that the pointer ptr should point to a
variable val of type int. The correct code in that case would be as
given below:
int*ptr;
intval;
ptr=&val;
*ptr=50;
The result of the above program segment is shown in Figure 5.12.
Pointers and arrays are very closely related to each other. In C, the
name of an array is a pointer that contains the base address of the array.
In fact, it is a pointer to the first element of the array. Consider the array
declaration given below:
intlist[]={20,30,35,36,39};
This array will be stored in the contiguous locations of the main memory
with starting address equal to 1001(assumed value), as shown in Figure
5.13. Since the array called list is of integer type, each element of this
array occupies two bytes. The name of the array contains the starting
address of the array, i.e., 1001.
Fig. 5.13 The contiguous storage allocation with starting address = 1001
Consider the following program segment:
printf(\nAddressofzerothelementofarraylist=
%d,list);
printf(\nValueofzerothelementofarraylist=
%d,*list);
Once the above program segment is executed, the output would be as
shown below:
Addressofzerothelementofarraylist=1001
Valueofzerothelementofarraylist=20
We could have achieved the same output by the following program
segment also:
printf(\nAddressofzerothelementofarraylist=
%d,&list[0]);
printf(\nValueofzerothelementofarraylist=
%d,list[]);
Both the above given approaches are equivalent because of the following
equivalence relations:
list?&list[0]bothdenoteaddressofzeroth
elementofthearraylist
*list?list[0]bothdenotevalueofzerothelement
ofthearraylist
The left side approach is known as pointer method and right side as
array indexing method. Let us now write a program that prints out a list
by array indexing method.
/*Arrayindexingmethod*/
#include<stdio.h>
main()
{
staticintlist[]={20,30,35,36,39};
inti;
printf(\nThelistis);
for(i=0;i<5;i++)
printf(\n%d%delement,list[i],i);
}
The output of this program would be:
20
0element
30
1element
35
2element
36
3element
39
4element
following operations on list are illegal because they try to change the
contents of a list, a pointer constant.
List=NULL;
/*Notallowed*/
List=&Val;
/*Notallowed*/
list++
/*Notallowed*/
list
/*Notallowed*/
intlist={20,30,35,36,39};
int*p;/*pointervariablep*/
p=list;/*assignthestartingaddressofarray
listtopointerp*/
Since p is a pointer variable and has been assigned the starting address
of array list, the following operations become perfectly valid on this
pointer:
p++,petc.
Let us now write a third version of the program that prints out the array.
We will use pointer variable p in this program.
/*Pointervariablemethodofprocessinganarray*/
#include<stdio.h>
main()
{
staticintlist[]={20,30,35,36,39};
int*p;
inti=0;
p=list;/*Assignthestartingaddressofthelist
*/
printf(\nThelistis);
while(i<5)
{
printf(\n%d%delement,*p,i);
i++;
p++;/*incrementpointer*/
}
}
The output of the above program would be as shown below:
20
0element
30
1element
35
2element
36
3element
39
4element
From our discussion on arrays, we know that the strings are stored and
manipulated as array of characters with last character being a null
character (i.e., \0).
For example, the string ENGINEERING can be stored in an array (say
text) as shown in Figure 5.14.
char*p;/*Thepointer*/
p=text;/*Assignthestartingaddressofstring
top*/
printf(\nThestring..);/*Printthestring*/
while(*p!=\0)
{printf(%c,*p);
p++;
}
}
Example 2: What would be the output of the following code?
#include<stdio.h>
{
char*ptr;
ptr=Nice;
printf(\n%c,*&ptr[1]);
}
Solution: The output would be: i.
Example 3: What would be the output of the following code?
#include<stdio.h>
main()
{
intlist[5]={2,5,6,0,9};
3[list]=4[list]+6;
printf(%d,*(list+3));
}
Solution: The output would be 15.
5.4 ARRAY OF POINTERS
Like any other array, we can also have an array of pointers. Each element
of such an array can point to a data item such as variable, array etc. For
example, consider the declaration given below:
float*x[20];
Since ptr is a pointer to the structure variable abc, the members of the
structure can also be accessed through a special operator called arrow
operator, i.e., (minus sign followed by greater than sign). For
example, the members a and y of the structure variable abc pointed
by ptr can be assigned values 30 and 50.9, respectively by the following
statement:
ptr>a=30;
ptr>y=50.9;
The arrowoperator> is also known as a pointertomember
operator.
Example 4: Write a program that defines a structure called item for
the record structure given below. It reads the data of the record through
dot operator and prints the record by arrowoperator. Use suitable
data types for the members of the structure called item.
Solution: We will use a pointer ptr to point to the structure called
item. The required program is given below:
/*Thisprogramdemonstratestheusageofanarrow
operator*/
#include<stdio.h>
main()
{
structitem{
charcode[5];
intQty;
floatcost;
};
structitemitem_rec;/*Defineavariableofstruct
type*/
structitem*ptr;/*Defineapointeroftypestruct
*/
/*Readdatathroughdotoperator*/
printf(\nEnterthedataforanitem);
printf(\nCode:);scanf(%s,&item_rec.code);
printf(\nQty:);scanf(%d,&item_rec.Qty);
printf(\nCost:);scanf(%f,&item_rec.cost);
/*Assigntheaddressofitem_rec*/
ptr=&item_rec;
/*Printdatathrougharrowoperator*/
printf(\nThedatafortheitem);
printf(\nCode:%s,ptr>code);
printf(\nQty:%d,ptr>Qty);
printf(\nCost:%5.2f,ptr>cost);
}
From the above program, we can see that the members of a static
structure can be accessed by both dot and arrow operators. However, dot
operator is used for simple variable whereas the arrow operator is used
for pointer variables.
Example 5: What is the output of the following program?
#include<stdio.h>
main()
{
structpoint
{
intx,y;
}polygon[]={{1,2},{1,4},{2,4},{2,2}};
structpoint*ptr;
ptr=polygon;
ptr++;
ptr>x++;
printf(\n%d,ptr>x);
}
Solution: From the above program, it can be observed that ptr is a
pointer to an array of structures called polygon. The statement ptr+
+ moves the pointer from zeroth location (i.e., {1, 2}) to first location
(i.e., {1, 4}). Now, the statement x++ has incremented the field x of the
structure by one (i.e., 1 has incremented to 2).
The output would be 2.
5.6 DYNAMIC ALLOCATION
<type:
>
malloc
:
<size>
:
P=(int*)malloc(Sz);
The above set of statements is executed in the following manner;
1. The pointer p is created. It is dangling, i.e., it is not connected anywhere
(see Figure 5.20 (a))
2. A variable called Sz is declared to store the size of dynamic variable.
3. The size of dynamic variable (int in this case) is stored in the variable
called Sz.
4. A dynamic variable of type integer is taken through malloc() function and
the dangling pointerp is connected to the variable (see Figure 5.20 (b)).
*Bptr=70;
The outcome of the above program segment would be as shown in Figure
5.22.
free:
is a reserved word
e
p_var
:
Fig. 5.24 The two dangling pointers and a lost dynamic memory
variable
Example 6: Write a program that dynamically allocates an integer. It
initializes the integer with a value, increments it, and print the
incremented value.
p=(int*)malloc(Sz);
printf(\nEnteravalue:);
scanf(%d,p);
*p=*p+1;
/*Printtheincrementedvalue*/
printf(\nTheValue=%d,*p);
free(p);
}
Example 7: Write a program that dynamically allocates a structure
whose structure diagram is given below. It reads the various members of
the structure and prints them.
Solution: We will use the following steps to write the required program:
1.
2.
3.
4.
5.
6.
7.
printf(\nName:);/*fflush(stdin);*/gets(ptr>
name);
printf(\nAge:);scanf(%d,&ptr>age);
printf(\nRoll:);scanf(%d,&ptr>roll);
printf(\nThedatais...);
printf(\nName:);puts(ptr>name);
printf(\nAge:%d,ptr>age);
printf(\nRoll:%d,ptr>roll);
free(ptr);
}
Note: An array can also be dynamically allocated as demonstrated in the
following example.
Example 8: Write a program that dynamically allocates an array of
integers. A list of integers is read from the keyboard and stored in the
array. The program determines the smallest in the list and prints its
location in the list.
Solution: The solution to this problem is trivial and the required
program is given below:
/*Thisprogramillustratestheusageofdynamically
allocatedarray*/
#include<stdio.h>
main()
{
inti,min,pos;
int*list;
intSz,N;
printf(\nEnterthesizeofthelist:);
scanf(%d,&N);
Sz=sizeof(int)*N;/*Computethesizeofthe
list*/
/*AllocatedynamicarraysizeN*/
list=(int*)malloc(Sz);
printf(\nEnterthelist);
/*Readthelist*/
for(i=0;i<N;i++)
{
printf(\nEnterNumber:);
scanf(%d,(list+i));
}
pos=0;/*Assumethatthezerothelementismin*/
min=*(list+0);
/*Findtheminimum*/
for(i=1;i<N;i++)
{
if(min>*(list+i))
{min=*(list+i);
pos=i;
}
}/*Printtheminimumanditslocation*/
printf(\nTheminimumis=%datposition=%d,
min,pos);
free(list);
}
From the above example, it can be observed that the size of a
dynamically allocated array can be specified even at the run time and the
required amount of memory is allocated. This is in sharp contrast to
static arrays for whom the size have to be declared at the compile time.
5.6.1 Self Referential Structures
When a member of a structure is declared as a pointer to the structure
itself then the structure is called a self referential structure. Consider the
following declaration:
structchain{
intval;
structchain*p;
};
The structure chain consists of two members: val and p. The member
val is a variable of type intwhereas the member p is a pointer to a
structure of type chain. Thus, the structure chain has a member that can
point to a structure of type chain or may be itself. This type of self
referencing structure can be viewed as shown in Figure 5.25.
These above steps have been coded in the program segment given below:
structchain{/*Declarestructurechain*/
intval;
chain*p;
};
structchainA,B;/*DeclarestructurevariablesA
andB*/
A.p=&B;/*ConnectAtoB*/
From Figure 5.26 and the above program segment, we observe that the
pointer p of structure variable Bis dangling, i.e., it is pointing to
nowhere. Such pointer can be assigned to NULL, a constant indicating
that there is no valid address in this pointer. The following statement
will do the desired operation:
B.p=NULL;
The data elements in this linked structure can be assigned by the
following statements:
A.val=50;
B.val=60;
The linked structure now looks like as shown in Figure 5.27.
Solution: We will use the following self referential structure for the
purpose of creating a node of the linked list.
structstud{
charname[15];
introll;
structstudnext;
};
The required linked list would be created by using three
pointers: first,farandback. The following algorithm would be
employed to create the list:
Step
1. Take a new node in pointer called first.
Sz=sizeof(structstudent);
printf(\nEnterthenumberofstudentsinthe
class);
scanf(%d,&N);
/*Takefirstnode*/
First=(structstudent*)malloc(Sz);
/*Readthedataofthefirststudent*/
printf(\nEnterthedata);
printf(\nName:);fflush(stdin);gets(First>
name);
printf(\nRoll:);scanf(%d,&First>roll);
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far>
name);
printf(\nRoll:);scanf(%d,&Far>roll);
/*ConnectBacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*Repeattheprocess*/
Far>next=NULL;
/*Printthecreatedlinkedlist*/
Far=First;/*Pointtothefirstnode*/
printf(\nTheDatais);
while(Far!=NULL)
{
printf(\nName:%s,Roll:%d,Far>name,Far
>roll);
/*Pointtothenextnode*/
Far=Far>next;
}
}
Example 10: Modify the program developed in Example 1 such that
1. assume that the number of students in the list is not known.
2. the list of students is split into the two sublists pointed by two
pointers: front_half andback_half. The front_half points to the
front half of the list and the back_half to the back half. If the number of
students is odd, the extra student should go in the front list.
printf(\nRoll:);scanf(%d,&First>roll);
First>next=NULL;
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*Readdataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far>
name);
printf(\nRoll:);scanf(%d,&Far>roll);
/*ConnectBacktoFar*/
back>next=Far;
/*PointbackwhereFarpoints*/
back=Far;
}/*Repeattheprocess*/
Far>next=NULL;
/*Countthenumberofnodes*/
Far=First;/*Pointtothefirstnode*/
count=0;
while(Far!=NULL)
{
count++;
Far=Far>next;
}/*Splitthelist*/
if(count==1)
{
printf(\nThelistcannotbesplit);
front_half=First;
}
else
{
/*Computethemiddle*/
if((count%2)==0)middle=count/2;
elsemiddle=count/2+1;
/*Travelthelistandsplitit*/
front_half=First;
Far=First;
for(i=1;i<=middle;i++)
{
back=Far;
Far=Far>next;
}
back_half=Far;
back>next=NULL;
}
/*Printthetwolists*/
Far=front_half;
printf(\nTheFrontHalf:);
for(i=1;i<=2;i++)
{
while(Far!=NULL)
{
printf(Name:%s,Roll:%d,Far>name,Far>
roll);
Far=Far>next;
}
if(count==1||i==2)break;
printf(\nTheBackHalf:);
Far=back_half;
}
}
FREQUENTLY ASKED QUESTIONS
1. What is meant by a pointer?
Ans. It is a variable which can only store the address of another variable.
2. Are the expressions (*p)++and++*p equivalent?
Ans. Yes
3. What happens if a programmer assigns a new address to a dynamic variable
before he either assigns its address to another pointer or deletes the
variable?
Ans. The dynamic variable will be lost, i.e., it becomes inaccessible to
program as well as the operating system.
4. What is meant by memory bleeding?
Ans. The lost dynamic variables lead to memory bleeding.
5. Pointers always contain integers. Comment.
Ans. We know that a pointer contains an address of other variables. Since
an address can only be a whole number or integer, the pointers contain
integers only.
6. What does the term **j mean?
Ans. The term **j means that j is a pointer to a pointer.
7. Are the expressions *p++and++*p equivalent?
Ans. No. The expression *p++ returns the contents of the variable pointed
by p and then increments the pointer. On the other hand, the expression +
+*p returns the incremented contents of the variable pointed by p.
8. Differentiate between an uninitialized pointer and a NULL pointer.
Ans. An un initialized pointer is a dangling pointer which may contain any
erroneous address. ANULL pointer does not point to any address location.
#include<stdio.h>
main()
{
structval{
intNet;
};
structvalx;
structval*p;
p=&x;
p>Net=50;
printf(\n%d,++(x.Net));
}
Ans. The output would be 50.
7. What would be the output of the following code?
#include<stdio.h>
main()
{
structval{
intNet;
};
structvalx;
structval*p;
p=&x;
p>Net=50;
printf(\n%d,(x.Net)++);
}
Ans. The output would be 21.
EXERCISES
1. Explain the & and * operators in detail with suitable examples.
2. Find the errors in the following program segments:
a.
intval=10
int*p;
p=val;
b.
charlist[10];
charp;
list=p;
2. (i) What will be the output of the following program:
#include<stdio.h>
main()
{
intval=10;
int*p,**k;
p=&val;
k=&p;
printf(\n%d%d%d%d,p,*p,*k,**k);
}
(ii)
#include<stdio.h>
main()
{
charch;
char*p;
ch=A;
p=&ch;
printf(\n%c%c,ch,(*p)++);
}
3. What will be the output of the following:
#include<stdio.h>
main()
{
intlist[5],i;
*list=5;
for(i=1;i<5;i++)
*(list+i)=*(list+i1)*i;
printf(\n);
for(i=0;i<5;i++)
printf(%d,*(list+i));
}
4. Find the errors in the following program segment:
structxyz{
char*A;
char*B;
intval;
};
xyzs;
A=Firsttext;
B=Secondtext;
5. What will be the output of the following program segment?
#include<stdio.h>
main()
{
structMyrec{
charch;
structMyrec*link;
};
structMyrecx,y,z,*p;
x.ch=x;
y.ch=y;
z.ch=z;
x.link=&z;
y.link=&x;
z.link=NULL;
p=&y;
while(p!=NULL)
{
printf(%c,p>ch);
p=p>link;
}
}
6. What will be the output of the following program?
#include<stdio.h>
main()
{
floatcost[]={35.2,37.2,35.42,36.3};
float*ptr[4];
inti;
for(i=0;i<4;i++)
*(ptr+i)=cost+i;
for(i=0;i<4;i++)
printf(%f,*(*(ptr+i)));
}
Assume the base address of the array cost to be 2025.
7. What will be the output of the following program:
#include<stdio.h>
main()
{
charitem[]=COMPUTER;
inti;
for(i=7;i>=0;i)
printf(%c,*(item+i));
}
6
Linked Lists
CHAPTER OUTLINE
6.1 Introduction
6.2 Linked Lists
6.3 Operations on Linked Lists
6.4 Variations of Linked Lists
6.5 The Concept of Dummy Nodes
6.6 Linked Stacks
6.7 Linked Queues
6.8 Comparison of Sequential and Linked Storage
6.9 Solved Problems
6.1 INTRODUCTION
The above problems can be dealt with the help of linked structures,
discussed in the subsequent sections.
6.2 LINKED LISTS
Let us now insert a new node containing data called Samridhi between
Preksha and Sagun. This insertion operation can be carried out by
the following steps:
Step
1. Getanodecalledptr.
2. DATA(ptr)=Samridhi
3. NEXT(ptr)=NEXT(List)
4. NEXT(List)=ptr
The third step means that the Next pointer of ptr should point to the
same location that is currently being pointed by Next of List.
The effect of above steps (14) is shown in Figure 6.3.
Creation of a linkedlists
Traversing a linkedlists
Searching in a linkedlists
Inserting a node in a linkedlists
Deleting a node from a linkedlists
These steps have been coded in the program segment given below:
structchain{/*declarestructurechain*/
intval;
chain*p;
};
structchainA,B;/*declarestructurevariablesA
andB*/
A.p=&B;/*connectAtoB*/
From Figure 6.7 and the above program segment, we observe that the
pointer p of structure variable Bis dangling, i.e., it is pointing to
nowhere. Such pointer can be assigned to NULL, a constant indicating
that there is no valid address in this pointer. The following statement
will do the desired operation:
B.p=NULL;
The data elements in this linked structure can be assigned by the
following statements:
Fig. 6.8 Value assignment to data elements
A.val=50;
B.val=60;
The linked structure now looks like as shown in Figure 6.8.
We can see that the members of structure B can be reached by the
following two methods:
The effect of the above statements is shown in Figure 6.9 (b). It may be
noted that the node pointed byptr has no name. In fact all dynamic
memory allocations are anonymous.
Let us now use self-referential structures to create a linked list of nodes
of structure type given inFigure 6.10. While creating the
linked list, the student data is read. The list is also travelled to print
the data.
Fig. 6.10 The self-referential structure student
We will use the following self-referential structure for the purpose of
creating a node of the linked list:
structstud{
charname[15];
introll;
structstud*next;
};
The required linked list would be created by using three pointers
first,far, and back. The following algorithm would be employed to
create the list:
Algorithm createLinkList()
{
Step
1.Takeanewnodeinpointercalledfirst.
2.Readfirst>nameandfirst>roll.
3.Pointbackpointertothesamenodebeingpointed
byfirst,i.e.,back=first.
4.Bringanewnodeinthepointercalledfar.
5.Readfar>nameandfar>roll.
6.Connectnextofbacktofar,i.e.,back>next=
far.
7.Takebacktofar,i.e.,back=far.
8.Repeatsteps4to7tillwholeofthelistis
constructed.
9.PointnextoffartoNULL,i.e.,far>next=
NULL.
10.Stop.
}
structstudent*First,*Far,*back;
intN,i;
intSz;
Sz=sizeof(structstudent);
printf(\nEnterthenumberofstudentsinthe
class);
scanf(%d,&N);
if(N==0)returnNULL;/*Listisempty*/
/*Takefirstnode*/
First=(structstudent*)malloc(Sz);
/*Readthedataofthefirststudent*/
printf(\nEnterthedata);
printf(\nName:);fflush(stdin);gets(First
>name);
printf(\nRoll:);scanf(%d,&First>roll);
First>next=NULL;
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far>name);
printf(\nRoll:);scanf(%d,&Far>roll);
/*ConnectBacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*Repeattheprocess*/
Far>next=NULL;
returnFirst;/*returnthepointertothe
linkedlist*/
}
/*Printthecreatedlinkedlist*/
voiddispList(structstudent*First)
{structstudent*Far;
Far=First;/*Pointtothefirstnode*/
printf(\nTheListis.);
while(Far!=NULL)
{
printf(%s%d,,Far>name,Far>roll);
/*Pointtothenextnode*/
Far=Far>next;
}
}
It may be noted that the next pointer of the last node of a linked list
always points to NULL to indicate the end of the list. The pointer First
points to the beginning of the list. The address of an in between node is
not known and, therefore, whenever it is desired to access a particular
node, the travel to that node has to start from the beginning.
6.3.2 Travelling a Linked List
Many a times, it is required to travel whole or a part of the list. For
example, the operations such as counting of nodes, processing of
information of some nodes, or printing of the list, etc. requires traversal
of the list.
Consider the linked list shown in Figure 6.12. It is pointed by First. Let
us travel this list for the purpose of counting the number of nodes in the
list.
4.2ptr=NEXT(ptr);
}
5.printThenumberofnodes=,count;
6.End
In the above algorithm, step4.2, given below, is worth noting:
ptr=NEXT(ptr)
The above statement says that let ptr point to the same location which is
currently being pointed by NEXT of ptr. The effect of this statement on
the list of Figure 6.12 is shown in Figure 6.13.
It may be noted that the step4.2 takes the ptr to next node. Since
this statement is within the scope of the while loop, ptr travels from the
First to the NULL pointer, i.e., the last node of the list.
Example 1: Write a program that travels a linked list consisting of
nodes of following struct type. While traveling, it counts the number of
nodes in the list. Finally, the count is printed.
structstudent{
charname[15];
introll;.
structstudent*next;
};
/*Thisprogramtravelsalinkedlistandcountsthe
numberofnodes*/
#include<stdio.h>
#include<alloc.h>
structstudent{
charname[15];
introll;
structstudent*next;
};
structstudent*create();
intcountNode(structstudent*First);
voidmain()
{
intcount;
structstudent*First;
First=create();
count=countNode(First);
printf(\nThenumberofnodes=%d,count);
}
/*Thisfunctioncreatesalinkedlist*/
structstudent*create()
{
structstudent*First,*Far,*back;
inti;
intSz;
Sz=sizeof(structstudent);
/*Takefirstnode*/
First=(structstudent*)malloc(Sz);
/*Readthedataofthefirststudent*/
printf(\nEnterthedataofthefirststudent);
printf(\nName:);fflush(stdin);gets(First
>name);
printf(\nRoll:);scanf(%d,&First>roll);
First>next=NULL;
if(First>roll<=0){
printf(\nNodes=0);
First=NULL;
exit(1);}/*Emptylist*/
/*pointbackwhereFirstpoints*/
back=Far=First;
while(Far>roll>0)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far
>name);
printf(\nRoll:);scanf(%d,&Far>roll);
if(Far>roll<=0)break;/*Endofthelist*/
/*ConnectBacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*repeattheprocess*/
back>next=NULL;
returnFirst;
}
/*Travelandcountthenumberof
nodes*/
intcountNode(structstudent*First)
{
structstudent*ptr;
intcount;
count=0;
ptr=First;/*Pointtothefirstnode*/
while(ptr!=NULL)
{count=count+1;
/*Pointtothenextnode*/
ptr=ptr>next;
}
returncount;
}
Example 2: Write a program that travels a linked list consisting of
nodes of following struct type. Assume that the number of students in
the list is not known.
structstudent{
charname[15];
introll;
structstudent*next;
};
While travelling the list of students, it is split into two sub-lists pointed
by two pointers: front_half andback_half. The front_half points to the
front half of the list and the back_half to the back half of the list. If the
number of students is odd, the extra student should go into the front list.
Solution: The list of students would be created in the same fashion as
done in Example 1. The number of nodes would be counted during the
creation of the linked list itself. Assume that the linked list is being
pointed by First. The following steps would be followed to split the list in
the desired manner:
1. Computethemiddleofthelist.
2. Pointfront_halftoFirst.
3. PointanotherpointerFartoFirst.LetFartravelthe
liststartingfromthefirstandreachtothemiddle.
4. Pointback_halftothenodethatsucceedsthemiddleof
thelist.
5. AttachNULLtothenextpointerofthemiddlenode,now
pointedbyFar.
6. Printthetwolists,i.e.,pointedbyfront_halfand
back_half.
voiddispList(structstudent*First);
voidmain()
{structstudent*First,*front_half,*back_half;
intcount;
First=create(&count);
if(count==1)
{
printf(\nThelistcannotbesplit);
front_half=First;
}
else
{
front_half=First;
back_half=split(First,&count);
}
printf(\nTheFirstHalf&);
dispList(front_half);
printf(\nTheSecondHalf&);
dispList(back_half);
}
structstudent*create(int*count)
{
structstudent*First,*Far,*back;
intSz;
Sz=sizeof(structstudent);
/*Takefirstnode*/
First=(structstudent*)malloc(Sz);
/*Readthedataofthefirststudent*/
printf(\nEnterthedataofthefirststudent);
printf(\nName:);fflush(stdin);gets(First
>name);
printf(\nRoll:);scanf(%d,&First>roll);
First>next=NULL;
if(First>roll<=0){
printf(\nNodes=0);
First=NULL;
exit(1);}/*Emptylist*/
*count=1;
/*pointbackwhereFirstpoints*/
back=Far=First;
while(Far>roll>0)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far
>name);
printf(\nRoll:);scanf(%d,&Far>roll);
if(Far>roll<=0)break;/*Endofthelist*/
*count=*count+1;
/*ConnectbacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*repeattheprocess*/
back>next=NULL;
returnFirst;
}
structstudent*split(structstudent*First,int
*count)
{
structstudent*Far,*back;
intmiddle,i;
Far=First;/*Pointtothefirstnode*/
/*Splitthelist*/
{
/*computethemiddle*/
if((*count%2)==0)middle=*count/2;
elsemiddle=*count/2+1;
/*Travelthelistandsplitit*/
for(i=1;i<=middle;i++)
{
back=Far;
Far=Far>next;
}
back>next=NULL;
returnFar;
}
}
/*Printthelist*/
voiddispList(structstudent*First)
{
structstudent*Far;
Far=First;
while(Far!=NULL)
{
printf(%s,%d,Far>name,Far>roll);
Far=Far>next;
}
}
6.3.3 Searching a Linked List
Search is an operation in which an item is searched in the linked
list. In fact, the list is travelled from the first node to the last node. If
a visited node contains the item, then the search is successful and the
travel immediately stops. This means that in the worst case, whole of the
list would be travelled. An algorithm for searching an item X in the list
is given below.
In this algorithm a linked list, pointed by First, is travelled. While
travelling, the data part of each visited node is compared with the item
X. If the item is found, then the search stops otherwise the process
continues till the end of the list (i.e., NULL) is encountered. A
pointer ptr is used to visit the various nodes of the list.
Algorithm searchList()
{
Step
1.ifFirst==NULL{
printListEmpty;exit();}
2.ReadtheitemX
3.ptr=First;
4.flag=0;
5.while(ptr!=NULL)
{
5.1if(X==DATA(ptr)then{flag=1;break;}
5.2.ptr=NEXT(ptr);
}
6.if(flag==1)thenprintItemfoundelseprint
Itemnotfound
7.Stop
}
/*Thisprogramsearchesaniteminalinkedlist*/
#include<stdio.h>
#include<alloc.h>
#include<process.h>
structstudent{
charname[15];
introll;
structstudent*next;
};
structstudent*create();
intsearchList(structstudent*First,intRoll);
voidmain()
{
structstudent*First;
intRoll,result;
First=create();
printf(\nEntertheRollofthestudenttobe
searched);
scanf(%d,&Roll);
result=searchList(First,Roll);
if(result==1)
printf(\nTheStudentfound);
else
printf(\nTheStudentnotfound);
}
structstudent*create()/*Anyofthecreate()
functiondesignedin
aboveexamplescanbeused*/
{
}
intsearchList(structstudent*First,intRoll)
{structstudent*Far;
intflag;
Far=First;/*Pointtothefirstnode*/
flag=0;
while(Far!=NULL)
{
if(Roll==Far>roll)
{
flag=1;
break;
}
/*Pointtothenextnode*/
Far=Far>next;
}
if(flag==1)return1;
elsereturn0;
}
6.3.4 Insertion in a Linked List
We have already appreciated that linkedlists are most suitable and
efficient data structures for insertion and deletion operations. The
insertion of a node in a linked list involves the following three steps:
1. Take a node. Store data into the node.
2. Search the location in the list where the node is to be inserted.
3. Insert the node.
The location where the node is to be inserted in a linked list can either
be at the beginning or at any other arbitrary position in the list. An
algorithm that inserts a node at the beginning of the list is given below:
In this algorithm, a node pointed by ptr is inserted in a linked list
whose first node is pointed by the pointer First.
Algorithm insertAtHead()
{
Step
1.Takeanodeinptr.
2.ReadDATA(ptr).
3.If(First==NULL)then
{
3.1First=ptr;
3.2NEXT(First)=NULL;
}
else
{
3.1NEXT(Ptr)=First;
3.2First=ptr;
}
4.Stop
}
The list structure before and after the insertion operation is shown
in Figure 6.14.
2.while(ptr!=NULL)
{
2.1if(DATA(ptr)=val)
{takeanodeinnptr;
ReadDATA(nptr);
Next(nptr)=Next(ptr);
Next(ptr)=nptr;
break;
}
2.2ptr=Next(ptr);
}
3.Stop
}
The list structure before and after the insertion operation is shown
in Figure 6.15.
#include<stdio.h>
#include<alloc.h>
#include<process.h>
structstudent{
charname[15];
introll;
structstudent*next;
};
structstudent*create();
structstudent*insertList(structstudent*First,
intRoll,int*flag);
voiddispList(structstudent*First);
voidmain()
{
structstudent*First;
intRoll,result;
First=create();
if(First!=NULL)/*Insertinbetween*/
{
printf(\nEntertherollno.ofthestudent);
printf(\nafterwhichtheinsertionistobe
made:);
scanf(%d,&Roll);
}
First=insertList(First,Roll,&result);
if(result==1)
{printf(\nThelist.);
dispList(First);
}
else
printf(\nTheplacenotfound);
}
structstudent*create()/*Hereanyofthe
create()functiondesigned
inaboveexamplescanbeused*/
{
}
structstudent*insertList(structstudent*First,int
Roll,int*flag)
{
structstudent*ptr,*Far;
intSz;/*InsertattheHeadofthelist*/
if((First==NULL)||(First>roll>Roll))
{
ptr=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(ptr
>name);
printf(\nRoll:);scanf(%d,&ptr>roll);
ptr>next=First;
First=ptr;
*flag=1;/*indicatesthatinsertionwasmade*/
}
else
{Far=First;/*Pointtothefirstnode*/
*flag=0;
while(Far!=NULL)
{
if(Roll==Far>roll)
{
*flag=1;
break;/*Thelocationoftheinsertion
found*/
}
/*Pointtothenextnode*/
Far=Far>next;
}
if(*flag==1)
{
ptr=(structstudent*)malloc(Sz);
/*ReadDataofstudentforinsertion*/
printf(\nEnterthedataofstudent);
printf(\nName:);fflush(stdin);gets(ptr
>name);
printf(\nRoll:);scanf(%d,&ptr>roll);
ptr>next=Far>next;/*Insertnode*/
Far>next=ptr;
}
}
ptr=First;
returnptr;
}
voiddispList(structstudent*First)/*Hereanyof
thedispList()
functiondesignedinaboveexamplescanbeused*/
{
}
Note: It is easier to insert a node after a selected node in a linked list as
only one pointer is needed to select the node. For example, in the above
program, the Far pointer has been used to select the nodeafter which
the insertion is made.
However, if insertion is needed to be made before a selected node then it
is cumbersome to insertnode with only one pointer. The reason being
that when the node is found before which the insertion is desired, there
is no way to reach to its previous node and make the insertion. The
easier and elegant solution is to use two pointers (Far and back) which
move in tandem, i.e., the pointer Far is followed by back.
When Far reaches the desired location, back points to its previous
location as shown inFigure 6.16.
back=Far;
Far=Next(Far);
If(DATA(Far)==Val)/*Checkifthenodeisthe
desirednode*/
{
takeanewnodeinptr;
ReadDATA(ptr);
Next(ptr)=Far;
Next(back)=ptr;
break;
}
}
}
Example 5: A sorted linked list of students in the order of their roll
numbers is given. Write a program that asks from the user to
enter data for a missing student which is desired to be inserted into the
list at its proper place.
Solution: The sorted linked list of students in the order of their roll
numbers would be created by using function create(), developed in
the previous programs. The data of the student which is to be inserted is
read and its proper location found, i.e., before the node whose roll
number is greater than the roll number of this in coming student. The
algorithm insertBefore() would be used for the desired insertion
operation.
The required program is given below:
/*Thisprograminsertsanodeinalinkedlist*/
#include<stdio.h>
#include<alloc.h>
#include<process.h>
structstudent{
charname[15];
introll;
structstudent*next;
};
structstudent*create();
structstudent*insertList(structstudent*First,
structstudentnewStud,
int*flag);
voiddispList(structstudent*First);
voidmain()
{
structstudent*First,stud;
intRoll,result;
First=create();
printf(\nEnterthedataofthestudentwhichisto
beinserted);
scanf(%s%d,&stud.name,&stud.roll);
First=insertList(First,stud,&result);
if(result==1)
{printf(\nThelist.);
dispList(First);
}
else
printf(\nTheplacenotfound);
}
structstudent*create()/*Hereanyofthe
create()functiondesigned
inaboveexamplescanbeused*/
{
}
structstudent*insertList(structstudent*First,
structstudentnewStud,
int*flag)
{
structstudent*ptr,*Far,*back;
intSz;/*InsertattheHeadofthelist*/
if((First==NULL)||(First>roll>newStud.roll))
{
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
strcpy(Far>name,newStud.name);
Far>roll=newStud.roll;
Far>next=First;
First=Far;
*flag=1;/*indicatesthatinsertionwasmade*/
}
else
{ptr=back=First;/*Pointtothefirstnode*/
*flag=0;
while(ptr!=NULL)
{
ptr=ptr>next;
if(newStud.roll>back>roll&&newStud.roll<ptr
>roll)
{
*flag=1;
break;/*Thelocationoftheinsertionfound*/
}
back=ptr;
}
if(*flag==1)
{
Far=(structstudent*)malloc(Sz);
/*ReadDataofstudentforinsertion*/
strcpy(Far>name,newStud.name);
Far>roll=newStud.roll;
Far>next=ptr;/*Insertnode*/
back>next=Far;
}
}
ptr=First;
returnptr;
}
voiddispList(structstudent*First)/*Hereanyof
thedispList()function
designedinaboveexamplescanbeused*/
{
}
6.3.5 Deleting a Node from a Linked List
A delete operation involves the following two steps:
1. Search the list for the node which is to be deleted.
2. Delete the node.
The first step requires that two pointers in tandem be used to search
the node needs to be deleted. After the search, one pointer points to the
selected node and the other points to immediate previousnode.
An algorithm for deletion of a node from a list pointed by First is given
below. It uses two pointers,ptr and back, that travel the list in such a
way that each visited node is checked. If it is the node to be deleted,
then ptr points to this selected node and the pointer back points to its
immediate previousnode as shown in Figure 6.17.
Algorithm delNode()
{
1.if(DATA(First)=VAL/*Checkifthestarting
nodeisthedesired
one*/
{
1.1ptr=First;
1.2First=Next(First);
1.3Free(ptr);
1.4Stop;
}
2.Back=First;
3.ptr=Next(First)
4.while(ptr!=NULL)
{
4.1if(DATA(ptr)=VAL)
{
Next(Back)=Next(ptr);/*Thenodeisdeleted*/
Free(ptr);
break;
}
4.2back=ptr;
4.3ptr=Next(ptr);
}
}
5.Stop
}
The list structure before and after the deletion operation is shown
in Figure 6.17.
voidmain()
{
structstudent*First;
intRoll,result;
First=create();
printf(\nEntertheRollofthestudentwhichisto
bedeleted);
scanf(%d,&Roll);
First=delNode(First,Roll,&result);
if(result==1)
{printf(\nThelist.);
dispList(First);
}
else
printf(\nTheplacenotfound);
}
structstudent*create()/*Hereanyofthecreate()
functiondesigned
inaboveexamplescanbeused*/
{
}
structstudent*delNode(structstudent*First,int
Roll,int*flag)
{
structstudent*ptr,*Far,*back;
intSz;
if(First==NULL){*flag=0;returnFirst;}
/*deletefromtheHeadofthelist*/
if(First>roll==Roll)
{
ptr=First;
First=First>next;
free(ptr);
*flag=1;/*indicatesthatdeletionwasmade*/
}
else
{ptr=back=First;/*Pointtothefirstnode*/
*flag=0;
while(ptr!=NULL)
{
ptr=ptr>next;
if(Roll==ptr>roll)
{
*flag=1;
break;/*Thelocationofthedeletionfound*/
}
back=ptr;
}
if(*flag==1)
{back>next=ptr>next;
free(ptr);
}
}
returnFirst;
}
voiddispList(structstudent*First)/*Hereanyof
thedispList()function
designedinaboveexamplescanbeused*/
{
}
6.4 VARIATIONS OF LINKED LISTS
There are many limitations of a linear linked list. Some of them are as
follows:
1. The movement through the linked list is possible only in one direction, i.e.,
in the direction of the links.
2. As discussed above, a pointer is needed to be pointed to a node before
the node that needs to be deleted.
3. Insertion or appending of a node at the end of the linked list can only be
done after travelling whole of the list.
forms a circle, i.e., there is no NULL pointer. In fact, every node has a
successor.
structstudent*create()
{
structstudent*First,*Far,*back;
intN,i;
intSz;
Sz=sizeof(structstudent);
printf(\nEnterthenumberofstudentsinthe
class);
scanf(%d,&N);
if(N==0)returnNULL;/*Listisempty*/
/*Takefirstnode*/
First=(structstudent*)malloc(Sz);
/*Readthedataofthefirststudent*/
printf(\nEnterthedata);
printf(\nName:”);fflush(stdin);
gets(First>name);
printf(\nRoll:”);scanf(%d,&First
>roll);
First>next=NULL;
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:”);fflush(stdin);
gets(Far>name);
printf(\nRoll:”);scanf(%d,&Far
>roll);
/*ConnectBacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*Repeattheprocess*/
Far>next=First;/*Pointthelastpointertothe
firstnode*/
returnFirst;/*returnthepointertothelinked
list*/
}
structstudent{
charname[15];
introll;
structstudent*next;
};
Solution: A circular linked list of nodes of student type would be
created by using the function calledcreate() developed in Example 7.
The list would be travelled from the first node till we reach backto the
first node. While travelling, the visited nodes would be counted. The
required program is given below:
/*Thisprogramtravelsacircularlinkedlistand
countsthenumberof
nodes*/
#include<stdio.h>
#include<alloc.h>
structstudent{
charname[15];
introll;
structstudent*next;
};
structstudent*create();
intcountNode(structstudent*First);
voidmain()
{
intcount;
structstudent*First;
First=create();
count=countNode(First);
printf(\nThenumberofnodes=%d,count);
}
structstudent*create()/*Herethecreate()
functionofExample7can
beused*/
{
}
/*Travelandcountthenumberofnodes*/
intcountNode(structstudent*First)
{
structstudent*ptr;
intcount;
count=0;
ptr=First;/*Pointtothefirstnode*/
do
{count=count+1;
/*Pointtothenextnode*/
ptr=ptr>next;
}
while(ptr!=First);/*Thetravelstopsatthe
firstnode*/
returncount;
}
Note: The above representation of circular linkedlist (Figure 6.18)
has a drawback as far as the insertion operation is concerned. Even if
the node is to be added at the head of the list, the complete list will have
to be travelled so that the last pointer is made to point to the new node.
This operation ensures that the list remains circular after the insertion.
It may be noted that this extra travel was not required in the case of
linear linked list.
The above drawback of circular linkedlists can be avoided by
shifting the First pointer to the lastnode in the list as shown in Figure
6.19.
It may be noted that by having this representation, both ends of the list
can be accessed through theFirst. The First points to one end and
the Next(First) points to another.
1.Takeanewnodeinptr.
2.ReadDATA(ptr).
3.Next(ptr)=Next(First).
4.Next(First)=ptr.
}
The effect of the above algorithm is shown in Figure 6.20.
Fig. 6.20 Insertion of a node at the head of a circular linked list without
travel
A node can be added at the tail (i.e., last) of the list by the following
algorithm:
Algorithm insertTail()
{
Step
1.Takeanewnodeinptr.
2.ReadDATA(ptr).
3.Next(ptr)=Next(First).
4.Next(First)=ptr.
5.First=ptr.
}
The effect of the above algorithm is shown in Figure 6.21.
Fig. 6.21 Insertion of a node at the tail of a circular linked list without
travel
It may be noted that in both the cases of insertion (at the head and tail),
no travel of the circular linked list was made. Therefore, the new
representation has removed the drawback of the ordinary circular linked
list. In fact, it offers the solution in the order of O(1).
6.4.2 Doubly Linked List
Another variation of linked list is a doubly linked list. It is a list in which
each node contains two links:leftLink and rightLink.
The leftLink of a node points to its preceding node and
therightLink points to the next node in the list (see Figure 6.22).
Fig. 6.22 Node of a doubly linked list
From Figure 6.22, it can be noticed that a node in the doubly linked list
has the following three fields:
1. Data: for the storage of information.
2. leftLink:
pointer to the preceding node.
3. rightLink:
pointer to the next node.
printf(\nRoll:);scanf(%d,&First>roll);
First>rightList=NULL;
First>leftList=NULL;
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far
>name);
printf(\nRoll:);scanf(%d,&Far>roll);
/*ConnectBacktoFar*/
back>rightList=Far;
/*pointbackwhereFarpoints*/
Far>leftList=back;
back=Far;
}/*Repeattheprocess*/
Far>rightList=NULL;
returnFirst;
}
/*Printthecreatedlinkedinforwarddirection*/
voiddispForward(structstudent*First)
{structstudent*Far;
Far=First;/*Pointtothefirstnode*/
printf(\nTheForwardListis.);
while(Far!=NULL)
{
printf(%s%d,,Far>name,Far>roll);
/*Pointtothenextnode*/
Far=Far>rightList;
}
}
/*Printthecreatedlinkedinbackward
direction*/
voiddispBackward(structstudent*First)
{structstudent*Far;
Far=First;/*Pointtothefirstnode*/
while(Far>rightList!=NULL)
{/*Traveltothelastnode*/
Far=Far>rightList;
}
printf(\nTheBackwordListis.);
while(Far!=NULL)/*Travelthelistinbackward
direction*/
{
printf(%s%d,,Far>name,Far>roll);
/*Pointtothenextnode*/
Far=Far>leftList;
}
}
Consider the doubly linked list given in Figure 6.24.
The following relations hold good for the pointer ptr:
1. ptr ptr -> leftLink -> rightLink
2. ptr ptr -> rightLink -> leftLink
Fig. 6.24 The left of right and right of left is the same node
6.4.2.1 Insertion in Doubly Linked List
Insertion of a node X before a node pointed by ptr can be done by the
following program segment:
1.
2.
3.
4.
The effect of the above program segment is shown in Figure 6.25. The
above four operations have been labelled as 14 in Figure 6.25.
The effect of the above program segment is shown in Figure 6.26. The
above four operations have been labeled as 14 in Figure 6.26.
6.4.2.2 Deletion in Doubly Linked List
Deletion of a node pointed by ptr can be done by the following program
segment:
1. ptr>left>right=ptr>right;
2. ptr>right>left=ptr>left;
3. free(ptr);
With the help of rightLink, the list can be traversed in forward direction.
With the help of leftLink, the list can be traversed in backward direction.
Insertion after or before a node is easy as compared to singly linked list.
Deletion of a node is also very easy as compared to singly linked list.
The linked list and its variants, discussed above, have a major drawback.
Every algorithm has to take care of special cases. For instance, besides a
normal operation, an algorithm has to make sure that it works for the
following exceptional or boundary cases:
1. the empty list
2. on the head element of the list
3. on the last element of the list
Fig. 6.31 Comparison of empty lists with and without dummy nodes
It may be noted that with the help of dummy nodes, the basic nature
of linkedlists remains intact.
6.6 LINKED STACKS
We know that a stack is a LIFO (last in first out) data structure. Earlier in
the book, the stack was implemented using an array. Though the array
implementation was efficient but a considerable amount of storage space
was wasted in the form of unutilized locations of the array. For example,
let us assume that at a given time, there are five elements in a stack. If
the stack is implemented using 20 locations, then 15 locations out of 20
are being wasted.
1.if(Top=NULL)thenpromptstackempty;Stop
2.ptr=Top
3.Top=Next(Top);
4.returnDATA(ptr)
5.Freeptr
}
The structure of linked stack after a POP operation is shown in Figure
6.34.
structnode
{
intitem;
structnode*next;
};
Therequiredprogramisgivenbelow:
/*Thisprogramsimulatesalinkedstackforinteger
dataitems*/
#include<stdio.h>
#include<alloc.h>
#include<conio.h>
structnode
{
intitem;
structnode*next;
};
structnode*push(structnode*Top,intitem);
structnode*pop(structnode*Top,int*item);
voiddispStack(structnode*Top);
voidmain()
{
intitem,choice;
structnode*Top;
Top=NULL;
do
{clrscr();
printf(\nMenu);
printf(\nPush1);
printf(\nPop2);
printf(\nDisplay3);
printf(\nQuit4);
printf(\n\EnterChoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEntertheintegeritemtobe
pushed:);
scanf(%d,&item);
Top=push(Top,item);
break;
case2:if(Top==NULL)
{printf(\nStackempty);
break;
}
Top=pop(Top,&item);
printf(\nThepoppeditem=%d,item);
break;
case3:dispStack(Top);
break;
}
printf(\npressanykey);getch();
}
while(choice!=4);
}
structnode*push(structnode*Top,intitem)
{
structnode*ptr;
intsize;
size=sizeof(structnode);
ptr=(structnode*)malloc(size);
ptr>item=item;
ptr>next=Top;
Top=ptr;
returnTop;
}
structnode*pop(structnode*Top,int*item)
{
structnode*ptr;
if(Top==NULL)returnNULL;
ptr=Top;
Top=Top>next;
*item=ptr>item;
free(ptr);
returnTop;
}
voiddispStack(structnode*Top)
{
if(Top==NULL)
printf(\nStackEmpty);
else
{
printf(\nTheStackis:);
while(Top!=NULL)
{
printf(%d,Top>item);
Top=Top>next;
}
}
}
6.7 LINKED QUEUES
We know that a queue is a FIFO (first in first out) data structure. Since
this data structure also involves deletions and additions at front end
and rear end respectively, a linked list is definitely a better choice from
the point of implementation.
A linked queue will have the same node structure as in the case of a
stack as shown in Figure 6.35. Two pointers called Front and Rear would
keep track of the front and rear end of the queue.
structnode
{
intitem;
structnode*next;
};
structnode*addQ(structnode*Rear,structnode
*Front,intitem);
structnode*delQ(structnode*Rear,structnode
*Front,int*item);
voiddispQ(structnode*Rear,structnode*Front);
voidmain()
{
intitem,choice,size;
structnode*Front,*Rear;
size=sizeof(structnode);
Front=(structnode*)malloc(size);
Front>item=9999;/*dummynode*/
Front>next=NULL;
Rear=Front;/*InitializeQueuetoempty*/
do
{clrscr();
printf(\nMenu);
printf(\nAdd1);
printf(\nDelete2);
printf(\nDisplay3);
printf(\nQuit4);
printf(\n\nEnterChoice:);
scanf(%d,&choice);
switch(choice)
{
case1:printf(\nEntertheintegeritemtobe
added:);
scanf(%d,&item);
Rear=addQ(Rear,Front,item);
break;
case2:if(Front==Rear)
{printf(\nQueueempty);
break;
}
Front=delQ(Rear,Front,&item);
printf(\nThedeleteditem=%d,item);
break;
case3:dispQ(Rear,Front);
break;
}
printf(\npressanykey);getch();
}
while(choice!=4);
}
structnode*addQ(structnode*Rear,structnode
*Front,intitem)
{
intsize;
structnode*ptr;
size=sizeof(structnode);
ptr=(structnode*)malloc(size);
ptr>item=item;
ptr>next=Rear>next;
Rear>next=ptr;
Rear=ptr;
returnptr;
}
structnode*delQ(structnode*Rear,structnode
*Front,int*item)
{
structnode*ptr;
ptr=Front;
Front=Front>next;
*item=Front>item;
Front>item=9999;/*Thedummynode*/
free(ptr);
returnFront;
}
voiddispQ(structnode*Rear,structnode*Front)
{
if(Front==Rear)
printf(\nThequeueempty);
else
{printf(\nThequeueis...);
do
{
Front=Front>next;
printf(%d,Front>item);
}
while(Front!=Rear);
}
}
6.8 COMPARISON OF SEQUENTIAL AND LINKED STORAGE
printf(\nName:);fflush(stdin);gets(First
>name);
printf(\nRoll:);scanf(%d,&First>roll);
First>next=NULL;
/*pointbackwhereFirstpoints*/
back=First;
for(i=2;i<=N;i++)
{/*BringanewnodeinFar*/
Far=(structstudent*)malloc(Sz);
/*ReadDataofnextstudent*/
printf(\nName:);fflush(stdin);gets(Far
>name);
printf(\nRoll:);scanf(%d,&Far>roll);
/*ConnectBacktoFar*/
back>next=Far;
/*pointbackwhereFarpoints*/
back=Far;
}/*repeattheprocess*/
Far>next=NULL;
returnFirst;
}
structstudent*revLink(structstudent*List)/*The
functionthatreverses
thelist*/
{
structstudent*back,*ptr,*ahead;
if(List==NULL)
{
printf(\nThelistisempty);
returnNULL;
}
ptr=List;
back=List;
ahead=ptr>next;
List>next=NULL;
while(ahead!=NULL)
{
ptr=ahead;
ahead=ahead>next;
ptr>next=back;/*Connectthepointerto
precedingnode*/
back=ptr;
}
List=ptr;
returnList;
}
/*Printthecreatedlinkedlist*/
voiddispList(structstudent*First)
{structstudent*Far;
Far=First;/*Pointtothefirstnode*/
printf(\nTheListis.);
while(Far!=NULL)
{
printf(%s%d,,Far>name,Far>roll);
/*Pointtothenextnode*/
Far=Far>next;
}
}
Problem 3: Write a program that adds two polynomials Pol1and
Pol2, represented as linkedlists of terms, and gives a third
polynomial Pol3suchthatPol3=Pol1+Pol2.
Solution: We will use the following structure for the term of a
polynomial:
As done in Chapter 3, the polynomials will be added by merging the
two linkedlists. The required program is given below:
/*Thisprogramaddstwopolynomialsrepresentedas
linkedlistspointedbytwopointerspol1andpol2
andgivesathirdpolynomialpointedbyathird
pointerpol3*/
#include<stdio.h>
structterm
{
intcoef;
intexp;
structterm*next;
};
structterm*create_pol();
voidmain()
{
structterm*pol1,*pol2,*pol3,*ptr1,*ptr2,*ptr3,
*back,*bring,*ptr;
intsize;
size=sizeof(structterm);
printf(\nPolynomial1);
pol1=create_pol();
printf(\nPolynomial2);
pol2=create_pol();
ptr1=pol1;
ptr2=pol2;
ptr3=pol3=NULL;
while(ptr1!=NULL&&ptr2!=NULL)/*Add
Polynomialsbymerging*/
{if(ptr1>exp>ptr2>exp)
{
if(pol3==NULL)
{
pol3=(structterm*)malloc(size);
pol3>coef=ptr1>coef;
pol3>exp=ptr1>exp;
ptr3=pol3;
ptr1=ptr1>next;
ptr3>next=NULL;
}
else
{
bring=(structterm*)malloc(size);
bring>coef=ptr1>coef;
bring>exp=ptr1>exp;
ptr3>next=bring;
ptr3=bring;
ptr1=ptr1>next;
ptr3>next=NULL;
}
}
else
{
if(ptr1>exp<ptr2>exp)
{
if(pol3==NULL)
{
pol3=(structterm*)malloc(size);
pol3>coef=ptr2>coef;
pol3>exp=ptr2>exp;
ptr3=pol3;
ptr2=ptr2>next;
ptr3>next=NULL;
}
else
{
bring=(structterm*)malloc(size);
bring>coef=ptr2>coef;
bring>exp=ptr2>exp;
ptr3>next=bring;
ptr3=bring;
ptr2=ptr2>next;
ptr3>next=NULL;
}
}
else
{
if(pol3==NULL)
{
pol3=(structterm*)malloc(size);
pol3>coef=ptr1>coef+ptr2>coef;
pol3>exp=ptr2>exp;
ptr3=pol3;
ptr2=ptr2>next;
ptr1=ptr1>next;
ptr3>next=NULL;
}
else
{
bring=(structterm*)malloc(size);
bring>coef=ptr1>coef+ptr2>coef;
bring>exp=ptr2>exp;
ptr3>next=bring;
ptr3=bring;
ptr2=ptr2>next;
ptr1=ptr1>next;
ptr3>next=NULL;
}
}
}
}/*while*/
/*AppendrestofthePolynomial*/
if(ptr2>next==NULL)ptr3>next=ptr1;
if(ptr1>next==NULL)ptr3>next=ptr2;
/*PrinttheFinalPolynomial*/
ptr=pol3;
printf(\nFinalPolynomial);
while(ptr!=NULL)
{
printf(%d^%d,ptr>coef,ptr>exp);
if(ptr>exp!=0)printf(+);
ptr=ptr>next;
}
}
/*Readthetermsofapolynomoialandcreate
thelinkedlist*/
structterm*create_pol()
{
intsize;
structterm*first,*back,*bring;
size=sizeof(structterm);
first=(structterm*)malloc(size);
printf(\ninputPolynomialtermbyterm.Lastterm
withexp=0);
printf(\nEnteratermcoefexp);
scanf(%d%d,&first>coef,&first>exp);
first>next=NULL;
back=first;
while(back>exp!=0)
{
bring=(structterm*)malloc(size);
printf(\nEnteratermcoefexp);
scanf(%d%d,&bring>coef,&bring>exp);
back>next=bring;
back=bring;
}
back>next=NULL;
returnfirst;
}
EXERCISES
1. What is a linear linkedlist?
2. What is the need for a linkedlist? On what account, linked
lists are better than arrays?
3. Write a program that takes a linkedlist pointed by List and traverses
it in such a manner that after the travel the links of visited nodes become
reversed.
4. Write a program to create a linked list and remove all the duplicate
elements of the list.
5. What is a circular linked list? What are its advantages over linear linked
list? Write a program/ algorithm to insert a node at desired position in a
circular linked list.
6. Write a program that takes two ordered linkedlists as input and
merges them into single ordered linked list.
7. What is a doubly linked list? Write program/algorithm for showing the
following operations on a doubly linked list:
Create
Insert
Delete
2. What are the advantages of doubly linked list over singly linkedlist?
3. Write the program that inserts a node in a linkedlist after a
given node.
4. Write the program that inserts a node in a linkedlist before a
given node.
5. Write a program that deletes a given node from a linkedlist.
6. Write a function that deletes an element from a doubly linkedlist.
7. Implement a stack (LIFO) using singly linkedlist.
8. Write a program to implement a queue (FIFO) using singly linked list.
9. How do you detect a loop in a singly linked list? Write a C program for the
same.
10. How do you find the middle of a linked list without counting the nodes?
Write a C program for the same.
11. Write a C program that takes two lists List1 and List2 and compare
them. The program should return the following:
1:ifList1issmallerthanList2
0:ifList1isequaltoList2
1:ifList1isgreaterthanList2
12. Write a function that returns the nth node from the end of a linked
list.
13. Write a program to create a new linear linked list by selecting alternate
element of a given linearlinkedlist.
14. Write an algorithm to search an element from a given linear linked
list.
15. Explain the merits and demerits of static and dynamic memory allocation
techniques.
16. Define a header linkedlist and explain its utility.
7
Trees
CHAPTER OUTLINE
7.1 Introduction
There are situations where the nature of data is specific and cannot be
represented by linear or sequential data structures. For example, as
shown in Figure 7.1 hierarchical data such as types of computers is nonlinear by nature.
:A
Child nodes of A
:B, C, D
Sub-Trees of A
:T1, T2, T3
Root of T1
:B
Root of T2
:C
Root of T3
:D
Leaf nodes
:E, G, H, I, J, K, L
Internal node: Nodes other than root and leaf nodes are
called internal nodes or non-terminalnodes.
Example: B, C, D and F are internal nodes.
Root: The node which has no parent.
Example: A is root of the tree given in Figure 7.5. Edge: A line from a
parent to its child is called an edge. Example: A-B, A-C, F-K are edges.
Path: It is a list of unique successive nodes connected by edges.
Example: A-B-F-L and B-F-K are paths.
Depth: The depth of a node is the length of its path from root node. The
depth of root node is taken as zero.
Example: The depths of nodes G and L are 2 and 3, respectively.
Height: The number of nodes present in the longest path of the tree
from root to a leaf node is called the height of the tree. This will contain
maximum number of nodes. In fact, it can also be computed as one more
than the maximum level of the tree.
Example: One of the longest paths in the tree shown in Figure 7.3 is A-BF-K. Therefore, the height of the tree is 4.
Degree: Degree of a node is defined as the number of children present
in a node. Degree of a tree is defined equal to the degree of a node with
maximum children.
Example: Degree of nodes C and D are 1 and 2, respectively. Degree of
tree is 3 as there are two nodes A and B having maximum degree equal to
3.
It may be noted that the node of a tree can have any number of child
nodes, i.e., branches. In the same tree, any other node may not have any
child at all. Now the problem is how to represent such a general tree
where the number of branches varies from zero to any possible number.
Or is it possible to put a restriction on number of children and still
manage this important non-linear data structure? The answer is Yes.
We can restrict the number of child nodes to less than or equal to 2. Such
a tree is calledbinary tree. In fact, a general tree can be represented as
a binary tree and this solves most of the problems. A detailed discussion
on binary trees is given in next section.
Both the sub-trees are themselves binary trees as shown in Figure 7.6.
The leaf nodes at the last level will have zero children and the other nonleaf nodes will have exactly two children as shown in Figure 7.8.
A complete binary tree is a full tree except at the last level where all
nodes must appear as far left as possible. A complete binary tree is
shown in Figure 7.9. The nodes have been numbered and there is no
missing number till the last node, i.e., node number 12.
Consider the tree given in Figure 7.8. Its linear representation using an
array called linTree is given inFigure 7.11.
Fig. 7.11 Linear representation using linTree (of the tree of Figure 7.8)
In an array of size N, for the given node i, the following relationships are
valid in linear representation:
1. leftChild(i)=2*i{When2*i>Nthenthereisnoleft
child}
2. rightChild(i)=2*i+1{When2*i+1>Nthenthereis
norightchild}
3. parent(i)=[i/2]{node0hasnoparentitisaroot
node}
The following examples can be verified from the tree shown in Figure
7.8:
o The parent of node at i = 5 (i.e., E) would be at [5/2] = 2, i.e., B.
o The left child of node at i = 4 (i.e., D) would be at (2*4) = 8, i.e., H.
o The right child of node i = 10 (i.e., J) would be at (2*10) + 1 5 21 which is >
15, indicating that it has no right child.
It may be noted that out of 15 locations, only 4 locations are being used
and 11 are unused resulting in almost 300 per cent wastage of storage
space.
The disadvantages of linear representation of binary tree are as follows:
1. In a skewed or scanty tree, a lot of memory space is wasted.
2. When binary tree is full then space utilization is complete but insertion and
deletion of nodes becomes a cumbersome process.
3. In case of a full binary tree, insertion of a new node is not possible because
an array is a static allocation and its size cannot be increased at run time.
provided that creates a generalized binary tree with the help of a stack.
The generalized binary tree would be constructed and implemented in C
so that the reader gets the benefit of learning the technique for building
binary trees.
7.4.2.1 Creation of a Generalized Binary Tree
Let us try to create the tree shown in Figure 7.15.
By applying the above steps on binary tree of Figure 7.15, we get the
following list:
{A,(,B,(,D,(,H,
I,),E,(,J,$,)
,),C,(,F,(,K,L,
),G,),),#};
The algorithm that uses the treeList and a stack to create the required
binary tree is given below:
Algorithm createBinTree( )
{
Step
1.TakeanelementfromtreeList;
2.If(element!=(&&element!=)&&element!=
$&&element!=
#)
{
Takeanewnodeinptr;
DATA(ptr)=element;
leftChild(ptr)=NULL;
rightChild(ptr)=NULL;
Push(ptr);
}
3.if(element=))
{
Pop(LChild);
Pop(RChild);
Pop(Parent);
leftChild(Parent)=LChild;
rightChild(Parent)=RChild;
Push(Parent);
}
4.if(element=()donothing;
5.if(element=$)Push(NULL);
6.repeatsteps1to5tillelement!=#;
7.Pop(binTree);
8.stop.
}
A simulation of the above algorithm for the following treeList is given
in Figure 7.16.
treeList[]
=
{A,(,B,(,D,(,H,
I,),E,(,J,$,)
,),C,(,F,(,K,
L,),G,),),#};
The travel of the tree can be done in any combination of the above given
operations. If we restrict ourselves with the condition that left child node
would be visited before right child node, then the following three
combinations are possible:
/*Thisprogramcreatesabinarytreeandtravelsit
usinginorderfashion*/
#include<stdio.h>
#include<alloc.h>
#include<conio.h>
structtnode
{
charch;
structtnode*leftChild,*rightChild;
};
structnode
{
structtnode*item;
structnode*next;
};
structnode*push(structnode*Top,structnode
*ptr);
structnode*pop(structnode*Top,structnode
**ptr);
voiddispStack(structnode*Top);
voidtreeTravel(structtnode*binTree);
voidmain()
{inti;
/*chartreeList[]={1,(,2,(,$,
4,
),3,(,5,6,),),#};*/
chartreeList[]={A,(,B,(,D,(,H,
I,),E,(,
J,$,),),C,(,F,(,K,L,),
G,),),#};
structnode*Top,*ptr,*LChild,*RChild,*Parent;
structtnode*binTree;
intsize,tsize;
size=sizeof(structnode);
Top=NULL;
i=0;
while(treeList[i]!=#)
{
switch(treeList[i])
{
case):Top=pop(Top,&RChild);
Top=pop(Top,&LChild);
Top=pop(Top,&Parent);
Parent>item>leftChild=LChild>item;
Parent>item>rightChild=RChild>item;
Top=push(Top,Parent);
break;
case(:break;
default:
ptr=(structnode*)malloc(size);
if(treeList[i]==$)
ptr>item=NULL;
else
{
ptr>item>ch=treeList[i];
ptr>item>leftChild=NULL;
ptr>item>rightChild=NULL;
ptr>next=NULL;
}
Top=push(Top,ptr);
break;
}
i++;
}
Top=pop(Top,&ptr);
binTree=ptr>item;
printf(\nTheTreeis..);
treeTravel(binTree);
}
structnode*push(structnode*Top,structnode
*ptr)
{
ptr>next=Top;
Top=ptr;
returnTop;
}
structnode*pop(structnode*Top,structnode
**ptr)
{
if(Top==NULL)returnNULL;
*ptr=Top;
Top=Top>next;
returnTop;
}
voiddispStack(structnode*Top)
{
if(Top==NULL)
printf(\nStackEmpty);
else
{
printf(\nTheStackis:);
while(Top!=NULL)
{
printf(%c,Top>item>ch);
Top=Top>next;
}
}
}
voidtreeTravel(structtnode*binTree)
{
if(binTree==NULL)return;
treeTravel(binTree>leftChild);
printf(%c,binTree>ch);
treeTravel(binTree>rightChild);
}
It may be noted that a linked stack has been used for the
implementation. The program has been tested for the binary tree given
in Figure 7.15 represented using the following tree list:
treeList[]
=
{A,(,B,(,D,(,H,
I,),E,(,J,$,)
,),C,(,F,(,K,L,),
G,),),#};
{1,(,2,(,$,4,
),3,(,5,6,),),#}
241536
It is left as an exercise for the students to generate the binary tree from
the tree list and then verify the output.
7.4.3.2 Preorder Travel (V-L-R)
This order of travel, i.e., V-L-R requires that while travelling the binary
tree, the data of the visited node be processed first before the left subtree of a node is travelled. Thereafter, its right sub-tree is travelled.
Consider the binary tree given in Figure 7.18. Its root node is pointed by
a pointer called Tree.
preorderTravel(rightChild(Tree));
}
}
Example 2: Write a program that creates a generalized binary tree and
travels it using preorder strategy. While travelling, it should print the
data of the visited node.
Solution: We would use the
algorithms createBinTree() and preorderTravel(Tree) to
write the program. However, the code will be similar to Example1.
Therefore, only the functionpreOrderTravel() is provided.
The required program is given below:
void preOrderTravel(structtnode*binTree)
{
if(binTree==NULL)return;
printf(%c,binTree>ch);
preOrderTravel(binTree>leftChild);
preOrderTravel(binTree>rightChild);
}
The function has been tested for the binary tree given in Figure
7.15 represented using the following tree list:
treeList[]
=
{A,(,B,(,D,(,H,
I,),E,(,J,$,
),),C,(,F,
(,K,L,),G,),),#};
{1,(,2,(,$,4,
),3,(,5,6,),),#}
124356
It is left as an exercise for the students to generate the binary tree from
the treeList and then verify the output.
7.4.3.3 Postorder Travel (L-R-V)
This order of travel, i.e., L-R-V requires that while travelling the binary
tree, the data of the visited node be processed only after both the left and
right sub-trees of a node have been travelled.
Consider the binary tree given in Figure 7.19. Its root node is pointed by
a pointer called Tree.
processDATA(Tree);
}
}
Example 3: Write a program that creates a generalized binary tree and
travels it using preorder strategy. While travelling, it should print the
data of the visited node.
Solution: We would use the
algorithms createBinTree() and postOrderTravel(Tree) to
write the program. However, the code will be similar to Example 1.
Therefore, only the functionpostOrderTravel() is provided.
The required function is given below:
voidpostorderTravel(structtnode*binTree)
{
if(binTree==NULL)return;
postOrderTravel(binTree>leftChild);
postOrderTravel(binTree>rightChild);
printf(%c,binTree>ch);
}
The function has been tested for the binary tree given in Figure
7.15 represented using the following tree list:
treeList[]
=
{A,(,B,(,D,(,H,
I,),E,(,J,$,
),),C,(,F,(,K,L,
),G,),),#};
{1,(,2,(,$,4,
),3,(,5,6,),),#}
425631
It is left as an exercise for the students to generate the binary tree from
the treeList and then verify the output.
7.5 TYPES OF BINARY TREES
There are many types of binary trees. Some of the popular binary trees
are discussed in subsequent sections.
7.5.1 Expression Tree
An expression tree is basically a binary tree. It is used to express an
arithmetic expression. The reason is simple because both binary tree and
arithmetic expression are binary in nature. In binary tree, each node has
two children, i.e., left and right child nodes whereas in an arithmetic
expression, each arithmetic operator has two operands. In a binary tree,
a single node is also considered as a binary tree. Similarly, a variable or
constant is also the smallest expression.
Consider the preorder arithmetic expression given below:
*+A*BC-DE
The above arithmetic expression can be expressed as an expression tree
given in Figure 7.20.
ABC*+DE*
Similarly, the preorder travel of the tree given in Figure 7.20 shall
produce the following prefix expression:
*+A*BCDE
From the above discussion, we can appreciate the rationale behind the
naming of the travel strategies VLR, LVR, and LRV as preorder, inoder,
and postorder, respectively.
Example 4: Write a program that travels the expression tree of Figure
7.21 in an order chosen by the user from a menu. The data of visited
nodes are printed. Design an appropriate menu for this program.
#include<alloc.h>
#include<conio.h>
structtnode
{
charch;
structtnode*leftChild,*rightChild;
};
structnode
{
structtnode*item;
structnode*next;
};
structnode*push(structnode*Top,structnode
*ptr);
structnode*pop(structnode*Top,structnode
**ptr);
voiddispStack(structnode*Top);
voidpreOrderTravel(structtnode*binTree);
voidinOrderTravel(structtnode*binTree);
voidpostOrderTravel(structtnode*binTree);
voidmain()
{inti,choice;
chartreeList[]={,(,+,(,P,/,(,
Q,A,),)
,B,),#};
structnode*Top,*ptr,*LChild,*RChild,*Parent;
structtnode*binTree;
intsize,tsize;
size=sizeof(structnode);
Top=NULL;
i=0;
while(treeList[i]!=#)
{
switch(treeList[i])
{
case):Top=pop(Top,&RChild);
Top=pop(Top,&LChild);
Top=pop(Top,&Parent);
Parent>item>leftChild=LChild>item;
Parent>item>rightChild=RChild>item;
Top=push(Top,Parent);
break;
case(:break;
default:
ptr=(structnode*)malloc(size);
if(treeList[i]==$)
ptr>item=NULL;
else
{
ptr>item>ch=treeList[i];
ptr>item>leftChild=NULL;
ptr>item>rightChild=NULL;
ptr>next=NULL;
}
Top=push(Top,ptr);
break;
}
i++;
}
Top=pop(Top,&ptr);
/*displayMenu*/
do{
clrscr();
printf(\nMenuTreeTravel);
printf(\n\nInorder1);
printf(\nPreorder2);
printf(\nPostorder3);
printf(\nQuit4);
printf(\n\nEnteryourchoice:);
scanf(%d,&choice);
binTree=ptr>item;
switch(choice)
{
case1:printf(\nTheTreeis..);
inOrderTravel(binTree);
break;
case2:printf(\nTheTreeis..);
preOrderTravel(binTree);
break;
case3:printf(\nTheTreeis..);
postOrderTravel(binTree);
break;
};
printf(\nEnteranykeytocontinue);
getch();
}
while(choice!=4);
}
structnode*push(structnode*Top,structnode
*ptr)
{
ptr>next=Top;
Top=ptr;
returnTop;
}
structnode*pop(structnode*Top,structnode
**ptr)
{
if(Top==NULL)returnNULL;
*ptr=Top;
Top=Top>next;
returnTop;
}
voiddispStack(structnode*Top)
{
if(Top==NULL)
printf(\nStackEmpty);
else
{
printf(\nTheStackis:);
while(Top!=NULL)
{
printf(%c,Top>item>ch);
Top=Top>next;
}
}
}
voidpreOrderTravel(structtnode*binTree)
{
if(binTree==NULL)return;
printf(%c,binTree>ch);
preOrderTravel(binTree>leftChild);
preOrderTravel(binTree>rightChild);
}
voidinOrderTravel(structtnode*binTree)
{
if(binTree==NULL)return;
inOrderTravel(binTree>leftChild);
printf(%c,binTree>ch);
inOrderTravel(binTree>rightChild);
}
voidpostOrderTravel(structtnode*binTree)
{
if(binTree==NULL)return;
postOrderTravel(binTree>leftChild);
postOrderTravel(binTree>rightChild);
printf(%c,binTree>ch);
}
The output of the program for choice 1 (inorder travel) is given below:
The Tree is : P + Q / A B
The output of the program for choice 2 (preorder travel) is given below:
The Tree is : + P / Q A B
The output of the program for choice 3 (postorder travel) is given below:
The Tree is : P Q A / + B
It is left as an exercise for the readers to verify the results by manually
converting the expression to the required form.
7.5.2 Binary Search Tree
As the name suggests, a binary search tree (BST) is a special binary tree
that is used for efficient searching of data. In fact a BST stores data in
such a way that binary search algorithm can be applied.
The BST is organized as per the following extra conditions on a binary
tree:
o The data to be stored must have unique key values.
o The key values of the nodes of left sub-tree are always less than the key
value of the root node.
o The key values of the nodes of right sub-tree are always more than the key
value of the root node.
o The left and right sub-trees are themselves BSTs.
In simple words we can say that the key contained in the left child is less
than the key of its parent node and the key contained in the right child is
more than the key of its parent node as shown in Figure 7.22.
{
takeanodeinptr
readDATA(ptr);
if(DATA(ptr)isnotvalid)break;
leftChild(ptr)=NULL;
rightChild(ptr)=NULL;
attach(binTree,ptr);
}
}
returnbinTree;
Algorithmattach(Tree,node)
{
if(Tree==NULL)
Tree=node;
else
{
if(DATA(node)<DATA(Tree)
attach(Tree>leftChild);
else
attach(Tree>rightChild);
}
returnTree;
}
Example 5: Write a program that creates a BST wherein each node of
the tree contains non-negative integer. The process of creation stops as
soon as a negative integer is inputted by the user. Travel the tree using
inorder travel to verify the created BST.
Solution: The algorithm createBST() and its associated
algorithm attach() would be used to write the program. The required
program is given below:
/*ThisprogramcreatesaBSTandverifiesitby
travellingitusinginordertravel*/
#include<stdio.h>
#include<alloc.h>
#include<conio.h>
structbinNode
{
intval;
structbinNode*leftChild,*rightChild;
};
structbinNode*createBinTree();
structbinNode*attach(structbinNode*tree,struct
binNode*node);
voidinOrderTravel(structbinNode*binTree);
voidmain()
{
structbinNode*binTree;
binTree=createBinTree();
printf(\nThetreeis);
inOrderTravel(binTree);
}
structbinNode*createBinTree()
{intval,size;
structbinNode*treePtr,*newNode;
treePtr=NULL;
printf(\nEnterthevaluesofthenodesterminated
byanegativevalue);
val=0;
size=sizeof(structbinNode);
while(val>=0)
{
printf(\nEnterval);
scanf(%d,&val);
if(val>=0)
{
newNode=(structbinNode*)malloc(size);
newNode>val=val;
newNode>leftChild=NULL;
newNode>rightChild=NULL;
treePtr=attach(treePtr,newNode);
}
}
returntreePtr;
}
structbinNode*attach(structbinNode*tree,struct
binNode*node)
{
if(tree==NULL)
tree=node;
else
{
if(node>val<tree>val)
tree>leftChild=attach(tree>leftChild,node);
else
tree>rightChild=attach(tree>rightChild,node);
}
returntree;
}
voidinOrderTravel(structbinNode*binTree)
{
if(binTree==NULL)return;
inOrderTravel(binTree>leftChild);
printf(%d,binTree>val);
inOrderTravel(binTree>rightChild);
}
The above program was given the following input:
56 76 43 11 90 65 22
And the following output was obtained when this BST was travelled
using inorder travel:
11 22 43 56 65 76 90
It may be noted that the output is a sorted list verifying that the
created BST is correct. Similarly, the program was given the following
input:
45 12 34 67 78 56
and the following output was obtained:
12 34 45 56 67 78
The output is a sorted list and it verifies that the created BST is correct.
}
Example 6: Write a program that searches a given key in a binary
search tree through a function called searchBST(). The function
returns 1 or 0 depending upon the search being successful or a failure.
The program prompts appropriate messages like Search successful or
Search unsuccessful.
Solution: The above algorithm searchBST() is used. The required
program is given below:
#include<stdio.h>
#include<alloc.h>
#include<conio.h>
structbinNode
{
intval;
structbinNode*leftChild,*rightChild;
};
structbinNode*createBinTree();
structbinNode*attach(structbinNode*tree,struct
binNode*node);
intsearchBST(structbinNode*binTree,intval);
voidmain()
{intresult,val;
structbinNode*binTree;
binTree=createBinTree();
printf(\nEnterthevaluetobesearched);
scanf(%d,&val);
result=searchBST(binTree,val);
if(result==1)
printf(\nSearchSuccessful);
else
printf(\nSearchUnSuccessful);
}
structbinNode*createBinTree()
{}
/*thisfunctioncreateBinTree()issameasdeveloped
inaboveexamples*/
intsearchBST(structbinNode*binTree,intval)
{intflag;
if(binTree==NULL)
return0;
if(val==binTree>val)
return1;
else
if(val<binTree>val)
flag=searchBST(binTree>leftChild,val);
else
flag=searchBST(binTree>rightChild,val);
returnflag;
}
The above program has been tested.
7.5.2.3 Insertion into a Binary Search Tree
The insertion in a BST is basically a search operation. The item to be
inserted is searched within theBST. If it is found, then the insertion
operation fails otherwise when a NULL pointer is encountered the item is
attached there itself. Consider the BST given in Figure 7.24.
It may be noted that the number 49 is inserted into the BST shown
in Figure 7.24. It is searched in theBST which comes to a dead end at the
node containing 48. Since the right child of 48 is NULL, the number 49
is attached there itself.
Note: In fact, a system programmer maintains a symbol table as
a BST and the symbols are inserted into the BST in search-insert fashion.
The main advantage of this approach is that duplicate entries into the
table are automatically caught. The search engine also stores the
information about downloaded web pages in search-insert fashion so
that possible duplicate documents from mirrored sites could be caught.
An algorithm for insertion of an item into a BST is given below:
Algorithm insertBST(binTree,item)
{
if(binTree==NULL)
{
Takenodeinptr;
DATA(ptr)=item;
leftChild(ptr)=NULL;
rightChild(ptr)=NULL;
binTree=ptr;
returnsuccess;
}
else
if(DATA(binTree)==item)
returnfailure;
else
{Takenodeinptr;
DATA(ptr)=item;
leftChild(ptr)=NULL;
rightChild(ptr)=NULL;
if(DATA(binTree)>item)
insertBST(binTree>leftChild,ptr);
else
insertBST(binTree>rightChild,ptr);
}
}
/*thisfunctioncreateBinTree()issameasdeveloped
inaboveexamples*/
{}
structbinNode*attach(structbinNode*tree,struct
binNode*node)
{
if(tree==NULL)
tree=node;
else
{
if(node>val<tree>val)
tree>leftChild=attach(tree>leftChild,node);
else
tree>rightChild=attach(tree>rightChild,node);
}
returntree;
}
structbinNode*insertBST(structbinNode*binTree,
intval,int
*flag)
{intsize;
structbinNode*ptr;
size=sizeof(structbinNode);
if(binTree==NULL)
{ptr=(structbinNode*)malloc(size);
ptr>val=val;
ptr>leftChild=NULL;
ptr>rightChild=NULL;
binTree=ptr;
*flag=1;
returnbinTree;
}
if(val==binTree>val)
{
*flag=0;
returnbinTree;
}
else
if(val<binTree>val)
binTree>leftChild=insertBST(binTree>leftChild,val,
flag);
else
binTree>rightChild=insertBST(binTree>rightChild,
val,flag);
returnbinTree;
}
voidinOrderTravel(structbinNode*Tree)
{
if(Tree!=NULL)
{
inOrderTravel(Tree>leftChild);
printf(%d,Tree>val);
inOrderTravel(Tree>rightChild);
}
}
The above program was tested for the following input:
Nodes of the BST: 45 12 67 20 11 56
Value to be inserted: 11
The output: Duplicate value was obtained indicating that the value is
already present in the BST. Similarly, for the following input:
Nodes of BST: 65 78 12 34 89 77 22
Value to be inserted: 44
The output: Insertion successful, the Tree is .. 12 22 34 44 65 77 78 89,
indicating that the insertion at proper place in BST has taken place.
7.5.2.4 Deletion of a Node from a Binary Search Tree
Deletion of a node from a BST is not as simple as insertion of a node. The
reason is that the node to be deleted can be at any of the following
positions in the BST:
1. The node is a leaf node.
2. The node has only one child.
3. The node is an internal node, i.e., having both the children.
Case 1: This case can be very easily handled by setting the pointer to the
node from its parent equal toNULL and freeing the node as shown
in Figure 7.25.
The successor of a node can be reached by first moving to its right child
and then keep going to left till aNULL is found as shown in Figure 7.27.
It may be further noted that if an internal node ptr is to be deleted then
the contents of inorder successor of ptr should replace the contents
of ptr and the successor be deleted by the methods suggested for Case 1
or Case 2, discussed above . The mechanism is shown in Figure 7.28.
An algorithm to find the inorder successor of a node is given below. It
uses a pointer called succPtrthat travels from ptr to its successor and
returns it.
Algorithm findSucc(ptr)
{
succPtr=rightChild(ptr);
while(leftChild(succPtr!=NULL))
succPtr=leftChild(succptr);
returnsuccPtr;
}
if(leftChild(ptr)!=NULL&&rightChild(ptr)==
NULL)/*Case2*/
{
if(leftChild(Parent)==ptr)
leftChild(Parent)=leftChild(ptr);
else
if(rightChild(Parent)==ptr)
rightChild(Parent)=leftChild(ptr);
free(ptr);
}
else
if(leftChild(ptr)==NULL&&rightChild(ptr)!=
NULL)/*Case2*/
{
if(leftChild(Parent)==ptr)
leftChild(Parent)=rightChild(ptr);
else
if(rightChild(Parent)==ptr)
rightChild(Parent)=rightChild(ptr);
free(ptr);
}
if(leftChild(ptr)!=NULL&&rightChild(ptr)!=
NULL)/*Case3*/
{
succPtr=findSucc(ptr);
DATA(ptr)=DATA(succPtr);
delNodeBST(Tree,succPtr);
}
Example 8: Write a program that deletes a given node form binary
search tree. The program prompts appropriate message like Deletion
successful. Verify the deletion for all the three cases by travelling the
tree in inorder and displaying its contents.
Solution: The above algorithms
del NodeBST() and findSucc() are used. The required program is
given below:
/*ThisprogramdeletesanodefromaBST*/
#include<stdio.h>
#include<alloc.h>
#include<conio.h>
structbinNode
{
intval;
structbinNode*leftChild,*rightChild;
};
structbinNode*createBinTree();
structbinNode*attach(structbinNode*tree,struct
binNode*node);
structbinNode*searchBST(structbinNode*binTree,
intval,int*flag);
structbinNode*findParent(structbinNode*binTree,
structbinNode*ptr);
structbinNode*delNodeBST(structbinNode*binTree,
intval,int*flag);
structbinNode*findSucc(structbinNode*ptr);
voidinOrderTravel(structbinNode*Tree);
voidmain()
{intresult,val;
structbinNode*binTree;
binTree=createBinTree();
printf(\nEnterthevaluetobedeleted);
scanf(%d,&val);
binTree=delNodeBST(binTree,val,&result);
if(result==1)
{printf(\nDeletionsuccessful,TheTreeis..);
inOrderTravel(binTree);
}
else
printf(\nNodenotpresentinTree);
}
structbinNode*createBinTree()/*thisfunction
createBinTree()issameasdevelopedinabove
examples*/
{
}
structbinNode*attach(structbinNode*tree,struct
binNode*node)
{/*thisfunctionattach()issameasdevelopedin
aboveexamples*/
}
structbinNode*delNodeBST(structbinNode*binTree,
intval,int*flag)
{intsize,nval;
structbinNode*ptr,*parent,*succPtr;
if(binTree==NULL)
{*flag=0;
returnbinTree;
}
ptr=searchBST(binTree,val,flag);
if(*flag==1)
parent=findParent(binTree,ptr);
else
returnbinTree;
if(ptr>leftChild==NULL&&ptr>rightChild==
NULL)/*Case1*/
{
if(parent>leftChild==ptr)
parent>leftChild=NULL;
else
if(parent>rightChild==ptr)
parent>rightChild=NULL;
free(ptr);
}
if(ptr>leftChild!=NULL&&ptr>rightChild==
NULL)
{
if(parent>leftChild==ptr)
parent>leftChild=ptr>leftChild;
else
if(parent>rightChild==ptr)
parent>rightChild=ptr>leftChild;
free(ptr);
returnbinTree;
}
else
if(ptr>leftChild==NULL&&ptr>rightChild!=
NULL)
{
if(parent>leftChild==ptr)
parent>leftChild=ptr>rightChild;
else
if(parent>rightChild==ptr)
parent>rightChild=ptr>rightChild;
free(ptr);
returnbinTree;
}
if(ptr>leftChild!=NULL&&ptr>rightChild!=
NULL)
{
succPtr=findSucc(ptr);
nval=succPtr>val;
delNodeBST(binTree,succPtr>val,flag);
ptr>val=nval;
}
returnbinTree;
}
voidinOrderTravel(structbinNode*Tree)
{
if(Tree!=NULL)
{
inOrderTravel(Tree>leftChild);
printf(%d,Tree>val);
inOrderTravel(Tree>rightChild);
}
}
structbinNode*findSucc(structbinNode*ptr)
{structbinNode*succPtr;
getch();
succPtr=ptr>rightChild;
while(succPtr>leftChild!=NULL)
succPtr=succPtr>leftChild;
returnsuccPtr;
}
structbinNode*searchBST(structbinNode*binTree,
intval,int*flag)
{
if(binTree==NULL)
{*flag=0;
returnbinTree;
}
else
{
if(binTree>val==val)
{
*flag=1;
returnbinTree;
}
else
{if(val<binTree>val)
returnsearchBST(binTree>leftChild,val,flag);
else
returnsearchBST(binTree>rightChild,val,flag);
}
}
}
structbinNode*findParent(structbinNode*binTree,
structbinNode*ptr)
{structbinNode*pt;
if(binTree==NULL)
returnbinTree;
else
{
if(binTree>leftChild==ptr||binTree>rightChild
==ptr)
{pt=binTree;returnpt;}
else
{if(ptr>val<binTree>val)
pt=findParent(binTree>leftChild,ptr);
else
pt=findParent(binTree>rightChild,ptr);
}
}
returnpt;
}
The above program was tested for the following input:
Case 1:
Nodes of the BST: 38 27 40 11 39 30 45 28
Value to be deleted: 11, a leaf node
On the contrary, if a node contains data greater than or equal to the data
contained in its left and right child, then the heap is called as max
heap The reason being that as we move from leaf nodes to the root, the
data values increase and, therefore, in a max heap the data in the root is
the maximum data value in the tree as shown in Figure 7.30.
Thus, every successor of a node has data value less than or equal to the
data value of the node. The height of a heap with N nodes = |log 2 N|.
7.5.3.1 Representation of a Heap Tree
Since a heap tree is a complete binary tree, it can be comfortably and
efficiently stored in an array. For example, the heap of Figure 7.30 can be
represented in an array called heap as shown in Figure 7.31. The zeroth
position contains total number of elements present in the heap. For
example, the zeroth location of heap contains 10 indicating that there are
10 elements in the heap shown in Figure 7.31.
Fig. 7.31 Linear representation of a heap tree
It may be noted that this representation is simpler than the linked
representation as there are no links. Moreover from any node of the tree,
we can move to its parent or to its children, i.e., in both forward and
backward directions. As discussed in Section 7.4.1, the following
relationships hold good:
In an array of size N, for the given node i, the following relationships are
valid in linear representation:
(1) leftChild (i) = 2*i
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;/*Parentbecomeschild*/
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
}
Note: The insertion operation is important because the heap itself is
created by iteratively inserting the various elements starting from an
empty heap.
Example 9: Write a program that constructs a heap by iteratively
inserting elements input by a user into an empty heap.
Solution: The algorithm insertHeap() would be used. The required
program is given below:
/*Thisprogramcreatesaheapbyiteratively
insertingelementsintoaheapTree*/
#include<stdio.h>
#include<conio.h>
voidinsertHeap(intheap[],intitem,intsize);
voiddispHeap(intheap[]);
voidmain()
{
intitem,i,size;
intmaxHeap[20];
printf(\nEnterthesize(<20)ofheap);
scanf(%d,&size);
printf(\nEntertheelementsofheaponebyone);
for(i=1;i<=size;i++)
{
printf(\nElement:);
scanf(%d,&item);
insertHeap(maxHeap,item,size);
}
printf(\nTheheapis..);
dispHeap(maxHeap);
}
voidinsertHeap(intheap[],intitem,intsize)
{intlast,I,J,temp,Flag;
if(heap[0]==0)
{heap[0]=1;
heap[1]=item;
}
else
{
last=heap[0]+1;
if(last>size)
{
printf(\nHeapFull);
}
else
{
heap[last]=item;
heap[0]++;
I=last;
Flag=1;
while(Flag)
{
J=(int)(I/2);
if(J>=1)
{/*findparent*/
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
voiddispHeap(intheap[])
{
inti;
for(i=1;i<=heap[0];i++)
printf(%d,heap[i]);
}
The above program was tested for the input: 22 15 56 11 7 90 44
The output obtained is: 90 15 56 11 7 22 44, which is found to be
correct.
In the next run, 89 was added to the list, i.e., 22 15 56 11 7 90 44 89.
We find that the item 89 has been inserted at proper place as indicated
by the output obtained thereof: 90 89 56 15 7 22 44 11
It is left as an exercise for the reader to verify above results.
7.5.3.3 Deletion of a Node from a Heap Tree
The deletion operation on heap tree is relevant when its root is deleted.
Deletion of any other element has insignificant applications. The root is
deleted by the following steps:
Step
1.
2.
3.
4.
Let us delete its root (i.e., 70) by bringing the rightmost leaf (i.e., 56) into
the root as shown in Figure 7.34. The root has been saved in temp.
An algorithm that deletes the maximum element (i.e., root) from a heap
represented in an array of size N called maxHeap is given below. The
number of elements currently present in the heap is stored in the zeroth
location of maxHeap.
Flag=1;
while(Flag)
{
J=I*2;
if(J<=K)
{/*findbiggerchildandstoreitspositioninpos
*/
if(heap[J]>heap[J+1])
pos=J;
else
pos=J+1;
if(heap[pos]>heap[I])/*performreheap*/
{
temp=heap[pos];
heap[pos]=heap[I];
heap[I]=temp;
I=pos;/*parentbecomesthechild*/
}
else
Flag=0;
}
else
Flag=0;
}
returntempVal;
}
Example 10: Write a program that deletes and displays the root of a
heap. The remaining elements of the heap are put to reheap operation.
Solution: The algorithm delMaxHeap() would be used. The required
program is given below:
#include<stdio.h>
#include<conio.h>
intdelMaxHeap(intheap[]);
voidinsertHeap(intheap[],intitem,intsize);
voiddispHeap(intheap[]);
voidmain()
{
intitem,i,size;
intmaxHeap[20];
printf(\nEnterthesize(<20)ofheap);
scanf(%d,&size);
printf(\nEntertheelementsofheaponebyone);
for(i=1;i<=size;i++)
{
printf(\nElement:);
scanf(%d,&item);
insertHeap(maxHeap,item,size);
}
printf(\nTheheapis..);
dispHeap(maxHeap);
item=delMaxHeap(maxHeap);
printf(\nThedeleteditemis:%d,item);
printf(\nTheheapafterdeletionis..);
dispHeap(maxHeap);
}
voidinsertHeap(intheap[],intitem,intsize)
{intlast,I,J,temp,Flag;
if(heap[0]==0)
{heap[0]=1;
heap[1]=item;
}
else
{
last=heap[0]+1;
if(last>size)
{
printf(\nHeapFull);
}
else
{
heap[last]=item;
heap[0]++;
I=last;
Flag=1;
while(Flag)
{
J=(int)(I/2);
if(J>=1)
{/*findparent*/
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
voiddispHeap(intheap[])
{
inti;
for(i=1;i<=heap[0];i++)
printf(%d,heap[i]);
}
intdelMaxHeap(intheap[])
{
inttemp,I,J,K,val,last,Flag,pos;
if(heap[0]==1)returnheap[1];
last=heap[0];
val=heap[1];
heap[1]=heap[last];
heap[0];
K=last1;
I=1;
Flag=1;
while(Flag)
{
J=I*2;
if(J<=K)
{/*findchild*/
if(heap[J]>heap[J+1])
pos=J;
else
pos=J+1;
if(heap[pos]>heap[I])
{
temp=heap[pos];
heap[pos]=heap[I];
heap[I]=temp;
I=pos;
}
else
Flag=0;
}
else
Flag=0;
}
returnval;
}
The above program was tested for input: 34 11 56 78 23 89 12 5
and the following output was obtained:
The heap is: 89 56 78 11 23 34 12 5
The deleted item is: 89
The heap after deletion is: 78 56 34 11 23 5 12
It is left as an exercise for the students to verify the results by manually
making the heaps before and after deletion of the root.
7.5.3.4 Heap Sort
From discussion on heap trees, it is evident that the root of
a maxHeap contains the largest value. When the root of the max heap is
deleted, we get the largest element of the heap. The remaining elements
are put to reheap operation and we get a new heap. If the two operations
delete and reheap operation is repeated till the heap becomes empty and
the deleted elements stored in the order of their removal, then we get a
sorted list of elements in descending order as shown in Figure 7.35.
{
printf(\nElement:);
scanf(%d,&item);
insertHeap(maxHeap,item,size);
}
printf(\nTheheapis..);
dispHeap(maxHeap);
getch();
last=maxHeap[0];
for(i=0;i<last;i++)
{
item=delMaxHeap(maxHeap);
sortList[i]=item;
}
printf(\nThesortedlist);
for(i=0;i<last;i++)
printf(%d,sortList[i]);
}
voidinsertHeap(intheap[],intitem,intsize)
{intlast,I,J,temp,Flag;
if(heap[0]==0)
{heap[0]=1;
heap[1]=item;
}
else
{
last=heap[0]+1;
if(last>size)
{
printf(\nHeapFull);
}
else
{
heap[last]=item;
heap[0]++;
I=last;
Flag=1;
while(Flag)
{
J=(int)(I/2);
if(J>=1)
{/*findparent*/
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
voiddispHeap(intheap[])
{
inti;
for(i=1;i<=heap[0];i++)
printf(%d,heap[i]);
}
intdelMaxHeap(intheap[])
{
inttemp,I,J,K,val,last,Flag,pos;
if(heap[0]==1)returnheap[1];
last=heap[0];
val=heap[1];
heap[1]=heap[last];
heap[0];
K=last1;
I=1;
Flag=1;
while(Flag)
{
J=I*2;
if(J<=K)
{/*findchild*/
if(heap[J]>heap[J+1])
pos=J;
else
pos=J+1;
if(heap[pos]>heap[I])
{
temp=heap[pos];
heap[pos]=heap[I];
heap[I]=temp;
I=pos;
}
else
Flag=0;
}
else
Flag=0;
}
returnval;
}
The above program was tested for the input: 87 45 32 11 34 78 10 31
The output obtained is given below:
The heap is: 87 45 78 31 34 32 10 11
The sorted list is 87 78 45 34 32 31 11 10
Thus, the program has performed the required sorting operation using
heap sort.
Note: The above program is effective and gives correct output but it is
inefficient from the storage point of view. The reason being that it is
using an extra array called sortList to store the sorted list.
The above drawback can be removed by designing an in-place heap
sort algorithm that uses the same array to store the sorted list in which
the heap also resides. In fact, the simple modification could be that let
root and the last leaf node be exchanged. Without disturbing the last
node, reheap the remaining heap. Repeat the exchange and reheap
operation iteratively till only one element is left in the heap. Now, the
heap contains the elements in ascending order. The mechanism is
illustrated inFigure 7.36.
pos=J+1;
if(heap[pos]>heap[I])
{
temp=heap[pos];/*exchangeparentandchild
*/
heap[pos]=heap[I];
heap[I]=temp;
I=pos;
}
else
Flag=0;
}
else
Flag=0;
}
}
while(last>1);
}
}
It may be noted that the element at the root is being exchanged with the
last leaf node and the rest of the heap is being put to reheap operation.
This is iteratively being done in the do-while loop till there is only one
element left in the heap.
Example 12: Write a program that in-place sorts a given list of
numbers by heap sort.
Solution: We would employ the algorithm IpHeapSort() wherein the
element from the root is exchanged with the last leaf node and rest of the
heap is put to reheap operation. The exchange and reheap is carried out
iteratively till the heap contains only 1 element.
The required program is given below:
/*Thisprograminplacesortsagivenlistof
numbersusingheaptree*/
#include<stdio.h>
#include<conio.h>
voidsortMaxHeap(intheap[]);
voidinsertHeap(intheap[],intitem,intsize);
voiddispHeap(intheap[]);
voidmain()
{
intitem,i,size,last;
intmaxHeap[20];
printf(\nEnterthesize(<20)ofheap);
scanf(%d,&size);
printf(\nEntertheelementsofheaponebyone);
for(i=1;i<=size;i++)
{
printf(\nElement:);
scanf(%d,&item);
insertHeap(maxHeap,item,size);
}
printf(\nTheheapis..);
dispHeap(maxHeap);
getch();
sortMaxHeap(maxHeap);
printf(\nThesortedlist);
for(i=1;i<=size;i++)
printf(%d,maxHeap[i]);
}
voidinsertHeap(intheap[],intitem,intsize)
{intlast,I,J,temp,Flag;
if(heap[0]==0)
{heap[0]=1;
heap[1]=item;
}
else
{
last=heap[0]+1;
if(last>size)
{
printf(\nHeapFull);
}
else
{
heap[last]=item;
heap[0]++;
I=last;
Flag=1;
while(Flag)
{
J=(int)(I/2);
if(J>=1)
{/*findparent*/
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
voiddispHeap(intheap[])
{
inti;
for(i=1;i<=heap[0];i++)
printf(%d,heap[i]);
}
voidsortMaxHeap(intheap[])
{
inttemp,I,J,K,val,last,Flag,pos;
if(heap[0]>1)
{
do
{
last=heap[0];
temp=heap[1];/*Exchangerootwiththelastnode
*/
heap[1]=heap[last];
heap[last]=temp;
heap[0];
last=heap[0];
K=last1;
I=1;
Flag=1;
while(Flag)
{
J=I*2;
if(J<=K)
{/*findchild*/
if(heap[J]>heap[J+1])
pos=J;
else
pos=J+1;
if(heap[pos]>heap[I])
{
temp=heap[pos];
heap[pos]=heap[I];
heap[I]=temp;
I=pos;
}
else
Flag=0;
}
else
Flag=0;
}
}
while(last>1);
}
}
7.5.3.5 Merging of Two Heaps
Merging of heap trees is an interesting application wherein two heap
trees are merged to produce a tree which is also a heap. The merge
operation is carried out by the following steps:
Given two heap trees: Heap1 and Heap2
Step
1. Delete a node from Heap2.
2. Insert the node into Heap1.
3. Repeat steps 1 and 2 till Heap 2 is not empty.
printf(\nEntertheelementsofheaponebyone);
for(i=1;i<=size2;i++)
{
printf(\nElement:);
scanf(%d,&item);
insertHeap(maxHeap2,item,size2);
}
printf(\nTheheap2is..);
dispHeap(maxHeap2);
for(i=1;i<=size2;i++)
{
item=delMaxHeap(maxHeap2);
size1=size1+1;
insertHeap(maxHeap1,item,size1);
}
printf(\nTheheapsaftermergeis..);
dispHeap(maxHeap1);
}
voidinsertHeap(intheap[],intitem,intsize)
{intlast,I,J,temp,Flag;
if(heap[0]==0)
{heap[0]=1;
heap[1]=item;
}
else
{
last=heap[0]+1;
if(last>size)
{
printf(\nHeapFull);
}
else
{
heap[last]=item;
heap[0]++;
I=last;
Flag=1;
while(Flag)
{
J=(int)(I/2);
if(J>=1)
{/*findparent*/
if(heap[J]<heap[I])
{
temp=heap[J];
heap[J]=heap[I];
heap[I]=temp;
I=J;
}
else
Flag=0;
}
else
Flag=0;
}
}
}
}
voiddispHeap(intheap[])
{
inti;
for(i=1;i<=heap[0];i++)
printf(%d,heap[i]);
}
intdelMaxHeap(intheap[])
{
inttemp,I,J,K,val,last,Flag,pos;
if(heap[0]==1)returnheap[1];
last=heap[0];
val=heap[1];
heap[1]=heap[last];
heap[0];
K=last1;
I=1;
Flag=1;
while(Flag)
{
J=I*2;
if(J<=K)
{/*findchild*/
if(heap[J]>heap[J+1])
pos=J;
else
pos=J+1;
if(heap[pos]>heap[I])
{
temp=heap[pos];
heap[pos]=heap[I];
heap[I]=temp;
I=pos;
}
else
Flag=0;
}
else
Flag=0;
}
returnval;
}
The above program was tested on the heap trees given in Figure 7.37.
The input provided is given below:
Heap1: 67 31 34 8 10 5
Heap2: 22 12 17 4 9 615
The output obtained is given below:
The heap after merge is 67 31 34 17 12 6 22 8 15 10 9 5 4
It is left for the reader to verify the output from Figure 7.37.
Note: Both MaxHeap and MinHeap trees can be merged. The type of
output merged tree will depend upon the type
of Heap1. If Heap1 is maxHeap, then the heap obtained after merge
operation would also be of type maxHeap. Similarly, if Heap1 is
minHeap, then the heap obtained after merge operation would also be of
type minHeap.
7.5.4 Threaded Binary Trees
From the discussion on binary trees, it is evident that creation of a tree is
a one time process. But, a tree is travelled numerous times consuming a
large part of the overall time taken by an algorithm. Moreover, the travel
algorithms are recursive in nature amounting to large usage of storage.
Consider the binary tree given in Figure 7.38 wherein a binary tree has
been assigned the threads. It may be noted that the pointers have been
shown with thick lines and the threads with dotted lines.
Fig. 7.38 The vacant NULL pointers have been replaced by threads
The resultant binary tree of Figure 7.38 is called a threadedbinary
tree(TBT). It may be noted that the characteristic of this special tree
is that a left thread of a node points to its inorder predecessor and the
right thread to its inorder successor. For example, the left thread of E is
pointing to B, its predecessor and the right thread of E is pointing to
A, its successor.
Fig. 7.40 The threaded binary tree with node structure having the Tag
bits
A left thread pointing to NULL indicates that there is no more inorder
predecessor. Similarly, a NULLright thread indicates that there is no
more inorder successor. For example, the left thread of H is pointing
to NULL and the right thread of G is pointing to NULL. This anomalous
situation can be handled by keeping a head node having a left child and a
right thread as shown in Figure 7.41. This dummy node does not contain
any value. In fact, it is an empty threaded binary tree because both its
left child and right thread are pointing to itself.
As the threaded binary tree has been designed to honour inorder travel,
the insertion and deletion have to be carried out in such a way that after
the operation the tree remains the inorder threaded binary tree. A brief
discussion on both the operations is given in the subsequent sections.
7.5.4.1 Insertion into a Threaded Binary Tree
A node X, having Data part equal to T, can be inserted into an inorder
threaded binary tree after node Y in either of the following four ways:
Case 1: When X is inserted as left child of Y and Y has an empty left child.
Case 2: When X is inserted as right child of Y and Y has an empty right
child.
Case 3: When X is inserted as left child of Y and Y has a non-empty left
child
Case 4: When X is inserted as right child of Y and Y has a non-empty
right child.
CASE 1:
When Y (Say Data part equal to F) has an empty left child, then it must
be a thread. The insertion can be made by making the left thread of X
(Data part equal to T) to point where the left thread of Y is currently
pointing to. Thereafter, X becomes the left child of Y for which LTag of Y
is reset to 1. The right child of X is set as thread pointing to the node Y,
indicating that Y is the successor of X. The insertion operation is shown
in Figure 7.43.
{
if(LTag(Y)==0)
{
LTag(X)=0;
leftChild(X)=leftChild(Y);
LTAg(Y)=1;
leftChild(Y)=X;
RTag(X)=0;
rightChild(X)=Y;
}
}
CASE 2:
When Y (Data part equal to F) has an empty right child, then it must be
a thread. The insertion can be made by making the right thread of X
(Data part equal to T) to point where the right thread of Y is currently
pointing to. Thereafter, X becomes the right child of Y for which RTag of
Y is reset to 1. The left child of X is set as a thread pointing to the node Y,
indicating that Y is the predecessor of X. The insertion operation is given
in Figure 7.44.
It may be noted that the node containing T has become the right child
of node containing F. After the insertion, the tree is still a threaded
binary tree. The predecessor of T is F and the successor of T is C.
An algorithm for insertion of node as right thread is given below:
Algorithm inserAtRThread(X,Y)
{
if(RTag(Y)==0)
{
RTag(X)=0;
rightChild(X)=rightChild(Y);
RTAg(Y)=1;
rightChild(Y)=X;
LTag(X)=0;
leftChild(X)=Y;
}
}
CASE 3:
When Y (Data part equal to C) has non-empty left child, the insertion
can be made by making the left child of X (Data part equal to T) point
where the left child of Y is currently pointing to. Thereafter, X becomes
the left child of Y for which Lchild of Y is pointed to X. The right child
of X is set as thread pointing to the node Y, indicating that Y is the
successor of X.
Now the most important step is to find the inorder predecessor of Y
(pointed by ptr) and make it inorder predecessor of X by making ptrs
right thread to point to X. The insertion operation is shown in Figure
7.45.
It may be noted that the node containing T has become the left child of
node containing C. After the insertion, the tree is still a threaded binary
tree. The predecessor of T is F and the successor of T is C.
An algorithm for insertion of node as left child is given below:
Algorithm insertAtLchild(X,Y)
{
RTag(X)=0;/*ConnecttherightthreadofXtoY
*/
rightChild(X)=Y
LTAG(X)=1;/*ConnecttheleftChildofXtothe
nodepointedby
leftchildoY*/
leftChild(X)=leftChild(Y)
/*PointtheleftchildofYtoX*/
leftChild(Y)=X;
ptr=inOrderPred(Y)/*Findtheinorder
predecessorofY*/
rightChild(ptr)=X;/*Connecttherightthread
of(ptr)predecessortoX*/
}
CASE 4:
When Y (Data part equal to C) has non-empty right child, the insertion
can be made by making the right child of X (Data part equal to T) point
where the right child of Y is currently pointing to. Thereafter, X becomes
the right child of Y by pointing the right child of Y to X. The left thread of
X is set as a thread pointing to the node Y, indicating that Y is the
predecessor of X.
Fig. 7.45 Insertion of a node at non empty left child of a given node
Now, the most important step is to find the inorder successor of Y
(pointed by ptr) and make it inorder successor of X by making ptrs left
thread point to X. The insertion operation is shown inFigure 7.46.
It may be noted that the node containing T has become the right child
of node containing C. After the insertion, the tree is still a threaded
binary tree. The predecessor of T is C and the successor of T is G.
An algorithm for insertion of node as right child is given below:
Algorithm insertAtRchild(X,Y)
{
LTag(X)=0;/*ConnecttheleftthreadofXtoY
*/
leftChild(X)=Y
RTag(X)=1;/*ConnecttheleftChildofXtothe
nodepointedbyleftchildoY*/
rightChild(X)=rightChild(Y)
/*PointtheleftchildofYtoX*/
rightChild(Y)=X;
ptr=inOrderSucc(Y)/*Findtheinorder
successorofY*/
leftChild(ptr)=X;/*Connecttheleftthreadof
ptr(successor)toX*/
}
if(succ>leftChild==X)
Y=succ;/*Parentfound,pointitbyY*/
else
{suc=leftChild(succ);
while(rightChild(succ)!=X)
succ=rightChild(succ);
Y=succ;
}
returnY;
}
Now the algorithm findParent() can be conveniently used to delete a
node from a threaded binary tree. The various cases of deletion are
discussed below:
CASE 1:
When X is a left leaf node, then its parent Y is found (see Figure 7.47).
The left child of Y is made to point where Xs left thread is currently
pointing to. The LTag of Y is set to 0, indicating that it has become a
thread now.
An algorithm for deletion of a node X which is a left leaf node is given
below:
Algorithm delLeftLeaf(X,Y)
{
Y=findParent(X);
if(leftChild(Y)==X)
{
leftChild(Y)=leftChild(X);/*Makeleft
childofparentasthreadandletitpointwhereXs
leftthreadispointing*/
LTag(Y)=0;
}
returnX;
}
It may be noted from Figure 7.47 that node X has been deleted and the
left child of its parent Y has been suitably handled.
}
This case is similar to CASE 1 and, therefore, illustration for this case is
not provided.
CASE 3:
When X is having a right sub-tree, its parent Y is found. A pointer called
grandson is make to point to the right child of X (see fig 7.48). The
right child of Y is pointed to grandson. The successor of X is found. The
left thread of successor is pointed to Y, indicating that the successor of X
has now become the successor of parent as shown in Figure 7.48.
Y=findParent(X);
if(RTag(X)==1)
{
grandson=rightChild(X);
rightChild(Y)=grandson;
succ=inOrderSucc(X);
leftChild(succ)=Y;
}
returnX
}
CASE 4:
When X is having only left sub-tree, its parent Y is found. A pointer
called grandson is made to point to the left child of X. The left child of Y
is pointed to grandson. The predecessor of X is found. The right thread
of predecessor is pointed to Y, indicating that the predecessor of X has
now become the predecessor of parent.
An algorithm for deletion of X when X is having a left sub-tree is given
below:
Algorithm delNonLeafL(X,Y)
{
Y=findParent(X);
if(LTag(X)==1)
{
grandson=leftChild(X);
leftChild(Y)=grandson;
pred=inOrderPred(X);
rightChild(pred)=Y;
}
returnX
}
This case is similar to CASE3 and, therefore, illustration for this case is
not provided.
CASE 5:
When X is having both sub-trees, then X is not deleted but its successor
is found. The data of successor is copied in X and the successor is deleted
using either CASE1orCASE2, whichever is applicable.
binary trees. Huffman gave a bottom up approach for this problem. The
simple steps for solving this problem are given below:
1. Sort the given list of weights into increasing order, place them on a priority
queue called List.
2. Pick two minimum weights from the List as external nodes and form a subtree
3. Compute the weighted path list of the sub-tree obtained in Step 2. Insert
this weight in the List
4. Repeat steps 23 till only one weight is left on the List.
Consider the external weights of the tree given in Figure 7.50. The
external nodes with their weights are:
G H I J K L M
3 5 7 2 6 1 4
The sorted list is given below:
L J G M H K I
1 2 3 4 5 6 7
The trace of above given steps is provided in Figure 7.51:
Normally, the alphabets are stored using standard codes such as ASCII,
UNICODE, EBCDIC etc. These are fixed length codes. The ASCII coding
scheme uses 8 bits for a character whereas the UNICODE uses 16 bits.
These codes are useful only when the text, to be stored, contains all the
alphabets with equal number of their instances in the text, otherwise it
becomes a costly method in terms of storage. For example, the string
madam would use 40 bits of storage for using ASCII code and 80 bits
for UNICODE.
However, a closer look reveals that the string consists of 2 instances of
alphabet m, 2 instances of alphabet a, and 1 instance of alphabet d.
Let us now assign them the following binary codes:
Alphabet
Binary code
00
01
10
Using the above codes, we can compress the string madam into a much
shorter code of 10 bits as shown below:
1001000110
Thus, we have saved a significant amount of storage space i.e. 10 bits as
compared to 40 or 80 bits. But the coding scheme used by us is still a
fixed length coding scheme.
Huffman provided an excellent variable length coding scheme which
effectively compresses the data by assigning short codes to most
frequently occurring characters. The less frequent characters get longer
codes. Thus, the technique saves the storage space in terms of 2590 per
cent of the original size of the text. For a given piece of text, the following
steps are used for developing the codes for various alphabets contained
in the text:
1. Count the frequency of occurrence of each alphabet in the text.
2. Based on the frequency, assign weights to the alphabets.
3. Create a Huffman tree using the weights assigned to the alphabets. Thus,
the alphabets become the external nodes of the Huffman tree.
4. Starting from root, label the left edge as 0 and right as 1.
5. Traverse the sequence of edges from root to an external node i.e. an
alphabet. The assembly of labels of the edges from root to the alphabet
becomes the code for the alphabet.
The Huffman codes applied to Huffman tree of Figure 7.52 are given
in Figure 7.54
Weight
00
11
011
010
101
1001
1000
It may be noted that the heaviest nodes: I and K have got shorter codes
than the lighter nodes.
Example 14: Develop Huffman code for this piece of text Nandini
Solution: The trace of steps followed for assigning the Huffman codes
to the various characters of the given text are given below:
1. The frequency count of each character appearing in the above text is given
in the following table:
Character
Frequency
Weight
00
010
011
10
11
Example: Input Text: When you are on the leftyou are on the right
and when you are on the right you are on the wrong.
The dynamic dictionary obtained from above input text is given below:
The dynamic dictionary
Word
position
When
you
are
on
the
left
right
and
{
chartext[50][20];
charDictionary[30][20];
inti,j,flag,wordCount,DictCount;
i=1;
printf(\nEnterthetextterminatedbya###:\n)
;
do
{
i++;
scanf(%s,text[i]);
}
while(strcmp(text[i],###));
wordCount=i;
strcpy(Dictionary[0],text[0]);
DictCount=0;/*TheDictionarygetsthefirst
word*/
printf(\nTheTextis:%s,text[0]);/*printthe
firstword*/
for(i=1;i<=wordCount;i++)
{
flag=0;
for(j=0;j<=DictCount;j++)
{
if(!strcmp(text[i],Dictionary[j]))
{
printf(%d,j+1);
flag=1;
break;
}
}/*EndofjLoop*/
if(flag==0)
{
DictCount++;
strcpy(Dictionary[DictCount],text[i]);
printf(%s,text[i]);
}
}/*EndiLoop*/
}
The sample output of the program is given below:
EXERCISES
1. What is the need of tree data structure? Explain with the help of examples.
List out the areas in which this data structure can be applied extensively.
2. What is a tree? Discuss why definition of tree is recursive. Why it is said to
be non-linear?
3. Define the following terms:
i.
ii.
iii.
iv.
Root
Empty tree
Leaf nodes
Sub-tree
i.
ii.
iii.
iv.
v.
vi.
vii.
viii.
ix.
2.
3.
4.
5.
6.
Parent
Child
Sibling
Internal node
Edge
Path
Depth
Height
Degree
Explain binary tree with the help of examples. Discuss the properties of
binary tree that need to be considered.
Discuss the concept of full binary tree and complete binary tree.
Differentiate between the two types with the help of examples.
Discuss various methods of representation of a binary tree along with their
advantages and disadvantages.
Present an algorithm for creation of a binary tree. Consider a suitable binary
tree to be created and discuss how the algorithm can be applied to create the
required tree.
What do you mean by tree traversal? What kinds of operations are possible
on a node of a binary tree? Give an algorithm for inorder traversal of a
binary tree. Taking an example, discuss how the binary tree can be
traversed using inorder traversal.
7.
8.
9.
10.
i.
ii.
iii.
iv.
v.
vi.
2.
3.
4.
5.
6.
Draw expression tree for the above arithmetic expressions. Give the inorder,
preorder and postorder traversal of the expression trees obtained.
What is a binary search tree? What are the conditions applied on a binary
tree to obtain a binary search tree? Present an algorithm to construct a
binary search tree and discuss it using a suitable example.
Discuss various operations that can be applied on a BST. Present an
algorithm that searches a key in a BST. Take a suitable binary tree and
search for a particular key in the tree by applying the discussed algorithm.
Discuss how the insert operation can be performed in a binary tree with the
help of an algorithm. Consider a binary tree and insert a particular value in
the binary tree.
Discuss how the delete operation can be performed in a binary tree. Also,
present an algorithm for the same. Consider a binary tree and delete a
particular value in the binary tree.
Discuss the process of deletion of a node in a binary tree for the following
cases by taking suitable examples:
i.
ii.
iii.
2.
3.
4.
+*-ABCD
+A+BC
/-ABC
/*A+BCD
+*AB/CD
*A+B/CD
i.
ii.
iii.
iv.
v.
vi.
5.
6.
2.
i.
ii.
iii.
iv.
v.
vi.
vii.
viii.
2.
3.
What do you mean by sorting? Discuss heap sort in detail with the help of
an example.
Consider the maxheap shown in Figure 7.59. Perform the desired
operations on the heap to obtain a sorted list of elements. Display the sorted
list obtained.
Chapter 8
Graphs
CHAPTER OUTLINE
8.1 Introduction
8.2 Graph Terminology
8.3 Representation of Graphs
8.4 Operations of Graphs
8.5 Applications of Graphs
8.1 INTRODUCTION
Now the problem is how to represent the data given in Figure 8.1. Arrays
are linear by nature and every element in an array must have a unique
successor and predecessor except the first and last elements. The
arrangement shown in Figure 8.1 is not linear and, therefore, cannot be
represented by an array. Though a tree is a non-linear data structure but
it has a specific property that a node cannot have more than one parent.
The arrangement of Figure 8.1 is not following any hierarchy and,
therefore, there is no parentchild relationship.
A close look at the arrangement suggests that it is neither linear nor
hierarchical but a network of roads connecting the participating cities.
This type of structure is known as a graph. In fact, a graph is a collection
of nodes and edges. Nodes are connected by edges. Each node contains
data. For example, inFigure 8.1, the cities have been shown as nodes and
the roads as edges between them. Delhi, Bhopal, Pune, Kolkata, etc. are
nodes and the roads connecting them are edges. There are numerous
examples where the graph can be used as a data structure. Some of the
popular applications are as follows:
o Model of www: The model of world wide web (www) can be represented
by a collection of graphs (directed) wherein nodes denote the documents,
papers, articles, etc. and the edges represent the outgoing hyperlinks
between them.
o Railway system: The cities and towns of a country are connected through
railway lines. Similarly, road atlas can also be represented using graphs.
o Airlines: The cities are connected through airlines.
o Resource allocation graph: In order to detect and avoid deadlocks, the
operating system maintains a resource allocation graph for processes that
are active in the system.
o Electric circuits: The components are represented as nodes and the
wires/connections as edges.
Graph: A graph has two sets: V and E, i.e., G = <V, E>, where V is the
set of vertices or nodes and E is the set of edges or arcs. Each element of
set E is a pair of elements taken from set V. Consider Figure 8.1 and
identify the following:
V = {Delhi, Bhopal, Calcutta, Indore.}
E = {(Delhi, Kota), (Bhopal, Nagpur), (Pune, Mumbai)}
Before proceeding to discuss more on graphs, let us have a look at the
basic terminology of graphs discussed in the subsequent section.
8.2 GRAPH TERMINOLOGY
It may be noted that if no node occurs more E E than once in a path then
the path is called a simple path. For example in Figure 8.2(b), A-B-C is a
simple path. However, the path A-D-E-C-D is not a simple path.
Cycle: A path that starts and ends on the same node is called a cycle. For
example, the path D- E-C-D in Figure 8.2(b) is a cycle.
Connected graph: If there exists a path between every pair of distinct
nodes, then the undirected graph is called a connected graph. For
example, the graphs shown in Figure 8.3(b), (c) and (d) are connected
graphs. However, the graph shown in Figure 8.3(a) is not a connected
graph.
Self loop: If the starting and ending nodes of an edge are same, then
the edge is called a self loop, i.e., edge (E, E) is a self loop as shown
in Figure 8.4(a).
Parallel edges: If there are multiple edges between the same pair of
nodes in a graph, then the edges are called parallel edges. For example,
there are two parallel edges between nodes B and C of Figure 8.4(b).
Multigraphs: A graph containing self loop or parallel edges or both is
called a multigraph. The graphs shown in Figure 8.4 are multigraphs.
Simple graph: A simple graph is a graph which is free from self loops
and parallel edges.
A graph can be represented in many ways and the most popular methods
are given below:
o Array-based representation
o Linked representation
o Set representation
Algorithm addVertex()
{
lastRow=lastRow+1;
lastCol=lastCol+1;
adjMat[lastRow][0]=verTex;
adjMat[0][lastCol]=verTex;
Setallelementsoflastrow=0;
SetallelementsoflastCol=0;
}
An algorithm for insertion of an edge into an undirected graph is given
below:
/*Thisalgorithmusesatwodimensionalmatrix
adjMat[][]tostoretheadjacencymatrix.Anewedge
(v1,v2)isaddedtothematrixbyaddingitsentry
(i.e.,1)intherowandcolumncorrespondingtov1
andv2,respectively.*/
Algorithm addEdge()
{
Findrowcorrespondingtov1,i.e.,rowV1;
Findcolcorrespondingtov2,i.e.,colV2;
adjMat[rowV1][ColV2]=1;/*makesymmetric
entries*/
adjMat[colV2][rowV1]=1;
}
Example 1: Write a program that implements an adjacency matrix
wherein it adds a vertex and its associated edges. The final adjacency
matrix is displayed.
Solution: Two functions
called addVertex() and addEdge() would be used for insertion of
a new vertex and its associated edges, respectively. Another function
called dispAdMat() would display the adjacency matrix.
The required program is given below:
/*Thisprogramimplementsanadjacencymatrix*/
#include<stdio.h>
#include<conio.h>
voidaddVertex(charadjMat[7][7],intnumV,char
verTex);
voidaddEdge(charadjMat[7][7],charv1,charv2,
intnumV);
voiddispAdMat(charadjMat[7][7],intnumV);
voidmain()
{/*AdjacencymatrixofFigure8.17*/
charadjMat[7][7]={_,A,B,C,D,E,,
A,0,1,0,1,0,,
B,1,0,1,0,0,,
C,0,1,0,1,1,,
D,1,0,1,0,1,,
E,0,0,1,1,0,,
,,,,,,};
intnumVertex=5;
charnewVertex,v1,v2;
charchoice;
dispAdMat(adjMat,numVertex);
printf(\nEnterthevertextobeadded);
fflush(stdin);
newVertex=getchar();
numVertex++;
addVertex(adjMat,numVertex,newVertex);
do
{
fflush(stdin);
printf(\nEnterEdge:v1v2);
scanf(%c%c,&v1,&v2);
addEdge(adjMat,v1,v2,numVertex);
dispAdMat(adjMat,numVertex);
fflush(stdin);
printf(\ndoyouwanttoaddanotheredgeY/N);
choice=getchar();
}
while((choice!=N)&&(choice!=n));
}
voidaddVertex(charadjMat[7][7],intnumV,char
verTex)
{inti;
adjMat[numV][0]=verTex;
adjMat[0][numV]=verTex;
for(i=1;i<=numV;i++)
{
adjMat[numV][i]=0;
adjMat[i][numV]=0;
}
}
i=0;
for(j=1;j<=numV;j++)
{
if(adjMat[i][j]==v1)
{
for(k=0;k<=numV;k++)
{
if(adjMat[k][0]==v2)
{
adjMat[k][j]=1;
adjMat[j][k]=1;break;/*making
symmetricentries*/
}
}
}
}
}
voiddispAdMat(charadjMat[7][7],intnumV)
{
inti,j;
printf(\nTheadjMatis\n);
for(i=0;i<=numV;i++)
{
for(j=0;j<=numV;j++)
{
printf(%c,adjMat[i][j]);
}
printf(\n);
}
printf(\n\nEnteranykeytocontinue);
getch();
}
The above program has been tested for data given in Figure 8.16. The
screenshots of the result are given in Figure 8.17.
Fig. 8.17 The adjacency matrix before and after insertion of vertex F
Note: In case of a directed graph, only one entry of the directed edge in
the adjacency matrix needs to be made. An algorithm for insertion of a
vertex in a directed graph is given below:
/*Thisalgorithmusesatwodimensionalmatrix
adjMat[][]tostoretheadjacencymatrix.Anew
vertexcalledverTexisaddedtothematrixbyadding
anadditionalemptyrowandanadditionalempty
column.*/
Algorithm addVertex()
{
lastRow=lastRow+1;
lastCol=lastCol+1;
adjMat[lastRow][0]=verTex;
adjMat[0][lastCol]=verTex;
Setallelementsoflastrow=0;
SetallelementsoflastCol=0;
}
An algorithm for insertion of an edge into a directed graph is given
below:
/*Thisalgorithmusesatwodimensionalmatrix
adjMat[][]tostoretheadjacencymatrix.Anewedge
(v1,v2)isaddedtothematrixbyaddingitsentry
(i.e.,1)intherowcorrespondingtov1.*/
Algorithm addEdge()
{
Findrowcorrespondingtov1,i.e.,rowV1;
Findcolcorrespondingtov2,i.e.,colV2;
adjMat[rowV1][ColV2]=1;
}
Accordingly, the function addEdge() needs to modified so that it
makes the adjacency matrix assymmetric. The modified function is given
below:
/*Themodifiedfunctionforadditionofedgesin
directedgraphs*/
voidaddEdge(charadjMat[7][7],charv1,charv2,
intnumV)
{inti,j,k;
j=0;
for(i=1;i<=numV;i++)
{
if(adjMat[i][j]==v1)
{
for(k=1;k<=numV;k++)
{
if(adjMat[0][k]==v2)
{
adjMat[i][k]=1;break;
}
}
}
}
}
8.4.2 Deletion Operation
The deletion of a vertex and its associated edges from an adjacency
matrix, for directed and undirected graph, involves the following
operations:
1. Deleting the row corresponding to the vertex.
2. Deleting the col corresponding to the vertex. The algorithm is straight
forward and given below:
Algorithm delVertex(verTex)
{
findtherowcorrespondingtoverTexandsetallits
elements=0;
findthecolcorrespondingtoverTexandsetallits
elements=0;
}
The deletion of an edge from a graph requires different treatment for
undirected and directed graphs. Both the cases of deletion of edges are
given below:
An algorithm for deletion of an edge from an undirected graph is given
below:
/*Thisalgorithmusesatwodimensionalmatrix
adjMat[][]tostoretheadjacencymatrix.Anewedge
(v1,v2)isdeletedfromthematrixbydeletingits
entry(i.e.,0)fromtherowandcolumn
correspondingtov1andv2,respectively.*/
Algorithm delEdge()/*undirectedgraph*/
{
Findrowcorrespondingtov1,i.e.,rowV1;
Findcolcorrespondingtov2,i.e.,colV2;
adjMat[rowV1][ColV2]=0;/*makesymmetricentries
*/
adjMat[colV2][rowV1]=0;
}
An algorithm for deletion of an edge from a directed graph is given
below:
/*Thealgorithmusesatwodimensionalmatrix
adjMat[][]tostoretheadjacencymatrix.Anedge
(v1,v2)isdeletedfromthematrixbydeletingits
entry(i.e.,0)fromtherowcorrespondingtov1*/
Algorithm delEdge()/*directedgraph*/
{
Findrowcorrespondingtov1,i.e.,rowV1;
Findcolcorrespondingtov2,i.e.,colV2;
adjMat[rowV1][ColV2]=0;
}
adjMat[i][0]=_;
for(k=1;k<=numV;k++)
{
adjMat[i][k]=0;
}
}
}
i=0;
for(j=1;j<=numV;j++)
{
if(adjMat[i][j]==verTex)
{
adjMat[0][j]=_;
for(k=1;k<=numV;k++)
{
adjMat[k][j]=0;
}
}
}
}
voiddispAdMat(charadjMat[7][7],intnumV)
{
inti,j;
printf(\nTheadjMatis\n);
for(i=0;i<=numV;i++)
{
for(j=0;j<=numV;j++)
{
printf(%c,adjMat[i][j]);
}
printf(\n);
}
printf(\n\nEnteranykeytocontinue);
getch();
}
The screenshots of the output of the program are shown in Figure 8.18.
Fig. 8.18 The adjacency matrix before and after deletion of vertex B
Example 3: Write a program that deletes an edge from an adjacency
matrix of an undirected graph. The final adjacency matrix is displayed.
Solution: A function called delEdge() would be used for deletion of
an edge from an adjacency matrix. Another function called dispAdMat(
) would display the adjacency matrix.
The required program is given below:
/*Thisprogramdeletesanedgeofanundirected
graphfromanadjacencymatrix*/
#include<stdio.h>
#include<conio.h>
voiddelEdge(charadjMat[7][7],charv1,charv2,
intnumV);
voiddispAdMat(charadjMat[7][7],intnumV);
voidmain()
{
charadjMat[7][7]={,A,B,C,D,E,,
A,0,1,0,1,0,,
B,1,0,1,0,0,,
C,0,1,0,1,1,,
D,1,0,1,0,1,,
E,0,0,1,1,0,,
,,,,,,};
intnumVertex=5;
charv1,v2;
dispAdMat(adjMat,numVertex);
printf(\nEntertheedgetobedeleted);
fflush(stdin);
printf(\nEnterEdge:v1v2);
scanf(%c%c,&v1,&v2);
delEdge(adjMat,v1,v2,numVertex);
dispAdMat(adjMat,numVertex);
}
voiddelEdge(charadjMat[7][7],charv1,charv2,
intnumV)
{inti,j,k;
i=0;
for(j=1;j<=numV;j++)
{
if(adjMat[i][j]==v1)
{
for(k=0;k<=numV;k++)
{
if(adjMat[k][0]==v2)
{
adjMat[k][j]=0;
adjMat[j][k]=0;break;/*making
symmetricentries*/
}
}
}
}
}
voiddispAdMat(charadjMat[7][7],intnumV)
{
inti,j;
printf(\nTheadjMatis\n);
for(i=0;i<=numV;i++)
{
for(j=0;j<=numV;j++)
{
printf(%c,adjMat[i][j]);
}
printf(\n);
}
printf(\n\nEnteranykeytocontinue);
getch();
}
The screenshots of the output of the program are given in Figure 8.19.
Fig. 8.19 The adjacency matrix before and after deletion of edge (D, A)
The function delEdge() needs to modified for directed graphs so
that it does not make the adjacency matrix as symmetric. The modified
function is given below:
/*Themodifiedfunctionfordeletionofedgesof
directedgraphs*/
voiddelEdge(charadjMat[7][7],charv1,charv2,
intnumV)
{inti,j,k;
j=0;
for(i=1;i<=numV;i++)
{
if(adjMat[i][j]==v1)
{
for(k=1;k<=numV;k++)
{
if(adjMat[i][k]==v2)
{
adjMat[i][k]=0;break;
}
}
}
}
}
8.4.3 Traversal of a Graph
Travelling a graph means that one visits the vertices of a graph at least
once. The purpose of the travel depends upon the information stored in
the graph. For example on www, one may be interested to search a
particular document on the Internet while on a resource allocation graph
the operating system may search for deadlocked processes.
In fact, search has been used as an instrument to find solutions. The epic
Ramayana uses the search of goddess Sita to find and settle the
problem of Demons of that yuga including Ravana. Nowadays, the
search of graphs and trees is very widely used in artificial intelligence.
A graph can be traversed in many ways but there are two popular
methods which are very widely used for searching graphs; they are:
Depth First Search (DFS) and Breadth First Search (BFS). A detailed
discussion on both is given in the subsequent sections.
8.4.3.1 Depth First Search (DFS)
In this method, the travel starts from a vertex then carries on to its
successors, i.e., follows its outgoing edges. Within successors, it travels
their successor and so on. Thus, this travel is same as inorder travel of a
tree and, therefore, the search goes deeper into the search space till no
successor is found. Once the search space of a vertex is exhausted, the
travel for next vertex starts. This carries on till all the vertices have been
visited. The only problem is that the travel may end up in a cycle. For
example, in a graph, there is a possibility that the successor of a
successor may be the vertex itself and the situation may end up in an
endless travel, i.e., a cycle.
In order to avoid cycles, we may maintain two
queues: notVisited and Visited. The vertices that have already been
visited would be placed on Visited. When the successors of a vertex are
generated, they are placed on notVisited only when they are not
already present on both Visited and notVisitedqueues thereby
reducing the possibility of a cycle.
Consider the graph shown in Figure 8.20. The trace of DFS travel of the
given graph is given in Figures 8.20 and 8.21, through entries into the
two queues Visited and notVisited.
if(successorisnotpresentonVisitedAND
successorisnotpresentonnotVisited)Thenadd
vertexonfrontofnotVisited;
}
if(vertexisnotpresentonVisited)Thenaddvertex
onVisited;
}
}
The final visited queue contains the nodes visited during DFS travel, i.e.,
A B C F E G D.
D,1,0,1,0,1,1,0,
E,0,0,0,1,0,1,0,
F,0,1,1,1,1,0,0,
G,0,1,1,0,0,0,0};
intnumVertex=7;
charvisited[7],notVisited[7];
charvertex,v1,v2;
clrscr();
dispAdMat(adjMat,numVertex);
first=7;
last=1;
addNotVisited(notVisited,&first,adjMat[1][0]);
while(first<7)
{
vertex=removeNode(notVisited,&first);
if(!ifPresent(visited,last,vertex))
addVisited(visited,&last,vertex);
pos=findPos(adjMat,vertex);
for(i=7;i>=1;i)
{
if(adjMat[pos][i]==1)
{
if(!ifPresent(notVisited,7,adjMat[0][i])&&
(!ifPresent(visited,7,adjMat[0][i])))
addNotVisited(notVisited,&first,adjMat[0]
[i]);
}
}
}
printf(\nFinalvisitednodes...);
dispVisited(visited,last);
}
voiddispAdMat(charadjMat[8][8],intnumV)
{
inti,j;
printf(\nTheadjMatis\n);
for(i=0;i<=numV;i++)
{
for(j=0;j<=numV;j++)
{
printf(%c,adjMat[i][j]);
}
printf(\n);
}
printf(\n\nEnteranykeytocontinue);
getch();
}
/*Thisfunctioncheckswhetheravertexispresent
onaQornot*/
intifPresent(charQ[],intlast,charvertex)
{
inti,result=0;
for(i=0;i<=last;i++)
{
if(Q[i]==vertex)
{result=1;
break;}
}
returnresult;
}
voidaddVisited(charQ[],int*last,charvertex)
{
*last=*last+1;
Q[*last]=vertex;
}
voidaddNotVisited(charQ[],int*first,char
vertex)
{
*first=*first1;
Q[*first]=vertex;
}
voiddispVisited(charQ[],intlast)
{
inti;
printf(\nThevisitednodesare);
for(i=0;i<=last;i++)
printf(%c,Q[i]);
}
charremoveNode(charQ[],int*first)
{
charch;
ch=Q[*first];
Q[*first]=#;
*first=*first+1;
returnch;
}
intfindPos(charadjMat[8][8],charvertex)
{
inti;
for(i=0;i<=7;i++)
{
if(adjMat[i][0]==vertex)
returni;
}
return0;
}
The output of the program is shown in Figure 8.22. The final list of
visited nodes matches with the results obtained in Figures 8.20 and 8.21,
i.e., the visited nodes are A B C F G D E.
1. voidaddNotVisited(charQ[],int*first,char
vertex);:
ThisfunctionaddsavertexonnotVisitedqueueatRear
ofthequeue.
2. voidaddVisited(charQ[],int*last,charvertex);
3. charremoveNode(charQ[],int*first);
4. intfindPos(charadjMat[8][8],charvertex);
5. voiddispAdMat(charadjMat[8][8],intnumV);
6. intifPresent(charQ[],intlast,charvertex);
7. voiddispVisited(charQ[],intlast);
voidaddVisited(charQ[],int*last,charvertex);
charremoveNode(charQ[],int*first);
intfindPos(charadjMat[8][8],charvertex);
voiddispAdMat(charadjMat[8][8],intnumV);
intifPresent(charQ[],intlast,charvertex);
voiddispVisited(charQ[],intlast);
voidmain()
{inti,j,first,last,pos,Rear;
charadjMat[8][8]={,A,B,C,D,
E,F,G,
A,0,1,0,1,0,0,0,
B,1,0,1,0,0,1,1,
C,0,1,0,1,0,1,1,
D,1,0,1,0,1,1,0,
E,0,0,0,1,0,1,0,
F,0,1,1,1,1,0,0,
G,0,1,1,0,0,0,0};
intnumVertex=7;
charvisited[7],notVisited[7];
charvertex,v1,v2;
clrscr();
dispAdMat(adjMat,numVertex);
first=1;
Rear=1;
last=1;
addNotVisited(notVisited,&Rear,adjMat[1][0]);
while(first<=Rear)
{
vertex=removeNode(notVisited,&first);
if(!ifPresent(visited,last,vertex))
addVisited(visited,&last,vertex);
pos=findPos(adjMat,vertex);
for(i=1;i<=7;i++)
{
if(adjMat[pos][i]==1)
{
if(!ifPresent(notVisited,7,adjMat[0][i])&&
(!ifPresent(visited,7,adjMat[0][i])))
{
addNotVisited(notVisited,&Rear,adjMat[0]
[i]);
}
}
}
}
printf(,\nFinalvisitednodes,,);
dispVisited(visited,last);
}
voiddispAdMat(charadjMat[8][8],intnumV)
{
inti,j;
printf(,\nTheadjMatis\n,);
for(i=0;i,<=numV;i++)
{
for(j=0;j,<=numV;j++)
{
printf(,%c,,adjMat[i][j]);
}
printf(,\n,);
}
printf(,\n\nEnteranykeytocontinue,);
getch();
}
/*Thisfunctioncheckswhetheravertexispresent
onaQornot*/
intifPresent(charQ[],intlast,charvertex)
{
inti,result=0;
for(i=0;i,<=last;i++)
{
if(Q[i]==vertex)
{result=1;
break;}
}
returnresult;
}
voidaddVisited(charQ[],int*last,charvertex)
{
*last=*last+1;
Q[*last]=vertex;
}
voidaddNotVisited(charQ[],int*Rear,charvertex)
{
*Rear=*Rear+1;
Q[*Rear]=vertex;
}
voiddispVisited(charQ[],intlast)
{
inti;
printf(\nThevisitednodesare);
for(i=0;i<=last;i++)
{
printf(%c,Q[i]);
}
}
charremoveNode(charQ[],int*first)
{
charch;
ch=Q[*first];
Q[*first]=#;
*first=*first+1;
returnch;
}
intfindPos(charadjMat[8][8],charvertex)
{
inti;
for(i=0;i<=7;i++)
{
if(adjMat[i][0]==vertex)
returni;
}
return0;
}
The output of the program is shown in Figure 8.25. The final list of
visited nodes matches with the results obtained in Figures 8.23 and 8.24,
i.e., the visited nodes are A B D C F G E.
Weight
AF
10
CD
12
BG
14
BC
16
DG
18
DE
22
EG
24
EF
25
AB
26
Now add the edges starting from shortest edge in sequence from A-F to
A-B. The step by step creation of the spanning tree is given in Figure
8.30 (Steps 1-6)
Having obtained an MST, we are now in a position to appreciate the
algorithm proposed by Kruskal as given below:
Input:
Example 6: Find the minimum spanning tree for the graph given
in Figure 8.31 using Kruskals algorithm.
Weight
FG
BC
AG
EF
AF
CG
AB
CD
BG
EG
DG
DE
Now add the edges starting from shortest edge in sequence from F-G to
D-E. The step by step creation of the spanning tree is given in Figure
8.32 (Steps 1-6)
Prims algorithm
In this algorithm the spanning tree is grown in stages. A vertex is chosen
at random and included as the first vertex of the tree. Thereafter a vertex
from remaining vertices is so chosen that it has a smallest edge to a
vertex present on the tree. The selected vertex is added to the tree. This
process of addition of vertices is repeated till all vertices are included in
the tree. Thus at given moment, there are two set of vertices: Ta set of
vertices already included in the tree, and Eanother set of remaining
vertices. The algorithm for this procedure is given below:
Input:
Step1.TakerandomnodevaddittoT.Addadjacent
nodesofvtoE
2.while(numberofnodesinT<N)
{2.1ifEisemptythenreportnospanningtree
ispossible.
Stop.
2.3foranodexofTchoosenodeysuchthat
theedge(x,y)haslowestcost
2.4ifnodeyisinTthendiscardtheedge
(x,y)andrepeatstep2
2.5addthenodeyandtheedge(x,y)toT.
2.6delete(x,y)fromE.
2.7addtheadjacentnodesofytoE.
}
3.returnT
}
It may be noted that the implementation of this algorithm would require
the adjacency matrix representation of a graph whose MST is to be
generated.
Let us consider the graph of Figure 8.31. The adjacency matrix of the
graph is given in Figure 8.33
Let us now construct the MST from the adjacency matrix given in Figure
8.33. Let us pick A as the starting vertex. The nearest neighbour to A is
found by searching the smallest entry (other than 0) in its row. Since 3 is
the smallest entry, G becomes its nearest neighbour. Add G to the sub
graph as edge AG (see Figure 8.33). The nearest neighbour to sub graph
A-G is F as it has the smallest entry i.e. 1. Since by adding F, no cycle is
formed then it is added to the sub-graph. Now closest to A-G-F is the
vertex E. It is added to the subgraph as it does form a cycle. Similarly
edges G-C, C-B, and C-D are added to obtain the complete MST. The step
by step creation of MST is shown in Figure 8.33.
Fig. 8.33 The adjacency matrix and the different steps of obtaining MST
8.4.5 Shortest path problem
The communication and transport networks are represented using
graphs. Most of the time one is interested to traverse a network from a
source to destination by a shortest path. Foe example, how to travel from
Delhi to Puttaparthy in Andhra Pradesh? What are the possible routes
and which one is the shortest? Similarly, through which route to send a
data packet from a node across a computer network to a destination
node? Many algorithms have been proposed in this regard but the most
important algorithms are Warshalls algorithm, Floyds algorithm, and
Dijkstras algorithm.
8.4.5.1 Warshalls Algorithm
This algorithm computes a path matrix from an adjacency matrix A. The
path matrix has the following property:
Fig. 8.34
The above method is compute intensive as it requires a series of matrix
multiplication operations to obtain a path matrix.
Example 7: Compute the transitive closure of the graph given in Figure
8.34.
Solution: The step wise computation of the transitive closure of the
graph is given in Figure 8.35.
{
for(j=1;j<=n;j++)
{
Path[i][j]=A[i][j];
}
}
/*Findpathfromvitovjthroughvk*/
for(k=1;k<=n;k++)
{for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{Path[i][j]=Path[i][j]||(Path[i][k]&&
Path[k][j]);
}
}
}
returnPath;
}
Example 8: Apply Warshalls on the graph given in Figure 8.34 to
obtain its path matrix
Solution: The step by step of the trace of Warshalls algorithm is given
in Figure 8.36.
Fig. 8.36
8.4.5.2 Floyds Algorithm
The Floyds algorithm is almost same as Warshalls algorithm. The
Warshals algorithm establishes as to whether a path between vertices vi
and vj exists or not? The Floyds algorithm finds a shortest path between
every pair of vertices of a weighted graph. It uses the following data
structures:
1. A cost matrix, Cost [][] as defined below:
In fact, for given vertices vi and vj, it finds out a path from vi to vj
through vk. If vi-vk-vj path is shorter than the direct path from vi to vj
then vi-vk-vj is selected. It uses the following formula (similar to
Warshall):
Dist [i][j] = min ( Dist[i][j], ( Dist[i][k] + Dist [k][j])
The algorithm is given below:
AlgorithmFloyd()
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
Dist[i][j]=Cost[i][j];/*copycostmatrixto
distancematrix*/
for(k=1;k<=n;k++)
{for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
Dist[i][j]=min(Dist[i][j],(Dist[i][k]+
Dist[k][j];
}
}
}
Example 9: For the weighted graph given in Figure 8.37, find all pairs
shortest paths using Floyds algorithm.
Solution: The cost matrix and the step by step of the trace of Floyds
algorithm is given in Figure 8.38.
Fig. 8.37
8.4.5.3 Dijkstras Algorithm
This algorithm finds the shortest paths from a certain vertex to all
vertices in the network. It is also known as single source shortest path
problem. For a given weighted graph G = (V, E), one of the vertices v0 is
designated as a source. The source vertex v0 is placed on a set S with its
shortest path taken as zero. Where the set S contains those vertices
whose shortest paths are known. Now iteratively, a vertex (say vi) from
remaining vertices is added to the set S such that its path is shortest from
the source. In fact this shortest path from v0 to vi passes only through
the vertices present in set S. The process stops as soon as all the vertices
are added to the set S.
Fig. 8.38
The dijkstra algorithm uses following data structures:
1. A vector called Visited[]. It contains the vertices which have been
visited. The vertices are numbered from 1 to N. Where N is number of
vertices in the graph. Initially Visited is initialized as zeros. As soon as a
vertex i is visited (i.e. its shortest distance is found) Visited[i] is set to 1.
2. A two dimensional matrix Cost[][]. An entry Cost[i][j] contains the
cost of an edge (i, j). If there is no edge between vertices i and j then cost[i]
[j] contains a very large quantity (say 9999).
3. A vector dist[]. An entry dist[i] contains the shortest distance of ith vertex
from the starting vertex v1.
4. A vector S contains those vertices whose shortest paths are now known.
Fig. 8.41
Example 10: For the graph given in Figure 8.41 , find the shortest path
from vertex A to all the remaining verices.
Solution: Let us number the vertices A-G as 1-7. The corresponding
cost matrix and the stepwise construction of shortest cost of all vertices
with respect to vertex 1 (i.e. A) is given in Figure 8.42.
EXERCISES
1. Explain the following graph theory terms:
a.
b.
c.
d.
e.
f.
Node
Edge
Directed graph
Undirected graph
Connected graph
Disconnected graph
Fig. 8.47
4. Write a function that inserts an edge into an undirected graph represented
using an adjacency matrix.
5. Write a function that inserts an edge into a directed graph represented
using an adjacency matrix.
6. Write a function that deletes an edge from a directed graph represented
using an adjacency matrix.
7. Write a function that deletes an edge from an undirected graph represented
using an adjacency matrix.
8. Find the minimum spanning tree for the graph given in Figure 8.39.
9. Write the Warshalls algorithm.
10. Write the Dijkstras algorithm.
Chapter 9
Files
CHAPTER OUTLINE
9.1.1 Data
The word data is a plural of datum, which means fact. Thus, data is a
collection of facts and figures. It can be represented by symbols. For
example, in a business house, data can be the name of an employee, his
salary, number of hours worked, etc. Similarly in an educational
institute, the data can be the marks of a student, roll numbers,
percentage, etc.
9.1.2 Information
It is the data arranged in an ordered and useful form. Thus, the data can
be processed so that it becomes meaningful and understandable
(see Figure 9.1).
Let us consider the information recorded in Figure 9.3. Each entry into
the table shown in this figure is a set of three data items: Roll, Name, and
Marks. The first entry states that the roll number and total marks of a
student named Ajay are 001 and 50, respectively. Similarly, the second
entry relates to a student named RAM whose roll number is 002 and
total marks are 67.
Let us assume that a particular class has 30 students. Now, the entire
information of the class consisting of 30 records is referred to as a file. A
file, therefore, consists of a number of records and each record consists
of a number of items known as fields. The creation and maintenance of
files is one of the most important activities in an organization.
Depending upon its purpose, a file can be called by a specific name or
type. The various types of files are tabulated in Table 9.1.
Purpose
Master file
Stu
Per
Inv
Sto
Transaction
file
Fee
Pur
Sal
Reference
file
This file contains information which is required for reference and changes
periodically, i.e., price list, students attendance, share rates, etc.
Stu
file
Sha
Report file
Stu
Sch
file
Sort file
Me
Back up file
Stu
bac
Archival
files
Copies made for long-term storage of data maintained for future reference.
These files are generally stored off-line, i.e., away from the computer
centre.
Sec
file
3. Chronological order: In this type of order, the records are stored in the
order of their occurrence, i.e., arranged according to dates or events. If the
key field is a date, i.e., date of birth, date of joining, etc., then this type of
arrangement is used. For example, the records of a file shown inFigure
9.5 are arranged according to date of birth (DOB).
The manual filing system is suitable for small organizations where fast
processing and storage of data is not required. In the EDP filing system,
large amount of data can be managed efficiently. The data storage and
retrieval becomes very fast. It may be noted that in the manual filing
system, the files are generally arranged in a meaningful sequence. The
records are located manually by the person in charge of the files.
However when the files are stored on the computer, the files have to be
kept in such a way that the records can be located, processed, and
selected easily by the computer program. The handling of files depends
on both input and output requirements, and the amount of data to be
stored. How best can the files be arranged for ease of access? This
All the programs that we have written so far have extensively used input
and output statements. The scanf statement is used to input data from
keyboard and printf statement to output or display data on visual display
unit (VDU) screen. Thus, whenever some data is required in a program,
it tries to read from the keyboard. The keyboard is an extremely slow
device. For small amount of data, this method of input works well. But
what happens when huge data is needed by a program? For example, a
program which generates the merit list of joint entrance examination
(JEE) may require data in the tune of 10,000 to 20,000 records. It is not
possible to sit down and type such a large amount of data in one go.
Another interesting situation is: what happens when the data or results
produced by one program are required subsequently by another
program? On the next day, the results may even be required by the
program that produced them. Therefore, we need to have a mechanism
such as files by virtue of which the program can read data from or write
data on magnetic storage medium.
When the data of a file is stored in the form of readable and printable
characters, then the file is known as a text file. On the other hand, if a file
contains non-readable characters in binary code then the file is called a
binary file. For instance, the program files created by an editor are stored
as text files whereas the executable files generated by a compiler are
stored as binary files. An introduction to files supported by C is given in
the following sections.
9.5 FILES AND STREAMS
Description
scanf
printf
Some of the important stream I/O functions are listed in Table 9.2.
Table 9.2 Some important stream I/O functions in C
Function
Purpose
fclose
close a stream
feof
flush
flush a stream
fgctc
fgetchar
fgets
fopen
open a stream
fprintf
fputc
fputs
fread
fscanf
fseek
fwrite
fputs
getw
putw
Some of the important console I/O functions are listed in Table 9.3.
Table 9.3 Some important console I/O functions in C
Function
Purpose
cgets
clrscr
cprintf
getch
getche
gotoxy
putch
Purpose
chmode
close
eof
filelength
lseek
open
Open a file
read
setmode
write
Thus, a file (say Myfile) can be opened for reading by using the
following steps:
STEP 1: Declare a pointer (say ptr) of type FILE i.e., FILE*ptr;
STEP 2: Open the file using a stream I/O function called fopen().
ptr = fopen (Myfile, r);
The above statement requests the system to open a file called myfile
in read mode and assigns its pointer to ptr.
A file can be closed by a function called fclose() which takes one
argument of type FILE.
Thus, the file (Myfile) can be closed by the following statement:
fclose(ptr);
Table 9.5 File opening modes
Mod
e
Purpose
Open a file for reading. If file does not exist, NULL is returned.
Open a file for writing. It the file does not exist, a new file is created. If the file exists, the ne
overwrites the previous contents.
r+
Open the file for both reading and writing. If the file does not exist, NULL is returned.
w+
Open the file for both reading and writing. If the file exists, the previous contents are overw
one.
Open a file for appending. If the file exists, the new data is written at the end of the file else
created.
a+
Open the file for reading and appending. If the file does not exist, a new file is created.
arguments, i.e., a character variable, and the file pointer whereas the
function fgetc() takes only one argument, i.e., the file pointer.
For example, ch=fgetc(ptr) reads a character in ch from a file
pointed by the pointer called ptr.fputc(ch,ptr) writes a character
stored in ch to a file pointed by the pointer called ptr.
Example 1: Write a program that copies the file
called message.dat to another file called new.dat.
Solution: The above program can be written by using the following
steps.
Steps
1. Open message.dat for reading.
2. Open new.dat for writing.
3. Read a character from message.dat. If the character is eof, then go to
step 5 else step 4.
4. Write the character in new.dat. Go to step 3.
5. Close message.dat and new.dat.
while(!feof(ptr1))
{
ch=fgetc(ptr1);
fputc(ch,ptr2);
}
fclose(ptr1);
fclose(ptr2);
}
The efficacy of the above program can be verified by opening the
new.dat either in notepad or in the Turbo-C editor.
It may be noted that till now we have used programs which are rigid in
nature, i.e., the programs work on particular files. For instance, the
above program works on message.dat and new.dat files and will
not work with other files. This situation can be easily handled by taking
the name of the file from the user at run time. Consider the program
segment given below:
.
.
.
charfilename[20];
FILE*ptr;
printf(\nEnterthenameofthefiletoopenfor
reading);
scanf(%s,filename);
ptr=fopen(filename,r);
.
.
.
The above program asks the user for the name of the file to be opened
giving freedom to the user to opt for any of the available files or to
provide altogether a new name for a file.
Example 2: Write a program that asks from the user for the file to be
opened and then displays the contents of the file.
Solution: The required program is given below:
#include<stdio.h>
#include<conio.h>
main()
{
charfilename[20],ch;
FILE*ptr;
printf(\nEnterthenameofthefiletobeopened
forreading:);
scanf(%s,filename);
ptr=fopen(filename,r);
if(!ptr)
{
printf(\nThefile%scannotbeopened,
filename);
exit(1);
}
clrscr();
while((ch=fgetc(ptr))!=EOF)
printf(%c,ch);
fclose(ptr);
}
Sampl
e
Input:
Output
:
/*Sortthelist*/
sort(List,i);
/*writeintotheoutputfile*/
for(j=0;j<=i;j++)
putw(List[j],outfile);
fclose(infile);
fclose(outfile);
}
voidsort(intList[100],intN)
{
inti,j,small,pos,temp;
for(i=0;i<N1;i++)
{
small=List[i];
pos=i;
for(j=i+1;j<N;j++)
{
if(List[j]<small)
{
small=List[j];
pos=j;
}
}
temp=List[i];
List[i]=List[pos];
List[pos]=temp;
}
}
Note: The files created in the above program cannot be verified by
opening them in normal text editor like notepad. The reason is that the
functions getw() and putw() read/write the numbers in a file with a
format different from text format.
Therefore, a file consisting of numbers can be read by getw() function.
In fact, following program can be used to create the datafile.dat.
#include<stdio.h>
main()
{
FILE*ptr;
intval,i;
ptr=fopen(datafile.dat,w);
do
{
printf(\nEnteravalue);
scanf(%d,&val);
if(val!=9999)putw(val,ptr);
}
while(val!=9999);
fclose(ptr);
}
The numbers are written in the file as long as system does not encounter
9999, a number used to identify the end of the list.
Similarly, the following program can be used to see the contents of a file
(say sortedfile.dat)
#include<stdio.h>
main()
{
FILE*ptr;
intval,i;
ptr=fopen(sortedfile.dat,r);
printf(\n);
while(!feof(ptr))
{
val=getw(ptr);
if(!feof(ptr))printf(%d,val);/*Donotprint
EOF*/
}
fclose(ptr);
}
It may be further noted that both the programs take care that the EOF (a
number) does not get included into the list of numbers, being
manipulated by them.
9.6.3 Formatted File I/O Operations
C supports fscanf() and fprintf() functions to read or to write
from files. The general format of these functions is given below:
fscanf(<filepointer>,<formatstring>,<argument
list>);
fprintf(<filepointer>,<formatstring>,<argument
list>);
where <filepointer> is the pointer to the file in which I/O
operations are to be done.
<formatstring> is the list of format specifiers, i.e., %d, %f, %s, etc.
<argumentlist> is the list of arguments.
Example 4: Write a program that takes a list of numbers from the user
through standard input device, i.e., keyboard. The number 9999 marks
the end of the list. The list is then written on a user defined file by
using fprintf() function.
Solution: The required program is given below:
#includestdio.h
main()
{
FILE*ptr;
intval,i;
charoutfile[20];
printf(\nEnterthenameofthefile:);
scanf(%s,outfile);
ptr=fopen(outfile,w);
do
{
printf(\nEnteravalue);
scanf(%d,&val);
if(val!=9999)fprintf(ptr,%d,val);
}
while(val!=9999);
fclose(ptr);
}
Example 5: Use the file created in Example 4. Read the list of numbers
contained in the file usingfscanf() function. Print the largest number
in the list.
Solution: The numbers from the file would be read one by one and
compared for getting the largest of them. After EOF, the largest would be
displayed.
The required program is given below:
#includestdio.h
main()
{
FILE*ptr;
intval,i,large;
charinfile[20];
printf(\nEnterthenameofthefile:);
scanf(%s,infile);
ptr=fopen(infile,r);
printf(\nThelistis...);
large=9999;
while(1)
{
fscanf(ptr,%d,&val);
if(feof(ptr))break;
printf(%d,val);
if(large<val)large=val;
}
printf(\nThelargestis:%d,large);
fclose(ptr);
}
Sample output:
Enterthenameofthefile:Mydata.dat
Thelistis1234567832412113
Thelargestis:78.
9.6.4 Reading or Writing Blocks of Data in Files
A block of data such as an array or a structure can be written in a file by
using fwrite() function. Similarly, the block of data can be read from
the file using fread() function.
The general formats of fread() and fwrite() functions are given
below:
fread(<addressofobject>,<sizeofobject>,<number
ofitems>,<filepointer>);
fwrite(<addressofobject>,<sizeofobject>,<number
ofitems>,<filepointer>);
where <addressofobject> is the address or pointer to the object
from which the data is to be read/ written on the file and vice versa.
ptr=fopen(test.dat,r);
fread(&ob1,sizeof(structstud),1,ptr);
fclose(ptr);
printf(\n%s,ob1.name);
printf(\n%d,ob1.roll);
}
Example 6: Write a program that creates a file
called Marks.dat and writes the records of all the students studying
in a class.
Solution: The required program is given below:
#include<stdio.h>
structstudent{
charname[20];
introll;
intmarks;
};
main()
{FILE*ptr;
structstudentstudob;
intt=sizeof(structstudent);
ptr=fopen(marks.dat,w);
printf(\nEnterthestudentdataterminatedbyroll
=9999);
while(1)
{
printf(\nName:);scanf(%s,studob.name);
printf(\nRoll:);scanf(%d,&studob.roll);
if(studob.roll==9999)break;
printf(\nmarks:);scanf(%d,&studob.marks);
fwrite(&studob,t,1,ptr);
}
fclose(ptr);
}
Example 7: A file called Marks.dat contains the records of students
having the following structure:
structstudent
{
charname[20];
introll;
intmarks;
};
Write a program that reads marks and creates the following two files:
1. Pass.dat should contain the roll numbers of students scoring more than
or equal to passmarks stored in a variable called pass_marks.
2. FAIL.dat should contain the roll numbers of students scoring below the
pass_marks.
This file organization is the simplest way to store and retrieve records of
a file. In this file, the records are stored in a meaningful order according
to a key field. The first record in the order is placed at the beginning of
the file. The second record is stored right after the first, the third after
the second, and so on. However, the records may be ordered in
ascending or descending order by the key field which can be numeric
(such as student roll number) or alphabetic (such as student name). It
may be noted that the order never changes afterwards. The arrangement
of records in a sequential file is shown in Figure 9.8.
Since all records in the file are stored sequentially by position, the
records can be identified by the key field only.
A flow chart for this process is given in Figure 9.9. The above mentioned
steps are carried out with the help of a program written in a high level
language. All the high level languages, such as BASIC, FORTRAN,
PASCAL and C, support sequential files.
9.7.2 Reading and Searching a Sequential File
Suppose student data of an engineering college is maintained in a file
(say stud.dat)
Let us assume that on a particular day, the principal of the school
requires some information from the file called stud.dat. Now, the
obvious step is to read the file to get the required information. The read
operation for a sequential file is explained below.
To read a sequential file, the system always starts at the beginning of the
file and the records are read one by one till the required record is
reached or end of the file is encountered. The end of the file (EOF)
indicates that all the records in the file have been read. For instance, if
the desired record happens to be 50th one in a file, the system starts at
the first record and reads ahead one record at a time until the 50th is
reached.
The reading of a file involves the following operations:
1. Open the file.
2. Read a record.
3. Check if the record is the desired one. If yes, then display information and
perform step 6.
4. Check for the end of the file (EOF). If yes, then perform step 6.
5. Go to step 2.
6. Close the file.
/*Thisprogramappendsrecordsinasequentialfile
*/
#include<stdio.h>
structstudent{
charname[20];
introll;
charclass[5];
chargame[15];
};
main()
{
FILE*ptr;
charmyfile[15];
intt=sizeof(structstudent);
structstudentstudrec;
printf(\nEnterthenameofthefile:);
scanf(%s,myfile);
ptr=fopen(myfile,r+);
printf(\nEntertherecordstobeappended);
while(!feof(ptr))
fread(&studrec,t,1,ptr);
studrec.roll=0;
while(studrec.roll!=9999)
{
printf(\nName:);fflush(stdin);
gets(studrec.name);
printf(\nRoll:);scanf(%d,&studrec.roll);
printf(\nClass:);fflush(stdin);
gets(studrec.class);
printf(\nGame:);fflush(stdin);
gets(studrec.game);
if(studrec.roll!=9999)
fwrite(&studrec,t,1,ptr);
}
fclose(ptr);
}
It may be noted that in the above program, the file was opened in r+
mode. In fact, the same job can be done by opening the file in a, i.e.,
append mode. The program that uses this mode is given below:
/*Thisprogramappendsrecordsinasequentialfile
*/
#include<stdio.h>
structstudent{
charname[20];
introll;
charclass[5];
chargame[15];
};
main()
{
FILE*ptr;
charmyfile[15];
intt=sizeof(structstudent);
structstudentstudrec;
printf(\nEnterthenameofthefile:);
scanf(%s,myfile);
ptr=fopen(myfile,a);
printf(\nEntertherecordstobeappended);
studrec.roll=0;
while(studrec.roll!=9999)
{
printf(\nName:);fflush(stdin);
gets(studrec.name);
printf(\nRoll:);scanf(%d,&studrec.roll);
printf(\nClass:);fflush(stdin);
gets(studrec.class);
printf(\nGame:);fflush(stdin);
gets(studrec.game);
if(studrec.roll!=9999)
fwrite(&studrec,t,1,ptr);
}
fclose(ptr);
}
Example 12: Modify the program written in Example 11 so that it
displays the contents of a file before and after the records are appended
to the file.
Solution: A function called show() would be used to display the
contents of the file. The required program is given below:
/*Thisprogramdisplaysthecontentsbeforeand
afteritappendsrecordsinasequentialfile*/
#include<stdio.h>
voidshow(FILE*p,charx[15]);
structstudent{
charname[20];
introll;
charclass[5];
chargame[15];
};
main()
{
FILE*ptr;
charmyfile[15];
intt=sizeof(structstudent);
structstudentstudrec;
printf(\nEnterthenameofthefile:);
scanf(%s,myfile);
printf(\nTheRecordsbeforeappend);
show(ptr,myfile);
ptr=fopen(myfile,a);
printf(\nEntertherecordstobeappended);
studrec.roll=0;
while(studrec.roll!=9999)
{
printf(\nName:);fflush(stdin);
gets(studrec.name);
printf(\nRoll:);scanf(%d,&studrec.roll);
printf(\nClass:);fflush(stdin);
gets(studrec.class);
printf(\nGame:);fflush(stdin);
gets(studrec.game);
if(studrec.roll!=9999)
fwrite(&studrec,t,1,ptr);
}
fclose(ptr);
printf(\nTheRecordsafterappend);
show(ptr,myfile);
}
voidshow(FILE*p,charmyfile[15])
{
structstudentstudrec;
p=fopen(myfile,r);
fread(&studrec,sizeof(studrec),1,p);
while(!feof(p))
{
printf(\nName:);fflush(stdout);
puts(studrec.name);
printf(\nRoll:);printf(%d,studrec.roll);
printf(\nClass:);fflush(stdout);
puts(studrec.class);
printf(\nGame:);fflush(stdout);
puts(studrec.game);
fread(&studrec,sizeof(studrec),1,p);
}
fclose(p);
}
9.7.4 updating a Sequential File
The updation of a file means that the file is made up-to-date with the
latest changes relating to it. The original file is known as a master file or
old master file and the temporary file which contains the changes or
transactions is known as transaction file. Examples of transactions are
making purchases, payment for purchases, total sales in the day, etc.
In order to update a sequential file, the transactions are sorted according
to the same key used for the old master file, i.e., both the files are sorted
in the same order on the same key. Thus, the master file and the
transaction file become co-sequential. A new file called new master file
is opened for writing. The old master file and the transaction file are
merged according to the following logic:
The key of the record from the transaction file is compared with key of
the record from the old master file. If identical, the transaction record
is written on the new master file. Otherwise, the master record is
written on the new master file.
Createtransactionfile
Generatenewmasterfile
Showcontentsofafile
Quit
printf(\nQuit4);
printf(\nEnteryourchoice:);scanf(%d,
&choice);
switch(choice)
{
case0:clrscr();
printf(\nEnterthenameofthefile);
fflush(stdin);gets(O_master);creat_O(O_master);
break;
case1:clrscr();
printf(\nEnterthenameofthefile);
fflush(stdin);gets(Trans);creat_T(Trans);
break;
case2:clrscr();
printf(\nEnterthenameofOldmasterfile);
fflush(stdin);gets(O_master);
printf(\nEnterthenameofNewmasterfile);
fflush(stdin);gets(N_master);
printf(\nEnterthenameofTransactionfile);
fflush(stdin);gets(Trans);
creat_N(O_master,Trans,N_master);
break;
case3:printf(\nEnterthenameofthefiletobe
displayed);
fflush(stdin);gets(fname);
printf(\nEnter0/1forMaster/Transaction);
scanf(%d,&f_type);showfile(fname,f_type);
break;
}
}
while(choice!=4);
}
voidcreat_O(charO_master[15])
{
FILE*ptr;
structAcnt_RecAob;
intt=sizeof(Aob);
ptr=fopen(O_master,w);
printf(\nEntertherecordsonebyoneterminatedby
A/c=9999);
Aob.Acnt_No=0;
while(Aob.Acnt_No!=9999)
{
printf(\nName:);fflush(stdin);gets(Aob.name);
printf(\nA/CNo:);scanf(\n%d,&Aob.Acnt_No);
printf(\nBalanceAmount:);scanf(\n%f,
&Aob.Balance_Amt);
if(Aob.Acnt_No!=9999)fwrite(&Aob,t,1,ptr);
}
fclose(ptr);
}
voidcreat_T(charTrans[15])
{FILE*ptr;
structTrans_RecAob;
intt=sizeof(Aob);
ptr=fopen(Trans,w);
printf(\nEntertherecordsonebyoneterminated
byA/c=9999);
Aob.Acnt_No=0;
while(Aob.Acnt_No!=9999)
{
printf(\nA/CNo:);scanf(\n%d,&Aob.Acnt_No);
printf(\nTransactionType(0/1):);scanf(\n
%d,&Aob.Trans_type);
printf(\nTransactionAmount:);scanf(\n%f,
&Aob.Trans_Amt);
if(Aob.Acnt_No!=9999)fwrite(&Aob,t,1,ptr);
}
fclose(ptr);
}
voidcreat_N(charO_master[15],charTran[15],char
N_master[15])
{
FILE*Old,*New,*Trans;
structAcnt_RecOob;
structTrans_RecTob;
intt1,t2;
t1=sizeof(structAcnt_Rec);
t2=sizeof(structTrans_Rec);
/*openfiles*/
Old=fopen(O_master,r);
Trans=fopen(Tran,r);
New=fopen(N_master,w);
/*performtransactionprocessing*/
fread(&Oob,t1,1,Old);
fread(&Tob,t2,1,Trans);
while(!feof(Old)&&!feof(Trans))
{
if(Oob.Acnt_No<Tob.Acnt_No)
{fwrite(&Oob,t1,1,New);
fread(&Oob,t1,1,Old);
}
else
{
while((Oob.Acnt_No==Tob.Acnt_No)&&!
feof(Trans))
{
if(Tob.Trans_type==0)
Oob.Balance_Amt=Oob.Balance_Amt
Tob.Trans_Amt;
else
Oob.Balance_Amt=Oob.Balance_Amt+Tob.Trans_Amt;
fread(&Tob,t2,1,Trans);
}
fwrite(&Oob,t1,1,New);
fread(&Oob,t1,1,Old);
}
}
/*Copyrestofthefile*/
while(!feof(Old))
{
fwrite(&Oob,t1,1,New);
fread(&Oob,t1,1,Old);
}
/*Closefiles*/
fclose(Old);
fclose(New);
fclose(Trans);
}
voidshowfile(charfname[15],intf_type)
{FILE*ptr;
charch;
structAcnt_RecAob;
structTrans_RecTob;
intt1=sizeof(Aob);
intt2=sizeof(Tob);
if(f_type==0)
{
ptr=fopen(fname,r);
fread(&Aob,t1,1,ptr);
while(!feof(ptr))
{
printf(\nName:);fflush(stdout);
puts(Aob.name);
printf(\nA/CNo:);printf(\n%d,
Aob.Acnt_No);
printf(\nBalanceAmount:);printf(\n%f,
Aob.Balance_Amt);
fread(&Aob,t1,1,ptr);
ch=getch();
}
}
else
{ptr=fopen(fname,r);
fread(&Tob,t2,1,ptr);
while(!feof(ptr))
{
printf(\nA/CNo:);printf(\n%d,
Tob.Acnt_No);
printf(\nTransactionType:);printf(\n%d,
Tob.Trans_type);
printf(\nTransactionAmount:);printf(\n%f,
Tob.Trans_Amt);
fread(&Tob,t2,1,ptr);
ch=getch();
}
}
fclose(ptr);
}
Value
Meaning
<Offset
>
From Figure 9.13, it may be noted that file pointer (ptr) is positioned
at the beginning of the file because the start position is equal to 0 (zero).
The offset (i.e., 4) would move the file pointer to the fourth record.
printf(\nEntertheroomdataonebyone);
for(i=1;i<=no_of_rooms;i++)
{rob.room_no=i;
printf(\nRoomNo.%d:,i);
printf(\nRoom_Type(S/D):);
fflush(stdin);scanf(%c,&rob.room_type);
rob.room_type=toupper(rob.room_type);
printf(\nRoom_Status(A/N):);
fflush(stdin);scanf(%c,&rob.room_status);
rob.room_status=toupper(rob.room_status);
fwrite(&rob,t,1,ptr);
}
fclose(ptr);
ptr=fopen(filename,r);
/*searcharecord*/
do
{
printf(\nMenu);
printf(\n);
printf(\nDisplayinfo1);
printf(\nQuit2);
printf(\nEnteryourchoice);
scanf(%d,&choice);
if(choice==1)
{
printf(\nEntertheroomno.);
scanf(%d,&i);
fseek(ptr,(i1)*t,0);
fread(&rob,t,1,ptr);
clrscr();
printf(\nRoomNo.=%d,rob.room_no);
printf(\nRoomType=%c,rob.room_type);
printf(\nRoomStatus=%c,rob.room_status);
}
else
break;
}
while(1);
fclose(ptr);
}
The ISAM files are very flexible in the sense that they can be processed
either sequentially or randomly, depending on the requirements of the
user.
9.9.3 Storage Devices for Indexed Sequential Files
The magnetic disks are the most suitable storage media for Indexed
sequential files. The ISAM files are mapped on to the cylinders of the
disk. We know that the index file consists of three parts: index, main file
(data part), and overflow area. A cylinder on the disk is accordingly
divided into three parts: index area, prime area, and overflow area as
shown in Figure 9.16
Macrodefinition(replacementtext)
YMCA
YMCAinstituteofEngineering
CE
ComputerEngineering
#define
PC
Personalcomputer
MEND
<textofthedocument>
The program implements the macro processor, i.e., reads the macros and
their definitions and does the text substitution for each macro call.
Solution: The following data structures would be employed to do the
required task:
1. A structure to store a macro name and its definition.
structmacro
{
charName[10];
charMdef[50];
}
2. An array of structures of type macro to store all macro definitions.
structmacroMlist[15]
The program would require the following functions:
Function
Description
1.gettaken()
toreadawordfromtheinput
textdocument.
2.build
Mtable()
tobuildatableofmacronames
anddefinitions
usingthearrayofstructure,
i.e.,Mlist[].
3.writeout()
writetheoutputtotheoutput
textfile.
{
chartoken[20];
charbreak_point;
intflag,j;
charch;
structmacroMlist[15];
printf(\nEnterthenameofinputfile:);
fflush(stdin);gets(inputfile);
printf(\nEnterthenameofoutputfile:);
fflush(stdin);gets(outputfile);
infile=fopen(inputfile,r);
outfile=fopen(outputfile,w);
build_Mtable(Mlist,infile);
flag=0;
clrscr();
/*flag==3indicatesEOFencountered*/
while(!feof(infile)&&flag!=3)
{flag=0;
token[0]=\0;
ch=;
get_token(infile,token,&ch,&flag);
writeout(outfile,token,ch,Mlist);
}
fclose(infile);
fclose(outfile);
}/*endofmain*/
voidget_token(FILE*infile,chartoken[30],char
*break_pt,int*flag)
{inti;
charch;
i=1;
while(!(*flag))
{ch=fgetc(infile);if(ch==EOF){*flag=3;
return;}
switch(ch)
{/*breakpointsforwordsortokens*/
case\n:
case:
case,:/*flag==1indicatesthatabreak
pointoccurred*/
case.:
case;:*break_pt=ch;*flag=1;token[++i]=
\0;
break;
default:token[++i]=ch;
}
}
}/*endofget_token*/
voidbuild_Mtable(structmacrolist[15],FILE*ptr)
{chartoken[20],ch1,temp[2];
inti;
token[0]=\0;
i=1;
while(strcmp(token,MEND))
{intflag=0;
charch=;
get_token(ptr,token,&ch,&flag);
if(!strcmp(token,define))
{i++;
flag=0;
ch=;
get_token(ptr,token,&ch,&flag);
strcpy(list[i].Mname,token);
list[i].Mdef[0]=\0;
while(ch!=\n)
{flag=0;
ch=;
get_token(ptr,token,&ch,&flag);
strcat(list[i].Mdef,token);
if(ch!=\n)
{temp[0]=ch;
temp[1]=\0;
strcat(list[i].Mdef,temp);
}
}
}
}
i++;/*Markstheendofthemacrolist*/
strcpy(list[i].Mname,END);
}/*endofbuild_Mtable*/
voidwriteout(FILE*outfile,chartoken[20],charch,
structmacroMlist[15])
{
inti=0;
while(strcmp(Mlist[i].Mname,END))
{
/*replacemacrobyitsdefinition*/
if(!strcmp(Mlist[i].Mname,token))
{fputs(Mlist[i].Mdef,outfile);
fputc(ch,outfile);
return;
};
i++;
}
fputs(token,outfile);
fputc(ch,outfile);
}
Sample Input:
#defineceComputerEngineering
#defineymcaYMCAInstituteofEngineering
#defineaksA.K.Sharma
#definefbdFaridabad
#defineMDUMaharishiDayanandUniversity,Rohtak
MEND
ymca is an Institute situated in fbd. It is a Government of Haryana
institute. It is affiliated to MDU. The institute is rated as the best
engineering college in the state of Haryana. The ce department is the
largest department of the institute. aks works in ce department as a
Professor.
Sample Output:
YMCA Institute of Engineering is an Institute situated in Faridabad.
It is a Government of Haryana institute. It is affiliated to Maharishi
Dayanand University, Rohtak. The institute is rated as the best
engineering college in the state of Haryana. The Computer
Engineering department is the largest department of the institute.
A.K. Sharma works in Computer Engineering department as a
Professor.
Note: The program does not perform syntax checking, i.e., if the terms
define and MEND are misspelled, the program will go hey wire.
Problem 2: Statements in BASIC language are written in such a
manner that each statement starts with an integer number as shown
below:
10REMThisisasamplestatement
20inputA,B
30goto150
:
:
:
100If(xy)goto20
Write a program that reads a file of a BASIC program. For a given offset,
it adds the offset to all the statement numbers. The arguments of the
goto statements should also be modified to keep the program consistent.
In fact, this type of activity is done by a loader, i.e., when it loads a
program into the main memory for execution, it adds the offset of the
address of the main memory to all address modifiable parts of the
program such as to goto or jump statements and the like.
Solution: The get_token()andwriteout() functions of previous
program would be used in this program to read a token and to write the
tokens to a given file. The following functions ofstdlib.h would be
used to convert a string to integer and vice versa.
(1) <int> atoi
(string)
/*functiontoreadawordfrominputtextfile*/
voidget_token(FILE*infile,chartoken[20],char
*break_point,int*flag);
charinputfile[15],outputfile[15];
/*functiontowriteawordtooutputtextfile*/
voidwriteout(FILE*outfile,chartoken[20],char
ch);
main()
{
FILE*infile,*outfile;
chartoken[20];
charlast_token[20]=;
charbreak_point;
intflag,j,temp;
intoffset;
charch,ch1;
printf(\nEnterthenameofinputfile:);
fflush(stdin);gets(inputfile);
printf(\nEnterthenameofoutputfile:);
fflush(stdin);gets(outputfile);
printf(\nEnterthevalueofoffset(int):);
scanf(%d,&offset);
infile=fopen(inputfile,r);
outfile=fopen(outputfile,w);
flag=0;
clrscr();
ch1=\n;
/*flag==3indicatesEOFencountered*/
while(!feof(infile)&&flag!=3)
{flag=0;
token[0]=\0;
ch=;
get_token(infile,token,&ch,&flag);
/*Checkifitisanumberappearingatthe
beginningofalineorasargumentof
gotostatement*/
if((isdigit(token[0])&&ch1==\n)||(isdigit
(token[0])&&(!strcmp(last_token,goto))))
{temp=atoi(token);
temp=temp+offset;
itoa(temp,token,10);
}
writeout(outfile,token,ch);
ch1=ch;
strcpy(last_token,token);
}
fclose(infile);
fclose(outfile);
}/*endofmain*/
voidget_token(FILE*infile,chartoken[30],char
*break_pt,int*flag)
{inti;
charch;
i=1;
while(!(*flag))
{ch=fgetc(infile);if(ch==EOF){*flag=3;
return;}
switch(ch)
{/*breakpointsforwordsortokens*/
case\n:
case:
case,:/*flag==1indicatesthatabreakpoint
occurred*/
case.:
case;:*break_pt=ch;*flag=1;token[++i]=
\0;
break;
default:token[++i]=ch;
}
}
}/*endofget_token*/
voidwriteout(FILE*outfile,chartoken[20],charch)
{
fputs(token,outfile);
fputc(ch,outfile);
}
Sample input:
10Remthisisaloader
20InputA,B
30If(A20)goto20
40C5A15*20;
50if(C50)goto100
60C5C1B
70goto50
100stop
Valueofoffset550
Sample output:
60Remthisisaloader
70InputA,B
80If(A20)goto70
90C5A15*20;
100if(C50)goto150
110C5C1B
120goto50
150stop
EXERCISES
1.
2.
3.
4.
5.
20 characters
Code:
integer type
NET:
float type
4. Write a program which searches a given file for a record equal to an input
value of emp-code. The component record structure of the file is as given
below:
Count
A
B
C
.
.
.
Z
6. A student record is defined as given in Example 5. Write a program which
reads a file of such records (say class-file) and prints the result in the
following format:
S.No.
Condition
No.ofStudents
Marks<50%
xx
Marks>50%and<60%
xx
Marks>60%and<75%
xx
Marks>75%and<90%
xx
Marks>=90
xx
created text file. Display the newly created file. In case number of lines
exceeds 22, file should be displayed one screen at a time.
12. Explain relative files in detail.
13. Explain indexed sequential organization.
14. Write an explanatory note on multilevel indexed files.
15. Define file activity ratio, response time, and volatility.
16. How records are edited or deleted from an indexed file?
10 Chapter
Advanced Data Structures
CHAPTER OUTLINE
Fig. 10.2 The binary search trees with BF labelled at each node
It may be noted that in Figure 10.2 (a), all nodes have BF within the
range, i.e. 1, 0, or 1. Therefore, it is an AVLTree. However, the root of
graph in Figure 10.2 (b) has BF equal to 2, which is a violation of the
rule and hence the tree is not AVL.
Note: A complete binary search tree is always height balanced but a
height balanced tree may or may not be a complete binary tree.
The height of a binary tree can be computed by the following simple
steps:
1. If a child is NULL then its height is 0.
2. The height of a tree is = 1 + max (height of left subtree, height of right
subtree)
Based on the above steps, the following algorithm has been developed to
compute the height of a general binary tree:
Algorithm compHeight(Tree)
{
if(Tree==NULL)
{height=0;returnheight}
hl=compHeight(leftChild(Tree));
hr=compHeight(rightChild(Tree));
if(hl>=hr)height=hl+1;
else
height=hr+1;
returnheight;
}
Figure10.7 (c) shows the application of pointers on the nodes of the tree
as per the AVL rotation.Figure 10.7 (d) shows the final AVLTree (height
balanced) after the AVL rotation, i.e., after the rearrangement of nodes.
10.1.2.2 Insertion of a Node in the Right Subtree of the Right Child of the Pivot
In this case, when a node in an AVLTree is inserted in the right subtree
of the right child (say RR) of the pivot then the imbalance occurs as
shown in Figure 10.8 (a).
Tree (height balanced) after the AVL rotation, i.e., after the
rearrangement of nodes.
10.1.2.3 Insertion of a Node in the Right Subtree of the Left Child of the Pivot
In this case, when a node in an AVLTree is inserted in the right subtree
of the left child (say LR) of the pivot then the imbalance occurs as shown
in Figure 10.10 (a).
The final balance tree after the double rotation is shown in Figure 10.10
(b).
The algorithm for this double rotation is given below:
/*TheTreepointstothePivot*/
Algorithm rotateLR(Tree)
{
ptr=leftChild(Tree);/*pointptrtoQ*/
rotateRR(ptr);/*performthefirstrotation*/
ptr=leftChild(Tree);/*pointptrtoR*/
rotateLL(ptr);
}
The trace of the above algorithm is shown in Figure 10.11 wherein
the AVLTree has become imbalanced because of insertion of node 50
in the right subtree of left child of pivot [see Figures 10.11 (a) and (b)].
Rotation 1: In this rotation, the right child of R becomes the left child
of Q and R becomes the right child of P. Q becomes the right child of R.
The left child of R remains intact.
Rotation 2: In this rotation, the left child of R becomes the right child
of P. R becomes the root. P becomes the left child of R.
The final balance tree after the double rotation is shown in Figure 10.12
(b).
Following is the algorithm for this double rotation:
/*TheTreepointstothePivot*/
Algorithm rotateRL(Tree)
{
ptr=rightChild(Tree);/*pointptrtoQ*/
rotateLL(ptr);/*performthefirstrotation*/
ptr=rightChild(Tree);/*pointptrtoR*/
rotateRR(ptr);
}
3. Union of sets: The union of two sets S1 and S2 is obtained by collecting all
members of either set without duplicate entries. This is represented
by S1 S2.
Example: If S1 = {1, 3, 4}, S2 5 {4, 5, 6, 7, 8}, then S1 S2 = {1, 3, 4, 5, 6,
7, 8}.
4. Intersection of sets: The intersection of two sets S1 and S2 is obtained by
collecting common elements of S1 and S2. This is represented by S1 S2.
Example: If S1 = {1, 3, 4}, S2 = {4, 5, 6, 7, 8}, then S1 S2 = {4}.
5. Disjoint sets: If two sets S1 and S2 do not have any common elements,
then the sets are called disjoint sets, i.e., S1 S2 = .
Example: S1 = {1, 3}, S2 = {4, 5, 6, 7, 8} are disjoint sets.
6. Cardinality: Cardinality of a set is defined as the number of unique
elements present in the set. The cardinality of a set S is represented as |S|.
Example: If S1 = {1, 3, 4}, S2 = {4, 5, 6, 7, 8}, then |S1| = 3 and |S2| = 5.
7. Equality of sets: If S1 is subset of S2 and S2 is subset of S1, then S1 and S2
are equal sets. This means that all elements of S1 are present in S2 and all
elements of S2 are present in S1.The equality is represented as S1 S2
Example: If S1 = {1, 3, 4}, S2 = {3, 1, 4}, then S1 S2.
List representation
Hash table representation.
Bit vector representation
Tree representation
Union
Intersection
Difference
Equality
/*Thisfunctionisusedtocopyasettoanother
emptyset,i.e.,Set1toSet3*/
Algorithm copy(S1,S3)
{
ptr=S1;
back=S3;
ahead=Null;
while(ptr!=Null)
{ahead=newnode;
DATA(ahead)=DATA(ptr);
NEXT(ahead)=Null;
if(back==Null)/*firstentryintoS3i.e.S3is
alsoNull*/
{
back=ahead;
S3=ahead;
}
else
{
NEXT(back)=ahead;
back=ahead;
}
ptr=NEXT(ptr);
}
returnS3;
}
/*Thisfunctionisemployedtotestthemembership
ofanelementpointedbyptr,inagivensetS*/
Algorithm ifMember(ptr,S)
{
Flag=0;
nptr=S;
while(nptr!=Null)
{
if(DATA(ptr)==DATA(nptr))
{
Flag=1;
break;
}
nptr=NEXT(nptr);
}
returnFlag;
}
10.2.2.2 Intersection of Sets
As per the definition, given two sets S1 and S2, the intersection of two
sets is obtained by collecting all the common members of both the sets.
Lets assume that the sets have been represented using lists as shown
in Figure 10.18. The intersection set S3 of S1 and S2 has been obtained
by the following steps:
1. Initialize S3 to Null;
2. last = S3;
3. For each element of S2, check if it is member of S1 or not and if it is present
in S1 then attach it at the end of S3.
{/*ifthenodeofS2ismemberofS1*/
nptr=newnode;
DATA(nptr)=DATA(ptr)
NEXT(nptr)=NULL;
If(last==Null)/*S3isemptyanditisfirst
entry*/
{
last=nptr;
S3=last;
}
else
{
NEXT(last)=nptr;
last=nptr;
}
}
ptr=Next(ptr);
}
returnS3;
}
Note: The ifMember() function is already defined in the previous
section.
10.2.2.3 Difference of Sets
As per the definition, given two sets S1 and S2, the difference S1 S2 is
obtained by collecting all the elements which are in S1 but not in S2. Let
us assume that the sets have been represented using lists as shown
in Figure 10.19. The difference set S3, i.e., S1 - S2 has been obtained by
the following steps:
1. Initialize S3 to Null;
2. last =Null;
3. For each element of S1, check if it is a member of S2 or not and if it is not
present in S2 then attach it at the end of S3.
ptr=Next(ptr);
}
returnS3;
}
Note: The ifMember() function is already defined in the previous
section.
10.2.2.4 Equality of Sets
As per the definition, given two sets S1 and S2, if S1 is subset of S2 and
S2 is subset of S1 then S1 and S2 are equal sets. Let us assume that the
sets have been represented using lists. The equality S1 = S2 has been
obtained by the following steps:
1. For each element of S1, check if it is a member of S2 or not and if it is not
present in S2 then report failure.
2. For each element of S2, check if it is a member of S1 or not and if it is not
present in S1 then report failure.
3. If no failure is reported in step1 and step 2, then infer that S1 = S2.
The algorithm for equality of two sets S1 and S2 is given in the following:
/*ThisalgorithmusesthefunctionifMember()which
teststhemembershipofanelementinagivenset*/
Algorithm equalSets(S1,S2)
{
ptr=S1;
flag=1;
while(ptr!=Null)
{
Res=ifMember(ptr,S2);/*Checkmembershipof
ptrinS2*/
if(Res==0)
{flag=0;
break;
}
ptr=NEXT(ptr);
}
if(flag==0){promptnotequal;exit();}
ptr=S2;
while(ptr!=Null)
{
Res=ifMember(ptr,S1);/*Checkmembershipof
ptrinS1*/
if(Res==0)
{flag=0;
break;
}
ptr=NEXT(ptr);
}
if(flag==0){promptnotequal;exit();}
else
promptSetsEqual;
}
Note: The ifMember() function is already defined in the previous
section.
10.2.3 Applications of Sets
There are many applications of sets and a few of them are given below:
(1) Web linked pages: For each item displayed to user, Google offers a
link called Similar pages (seeFigure 10.20), which is in fact the list of
URLs related to that item. This list can be maintained as a set of related
pages by using Union operation.
We get the following three related partitions, i.e., the disjoint sets:
1. P4, P7, P11, P9
2. P6 P12 P4
3. P5
10.4 B-TREES
shown in Figure 10.29. The mway tree shown in the figure is of the
order 3, i.e.,m = 3.
A close look at the mway tree suggests that it is a multilevel index
structure. In order to have efficient search within an m-way tree, it
should be height balanced. In fact,a height balanced mwaytreeis
calledasBtree. A precise definition of a Btree is given below
1. The root has at least two children. If the tree contains only a root, i.e., it is a
leaf node then it has no children.
2. An interior node has between |m/2| and m children.
3. Each leaf node must contain at least |m/2| 1 children.
4. All paths from the root to leaves have the same length, i.e., all leaves are at
the same level making it height balanced.
be the address of the record on the disk. Let us assume that 4th and 5th
digit from the right of K2 will be selected as the hash address as shown
below :
It may be noted that the records with K = 5314, 6218, and 9351, would be
stored at addresses 38, 63, and 41 respectively.
2. Division method: In this method the key K is divided by a prime
number where a prime number is a number that is evenly divisible
(without a remainder) only by one or by the number itself. After the
division, the quotient is discarded and the remainder is taken as the
address. Let us consider the examples given below:
(i) Let key K = 189235
Hash address
= K mod p
= 189235 mod 41
= 20 (remainder)
Hash address
= K mod p
= 5314 mod 41
= 25 (remainder)
3. Folding method: In this method, the key is split into pieces and a
suitable arithmetic operation is done on the pieces. The operations can
be add, subtract, divide etc. Let us consider the following examples:
1. Let key K = 189235
Let us split it into two parts 189 and 235
By adding the two parts we get,
189
235
Hash address
424
123
529
164
Hash address
816
It may be observed from the examples given above, that there are
chances that the records with different key values may hash to the same
address. For example, in folding technique, the keys 123529164 and
529164123 will generate the same address, i.e., 816. Such mapping of
keys to the same address is known as a collision and the keys are called
as synonyms.
Now, to manage the collisions, the overflowed keys must be stored in
some other storage space called overflow area. The procedure is
described below:
1. When a record is to be stored, a suitable hashing function is applied on the
key of the record and an address is generated. The storage area is accessed,
and, if it is unused, the record is stored there. If there is already a record
stored, the new record is written in the overflow area.
2. When the record is to be retrieved, the same process is repeated. The record
is checked at the generated address. If it is not the desired one, the system
looks for the record in the overflow area and retrieves the record from there.
Thus, synonyms cause loss of time in searching records, as they are not
at the expected address. It is therefore essential to devise hash
algorithms which generate minimum number of collisions. The
important features required in a hash algorithm are given in the
following section.
10.5.2 requirements for hashing Algorithms
The important features required in a Hash algorithm or functions are :
1. Repeatable: A capability to generate a unique address where a record can
be stored and retrieved afterwards.
a.
b.
c.
d.
e.
2.
3.
4.
5.
Note: Always start with the given tree, the questions are not accumulative.
Also, show both the data value and balance factor for each node.
Define set data structure. How are they different from arrays? Give
examples to show the applications of the set data structures.
Write an algorithm that computes the height of a tree.
Write an algorithm that searches a key K in an AVLTree.
What are the various cases of insertion of a key K in an AVLTree?
6. Define the terms: null set, subset, disjoint set, cardinality of a set, and
partition.
7. What are the various methods of representing sets?
8. Write an algorithm that takes two sets S1 and S2 and produces a third set S3
such that S3 = S1 S2.
9. Write an algorithm that takes two sets S1 and S2 and produces a third set S3
such that S3 = S1 S2.
10. Write an algorithm that takes two sets S1 and S2 and produces a third set S3
such that S3 = S1 S2.
11. How are web pages managed by a search engine?
12. Give the steps used for colouring a black and white photograph.
13. Write a short note on spell checker as an application of sets.
14. You are given a set of persons P and their friendship relation R, that is, (a,
b) R if a is a friend ofb. You must find a way to introduce person x to
person y through a chain of friends. Model this problem with a graph and
describe a strategy to solve the problem.
15. The subset-sum problem is defined as follows:
Input: a set of numbers A = {a1, a2, , aN} and a number x;
Output: 1 if there is a subset of numbers in A that add up to x. Write down
the algorithm to solve the subset-sum problem.
16. What is a skip list? What is the necessity of adding extra links?
17. Write an algorithm that searches a key K in a skip list.
18. What is an mwaytree.
19. Define a Btree. What are its properties?
20.Write an algorithm for searching a key K, in a Btree.
21. Write an algorithm for inserting a key K, in a Btree.
22. Write an explanatory note on deletion operation in a Btree.
23. What are the advantages of a Btree.
ppendix A
ASCII Codes (Character Sets)
A.1 ASCII (American Standard Code for Information
Interchange) Character Set
Appendix B
Table of Format Specifiers
Format
Specified Meaning
h*
[]*
Appendix C
Escape Sequences
Character
Escape Sequence
ASCII Value
bell (alert)
\a
backspace
\b
horizontal table
\t
\n
vertical tab
\v
11
form feed
\f
12
carriage return
\r
13
quotation mark ()
34
apostrophe ()
39
\?
63
backslash (\)
\\
92
null
octal number
\000
Appendix D
Trace of Huffman Algorithm