IIT Madras DSA Course Overview
IIT Madras DSA Course Overview
S. No Topic Page No
Week 1
1 Introduction to Computers and Programming 1
2 Writing your first program 15
3 Variables, Operators and Expressions 23
4 Variable declarations, more operators and precedence 34
5 Input and Output Statements 47
6 Conditionals 58
7 Loops 75
8 Video Solution to Digital Root Programming Assignment 96
Week 2
9 Introduction to arrays 114
10 Working with 1D arrays 126
11 Find prime numbers 134
12 Debugging demo 145
13 Multi-dimensional arrays 168
14 Pointers 179
15 More on pointers 191
16 Arrays and pointer arithmetic 201
17 Introduction to Strings 224
18 More on Strings 236
Video Solution to Print Elements of a Matrix in Spiral Order Programming
19 Assignment 246
Week 3
20 Introduction to functions 261
21 More details on functions 275
22 Arguments, variables and parameters 285
23 Pass parameters by reference 297
24 Recursive functions 310
25 Running time of a program 327
26 Computing time complexity 341
27 Video Solution to Palindrome Checker Programming Assignment 354
Week 4
28 Algorithms and Powering 375
29 Polynomial evaluation and multiplication 385
30 Linear and Binary Search Analysis 398
31 Analysis of minimum and maximum in an array 419
32 Sorting I: Insertion, Merge 425
33 Sorting II: Counting, Radix 439
34 Finding i-th smallest number 452
35 Video Solution to Sorting words Programming Assignment 463
Week 5
36 Structures 470
37 More on structures 480
38 Using structures and pointers to structures 490
39 Dynamic memory allocation 498
40 Linked Lists 517
41 Brief introduction to C++: Classes and objects 531
42 Data Structures: Abstract Data Type 551
43 Lists 558
44 Supplementary Lesson 571
Video Solution to Implementing a Hash Table ADT Programming
45 Assignment 596
Week 6
46 Stacks: Last In First Out 615
47 Queues: First In First Out 634
48 Trees 648
49 Tree traversal 660
50 Binary Search Trees 667
51 Heaps 685
52 Graphs and Representation 699
53 Supplementary Lesson 710
54 Video Solution to the Queue in a Hospital Programming Assignment 731
Week 7
55 Greedy Algorithms 742
56 Dynamic Programming 754
57 Matrix Chain Multiplication 775
Week 8
58 Dijkstra's Algorithm 789
Week 9
59 Boyer-Moore String Matching Algorithm 806
60 File I/O 830
61 Modular Programming 862
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 01
Lecture – 01
Introduction to Computers
So, before getting into programming we need to understand little bit about what
computers are about. What you are seeing in this picture here is ENIAC the, so called
first digital computer, which was built in the 1940s. And you can see how huge it is, it is
almost like a house in size. It was massive compared to the modern personal computer
standards. By no means ENIAC can be called the personal computer. It had 17000
vacuum tubes, 5 million hand solder joints, it weighed quite a bit and it consumed 150
kilo watts of power; no way this would be a personal computer.
1
(Refer Slide Time: 01:31)
However, June 2, 2000 what you are seeing on this picture is a micro photograph of
Pentium 4 and it was designed in the year 2000 and deployed in the market, it could run
at 1.5 gigahertz. That means, it can do 1.5 billion operations per second, it had 42 million
transistors as opposed to this puny ENIAC. And it was build in this technology called
0.18 micron technology, which means the transistors and the gates that were used to
design as small as 0.18 micron. So, in the 40 or 50 years computers have moved quite far
away from the first notion of digital computer.
2
In this picture you can see Google's data center. It is one of the data centers that they
have across the world. And what you are seeing is racks and racks of machines,
crunching data, running algorithms and running programs, various kinds of software that
we use on a daily basis. And this is just one of the data centers and this requires
enormous organization of the equipment, power systems, cooling systems and so forth.
But, all of this is basically a computing machine, all we see ENIAC, Pentium 4 all these
data centers are all build of what are called computing machines and the basic
abstraction is what we see here. We have a processor and we have memory and this is
what makes any computer, you have processor and memory. You can think of the
memory as a series of locations to store information.
Let us say for example, we have 256 megabytes of RAM and you too would be laid out
in some order and you can address them as location 0, location 1 so, on up to 256
megabytes, just like how you houses would be numbered in a, if they were all lined up in
a line and processor is the heart or the brain of the computing system.
3
(Refer Slide Time: 03:43)
So, typically memory is divided into two portions, there is some portion dedicated for
programs and some portion dedicated for data. A program is essentially a sequence of
instructions assembled to do some task. So, it could be again it could be a piece of
software that you write for this course, it could be a search engine, it could be browser, it
could be anything. And most of these instructions actually operate on data and the data is
something that you store in the memory as well.
There are instructions which could also control the flow of operations, it is not that all
the programs have what is called a straight line sequence, they do task a, task b, task c
and so on up to end. Based on the conditions that come through some branch operations
could happen and because of which control could change. We wiill see these in more
details later anyway.
4
(Refer Slide Time: 04:43)
But, the basic set up behind a processor is given in this picture here. We have an input
system - the input system could be a key board, a mouse or any other device; you have
an output system - this could be a monitor, some gears that are shifting, it depends on the
computer that you are building. And most systems have a reasonable amount of memory,
nowadays you probably have 2 gigabytes or 4 gigabytes of RAM on your desktops and
laptops.
And then there is this central processing unit. The central processing unit consists of two
things: one is call the control unit, the other one is call the ALU or the Arithmetic and
Logic Unit. Arithmetic and Logic unit is, it consists of various circuitry; it can do things
like, additions, subtractions, comparisons and so on. And control unit in some sense is
the over all master. So, it controls what happens in each of these units and how data gets
processed in each of these units and when data moves in, when data moves out and so
on.
5
(Refer Slide Time: 05:49)
So, let us look at the basic operations of a CPU. CPU can fetch an instruction from
memory. It can execute the instruction based on whatever instruction is given to it. It can
actually execute it. This could be addition, subtraction, multiplication, comparison, it
could be anything. It can also store the result back in memory. So, when you write a
program it is going to be translated into sequence of instructions.
And a basic machine instruction would have this following setup. You have an operation,
you have all the operands or the data on which the operation is going to be done. And
where is the result going to be stored, also call the destination. A simple operation of
could be this kind add a comma b, it adds the contents of memory locations a and b and
it could be storing the result back in a itself.
6
(Refer Slide Time: 06:41)
So, sometimes you may somewhere down the line, you may learn a language called the
assembly language. And we are seeing some small example here: an x86 Intel 32
processor can execute the following binary instruction. So, you have 1011 four 0s 0 1 1
four 0s and a 1, this is the binary code for moving 61 in hexadecimal to an internal
memory called a register of the name al. So, al is a register which is an internal memory
inside the CPU.
So, the meaning of this instruction is move number 61 in hexadecimal to al. And this is
something that you and I may not know, you and I may not even understand if you are
just given the binary bits. However, for the CPU everything is translated into these bits,
operating in these bite at this level is very, very hard for us humans. Therefore, we use,
so called high level languages. But, sometimes you also have this intermediate language
called the assembly language, which is human understandable, but at the same time it is
more detailed. For example, we have MVI AL comma value. So, this could mean move
the value to al and such instructions are called mnemonics. So, mnemonics are
essentially easy to remember and this binary sequence, we could write them as MVI AL
comma Val, this could be given to an assembler which would translate that in to this
binary code. But, even operating at this assembly level is quite hard, this does not
capture the kind of problems that we want to solve directly, they get too detail.
7
(Refer Slide Time: 08:30)
Instants we use, so call high level languages and a single high level statement could have
more than one assembly instruction in it. So, let us take a small example, let say I want to
add Z, Y and the result has to be stored in X. So, I would right this as X equals Y plus Z
and it could recover the following sequence of operations. You may have to fetch Y from
the memory, stored it in an internal memory location or a register called R1, you fetch Z
also from the memory, you store it in another register called R2. And the ALU would
then add R1 and R2 store the result back in R1 itself and it may require a move from R1
to the memory location name x.
So, a single operation X equals Y plus Z has resulted in four smaller operations and these
operations could be written in assembly language if you would like to. However, this
becomes too tedious. So, we would like to operate at a level which is much higher, then
what the processor can understand and something that easier for the human beings to
program with. And that is how the evolution of high level languages started. And in this
course, we are going to learn one such high level language namely c.
8
(Refer Slide Time: 09:55)
Once you have a program written in high level, you need a set of tools to convert them
into the machine level. And typically the programs that we write are called source code.
They are called source because, that capture the intention of the programmer and a set of
tools will convert the source code into machine code, you may have more than one such
source program available. And you give this to a tool called a compiler, compiler is
essentially a piece of software, which can convert this high level code into assembly
code.
And in turn an assembler can take this assembly code and generate machine code, at run
time you have a linker and loader which will actually execute the program. Even if we
don’t understand the details now, slowly and steadily we will build up an understanding
towards all these ideas, we will also see software demonstrated along the way. So, as of
now just remember that, you have a high level program which gets converted by a
compiler into a assembly code. And assembly code downwards is taken care by
assembler, linker and loader what we bother about is at the high level namely the high
level language.
9
(Refer Slide Time: 11:19)
So, when we write programs, we are actually looking at solutions, we are trying to solve
things. From the CPU perspective a program is nothing but, a sequence of instructions,
you have instruction 1, 2, 3, 4, instruction 5 could be a condition based on whatever the
result of instruction 5 is, you may execute instruction 6 for a instruction 10 and so on.
However, this is not the way we think about solving a problem. For us a program is a
solution to a problem and sometimes it is an frozen solution. By frozen what we mean is
we have written the program, it is already compiled and it is ready to execute. And at this
time, the solution is frozen, you cannot change the solution, unless you go and change
the program and compile it once more.
So, from the perspective of a human being a program is the representation of a solution
devised by a human being. And the nice thing about program is that, it can be compiled
and store and it is ready for execution from there on. You can also distribute the
programs to others for them to read understand or even comment and change, you could
distribute the machine version or machine code for others to execute, but, not be able to
see the program. You can do lot of things and you write the program once, you can run
the program as many times as you want.
10
(Refer Slide Time: 12:43)
Let us get into what programming is about, a program is essentially a piece of software
that you write and programming; however, is problem solving. So, this is what we really
want to do. So, in this course we will learn how to solve problems, but in the process we
will also learn this language called c, with which we can take problems, break them
down and write them using a program use tools to compile and run them, we will learn
this whole cycle.
So, any software development process starts with understanding the problem, you should
first of all understand what the problem is, typically the problem is stated in English. So,
that is called requirements analysis, from there you get a precise specification of the
problem, usually mathematically specified and you device the solution. So, given a
problem you go and device the solution or design the solution. And once you have the
design for the problem, then you go and write it in the program, you could write it in any
programming language, this is also called coding. And finally, once the coding is done
we have to tested before we deploy it. So, we start with requirements analysis and get the
specifications out, design a solution for the problem go and modulate and program it
using a language compile it, run it and test it before it is ready for use.
However, the most crucial part of this whole process is actually the solution design. So,
you have to solve the problem and analyze the steps and ensure that this problem is
captured properly and you have a solution, that is indeed correct, any ambiguity in the
11
specification can result in program, that does not behave as expected. So, you have to be
careful about understanding this specification. You should also understand the nuances
of the programming language itself. So, that the intent is captured carefully and you use a
programming language for that, finally you test it.
So, as I mentioned earlier we are going to use this language called C, C is a very old
language it is been therefore, almost four decades now very widely used in industry even
now. So, there are other languages like C plus plus and Java and so on. However,
learning C well and understanding C thoroughly is basic requirement in the industry even
now. C is a general purpose language, it is not for any specialized purpose, unlike a
language like a HTML and so on.
It is extremely effective and expressive, you will see this in a short while, it has a fairly
compact syntax. The language is not too big, it has a rich set of operators, pretty much
every arithmetic logic operation that you think about is already available as an operator
and C also has an extensive collection of libraries. So, you do not have a really write
every single thing down, you can call libraries, library functions whenever you need
them and C comes with a rich set of libraries and that is one of the basic reasons why we
pick c.
12
(Refer Slide Time: 15:57)
Let us look at a tiny C program. So, you may not understand it write away, but it is really
tiny, it has only 6 line of code. The first line is actually comment, it is says a first
program in C and the next line says hash include stdio dot h, there is this so called
function by name main and there is exactly one statement inside this main. So, watch the
mouse pointer to have a function called main and within the braces we have a single
statement call printf and printf seems to be taking this hello world.
So, this is been a custom for while now, almost every programming language book that
you go to will teach you how to printf hello world. So, let us see are let us break down
what this program is about, as I mentioned the first line is just a comment, is for us to
understand it does not really result in anything that is executable. So, the program has it,
but the machine code will not have these comments, it is not an instruction for the CPU
to do anything, it just for us to understand.
And let us look the next line, it is says hash include, it means include something which is
already built. And I mention earlier that C has several library calls that you can make and
stdio dot h is one such library with which you can do standard input and output. So, if I
want type something on a keyboard and if I want see something on a screen, that is what
you get from stdio dot h.
Every C program will have something called a main function. And the very first
instruction, that is executed will be from main and within this body of these curly braces,
13
you have a single statement here call printf. You can see that, this printf is a function we
will see what a function is a more detail later, it is seems to a taken argument or a
parameter. So, it can say printf hello world and it will printf hello world on the screen.
You can say print hello you are name will print hello and you are name on the screen and
so on. But, every statement is terminated with the semicolon and the body of the function
is usually within the braces. So, this is a fairly simple program, if we compile it and run
it will print hello world on the screen.
So, with this we are at the end of module 1, in the next module we will see how… we
will take up a small problem and we will see how to solve the problem and we will also
see how to write it as a program.
14
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 02
Lecture - 02
Problem Solving
We are going to take about small problem, which is finding the largest of three numbers,
it is a fairly simple problem. But, in this problem by looking at this we will see, how we
can take a problem break it down into a smaller chunks, we will see a how to do a flow
chart or at least come up with the sequence with which you can see solve the problem.
And I will also show the rudiments of how to write a C program and the software
environment that is required for it. So, let us jump write into the problem.
15
(Refer Slide Time: 00:44)
So, we are looking at largest of 3 integers or 3 numbers. So, let us call them A, B and C.
So, this forms the input to the program or input to the problem and what we are
interested in finding out is, what is the largest of the 3, is it A? Is it B? Or is it C? So, we
have to start some variant, we are start with comparisons. So, let say I compare A and B I
am going to check, if A is greater than B or not. There are two possibilities A is actually
greater than B or it is not.
So, if A is greater than B then it still possible that C could be greater than A. So, we go
and check if A is greater than C also if it is yes, then indeed A is the biggest of all. So,
you already checked it against B and you have checked it again C and therefore, A is the
largest. However, if this condition is not true, if A is actually less than C, we know that
A is greater than B therefore, C must be the greatest of all. So, this A is greater than B,
but it is less than C therefore, C is the largest.
Now, let us look at this other side A is greater than B is false, which means B is the
largest among A and B. And now we can also check, if B is indeed the largest by check
comparing it again C, if B is greater than C, if it is true, then B is the largest; otherwise,
C is the largest. So, let us take one look at this problem, before we go and write a
program for this, we first compared A against B and if it is true, we checked A against C,
if both are true, then A is indeed the largest. If A is not greater than B, then we checked
B against C. So, if B is greater than both A and B C.
16
So, if B is greater than A, but if it is less than C, then C is the largest, if B is greater than
both A and B then B is the largest and so, on. So, this is the basic setup that we have. So,
at this point of time, it may make sense to try with a few example and check this is
indeed correct, if this solution is correct. So, let us take this example A is 1, B is 2 and C
is 3. So, let us check this is A greater than B is the first thing that is checked. So, you
compare A and B is A greater than B no, A greater than B the answer is no.
Now, you are ready to check is B greater than C. So, to compare B and C, we take B, we
take C is B greater than C. So, B is not greater than C. So, the answer is again no and
therefore, the result must be C. So, essentially you have taken this path, let us take
another example A is minus 1, B is 5 and C is 2. So, again if we check A greater than B
it is false and B greater than C is true. Therefore, you have taken this path B is indeed the
largest.
So, with these two examples I am now fairly convinced that this solution is correct. Let
us go and write a program for this and you are going to write small c program and in the
process I am going to show you, what it means to write a program. So, I am going to use
this environment or an IDE also called an Integrated Development Environment called
Dev C plus plus. It is a piece of software that is available for windows and it is available
for free, it is widely used, lots of downloads, I can provides you links later for how to
download it and how to run it, I already have it installed.
17
So, let me start a new program. So, I am going to start a new program and I am going to
write a piece of comment in it saying, this is a program to find the largest of 3 numbers.
So, I will to be able to take input from the user, I mentioned earlier that we need a library
for input and output and that is going to come from stdio dot h and I am going to write
the main program. So, do not worry about the mechanics right now.
So, as I mentioned earlier main program is the very first instruction, that is executed and
to do that we… So, the body of the main program is enclosed within braces. So, I am
going to have 3 integers A, B and C, I am going to prompt the user for the values. So,
printf is a statement which will print things on the screen. So, it will print enter A, B, C
on the screen at this point of time we expect the user to actually print these numbers.
And there is some scanning that we have to do or reading from the user and we do that
with this library function called scanf. So, do not get scared by the statement that is there.
So, all is doing is reading A, B and C. So, and you can see that every statement ends with
the semicolon so, that is the terminator. Now, let us write this program. So, we want to
be able to compare A against B and A against C and so, on. At this point of time, it may
make sense to actually go and look at the diagram once.
So, we first compared A against B and based on yes or no we did further comparisons.
So, let us compare A against B first, if A greater than B, then A still might B less than C.
So, we have to check if A is greater than C also and if it is, then we are going to declare
A as the largest. However, if this condition fails, then C is the largest. So, again do not
look at the mechanics right now. So, just follow the logic we already had this, let us go
down further.
So, we have taken care of two cases, we have looked at A greater than B and we have
seen, this side we have taken care of this side. Now, we are ready to write code for the
right side. If B is… So, by this time we already know that B is greater than A, we want to
check if B is greater than C also, if B is greater than C already we know that B is greater
than A. Therefore, B is the largest; otherwise, C is the largest. So, this is actually a
complete program.
So, we have at line 2, So, you can see the line numbers on the left side at you can see it
here. So, watch the mouse pointer at line number 2, we have hash include stdio dot h
starting at line 4 ending at line 28 is our main program. And the body is from line 6 to
28, there seems to be something which is int A comma B comma C. So, seems to be
18
reading in the values of A, B and C here and we have some checks and we have some
print statements. So, this is the whole process.
So, what we have really done is, we have prompted the user for entering A, B and C we
read it from the user and we have written this piece of code, which actually declares
whether which one of A, B or C is the greatest of three. So, let us save this program, at
this point you are prompted for a name. So, I am going to write largest of 3 numbers. So,
I am giving fairly a big name to the program, but you may want to give a smaller name if
you wish to. So, I have this.
And this is not going to result in any thing. So, we just have a program by looking at the
program one can usually check whether things are correct or not. But, the proof is in the
putting, we have to a really make the computer run it, we have to give it test cases and
see. So, we started with the specification we have the design which was the diagram that
I did earlier and we have coded now. Now, let us go and compile it. To compile it you go
to execute compile in dev C plus plus and while a we do not have any else.
So, have return a program which has no compilation errors, we are ready to run the
program now, some I am going to press F10 for it, when press F10 the first statement
that is there is printf enter A, B and C. So, this line number 7 is really not an executable
statement, we will see why it is so, later, it is actually called a declaration. So, we have
declared variables A, B and C of a type called int which transfer integer. So, the first
executable statement is actually printf. So, printf enter A, B and C.
19
(Refer Slide Time: 12:52)
And if you see this screen, it just shows enter A, B and C, you can see that the double
quotation, the open and close code is actually missing in the output. So, this is the way in
which printf which is a library function takes an argument. So, library functions are just
like mathematical functions. So, if I ask you what sine of x is, you would write it as sine
with in parentheses x would you not? It is same thing here. So, printf is a function with
in parentheses, you pass the so, called parameter to it. And in this case the parameter is
the string enter A, B and C and we can see it on the screen here. So, now we can see the
blinking cursor here; that means, the user is expected to enter something. So, let us take
the two examples that we gave earlier. So, let us go back to the example ((Refer Time:
13:44)) we tested 1, 2 and 3 and minus 1, 5 and 2. So, I am going to enter 1, 2 and 3 I am
type the key from the key board and it says C is indeed the largest. So, far, so, good.
Let me go back and try another input. to do that I am not really change the program per
se, which means nothing in the logic of the program has changed, I could just use the
executable directly this what I mentioned earlier, you could write it once, compile it once
and run it how many ever times you want. So, I am running it the second time without
making any edits to the program, because I do not see any evidence that the program is is
erroneous.
20
(Refer Slide Time: 14:29)
So, enter A, B and C. So, I am going to use minus 1, 5, 2 as an input and it says B is the
largest. So, we have tested two cases this does not mean that it would work for every
case. So, what I am going to do is, I am going to just try and give all possible
combinations of starting from ((Refer Time: 14:52)) let us say 1 2 3 I am going to try
these combinations 1 2 3, 1 3 2, 2 1 3, 2 3 1, 3 1 2, and 3 2 1. So, I am going to try these
four and these two combinations, one after the other and if there is any bug I can
hopefully catch it. So, let me give it one option after the other.
So, I am going to run it once 1 2 3. So, C is the largest good, then let me run it once more
1 3 2, B is the largest good. So, I am going to try 2 1 3, C is the largest, 2 3 1, B is the
largest, 3 1 2, A is the largest, 3 2 1, A is the largest. So, I give six permutations that can
come from ordering 1 2 and 3 and it looks like this program is actually correct. So, this
by itself this not show that the program is correct for the input that I given is correct and
it this case it would actually sort any number.
So, we have also tried it on negative numbers. So, let us try all negative numbers it say
minus 1, minus 2, minus 3 and in this case A is the largest. So, just for fun let us try
instead of giving numbers, let us try something else. So, I am going to give input as let us
say S t and R. So, I gave three letters and I did not give numbers and it actually says B is
the largest. So, what happened here? So, we gave three letters S t and R and it says t is
the largest, for whatever reason. We will see why, how this happened? We will see why
the B is declared as largest. So, what is in fact, B what is, A and what is C, we will see in
a later module.
21
But, as of now if I give numbers things seem to work. So, just for one last time I will try
another input 200, 100 and 1000 and C is the largest, so, far, so, good. So, this we started
with the problem and we when through a sequence of steps and we designed a solution to
the problem and the design is here. So, this diagram is the design and once we have the
design we went and coded it, we check the code by testing it. So, we have the whole
setup.
So, let me close this IDE or Dev C plus plus. So, with this we are at the end of this demo,
we saw this very first program that we wrote, we tested it, we compiled it, we tested it
and we ran it multiple times to ensure that things work out.
22
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 03
Variables, Assignments and Operators
So, let us like a small problem again, this is a problem on polynomial multiplication, let
us say we given two polynomials ax plus b and cx plus d. So, by given what I mean as a
given the coefficients a comma b and you are also given the coefficients c comma d and
you are expected to compute the product of the polynomials. So, the product could be ac
x squared plus ad plus bc times x plus cd. So, what we really want is the coefficient of x
squared, the coefficient of x and cd itself.
23
(Refer Slide Time: 01:01)
So, to write a program for this problem P 1.1 we need the following steps. So, we need to
declare storage a, b, c, d are all coefficients, if you read from the user you need to store
them temporally somewhere. And once the declaration is done you need to actually read
the coefficients from the user, perform all the arithmetic operations required and finally,
print them on the screen. So, that is the basic steps.
Let us… So, I already have a small program written up here. So, we start with
declaration of storage that is step 1. So, again ignore the first few lines, you see that there
is int a comma b comma c comma d and int p 2, p 1, p naught. So, this is a very much
like what we did earlier. So, we have a, b, c, d so, we need four coefficients and the
24
product the polynomial has only three coefficients p 2, p 1 and p naught is the name that
I am giving them.
Step 2 is read the inputs. So, in this program I written into the such away that will
prompt the user for one coefficient at a time. So, there is printf enter a and the user is
expected to enter a number and it is read, using the statement scanf, then you have printf
enter b now you are ready to read d and so, on. So, four coefficients are read so, this step
2.
25
Step 3, calculate the coefficients and print. So, here there are three lines p 2, p 1 and p
naught, p 2 is a time c, p 1 is a into d plus b into c and p naught is b into d. So, this the
very first time we are seeing an assignment. So, the way to look at assignments is look at
the equality sign in the first line here, you see a star c. So, star is the operator for
multiplication. So, you are multiplying a with c and that is assigned to p 2.
So, the way to look at this line is you do the operation a time c and the values assigned to
p 2 which is on the left side, then you have a times d plus b times c. So, you do this
operation and you assign it to p 1 and b into d is assigned to p naught. So, at the end of
these three statements, we expect p 2, p 1 and p naught to reflect the point that you have
already taken care of the multiplication of the polynomial. And now we are ready to print
and the product is printed on the screen.
So, again forget all the fancy looking thinks there. So, you can see the printf the product
is and something there. So, ignore it for now, it is only printing the coefficients along
with the… in the polynomial format itself. So, let us run this program I have written this
program up and let us run this program.
So, you can see that the program is same as what I said. So, you have int a comma b
comma c comma d, then you have the three coefficients p 2, p 1 and p naught. The four
scan statement for reading the variables, three statements for doing all the assignments or
doing the arithmetic operations and one final statement which takes care of printing.
26
(Refer Slide Time: 04:22)
So, I will compile it now, it gives me no errors or no warnings,. So, far I am shown you
what errors and warnings could be I written programs which are all correct. Now, I am
ready to run.
So, as before we see the value prompt for a. So, I am going to enter the polynomial 1
comma 2. So, essentially 1 x plus 2 for the first polynomial and 3 x plus 4 for the second
polynomial. So, the result is 3 times x squared plus 10 x plus 8. So, it is 1 x plus 2 times
3 x plus 4. So, that is 3 x squared 10 x plus 8 ((Refer Time: 05:08)). So, let us look at the
program in it is entirety, we have the first few lines which has the comment.
27
So, there is a comment on the top here which says, this is the program to multiply two
polynomials ax plus b cx plus d. So, it is always good to show the intention of the
programmer and this kind of documentation is helpful for somebody else who is going to
read your program and understand what is happening. Then, as before we have this line
hash include stdio dot h, this is necessary for doing all the IO operations.
If you want to read from the keyboard or if you want print on the screen, you will need
this and then we have the declarations for a, b, c, d, p 2, p 1 and p naught. Then, we did a
series of printing on the screen and reading from the user and we did the operations and
printed the result on the screen. So, I already showed you the execution. So, this by itself
is not what I want to show, I want to drive something else, let us take a close look at the
program and see some of the nitty gritty details that are involved.
So, let us start with a comma b comma c comma d. So, I assumed in the polynomial is ax
plus b and cx plus d. So, however, we need the user to give these four coefficients and
these four coefficients have to be taken from the user and stored temporarily somewhere
and that is where we are going to use these variables a, b, c and d. So, in a little while
explain what the word variable means, at this point just assume that a, b, c and d are four
variables.
And it is not enough to just have these coefficients, because you are supposed to produce
the product terms. So, you have a, b, c, d and you have p 2, p 1, p naught and when you
have int a comma b comma c comma d, it actually allocates storage for a, b, c and d.
Because, once you read it from the user, you have to save it somewhere and when you
have p 2, p 1, p naught again you allocate this storage for p 2, p 1, p naught and store it
somewhere.
So, essentially it takes care of all the storage that you need to do all the operations and
this storage is going to be in the main memory. So, we already saw the model for the
computer. So, we have memory and we have a CPU and we have a other units. So, these
variables will be stored in the memory and if you look at this int, int basically means that
int stands for integer and it means that anything that follows this is going to be a variable
which is of an integer type.
So, when you have int a comma b comma c comma d, we have four integers or four
variables of the data type called integers. Similarly, we have p 2, p 1, p naught which
also have this int before it, which means there are three integers by name p 2, p 1 and p
28
naught. So, if the input coefficients a, b, c, d are all integers, the product and sum of
integers are always integers. So, therefore, I have used integer for p 2, p 1, p naught also
and this is called the data types.
So, whenever you want a variable it cannot be a variable of an unknown type, it has be a
variable of a known type. And there are a few basic data types that the language provides
you, there are other data types it you can build on your own, in this case I am going to
use basic data type provided by C which is called int. So, remember int stands for
integer.
Now, let see what is going to really happen from the memory point of view. So, let us see
the program. So, initially the memories on used I am showing a segment of the memory
here. So, let us assume that the memory is huge and it has several bytes of storage I am
showing only a portion of the memory on the right hand side. So, initially everything is
unused and the movement there is a declaration int a comma b comma c comma d. What
it really means is, when you run the program there are going to be four memory locations
which will be called by the names a, b, c and d. So, the memory that you have is initially
uncommitted, the declaration int a comma b comma c comma d will attach some names
to the memory locations. So, in this example this memory location is going to be called
a, this memory location is called b, this is called c and this location is called d.
So, I am purposely showing these locations without any values. Because, when you
declare, you should not assume that there is going to be some known value already
29
present in the memory. So, when you start the program we do not know what the values
are and we also expect the user to input these values a, b, c and d. And the next line was
int p 2, p 1, p naught, which means there are going to be three memory locations which
are going to be called p 2, p 1, p naught.
Now, let us look at the statements that follow. So, we have printf enter a and let us say it
prints this enter a on the screen, it is prompting the user and when it is comes to scanf, it
is going to read one number from the screen, let us assume that the user actually input 1.
So, we assume that the polynomial is ax plus b is 1 x plus 2 and cx plus d is 3 x plus 4.
So, at this point the user is expected to have entered 1 and that would go into the memory
location which is called a.
So, one goes into this memory location, then printf enter b that I will show enter b on the
screen, when the user inputs b in this case it is 2 that would go into this memory location
and so, on and so, forth. So, at the end of all the scanf statements shown here, if the user
entered 1, 2, 3, 4 these locations would contain 1, 2, 3, 4 p 2, p 1, p naught I am showing
blank, it does not mean that it does not have any value, it only means that we do not
30
know the value yet it has to calculated yet. So, at this point a, b, c, d are all ready and is
available in the program.
So, let us move to the next set of statements, the next set of statements are for calculation
of p 2, p 1 and p naught. So, remember in this computers model, I showed control unit
the CPU which contain the control unit and the ALU you had memory. So, in this at this
point I am showing the memory and the ALU, I am not showing the input and output and
other notes and so, on. So, ALU stands for arithmetic and logic unit and since we need
arithmetic operations here, like multiplication, addition and so, on I am also showing the
ALU.
So, let us see the very first statement here p 2 is a time c. So, this star means
multiplication. So, what you want is a and c to be multiplied and the result of that must
be stored in p 2, this is what you want, and the way it works is this. So, when you have p
2 is a time c, a and c are brought in from the memory into the ALU. The ALU does the
operation multiplication, in this case its 1 times 3 and the result 1 into 3 which is 3 is
written back into p 2.
So, if you look at this statement you read the value of a, which is 1, you read the value of
c which is 3, you do multiplication the result is 3 and the result is written back in the
memory location p 2, which is what we have. So, far, we read 1 and 3 in the result is 3
here. So, now, we are ready to move to the next statement, the next statement is slightly
more complicated, it has a star, it has a plus and it has another star. And the order in
31
which the operation is done is, you perform the operation a times b first, you then
perform the operation b times c, the result of these two or then added up and the end
result is put back into p 1.
So, that is what we are going to see the animation now. So, a and d are both read into the
ALU and it is not ready to be written back to p 1 yet, because you still have more
operations on the right hand side. So, this 1 times 4 the result is 4, it is temporarily stored
inside the ALU itself. Then, you have b times c, b and c are read from the memory and
the result is 6. So, 2 times 3 is 6 and you cannot right 6 to p 1 yet, because this is also a
temporary result, what we really want is a times d plus b times c. So, we cannot write 6
to the memory yet.
At this point the ALU has 4 and 6, which are required for this addition operation to be
performed. So, if you have only one of these operation let say a times d was available,
but b times c was not available, then the addition cannot be performed. But, it this point
of time you have both 4 and 6 and 4 plus 6 is executed by the ALU the result is 10. And
once you have the result, your ready to write it back into p 1.
So, the RHS or the Right Hand Side is ready, we have the result and we write the result
into p 1 next the CPU moves to the last line, which is p naught is b times d. So, again b
and d are brought from the main memory this time. So, b and d changed they are 2 and 4
and the result of that is written back into the main memory. So, at the end of the program
we have 3, 10 and 8. So, let us take a quick look at the program here ((Refer Time:
16:13)).
So, what we saw was we these four or these statement from line number 10 to 17.
Initially filled up the four memory locations a, b, c and d with values by reading from the
user, lines 18, 19 and 20 did operations the user is not involved here, the values from the
memory are read and the calculations are performed, the results are written back into the
memory. And finally, when line number 22 is executed, it is asking p 2, p 1 and p naught
to be printed on the screen, in a certain format and the way it is going to be printed is as
follows.
So, you have the product is percentage d x squared plus percentage d x plus percentage d
back slash n what that means, is. So, if you look at this percentage d and if you run this
program, you will never see this percentage d on the screen. So, I enter the same thing if
you see the result, the product is only 3 x squared plus 10 x plus 8. What is really doing
32
is, this percentage d is called a format specifier. So, where ever you have percentage d
here, you look at what is the value outside the codes.
So, it says print p 2 as integer that is what this percentage d means. So, p 2 is an integer,
print it as an integer, p 1 is also an integer, print it as an integer also and p naught s an
integer, print it as an integer also. So, that is why you get this result 3 times x squared
plus 10 x plus 8, this x char at 2 or x squared is verbatim in the program itself and this x
plus is also verbatim in the program, you see that in the result here and finally, 8.
So, we will get into the format specifier and so, on in more detail later. But, as of now
just remember that this percentage d is not something that is printed on the screen. So, to
print something on the screen, you need to p 2, p 1, p naught stored in the main memory,
but we have them as 3, 10 and 8. So, when you see the printf statement you can read
these 3, 10 and 8 and print them on the screen.
33
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 04
How a Program Runs
Assignment operator
Variables and constants
Variable declarations and data types
Arithmetic operators in C; Precedence
Increment and decrement
So, every memory location is given a name. So, if you have a variable you use a name
and this name is something that you gives so, that you can remember what it is about and
in turn this variable name is attached to a memory location, when you run the program.
So, the name is,. So, when you say a the variable name a, you are actually referring to the
value at location that we are going to call a. And when you say a times b, it is actually
going to take the value at location a and value at location b and multiply them together.
So, the name is actually referring to the data and you usually have names which make
some sense to you. So, it could be roll number, class size or in our example we use p 2, a,
b etcetera. So, these variables have some kind of data type attached with them. So, in our
example we expected a, b, c, d to be integers or we wrote the program to be in such a
way that they are integers and p 2, p 1, p naught are also integers.
34
So, because they are integers they cannot have something like a string, let us say it can
not have my name assigned to a, a equal to Shankar does not make sense, a should be a
number. Then, all the data internal to the computer is actually represented as a sequence
of 1s and 0s of some particular size or some predetermine size, which we will call word.
So, this integer is supposed to be of certain number of bytes and usually integers of size
4 bytes.
Then, there are these various operations that we did. So, if you look at a times c which is
assigned to p 2, then the star is an operation, the assignment itself is an operation and so,
on. So, instructions take the data that is stored in the variables as arguments and some of
these instructions actually perform operations. For example, if I do x equals x plus 1, it
takes the integer x add 1 to it and the result is then put back in the same location x.
And some other instructions actually do change the order in which the program is run
and these are called control instructions, we will see them in a little more detail later. So,
I want you do look at this x equals x plus 1 in little more carefully. So, if you do basic
algebra you may be confused about this statement. So, let us say x equals x plus 1 is
written in a C program and you look at it, if you do basic algebra from a 6th grade or 7th
grade, you would cancel x on both the sides and you would be looking at 0 equals 1.
So, but that is indeed not the case. So, the assignment operation is something which is
different, do not read the sign equals to as though it is an equality. So, the way it should
be looked at is, it is a statement it has to executed and the way it is done is, you execute
35
whatever is on the right side, you get a result from there, you take that result and put that
result on the variable on the left side.
So, let us look at the program, a program is a sequence of instructions. Normally, the
processor will work in the following sequence of steps. Step A would be pick the next
instruction in the sequence, then step B would be get data that is required for the
instruction to operate upon, execute the instruction on the data. So, if it is star then you
do multiply, if it is plus you add and so, on. And a step D would be take it and store it
back as a result somewhere, it could be either a memory location or it could be some
internal storage, like 4 and 6 were stored in our steps and finally, go back to step A itself
look for the next instruction and so, on.
So, this is going to be repeated in a loop. So, you pick the instruction, you pick the data
that is required for it, you do the operation, store the result, go back and pick the next
instruction and keep doing this till there are no more instructions for the particular
program.
36
(Refer Slide Time: 04:42)
So, let us look take a careful look at what the assignment itself means. So, this equality
or the equal symbol that you saw is called the assignment operator. So, when you see p 2
equals a into c, you multiply a with c and the result is assigned to the variable on the left
side called p 2. Therefore, we call this equality an assignment operator, it takes the value
and puts it in the memory location p 2. Therefore, it is doing an operation which is a
memory operation, in this case it is a memory write operation, you are writing to
memory. So, it is an assignment operator.
So, the value of a variable could be modified due to an assignment. So, the way it works
is the left hand side is the variable to be modified and right hand side is the value to be
assigned. So, you have variable name equals value. So, if you have a equals to 1 as the
assignments statement, then 1 is the value that get’s assigned to a. If you have a equals to
c, c could itself be another variable, you do not copy the character c into a, instead you
go and look at the variable called c, look at the value that c contains and copy that value
into a.
If you have something like a equals a into b plus d by e, then you actually take the
variable value contained in the variable a, multiply that by the value contained in the
variable b, store it temporarily. Then, take d and e the values contain in these variables
divide d by e, store it again temporarily at these two and put it back the memory location
a itself. So, the process is the right hand side is evaluated first. So, you would evaluate
this right hand side first and after completing and only after completing all the operation
on the right hand side, the assignment operation is perform this will write the result back
37
on to the left hand side.
So, variables and constants are two typical things. So, this is where I want to brink this
distinction between, what is a variable, and what is a constant. So, a variable is
something that can change during the execution of a program, where as a constant can
not change it is value during the execution. So, you look at a equals 1, a is a variable
here, 1 is number 1, number 1 is not going to be change during the execution of the
program. Therefore, 1 is a constant and a is a variable.
So, the variable names are made up of letters, digits and underscore. So, these are called
identifiers. So, for different memory locations you give different identifying names, just
like we have names, variables are the names for the memory locations. And the variable
names are what are called case sensitive, what that means, is upper case or capital letters
are different from lower case or small case letters.
So, if I use the variable name called class size with a capital S, it is going to be different
from a variable called class size with a lower case s. So, these two are two different
variables, which means they will get two different memory locations assigned to them.
And the maximum size, that you can have for a variable name is 31 letters, you cannot
have more than 31 letters for a variable. The first character for a variable that you have,
that you write must be a letter, it cannot be underscore or it cannot be a digit it has to be a
letter.
Typically, you use a meaningful and self-documenting name for the variables. So, for
38
instance let us say I want to use pi or pi. So, pi is a constant, pi is not going to change it’s
value, it is going to be 3.141 and. So, on, it is a constant. So, usually constants are given
names which are all capital letters, whereas variables are given names which are usually
a mix of lower case and upper case letters.
And then there or key words are words that are reserved by the programming language,
we already saw if statement in module 2, there are also other statements, other key words
like for and so, on, we will see them later. So, these are not names that you can assign to
your variables, you cannot have a variable by name if or for or else or float or int or
while and so, on.
So, the variable declaration goes as follows, once you declare you get storage already
showed that in the animation. Declaration is the general form, you have the data type
followed by the variable name or a list of variable names. The various types that are
allowed are integer, float, character and double, int is for integers. So, you can only store
integral values in them, float and double are for real values and you can store a dot
something is a 5.34, 3.14 and so, on, char is for character it can store a single letter,
single letter like a, b, c, d or such things.
So, if you look at the statement int x semicolon, what it really does is you have a variable
by name x and it is of data type integer. And there is a memory location, which is labeled
x for this execution of the program, it assigns a creation number of bytes for this.
Whenever, you use the variable x in your program, it will during the execution this value,
39
which is contained in this location x is used.
So, we actually use the term variable, because the value of a variable can change during
the execution of the program. So, a program is essentially a modification of variable
values. So, for instance even the variables a, b, c and d they had some unknown values
initially and they changed only when the user input those values. Similarly, p 2, p 1 and p
naught, were unknown values initially and they changed once the operations on the
respective right hand side where over.
So, each C program is a modification of variable values and the modification can happen
due to operations like, plus, minus, slash which is for division, star for multiplication and
so, on. These variables can also change values, because of functions or operators
provided by the system. So, for example, I could say a equals sin of x. So, x would be
treated as a parameter to the function called sine, sine is a built in function in the math
library, it will do sinusoid or sine of x. The result of sine of x will be stored in this
variable called a or this variables can change due to some functions itself, which the
programmers create. We have not written any such a functions yet, we will see them as
we go along.
40
(Refer Slide Time: 11:53)
Let us look at the kind of operators in C, there are four basic operators for arithmetic
namely, plus, minus, star and slash, they stand for addition, subtraction, multiplication
and division respectively. You can use these operations against integers and floating
point numbers. So, plus and minus for integer and floating point will just add the values
or subtract the values, star and slash have a little bit of meaning or a change in meaning
when you attach them to integers as opposed to floating point.
When we do integer division, the fractional part of the result is truncated if you do
integer division. So, for example, 1, 2 or number 12 is an integer and number 5 is an
integer. So, if you write 12 slash 5, you are dividing 12 by 5 and even though the result is
2.4, this 0.4 is truncated. So, the result of dividing an integer by another integer, this is
also an integer in this case it is only 2.
So, a more drastic case is, if you divide 5 by 9 for example.. So, 5 is smaller than 9, so,
when you divide 5 by 9 you have a fraction and know integer value. So, the result would
be 0. So, you have to watch out for this, when you program, but for now this is not a
problem and then there is this modulo operator or percentage. So, this percentage if you
write x percentage y on the right hand side of assignment statement, what it will do is, it
will take x and divide it by y.
But, instead of putting the quotient it finds out the reminder. So, a by b finds out the
quotient and a percentage b finds out the reminder. So, this is for integers. So, of course,
for floating point numbers a percentage b does not make sense, you can take any floating
41
point number, divide by another floating point number and you can always get a
coefficient with the modulo being 0. So, this is called the modulo operator, because it
finds out the remainder. So, percentage is defined only for integers, it is not defined for
floating point.
Let us look at these operators, if you look at operations just like in when you write
expressions in your basic algebra, there are rules that you have to follow the rules of
precedence. So, even in our example we had a times d plus b times c which was return as
a into d plus b into c. So, what is the order in which things are done. So, the first
precedence is for parenthesized expressions, the next precedence is for star and slash and
modulo and the last level of precedence is for plus and minus.
So, to illustrate what I mean by that, let us take this expression, this complicated
expression a plus b times c plus d and c times d and so, on. So, if you are not careful you
may end up doing something like this. So, let say I start looking at it from the left side, I
see a plus b I take a and b first add it and then I multiply the result by c I take the result
multiply by d take that result find the modulo by e and so, on. But this is not the way in
which we do this in algebra either.
So, in algebra if an expression like this is return, then you do not do the addition first,
you go and look for higher precedence operations first followed by lower precedence
operations. So, the numbers that you see on the right side or not the values attached to
these variables, instead that indicates the order in which the operations will be done. So,
42
if this expression is given to you, the very first operation that will be done would be b
times c. So, this star is the very first operation that will be done, the result of b time c
will be store temporarily somewhere, that result will be multiplied by d. So, this star is
the second operation that will be done. So, now, you have b times c times d the third
operation that will be done is percentage. So, b, c, d percentage e will find the remainder
of dividing b, c, d by e. Once that is done, then the next operation that is done is plus.
So, you will do a plus that whatever is in this expression here then finally, you do this
division followed by the subtraction. So, this is the order in which things will be done.
So, if you are looking for a parentheses expression which is equivalent to this, this
should be the parentheses expression. So, you start with b times c the inner most thing is
the evaluated first, then you have that times d, then you have that modulo e.
So, you will have a result from here that will be added to a and you do f by g and this
whole expression will be… So, this f by g will be subtracted from this whole expression
on this side. So, it is missing one parentheses here, so, we expect a parentheses to be
here. So, this is the order in which thinks are done. So, use parentheses all way if you are
in doubt, until you get familiar with the order of precedence. So, star slash and
percentage has higher precedence over plus and minus and parentheses has higher
precedence over star slash and plus.
So, let us see another example here,. So, a times b plus c percentage 5 plus x by 3 plus p
minus r minus i, it look complicated enough, it is also not a very good thing to write. So,
43
I am giving it only for illustration purposes, you should not be writing such things in
your programs. So, the evaluation order is b plus c and 3 plus p would be done first,
because of the brackets that you have around them. And once you have it, star slash and
percentage have the same precedence and you have star and percentage here.
So, star comes before percentage in the program order, if you look at it from left to right
star comes before percentage. So, star will be performed before the percentage. So, a
times b plus c is evaluated and then you do it modulo 5 over it. Similarly, when you have
x by 3 plus p, 3 plus p is evaluated already is you will do x by that, at the end of it you
will have some value for this whole expression, some value for this whole expression
minus r minus i.
So, now you have only pluses and minuses you simply go from the left to right side and
you will do this plus that, the result is then, you remove r from it and then you remove i
from it. So, that will be the order in which you do things. And finally, the whole
expression is evaluated and you have a value at the right hand side, that value is assigned
to this variable called value which is on the left hand side.
So, equal to is the only operator that violates the left to right rule. So, star you do the
calculate term something on the left side before you calculate something on the right
side, percentage also you evaluate something on the left side before evaluate something
on the right side. But, for the equal to operator you need to evaluate the right hand side
get it completely evaluated and then the result is assigned to the left hand side.
44
Let us look at a few other operators that are useful and very common in C and increment
and decrement operators, these operators which are actually unusual not uncommon
unusual. So, for example, this plus plus, see if you do a plus b it is addition of a and b,
but if you do a plus plus it is actually equivalent to a equals a plus 1. So, plus plus means
add 1 to the operand and minus minus means subtract 1 from the operand.
So, let us look at this small example n plus plus would mean, you take n add one to it and
put the result back in n itself. So, it will increment n after the use of n, whereas plus plus
n means increment before the use. So, let us see how this can be useful in an expression,
how it can be used in an expression n equals 4. So, there is a right hand side which is 4,
the left hand side as n, there is nothing to do on the right hand side it is a constant. So, 4
is assigned to n. So, there is nothing to do there, then let us look at this statement x
equals n plus plus.
So, what we have here is this plus plus is a post increment operation what; that means, is
use the current value of n, assign to the left side and then comeback and change the value
of n. So, in this case if n equal to 4 was already executed, the current value of n is 4 it is
assigned to x and then n is incremented to 5. So, at the end of these two statements n
would be 5 and x would be 4. Then, if you have this third statement y equals plus plus n,
n has a pre increment operator, which means n should be incremented before the use in
the right hand side.
So, the current value of n is 5, you pre increment the result is 6 and that result is given to
y. So, after the execution x would be 4, because you use the value before you
incremented n, y would be 6, because, 2 increments happen before you assigned it to y
and n would be 6, because n got to increments one as a post increment here and one has a
pre increment here. So, remember the memory layout there are three variables n x and y
which means there are three memory locations n x and y. And mentally simulate how this
read and write on the memory is happening, involving the ALU. So, just remember that
whenever you have a post increment, read assign to the left side and then do this
operation. Whenever you have a pre increment, do the operation first and then assign to
the left hand side so, it is as simple as that. So, we looked at the notion of variables and
we looked that the notion of what the memory layout is, we also looked at what the
various operators in C are the basic arithmetic operators and we looked at a small
program.
45
(Refer Slide Time: 23:25)
So, I want to show something small about the code before I wrap up this session lecture
1. So, I am going to run this program, we already had this program, I am going to run
this program, we did int a, b, c, d int p 2, p 1, p naught I am going to run this programs.
So, let us what I am going to do is, I am going to run it with an unexpected input. So, I
expect integers a, b, c and d to be given integral values, what if I violate that.
So, what I am going to do is, I am going to take two polynomials 1.5 x plus 1 and
multiply it with 1.5 x plus 1 itself. So, I will try 1.5 and right away I have some result
here with says the product is something, it did not even give me a chance to enter b, c, d
and so, on, it gave me some product and clearly this is not what we expect. So, the
variables a, b, c, d were integers and by just giving the input 1.5 somehow it got values
for b, c and d incorrect values of course, and it gave me some polynomial here as a result
also.
So, something bad happened here. So, will sit down and reason about this at a later point
of time. So, this is just a show you that in module 2, we looked at some values that I gave
to the program and for a, b and c it still gave me correct results. But, it is not because that
it got the correct values in place, something else happened there will sit down and reason
about that at a later point of time.
But, this is a program where entering a wrong data type 1.5 is a floating point, I gave it
when an integer was expected and the program so, called crashed, it gave me something
which is unexpected. So, with this we are at the end of lecture 1. So, see you in lecture 2.
46
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 05
I/O statements
Printf, Scanf
Simple statements,
Compound statements
So, we have been looking at printf and scanf for a while and I have not really explained
what printf and scanf are about yet. But, in this lecture we look at what is the syntax of
printf and what are the different things that go into the format itself. So, printf in a
general form has a format string followed by list of comma separated variable names. So,
the syntax is highlighted at the top, so you have format string comma, a comma
separated list of variables 1 to n.
So, format string is enclosed in double quotes. So, we are seen that in several examples
before, I will also show your examples later. And the format string essentially indicates
47
how many variables should it be printing on the screen, what are the types of the
variables, how many columns to use for printing them. So, numbers could occupy
different number of columns and so on. So, we could have right justification or left
justification and so on. So, we can specify the number of columns required, even though
that is not something that you would see in this lecture in detail.
And if you want any character string to be printed, for instance the very first program
that we wrote, we did not really have any variables to be printed, we just said printf hello
world.
So, let us look at this example, let us say I have int x and float y and let us say x equals
20 and y is minus 16 points something. If you have this printf statement value x equals
percentage d and value y equals percentage f back slash n comma x comma y. So, that is
the print f statement, so as you can see the very first part as the format string which is
enclosed to within double quotes and then there is a comma separator list of variables.
In this case it is a comma separator list of two variables namely x and y. And when you
look at this one, there is string value space x symbol equal to and then the something
called percentage d and then, there is some other string up to here percentage f back slash
n. So, percentage d says that, whatever value it is or variables it is going to get, it should
print it as integer and percentage f indicates that, whatever it is going to get as a variable
it is supposed to be printed as a real value.
48
So, this percentage d is the very first percentage symbol, that you see followed by some
letter d. So, this percentage d associate itself with the first variable x and the second one
percentage f here associated itself with y here. And so since percentage d is for integer x
is printed as integer and since percentage f is for real y is printed as real. So, this is the
basic example, there are other specifiers, but more often than not you will need only d f
and possibly a g.
So, the output for this would be value x equals 20 and value y equals minus 16.789. So,
the idea behind is that, if you halve this strings here. So, this value appears as it is this
space appears as it is x equals to appears as it is, only this percentage d is a place holder.
So, it is holding it is place for this variable to be printed in its appropriate format.
So, let us look at some of the printf statements we are use before, printf enter the three
numbers A, B and C. So, this is something that we are used before. So, this is just a
format specification, there is nothing to be printed at here, it will print as text as it is,
then look at this other statement printf, the product is percentage d x char at 2 plus
percentage d x plus percentage d back slash n. So, this is something that we saw in the
polynomial multiplication example in the previous lecture.
So, now, this percentage d associate itself with p 2 this one with p 1 and the third one
with p naught. So, the output that we got lost time was 3 x squared plus 10 x plus 4. So,
you can see that percentage d was place holding for 3 and percentage d was place
49
holding for 10 here and this one for 4 and everything else get printed as it is. So, back
slash n as a mention earlier, it is essentially is printing a new line. So, it will print this
statement, the product is this and the any other print that you give will go to the next line.
What about the input statements? So, in the input statement let us look at scanf. So, scanf
is also of the similar format, you have format string and you have a comma separated list
of variables that you want to be scanning. So, the format string as before is enclosed in
double quotes. And the format string indicates, how many variables to expect as it was in
printf, type of the data items should be stored in var 1, var 2 and so on.
So, it is very similar to printf except that you see the symbol ampersand in front of all the
variables. So, you have ampersand var 1, ampersand var 2 ampersand var n and so on.
So, the symbol ampersand is use to specify the memory address, where the value is to be
stored. So, remember var 1, whenever you use a directly in your program, it goes to the
memory location and gets the value. But, in this case scanf is supposed to take the value
from the user and put it in the memory location called var 1.
So, it do that you put ampersand of var 1, we look at this in more detail later, what you
are essentially passing onto scanf is a pointer to var 1 by prefixing and ampersand before
it. So, ampersand var 1 is a pointer to var 1 and it says, this is the memory address for var
1 instead of calling it by name, you now get the memory address and whatever the user
inputs will go to that memory address. So, we saw this example in the previous lecture
50
also, when scanf happened we went to the specific location and we got the values for a,
b, c, d and so on.
So, let us look at the other example, we have seen before, scanf percentage d percentage
d percentage d ampersand A comma ampersand B comma ampersand C. So, again the
order in which things are done is left to right for the format specifiers, as well as left to
right for the variables here. So, it will expect three integer from the user and put these
three in locations A, B and C respectively. Finally, if we have this kind of an example,
where percentage d percentage f comma ampersand marks ampersand average marks.
So, I am assuming that marks is an integer and average marks is a floating point value.
So, if the user keys in 16 let say some couple of spaces and 14.75. First the scanner will
come and look at it from the left side and keeps scanning tell it finds in integer, in this
case 16 is in integer and after that it is a whitespace, whitespace is not a part of an
integer. So, it will stop scanning, so there is a number 16 which the valid integer, that
goes into the very first variable namely marks.
Then, the format specifier says percentage f which means, the scanner should look for a
floating point number. And when it looks at this spaces 1, 2, 3, 4 spaces the spaces not
part of a number. Therefore, it keeps looking further it sees number 14, but there is a dot
and followed by 75 after that there is no more input, which means the input ends at
14.75, which is 14.75. So, 14.75 would go as the average marks.
51
So, usually space, comma, back slash n and so on or not specified in the format specifier,
in the format specifier which put within double quotes, you usually do not put space
comma and other things. In printf it is useful to print something on the screen, in scanf
you just want the data from the user and the user may enter space in sonic and scanf
automatically ignore set. So, usually you do not put it as part of the format specifier. So,
this scanf skips over space is necessary to get the next input.
There are several other the format specifier that you can use. But, the most commonly
needed are percentage d and percentage f, the other one that might come useful is
percentage c, where c is transfer character. So, percent percentage d is decimal,
percentage f is float, percentage c is character. There is also an exponent form for
floating point numbers, which goes by percentage e. For example, 1.523 e 2 means 1.523
into 10’s square or 10 power 2 and which is 152.3.
So, there are also several modification to percentage f and percentage d which are
possible, which you can use in print f. However, they only control how much space the
number takes on the screen, it does not really control how the value is itself. So, if you
say percentage d it is an integer, so it will get printed as an integer. But, depending on
percentage followed by a number and a d will take as many spaces as specify the
number, this is useful and justification and so on.
52
So, I am not going to cover how to have a fixed width of numbers and so on. So, that is
beyond the scope of this lecture. So, I suggest that it you go and read up a book on C to
get more details.
53
(Refer Slide Time: 11:21)
So, the first category we call simple statements, are those statements that are expressions
or function calls. So, for example if I have x equals 2, x equals 2 is single statement you
take the value to assigned two x. So, it is an assignment statement. Similarly x equals 2
plus 8 it compute 2 plus 8 on the right hand side gets the value 10 puts it to the left hand
side, it is a simple statement. Because, it is doing only an assignment, then there is a
function call printf hello world, so it is a simple function call.
And finally, sin of x, so x is passed as a parameter to function call sin, sin computes that
and returns the value which is placed in y. So, these are simple statements, because the
right hand side evaluates the value and the value is put into the variable on the left hand
side. So, simple statements are usually terminated by a semicolon and in this case, you
can see that all these examples all this four different simple statements have a semicolon
at the end. So, anywhere were you evaluate and expression and so on will all be simple
statements, anywhere where you call functions will all be simple statements as well.
54
(Refer Slide Time: 12:38)
So, the other category is what is called compound statements, a compound statement is a
group of declarations and statements collected together. So, compound statement
contains one or more set of simple statements and it may also have declarations on there
own. So, for example, a single simple statement is also a compound statements because,
compound statement contains one or more simple statements.
So, the basic idea begin compound statements is that, it forms one logical unit and to say
that this is one logical unit we surround the statements that are grouped together using
braces. So, sometimes the set of statements that are within the braces are also called a
block, so let see these examples.
55
(Refer Slide Time: 13:27)
So, we have seen something like this, let say I want to calculate the maximum of two
numbers. So, I have a small program segment which does that, so you have int max if a
is greater than d max is a and printf a is greater than b, else max is b and printf b is
greater than a. So, we are not only printing whether a is greater than d or b is greater than
a, we are also storing the maximum value in this variable call max.
So, if you notice you have braces left and right braces for, if a greater than b and we have
left and right braces for the else clause also and this is one logical unit. So, if a is greater
than b the maximum is indeed a and you can also print. So, this one logical unit, so I
highlight it in orange here; however, the compiler only we look at this left and right
bracket, anything with in this left and right brace is a block and this is treated as
everything that should go under the true case.
If a is greater than b is false, then max is equal to b and you can print that b is greater
than a. So, in fact b is greater than or equal to a in this case, it is not just greater it is also
greater than or equal to. So, this is one logical unit and the braces say that this is one
logical unit. So, this is a compound statement and this whole if statement by itself is a
compound statement, because if statement… So, this if followed by an expression
followed by a block else followed by a block, this whole thing is also one logical unit
therefore, it is also a compound statement.
56
So, compound statement may have one or more compound statements inside them and it
may also have simple statements inside them. A simple statement is one line expression
or a function call and so on.
So, compound statements in turn can come in two varieties, they can either be
conditional statements namely, if then else statements or it could be switch statements.
The other variety is also called the repetitive statements or loops I refer to them earlier as
repetitive statements. So, there are three varieties of loops available in C namely, for
loop, while loop and do while loop. So, we will look at more details in the next few
slides about, if then else statements and switch statements and later in the lecture you
will see things about loop statements.
So, for in this module what we are done is, we look that I/O statements and we look at
two categories name we, simple and compound statements. Simple statements we
already know the involve arithmetic expressions or function calls on the right side in
variable assignment on the left side, compound statements is a collection of either
compound statements or simple statements.
57
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 06
Conditional Statements
Selection Statements
If statement, If then else, Cascading if
Switch statement; Correct and incorrect usages
So, will start with the conditional statements, these are also called selection statements
for the reason that you actually have an expression, based on the evaluation of the
expression, you actually select what should be done. So, for example, there is the single
selection statement. So, let us see this example, if attendance is less than 75 grade equals
w, so what this is really doing is this, if attendance is less than 75, if the class attendance
is less than 75 percentage, grade is withdrawn. That is what this statement is supposed to
be.
So, if attendance is less than 75 grade equals w. So, this is does not have any other
choices, there is no else here. So, this is a single selection statement, so you choose to
58
assign w to grade or not depending on this expression attendances less that 75. If
attendances is actually greater than or equal to 75, this assignment will not be chosen for
execution, which means it will not be executed, so this is a single selection statement.
A double selection statement on the other hand has two things that you could do, you
could have, if marks less than 40 passed equals 0.. else passed equals 1. So, you have
two statements passed equals 0 and passed equals 1 and one of them will be executed
depending on the choice of marks. So, if marks is less than 40 passed equals 0 will be
executed; otherwise passed equals one will be executed. So, there is semicolon missing
here that has to be included. And otherwise it can be a switch statement, where it is
multiple selection. So, you have single selection, you have double selection or you have
multiple selection. So, we will first see this single selection and double selection and
query details before we go into the switch.
Let us look at the basic structure of a if statement. So, the if statement is start with these
keyword called if followed by an expression, followed by a block called statement 1,
optionally it can also have the else clause and a block for the else clause called statement
2. So, the meaning of this is, if expression evaluates to true, statement 1 will be executed.
So, the statement 1 is, remember it is a compound statement, it is not a single statement
stmt 1 is suppose to be a compound statement.
59
So, compound statement 1 will be executed, if the expression evaluated false, this
statement 2 will be executed. So, stmt 1 and stmt 2 are usually blocks of code need not
be just single line code, even though single line code is acceptable need not be single
statement code.
So, the else part is completely optional and that is what we have, when we have this
square bracket here. Square bracket is not part of the syntax, it is just to tell you that
everything that comes with in square bracket is optional here. So, if there is no else part
in the if statement, this becomes a single selection statement and if the expression
evaluates to true statement 1 will be executed; otherwise, the statement would have no
effect.
60
(Refer Slide Time: 03:43)
So, what it really does in terms of execution of programmers, let us say I do not have any
of these selection statements, you have series of simple statements. If you start from, let
us say line 1, you will do line 2, line 3 and so on up till line n, the last line in the program
and exit. So, this is a sequential structure whereas, once you have selection statements, it
could change the flow of the program.
So, let us say there is some piece of code here in this blue dot here and you evaluate an
expression, which is indicated by the diamond if the condition evaluates to true, you
execute the block of statement in the true branch, and if the condition evaluates to false,
you do not do anything and either way after executing this you come here or if the
condition is false you come here. So, this branch that you have here over the two case
will not happen if the expression evaluates to false and it will goes to the blue dot.
So, this is the single selection statement, you choose to execute this true block or not
depending on the expression here. Then, there is this other type which is double selection
statement, you have a body of code here and after that evaluate an expression. So, this
body of code could be scanning something from the user or some other piece of program
that you have written before, you evaluate an expression. Now, there are two choices
either true or false, if the condition evaluation to true you executed the right block, if the
condition evaluation to false you executed the left block. And once you execute one of
61
these blocks, you come and execute these blue block of code which is after the if
statement. So, this is the general structure of if than else.
So, let us look at several examples to now. So, starting with the example 1 where there is
no else clause. So, let us say the problem that I want you to solve is given a number find
out if it is a multiple of 3. So, I give you number x and you have to find out whether it is
multiple of 3 if it is multiple of 3, print that it so on the screen. So, the equivalent code
segment would be if x percentage 3 equal to equal to 0 printf x is the multiple of 3.
So, this printf is a simple statement here and this if is a compound statement, which
contains only one selection. If the condition is true, it will print this on the screen, if the
condition is false it will not do anything. So, remember this percentage as the modulo
operator that are introduced in lecture 1. This percentage 3 means, we are looking at x
mod 3 and this is something that is new here, which is this equal to equal to.
So, this is equal to equal to is essentially an operator in C which checks for equality, a
single equal to symbol is useful for expression evaluation. So, we did this in the previous
lecture, so p 2 equals something, p 1 something and so on. So, evaluate something on the
right side and assign it to the left side, but if you have two equal to symbols right after
the other, that is checking for equality. So, the meaning of this expression is, if x modulo
3 is equal to 0, then do this, else there is no else statement here. So, if this condition is
true, the statement will be executed.
62
(Refer Slide Time: 07:22)
Let us move to another example, where there is no else clause, but it is a compound
block. So, if the given number is multiple of 3, let us say you want ask the user for
another input. So, if x percentage 3 is 0 you not only want to tell the user that x is the
multiple of 3, you also want user to enter another number and you should able to scan it.
So, if x percentage 3 equal to equal to 0, so then block is between this and this brace.
So, printf x is the multiple of 3, please enter another number. So, you are prompting the
user to enter another number, since the user is expected to enter another number, you
have to scan it, scan of percentage d percent x. So, this is the collection of two simple
statements that goes into a code block and if this condition is true, you have a logical
collection of statements here are a logical sequence, we not only want to print it, we also
want to user to enter another input. So, you need a brace because there are two simple
statements inside.
63
(Refer Slide Time: 08:30)
So, there is a little bit of warning that I want to give here, let us say I want to solve the
same problem, but I left out the braces that is what you seeing here, if x percentage 3 is 0
then I have left out the brace here and here. So, from the view of the compiler, the if
statement x percentage 3 equals to 0 printf. That means, instead of becoming a
compound statement with two lines of simple statements, it instead becomes one
compound statement of if only this printf is evaluated if this condition is true and this
scanf statement is executed unconditionally.
So, what I mean by that is this printf will happen only if x percentage 3 is actually 0 or if
x is a multiple of 3 and this scanf will happen no matter x is modulo 3, x is modulo 3 is 0
or not. So, scanf statement is actually outside the if condition, even though it is formatted
in such a way that is printf and scanf seems to be inside the if block, the compiler does
not care about the spacing that you given here, this scanf is actually is outside the if
condition here.
So, the result would be the user will have to enter a number, even if x is not a multiple of
3 and that is not what we want, we wanted the user to enter a new number only if x is a
multiple of 3; otherwise, we want to do something else with that.
64
(Refer Slide Time: 10:05)
So, the simple thumb rules use the left and right brace to enclose if and else blocks. And
I typically use it, even if there is only one single statement inside, this will save you
several headaches the program can becomes slightly unreadable, because you have two
many of this left and right braces. However, it will cause you lesser headaches. So, that is
why if you go back to my lecture 1, you will see that is if then else clause that I had, even
though there were only single statement inside always enclosed them within braces.
65
Let us look at another example with an else clause here. So, if the given number is some
multiple of 3 ask for another number; otherwise, let us say I want to thank the user. So, if
x percentage 3 equal to 0, then you print this on the screen and you scan the another
input. So, this is one logical condition, now you have multiple selection though or double
selection else. So, if this is not true, you printf thank you, so this is the structure for else
and we have seen this before in our lecture 1 also.
So, one word of caution here, so just because you are scanning here it does not mean that
the user would enter number, which is not a multiple of 3, the user may still enter a
number which is multiple of 3, we are not checking here. So, I want to show this
program just show the structure of, if then else. So, at the end here x could still be a
multiple of 3. So, you need the mechanism by which you repeatedly scan the input here,
till the user input something else, if that is what you want. In this case we are not
checking for that, I just want to show you the structure of if then else.
So, there are two other useful collection of if statements. So, one that you see on the left
side is what is called a cascading if else and one that you see on the right side is a case of
what is called nested if else. So, in the cascading if else case what you have is, if
expression 1 evaluates to true, you execute the block of statements. In the else clause you
actually evaluate another expression, else if expression 2 evaluate you execute another
blob of code, else if expression 3 execute another blob of code and so on.
66
You can have a series of this else if statements, ending with a final else clause where
execute another blob of code. So, this is called cascading if else, we will see a quick
example later, the other cases nested if else. So, we have if expression 1 do a blob of
code, if expression 2 do a blob of code and so on. So, what happen here is, this blob of
code or this blob or this blob only one of them will execute depending on the conditions
for expression 1, expression 2 and so on.
So, if expression evaluates to true, this blob of code will execute and none of this will get
executed. If expression 1 is false and if expression two is true only this blob will get
executed and so on. So, in this cascading if else case, there is only one blob which will
really execute not all of them. However, if both expression 1 and expression 2 are true,
both this blob and that blob will be executed here.
So, what is really happening is, if you notice if expression 1, there is a curly brace here
and this curly brace here, between these two curly braces is iif expression 2, which
means this is the compound statement, which is within another compound statement. So,
this is completely contained within the evaluation of this expression. So, therefore, this
blob of code as well as this statement will be executed if expression 1 is true.
So, let us see an example of cascading if, let us say I am grading your course and let us
say this is my grading policy, if you get below 50 marks in the exam you get a D grade,
if you get between 50 and 59 you get C, if you get 60 to 75 you get B and 75 and above
67
will be A. Let us say this is my grading policy for the course, then I have the integer
marks and I have the character grade. So, marks is, something that you are going to enter
and grade is something that I am going to assign from the program.
So, you see this structure here, if marks is less than or equal to 50 then grade is D. So, if
it is less than or equal to 50 I assign the grade D, else if marks is less than or equal to 59
then grade is C, else if marks is less than or equal to 75 grade is B else grade is A. So,
what is happening is, we are seeing only one of these will be true, if you enter the mark
here only one of these things will be true and only one blob of code will execute, either
you will get grade equal to D or C or B or A, you can never have a case were more than
one assignment happens.
So, if my mark is 40 then this if condition is true, then the grade would be D, if the mark
is 65 then if this expression will evaluate false, this expression will also evaluate to false,
this expression will evaluate to true, you will get grade B. So, this is the structure of
cascading if and these are simple statements; therefore, I have not put braces, but putting
braces is always recommended. So, there should have been brace here and brace here,
there should have been brace here and brace here and so on.
And nested if, we saw this segment earlier, if A is greater than B and if A is greater than
C printf A is the largest else pritntf C is the largest. And there is some other blob of code
that comes as else, so I am just showing one segment of the maximum of three numbers.
68
So, if you are in doubt go back to the lecture 1 and the look at the code, we already used
the nested if statement inside the code there.
You should be a bit cautious about the use of the else clause. So, let us say I did this, if
marks is greater than 40 and within that if true condition, I have if mark is greater than
75 printf you got distinction, else printf sorry you repeat the course, let us say this is
what we have. So, what we really want to do is, if somebody got less than 40 we want to
tell them that they have to repeat the course. But, let us see what happens here.
So, if we start with this if statement, there is an expression here marks greater than 75
printf you got distinction, if marks is less than 75, even if it is greater than 40 what
happens is, this else clause attaches itself with this if clause, and not with this if clause.
So, else always attaches itself to the nearest statement, nearest if statement, without the
else clause.
So, because of that what happens is, let us say my mark was actually 65 it is not greater
than 75. So, I do not get distinction, however I will be asked to repeat the course which is
not what we intended. What we really intended is this, if marks is greater than 40 and if
the marks is greater than 75, printf you got distinction and the else should have been for
this if condition. So, since else always attaches itself the nearest if, not having this brace
here even though it is one single statement here, not having this brace here would result
in something that you do not desire to have.
69
So, be cautious about this that is why I said, whenever you have an if condition, if you
automatically put braces and for else also if you automatically put braces none of these
confusions will occur. So, go back to my basic thumb rule, if true block should have
braces, else false block should have ((Refer Time: 17:49)) block should have braces by
default and that will help in getting rid of all this headaches.
So, finally, let us look at the switch statement which is a multi way decision statement.
So, in a multi way decision statement it is not a single decision or double decision, it
could be more than two decisions. The basic syntax is you have this key word switch
followed by an expression, which is within parenthesis and you have braces. So, again
this is a block of code. So, switch is a compound statement and it can contain several
cases.
So, you have case constant expression statements, case constant expression statements
and so on, you can have several cases and you can also have an optional default clause
and an optional default. So, essentially what happens is, if we have multiple choices
depending on the value of expression, whichever evaluates to true those statements will
get executed. If none of these expressions evaluate to true, then the default clause will
kick in and this set of statements will get executed. So, let us see examples.
70
(Refer Slide Time: 18:58)
So, let us say I have a character c and I read this character from the user, if the character
is one of R, B and Y, I want to print something on this screen. So, if the user input capital
R as an input I want to printf RED, if the user inputs B I want to printf BLUE, if the user
inputs Y I want to printf YELLOW. So, if we look at this there is a switch c followed by
braces followed by one or more case statements, the case then there is a constant
expression here R, B or Y followed by a colon and there is a statement here, so that is the
structure here.
So, switch followed by expressions and there is something within the curly braces, you
have case constant expression colon statements. So, you can see that this program is
following that structure. Now, let us see what this constant expression is about, so this R,
B and Y are not variables. So, when you put something within single quotes, you are
actually looking for the character R or character B or character Y these are not variables
R, B, Y.
So, remember variables are those that can change value during the execution of the
program, whereas these are constants they cannot change their values during the
execution of the program. So, c is a variable, but within quotes R, within quotes B and
within quotes Y are all character literals they cannot change their values. So, what you
are looking for is, if c the character that you got from the user is one of these, then one of
these statements should execute.
71
The other thing that is new here is, this notion called break. So, what happens with this
switch clause is, if R is true then it prints RED and you put the break. So, that you do not
want any of this other printfs to execute or even the conditions to be checked. So, if you
have printf RED and you print the RED and your now out of the switch clause if you put
a break here, so this is something that is new. So, these are the choices that you put in R,
B and Y and this break, breaks away from the switch statement to outside the switch
statement. So, it starts executing a block of code after the switch statement.
So, let us look at another example where we want to handle both lower and upper case
choices, maybe I as a user enter lower case or upper case, I want to handle both of them.
So, this is also something that you can do with switch statements, so these two lines are
as before, this is also as before the changes in these three lines here to here. So, if you
look at case R, R case r the lower case r in both these cases we want to printf RED.
So, I could write this as case capital R printf RED break, case small case R printf RED
break and so on. But, we are executing the same set of statement for both these choices
small case r and upper case R. So, therefore, if we have a series of choices for which you
want to execute the same set of statements, we can just put them back to back and have
this set of statement exactly. The same thing we have here for both upper case B and
lower case b we want to print blue.
72
So, therefore, this set of statements is common for both upper and lower case B,
similarly this set of statements is true for both upper and lower case Y. So, one thing that
you should also notice here is, the last statement here does not have a break. Because,
any way it is the last condition you are checking automatically after checking these
conditions if both these are true, it will execute this and come out of this switch, if these
are false you will any way come out of this switch statement a break is not required in
the last choice of case.
So, there are two things that we have to watch out in the switch cases. So, one warning is
that variables cannot appear as choices. So, for example, let us say I put character char 1
is r and character char 2 is B and case char 1, case char 2. So, remember the expression
that you have here should be a constant expression whereas, you have use a variable
called character 1. Even though character 1 is initialize to a literal r here you are putting
a variable character 1 and that is not acceptable. So, c only allows constant expressions
to be in the choices, you cannot put variables of any kind here. So, this is an incorrect
program segment, so if you want go and try it writing this in a program, you will see that
the compiler actually indicates an error.
73
(Refer Slide Time: 23:38)
There is another thing that you have to be warned about namely using ranges. So, for
example, let us say I want to give these grades and for 0 to 49 I want to give D, for 50 to
59 I want to give C, for 60 to 74 I want to give B and for 75 to 100 I want to give A. Let
us say I want that, you cannot do something like this, case 0 dash 49 printf D or 50 dot
dot 59 things like what you would do in when you write in paper, you may say 0 dot dot
49 or 50 dash 59 and so on. Those things are not acceptable also you cannot provide a
range of values in a constant expression, it has to be a single value for this case clause.
So, whenever you have multiple values you have to have multiple cases. So, that is why
we had this here, there were multiple values R and r for which we want to printf RED,
we actually have multiple cases also specified explicitly. So, in summary we have a
switch statement which has multiple cases and for each of these cases it executes the
corresponding block, if there is a break it will break away from the switch statement, if
there is no break will go and evaluate the next cases also. So, this is not a valid way to
give grades you should use the if then else clauses here, I already showed you this in the
earliest slide. So, with this we had the end of module.
74
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 07
Lecture – 07
Contents
Repetitive statements or loops
Counter or sentinel
For loops with examples
Logical operators-AND, OR
Nested for loops
While loops with examples-GCD of two numbers by euclid's algorithm
Do-while loops with examples
Where to find the IDE Dev C++ (Sourceforge)
So, in module three, we will look at reparative statements. So far we have seen
conditional statements both if and select using switch statements. We will look at
repetitive statements.
So, repetitive statements are also called loops. It forms a very important class of
statements, because you get to iterate or repeat over a set of operations. This is useful in
lots of algorithms and it is a very basic construct. So, C offers three different kinds of
repetitive statements namely for statement, the while statement and the do while
75
statement. We will see each one of them and see motivating examples for each one of
them.
So, in any loop construct, no matter what language you pick, there are two different
kinds of loops that you can run. You can either have them counter controlled or what is
called sentinel controlled. So, in counter controlled, what you have is you have a set of
operations and you want to repeat that a certain number of times. And this number of
times, you have to repeat; it is already known to you. Whereas, in sentinel controlled,
you keep looping until a certain condition is met. So, for example, you may wait till the
user inputs minus 1 and you want to do a repeated set of actions till the user inputs minus
1; maybe you want to find the factors of a number; but, the user could keep on giving
these numbers. At some point of time, if the user inputs minus 1, you want to stop the
program. So, if you want something like that, upfront you do not know how many inputs
the user is going to give. In such cases, the sentinel controlled structures are useful. And
this structure is usually used when the number of iterations is dependent on the input and
not on the problem being solved.
76
(Refer Slide Time: 01:57)
So, let us start with the first one namely, the for loops. So, for loops is actually the… For
loop construct is a counter controlled repetition structure. What you have is you start
with an initial value of some counter and you modify the counter as we go along till you
reach a final value. So, maybe I start with number 1; go in steps of 2. And let us I stop at
43; in that case, I would be using all the odd numbers. So, I am going in steps of two
from 1. So, I would go 1, 3, 5, 7 and so on up till 43. Or I may say just for 20 times print
hello. In that case, I would start the initial value at 1; the counter would go by increments
of 1 and it stops at 20. So, this for loop – the way C does; it lets the programmers specify
an initial value, a modification to the counter as well as a final value; and on top of that,
it also lets you put in a body or what you would really want to do so many times.
77
(Refer Slide Time: 03:03)
Say let us look at the basic expression. You have a for loop and it starts… it works like
this. So, you have for, which is a key word. And you have these two parenthesis and you
have three expressions: expression 1, expression 2, and expression 3. And the body of
the loop goes inside this statement here. So, the meaning of this is you evaluate
expression 1. So, the expression 1 is just the initialization. And you repeat something.
And what you repeat? You evaluate expression 2. If it is true, you execute statement; if it
is false, you exit the loop. So, when you execute the statement, you check if the
expression 2 is true; if it is true, you execute the statement and evaluate expression 3. If it
is false, exit from the loop. Typically, the statement that you have is a block of code.
Therefore, usually, you have to put this within braces. So, you need braces here and here
typically, because that makes a code block.
78
(Refer Slide Time: 04:23)
So, let us take this small example computing the sum of first 20 odd numbers. So, to do
so what we are going to do is we are going to have a variable called sum, which is
initialized to 0; and to that, we are going to add k repeatedly; and k is just going to be a
sequence of odd numbers starting at 1. So, let us see the loop structure in place. If you
notice the structure of the for loop, the for loop runs from i equals 1; i less than or equal
to 20 i plus plus; which means this body that you are seeing here is going to be executed
exactly 20 times. So, let us check what the overall structure of the program is. We set k
to be the first odd number, which is 1; and i is the loop iterator or the loop control
variable; and i less than or equal to 20 is the termination condition. Since we are
incrementing i in terms of 1 using i plus plus, this loop will run exactly 20 times. And the
body of the loop is in such a way that, k tracks the i-th odd number; the very first
iteration k is 1; and the second iteration – k is supposed to be 3 and so on. It tracks the i-
th odd number and we are adding that to sum. And we are incrementing k in increments
of 2; which means from 1 we will go 3, 5, 7, 9 and so on. And this is the whole setup.
Let us say we want to mentally simulate this; so initially, sum would be 0 and… Initially,
sum is 0 and k is 1. We start the loop; when we start the loop i equals 1 and we are going
to check if i is less than or equal to 20; this is actually true. So, therefore, sum plus equal
to k. So, this plus equal to is essentially a shortcut for sum equals sum plus k. So, sum is
0. Now, we are adding 1 to it. So, sum becomes 1. And k – we are adding 2 to it. So, k
becomes 3. At the end of it, we go and evaluate expression 3, which is i plus plus. So,
79
now, i becomes 2. So, this is one body of the loop. At this point, we again go and check
is i less than or equal to 20 or what; it is true. So, now, we add sum plus equal to k. So,
we add 1 and 3; sum becomes 4 and k is incremented by 2. So, k becomes 5. And finally,
at the end of this iteration, i becomes 2 and so on. So, from… So, essentially, what we
are doing is we are incrementing i and we will do this exactly 20 times. So, we will go
from number 1, 3, 5, 7 and so on 20 times. So, it is a fairly simple program. But, it shows
you the basic structure of a for loop.
So, let us take a small detour and look at what are the different kinds of operators that
you can use. We already saw equal to equal to in an earlier lecture. We also saw greater
than in the lecture, where we are looking at a largest of the numbers and so on. There are
other operators namely, not equal to or less than or equal to, greater than, and so on. So,
these are the six operators that you can use on numbers. So, you can use them on both
integers and floating point numbers.
80
(Refer Slide Time: 07:46)
But, more importantly, we also need to understand this notion of what are called logical
operators. So, in all these things that we have seen so far, we have only one condition for
the expression that we are evaluating. Sometimes we need more than a few conditions.
So, let us look at this example; where, let us say if age is less than equal to 45 and salary
is greater than or equal to 5000, I want to do some work. So, I cannot… So, I want to
combine this into one condition and you can do this using this operator called AND. The
AND operator when you type, you type ampersand ampersand; and be careful; it is
twice; it is not a mistake.
We have to type ampersand twice. That is a logical AND operation. Sometimes I may
need something like a logical OR condition; either condition a is true or condition b is
true; in which case, we want logical OR operation. And you get that using what is called
the pipe operator. So, you press… you have this pipe twice; you usually see this pipe on
top of the back slash symbol. So, back slash symbol and pipe are in the same key
usually. And for example, if I want to see if a number is a multiple of 2 or a multiple of
3; then I could use this condition num percentage 2 equals 0; checks whether it is a
multiple of 2; num percentage three equal to equal to 0 checks whether the number is a
multiple of 3. And by sticking in this OR operator in between, I am checking if a number
is either a multiple of 2 or a multiple of 3. So, this is a very useful thing; and we may use
it in the next few lectures.
81
(Refer Slide Time: 09:26)
So, I want to give another example of a for loop. Let us say I want to print the n-th
triangular number. So, in the previous example, the number of iterations was actually
known in the program itself. So, we had i equal to 1; i less than or equal to 20. So, I said
whenever we will know the number of iterations, it is better to use a for loop. But, I want
to show an example here; where, we really do not know the number of iterations,
because the user is going to input it. But, we still know that, once the number of
iterations – once the user gives n, the number of iterations is fixed. So, let us see this
small example. Find the n-th triangular number; a triangular number is defined as a sum
of integers from 1 to n. So, I am showing a small segment of a code here. Let us say i as
before is the iterator and number is the number of the n-th triangular number that the user
is asking for; and sum is the result that we want to give to the user. So, we start with
prompting the user what is that triangular number that you want; and we are going to
scan it from the user; we will start with sum equal to zero. So, at this point, I want to run
it as many times as the number that the user wanted.
So, now, what we have is we have for i equals 1, i less than or equal to number i plus
plus. The key thing that I want you to notice is that, this number is not a constant. In the
previous example, we had it as a constant; now, it is not a constant. But, the moment this
is received from the user, the number of iterations is fixed and that makes it suitable for a
for loop. So, for i equals 1, i less than or equal to number; i plus plus – sum plus equals i.
So, the way this is going to work is you are going to check if i, which is equal to 1 is less
82
than or equal to the number or not. Let us say the number that the user inputs is 5; 1 is
less than or equal to 5; you will add 1 to sum; then i gets incremented to 2; 2 is less than
or equal to 5. Then 2 is added to sum and so on. So, you will add numbers 1, 2, 3, 4 and
5. And the result would be 15. So, the fifth triangular number is 15; that would be the
print out of this program.
Let us look at another example, where the user is going to ask for this multiple times. So,
the user is going to ask for sum n-th triangular number. But, he wants five such numbers.
So, one way in which you can do this is you can ask the user to run the program five
times. But, that is not the most desired way to do it. Instead, if we know upfront that, the
user wants five triangular numbers; but, each time, the user may ask for a different
triangular number. And that is what we have here. So, we have a for loop here and this
for loop is going to take care of the fact that, we are going to ask for five numbers from
the user. And within each body of this loop outside, we have this finding the n-th
triangular number.
So, we are doing five iterations of asking n from the user and finding the n-th triangular
number and printing it. And if you notice, we have this blue body, which is on this outer
for loop; and this orange body, which is the inner for loop. So, what we have done is we
have nested a for loop inside another for loop. And this is also a very useful thing to do.
So, we are having this outermost loop running on counter and the innermost loop running
83
on i. So, we have two iterators. And what this program does is or what this code segment
does is – it is going to ask the user five times for what is the triangular number that they
want. And each time once the number is entered, it will find the triangular number and
print it out. So, I suggest that, you go and write this code and see what happens.
So, let us move on to the next construct, which is the while construct. So, the general
form of the while construct is while expression statement. And as before, the statement is
a block of code. So, typically, you put that within braces. The semantics is that, you
repeat this following process; you evaluate the expression first; if the expression is true,
then you execute the statement; if the expression is false, you exit the loop. So, in the for
loop, exiting the loop is known upfront; you run it for a certain number of iterations and
you exit the loop; whereas, in while loop, you exit once the expression becomes false;
which means the expression must change inside the loop. If the expression does not
change inside the loop, you would end up within infinite loop, because the expression
would always be true; we will keep executing the body of the loop infinitely.
84
(Refer Slide Time: 14:25)
Let us see a small example. Let us say I want to just print the 5 integers – the first 5
integers – the first 5 positive numbers. So, I can do this with a for loop running from 1 to
5 and printing it. I am showing a small code segment, which does something different.
So, I have int count equals 1. So, right then we declare the initialized count equal to 1.
This is possible in C. Then while count is less than equal to 5; print count and count plus
plus. So, the key thing that you have to notice is that, this count that we have here is
actually changing. If we do not have this, count would be 1 forever while 1 is less than
equal to 5; print count. We would be printing 1 in an infinite loop. However, this count
plus plus is changing the value of count. So, the next time you would check if 2 is less
than or equal to 5; then you would check if 3 is less than or equal to 5 and so on. At some
point, count would become 6; 6 less than or equal to 5 would become false. At that point,
you exit the loop and you move to this location. You exit the while loop. So, this is a
natural use for a while loop. So, this can be returned using a for loop; but, this is a simple
example to show how the while loop works.
85
(Refer Slide Time: 15:45)
Let us see a more concrete example; where, the number of iterations is actually not
known. So, the problem that I am going to pose is finding the greatest common divisor of
two positive numbers. Let us say the user gives two positive numbers. Let us also assume
that, m is greater than n. There are two numbers: m and n that are given to you. And let
us assume that, m is actually greater than n. If it is not, you can ask the user to give it in
that order or you could change m and n, so that m actually become greater than n. That is
not the crux, but I want you to look at this notion of GCD. We are going to use an
algorithm called the Euclid’s algorithm. And this dates back to 300 years before BC. So,
it is a very old algorithm and it is a very neat algorithm. So, if I want to find out GCD of
two positive numbers, then GCD of m comma n is the same as GCD of n comma m
percentage n; where, percentage stands for modulo.
So, I have two examples below to show that. So, let us say I want to find out GCD of 43
and 13. See in this case, m is 43 and n is 13 and m is actually greater than 13; so the
condition that we want to satisfy. We start with 43 percentage 13. So, 43 percentage 13 is
4, because 13 times 3 is 39. So, 43 divided by 13 gives a reminder of 4. And now, you
take 13 and the reminder 4; you do 13 percentage 4; that is 1. And then you take 4
percentage 1; this is 0. So, at this point, you stop and you report this as the GCD. So, 43
and 13 are both prime numbers. There is no common factor. So, the common factor is
indeed 1. Therefore, 1 is the GCD.
86
Let us look at a difference example; let us say m equals 96 and n equals 28. We start
with 96 percentage 28. So, 28 times 3 is 84. Therefore, 12 is the reminder. Then you take
28 modulo – the reminder that you got in the previous iteration. 28 percentage 12 is 4;
and 12 percentage 4 is 0. And therefore, 4 is the GCD. So, even though in both these
examples, we do know that, the number of iterations is 3 because we have worked it out;
upfront you may not know what is the number of iteration that is required? So, given two
numbers, you do not know the number of iterations that is required upfront. In such
cases, the while loop is a natural choice.
So, let us see this basic code. We start with if m is greater than n; if m is greater than n,
GCD of m comma n is GCD of n comma m percentage n. This is what we wanted to do.
And the code segment is as follows. So, if v is already 0; then there is nothing to do;
however, otherwise, what we do is we have a temporary variable called temp; we find
out u percentage v, which is u modulo v; that goes as temp. And once you do that, u
becomes v itself and v becomes temp; which is what we did here. So, it is called m and n
here; it is called u and v in the program. So, what we did was we took the reminder and
we will call it v. And take the previous divisor; call it u and we repeat this. At the end of
it, v will become 0 at some point of time and you report u as the GCD.
So, now, let us pay attention to the construct itself and not the logic of the program. So,
what we have is we have while v is not equal to 0. So, we keep doing this till we violate
87
this condition. And we have two braces here indicating that, this is a body of the loop.
And this body of the loop – upfront we do not know the number of iterations; we can see
that, there is nothing, which is doing an iteration count; however, we are hoping that, v
will become 0 at some point of time. So, v does not become 0 at any point of time. This
while loop will become an infinite loop; but, because of the property of the numbers and
the operations that we are doing here, v will indeed become 0 at some point of time and u
is the GCD of two numbers. So, again I suggest that, you go and try the different
examples and ensure that, this is actually correct; we go and verify that, this algorithm is
actually correct. So, finally, I give one last example for a while loop.
So, again in this, at this point, we do not know the number of iterations that we are going
to execute. So, the problem is finding the reverse of a positive number. So, if I want the
reverse of the number 234, it is 432. So, I need three iterations to find out the reverse.
But, I could give a number, which is 19574 and this would be a five digit number; you
need five iterations. In general, if I give you an n-digit number, you need n iterations;
upfront you do not know the number of digits that the user is going to give. So, the
algorithm that we are going to follow is as follows. So, till the number becomes 0,
extract the last digit of the number by finding out the number modulo 10. See you take a
number; find number modulo 10; that gives you the last digit. Make it the next digit of
the result by multiplying the result by 10 and adding the current digit. So, I will show
you simple example to make this clear.
88
(Refer Slide Time: 21:38)
Let us say I want to… Let x be the given number and y be the number that is being
computed. Let us say the user input 56342 as the number, which has to be reversed. So, 2
is the first digit of the resultant number. We want 2 to be the first digit of the resultant
number. The way we do that is we take 56342 and do modulo 10; that will get the
reminder 2 alone. It will extract only the last digit. You take that; multiply the previous
one by 10 and add this to it; that will result in 2. And what you do is you take 56342;
divide that by 10 to get 5634. What I mean by divide is you get only the quotient and not
the fractional part. So, 56342 divided by 10; the quotient is 5634. At this point, you do
percentage 10 again; you will get only 4. You take 4 and add it to two times 10. The
number is 24. So, so far, we have taken the last two digits and reversed it. Then you take
5634 itself and again divide it by 10 to get 563 and so on. If you keeping doing this, at
some point, you get 24365, which is the reverse of 56342. But, at that point, x becomes 0
and it is a termination condition. So, you will keep doing this till x becomes 0; and this is
a natural way of using a while loop.
89
(Refer Slide Time: 23:19)
So, let us look at the program itself. We have x equals 0 and y equals 0; and you expect
the user to give an input number. So, input an integer and you scan it from the user. If the
user inputs 0 itself, you check the condition while x is greater than 0. Let us say the user
gives 0 as the input; then the while loop is not even executed once; you can directly go
and print y equals 0. So, we also assume that, the user inputs only positive numbers or 0.
So, if the user inputs 0, this while will not be executed. Therefore, the reverse number is
0 itself. However, if the user inputs any number greater than 0; then we have y equals 10
times y plus x mod 10. So, you remember the evaluation order; we have parenthesis here;
this will be evaluated first; and that is doing modulo 10 of x; that extracts the last digit,
but it adds it to 10 times y. And x itself is diminished by a factor of 10. Remember – x
being an integer – the integer division, you have integer divided by integer; that gives
you only the quotient and it truncates the fractional part. So, x keeps diminishing by a
factor of 10 every time. At the end of it, you have x equals 0. At that point, this while
condition is not true anymore; when it becomes false, you come to the end of the loop
and you print the reverse number. So, this is a full program segment. If you type it in
your editor and compile, it should work.
90
(Refer Slide Time: 25:10)
Let us move to the third construct, which is a do-while construct. And the general form is
do statement – while expression. The semantics is as follows. Execute the statements and
then evaluate the expression. So, the key concept is that, you execute it once and then
you evaluate the expression. If the expression is true, you re-execute the statement;
otherwise, exit the loop. So, this do-while construct is different from the for and while
construct. The for and while constructs – both check the condition once even before
executing the body of the loop even once. However, in do while, you execute the loop
body at least once before you go into checking the condition.
91
So, let us look at a specific example, where do while is natural. Let us say I want the
input numbers to be of a specific kind. So, I showed you an example earlier; where, we
were checking if the number that is input by the user is a multiple of 3 or not. If it is not,
we prompted the user to give this input once more and we scanned it. But, we had no
guarantee that, the user actually input a number, which is a multiple of 3; which means
we have to repeatedly keep asking the user till the user actually inputs a multiple of 3.
So, this is a natural thing to do with a do-while construct. So, let us see what is
happening in this setup here.
Let us as before, look at the logic first and then we look at the structure itself. So, the
very first time you have a do, there is nothing to check; you prompt the user by printing
this on the screen; and the user is expected to enter a number. Let us say the user inputs
minus 5; then after this, you go and check the loop. So, now, we see an example of
ampersand ampersand or the logical AND. So, while x is greater than 0 and x percentage
3 not equal to 0; so the user let us say entered minus 5; minus 5 is not greater than 0. So,
this condition evaluates to false and this condition also is actually true. So, minus 5 mod
3 is not equal to 0; however, since this evaluate to false and that evaluates to true, the
overall expression evaluates to false. Therefore, the user has to enter another number. At
this point, you go back here and there is nothing to check; you go and print this on the
screen once more; you scan the user input once more.
Let us say the user now input 9. If the input is 9, x greater than 0 would be true; however,
x mod 3 would still be 0. Therefore, this while condition is still false; you would go back
and do the print f statement. Let us say now the user inputs 5. So, we did minus 5 first;
we did 9 next. Let us say the user inputs 5 now. And at this point, 5 is greater than 0 is
true and 5 mod 3 is not equal to 0, is also true. Therefore, 5 is a valid user input. At this
point, the condition evaluates to true; and therefore, you stop here. So, you keep doing
this till a certain condition is satisfied. So, let us look at the construct itself; we have the
key word do followed by a block of code; and the body of the loop is this one. So, that is
the overall structure for the setup here.
92
(Refer Slide Time: 28:54)
So, let us look at this in summary. In the for loop, what we have is we start with an
initialization condition; we first check an expression; if it is true, you evaluate the body;
you do some loop increment and go back. And when you go back, you have to evaluate
the expression once more. At some point, this loop increment that you are doing will
violate the expression; at that point, you exit; and that is the false branch. If you look at
the while loop, the while loop starts with some body and it checks the expression first; if
it is true, you execute the loop body and comeback and evaluate the expression; if it is
false, you exit the loop. So, the key thing to notice is that, this body that you have here
must contain something that will change the evaluation of the expression. If it does not
change the evaluation of expression, you will keep doing this loop forever, which we
want to avoid.
And this picture clearly shows how do while is different from both these. We have a
body of the loop that is executed even before the expression is checked once. So, once
you execute the body, you then check expression; if it is true, you go back and execute
the body once more; if it is false, you exit the loop. And these are the three basic
constructs that is available in C. And depending on the problem, you need to pick one
appropriately. So, in summary, for loops are used whenever you know the number of
iterations that you have to run the body of the loop; a while loop is used if you want to
check on a condition at least once even before you get to the body, but you do not know
the number of iterations that you need upfront; and do while is used if you want to
93
execute a body at least once even before you check the expression, but you do not know
the number of times the body is supposed to be executed.
So, there are two ways to change the loop behavior. There are two key words namely,
break and continue. I am not talking about this in this module; we will see this later as
we see the need for them. So far, I have been using dev C plus plus to show you program
segments; I promised earlier that, I will actually point out where you can get this dev C
plus plus.
94
It can be found at sourceforge website – at sourceforge dot net slash projects slash
orwelldevcpp. It is roughly about 42 mega bytes in size; you need to download the
compiler, the debugger, the graphical environment and so on. It takes about 42 MB in
size. I suggest that, you download and start using that. And it takes a few minutes to set
up and not more. So, that is a very useful thing to download. So, of course, there are
several other development environments; if you are already using some IDE, go ahead
and use it. If you are coming from the Linux world, you are probably used to the shell
and you very likely have a GCC or some such compiler installed already; you can go
ahead and use those also if you are familiar with them. But, since I am showing a demo
using dev C plus plus, it may make sense to actually use it, so that you can track what
you are doing. So, this is especially useful if you are new to programming.
I suggest that, you actually practice the problems that I have given so for. At times, I
have given complete problem statements; at times I am giving only code segments; but,
you have to go and write a complete program. You write complete programs whenever
you can. And whenever it is needed, run the programs; test with your own inputs and
ensure that, the programs that I have return so far are indeed correct. So, it is possible
that, I have made some mistakes somewhere along. So, it is better that, you go and check
these answers once before you move on to the next lecture. So far, in this week, we have
seen basics of programming in C; we saw the notions of variables; we saw the if
statement; we saw the switch statement; and we saw for while and do while statements.
We also saw basic operators and how to print and scan inputs. From next week onwards,
we will see more sophisticate uses of these constructs in solving different kinds of
problems. So, we are at the end of lecture 2.
Thank you.
95
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module - 01
Lecture - 08
Solution: Digital Root Programming Assignment
Good morning all. So, I want to do a small problem session. So, there was this problem
called digital root of an integer that was given as part of the assignment, and I thought I
will show you how to solve it live. So, we will talk about the solution as we do it on
paper and pencil, and then we will see how to translate that to a program. So, I am going
to take five examples here namely, 1233, 1234 and so on. And I will show you what
really happens in each one of them.
So, the digital route is defined as sum of all the digits in a number. So, for 1233, it is 1
plus 2 plus 3 plus 3. So, in this case, the submission is 9. And once you hit a single
digital number, we stop there. So, 9 is a single digit number; we stop there; however, for
1234, it is 1 plus 2 plus 3 plus 4. So, in this case, the result would be a 10; but we do not
stop it there, because 10 is a two digit number. We take 1 and 0 and add it together; we
get 1. So, 1 is the final answer for this. Let us take this example 65536; so 6 plus 5 plus 5
96
plus 3 plus 6. So, that is 6 plus 10 – 16, 19, 25. So, this is 25. So, 2 plus 5 is 7; that is
already a single digit number; we can stop. Then, I have taken this slightly larger
example – 9 plus 1 plus five 9’s. So, that is six 9’s, which is 54 plus 1; which is 55. But
now, you add 5 and 5 together; you get a 10. We cannot stop here. In the previous cases,
you could have stopped slightly earlier; but now, we cannot stop.
Now, add 1 plus 0 together; the result is a 1. And this last example, where we have 9-6
followed by six 9’s. And that would be seven 9’s, which is 63 plus 6, which is 69. Now,
you add 6 plus 9 together; that gives you 15; and 1 plus 5 together; which is 6. So, when
we hit a single digit number, we can stop. So, this is the definition of a digital root. And
what I have done is it looks like I can see 1233 here and I can extract the digits. But you
cannot do this in a program like this. So, instead, what I am going to do is I am going to
show you how to write a program for this.
So, let us look at 1233. What do we need for 1233? I want to extract one digit at a time.
And to extract one digit, I am going to use two operators namely, the div operator or
slash and mod operator or percentage. So, if I do a percentage 10 of this, I get 3, which is
the last digit; but if I do a slash 10 of that or divide by 10; so that is an integer; 10 is an
integer. If I divide, I will get 123. So, this gives me one digit. So, let me write it down
97
here. Then, I take 123; I do a mod 10; I get 3. So, again I need to note it down. And 123
div 10 would be 12. So, I can add this up. The result is now 6. Then, I take 12 itself; do a
mod 10; that gives me 2. So, I can add 6 and 2; that is 8. And 12 divided by 10 is 1. And
finally, if I take 1 modulo 10, that is 1; and 1 div 10; this is 0. So, 1 module 10 is a 1 if I
add it together. So, 8 plus 1 is 9. So, at this point, it is a single digit number; and we have
destroyed the value of n. So, we have extracted all the digits of the number, that is, input.
So, we can stop and print this result. So, in this case, we have got slightly lucky, because
we went over one iteration of all the numbers and we ended up with a single digit
number itself. So, this may not be the case. But let us first write a program to add up all
the digits of a number and then we will worry about if the number is greater than 9 or
not. So, let us write a small program for that.
So, I am going to write... I am going to scan a number called n. But before that, we need
the basic template. So, that is the basic template. And what am I going to do? I am going
to have an integer n. But since I am also adding digits, we need two variables. So, one is
going to accumulate the sum and another is going to be n itself. So, I need a sum. And it
is always a good practice to initialize things that are being summed up. So, I will do it
right here; sum equals to 0 is initialized. And I am going to scan the number from the
user. So, I am scanning the number. Notice that, I did not prompt the user to say like
98
enter the number or anything; I am just scanning because that is what the portal takes.
So, I am scanning the number directly.
And what am I going to do? I am going to write a loop now; it says while n is greater
than 0; because once you hit 0, we know that we have actually extracted all the digits.
So, what I am going to do is – I am going to add sum plus equals n mod 10. So, that
extracts the last digit of n. But I also want to destroy n by a factor of 10 every round. So,
I do n equals n by 10. And what do you get here? So, at the end of this, the sum will be
the summation of everything that we have seen so far. So, this is not the final solution to
the problem. So, if I put sum here, I will see that.
So, let me save this; I am going to save it as a program. I have saved this and I will
compile it. So, thankfully, there were no compilation errors.
99
(Refer Slide Time: 06:14)
I will run it. And I am going try these examples. So, let us start with 1233 as an example.
So, let me try it alone – 1233. And 1233 gives me 9 as expected; so which is good. So, I
am going to try it on all the examples that I have just to ensure that, this first step is
correct; because if this step is wrong, everything else is going to be wrong. So, 1234 – if
add up; it is 10. Then, 65536 should add up to 25, which is good. Then, 911234 – five 9’s
should add up to 55, which is also correct. And finally, 96 followed by six 9’s should add
up to 69. So, we have... So far, we seem to have taken all the digits and added them up.
And we got a multi-digit number. but however, in this case, except for 1233, all the other
things gave two digit numbers.
And we may have to do this process once more. So, the program that we have is
currently not sufficient. Instead, we need to take this new sum as n and repeat the
process. So, I will just re-iterate what I said. So, at this point, in line number 12, we
know that, this is a program, which can take the digits of a number and sum it up. But
then this may result in a multi-digit number by itself. So, if I give you a sufficiently large
number, it may not even be a two digit number; it may become a three digit number. So,
however, the result is stored in a sum. But if I run the same program on that number once
more, I will get the result; correct?
100
(Refer Slide Time: 08:02)
So, let us say I put in 65536. So, I got 25. I can always ask the user – run the program
once more with 25 in it; you get 7. At this point, you stop. But that is not a good way to
write programs. In this case, we are asked to write a program, which will do this whole
process without having the user to run the program multiple times. So, the key thing is at
this point, this summation – the sum has the value that you need. And what I am going to
do is I am going to take N as sum. And let us say I have a mechanism by which I take
this point from line number 13; I need to go back to line number 8; and I iterate this
whole process once more. If I am capable of doing that; then, this would solve the
problem.
101
(Refer Slide Time: 08:52)
And what is it that we did at this point? At this point, since I am going for the second
round, the previous sum is incorrect any more. So, I need to initialize sum equals to 0
once more. And what I am going to do now is – I am going to put a while loop now. So,
when will this happen? When do we need a while loop? If the number is greater than 9,
that is when we will need a while loop. So, I did one iteration and the result was greater
than 9. Only then, we will need a while loop. So, I am going to base this condition while
n is greater than 9. Do everything here. So, I am going to adjust the indentation, so that
you know what is happening. So, at this point, it is still not what the program expects;
but let us try and compile it and run it.
102
(Refer Slide Time: 09:57)
So, I am going to try with 1234 to start with. So, 1234 gave a 101. So, that is actually 10
followed by a 1. So, to just see what the initial sums are, I am going to put a back slash n,
so that we can see what the individual iterations give. So, I compile and run again. I give
1233; that is a 9 itself. Then, I give 1234; that gives a 10 followed by 1, which is good.
65536 – 25; and then, 7; which is again good. And 91123456; so five 9’s; that is 55. So, 5
plus 5 is 10; 1 plus 0 is 1. So, it seems to be correct. And finally, 9612345 six 9’s; that
start with a 69 and then a 15 and then 6. It seems to be correct.
103
(Refer Slide Time: 10:55)
So, what I am going to do now is it seems to be a program, which is correct except for
this last thing that we are doing; this printf – we are printing everything. So, clearly, our
program does not expect all of this. So, what I am going to do is – comment this line and
put a printf here. So, I am going to print N here. So, if I put a printf here, then what it
means is N is guaranteed to be less than or equal to 9; because otherwise, you would not
have exited this while loop. So, let us try this.
104
(Refer Slide Time: 11:34)
So, again I will just give one test case and check 96123456; it gives me 6. So far, it
seems right.
So, I am going to take this program. So, I am cutting and pasting it into the online portal.
So, this is the online portal; I start with a black template and I am going to put it here.
105
(Refer Slide Time: 11:57)
I am cutting and pasting it. And I will save, and compile and run.
So, when I compile and run, let me see the results. I seem to the passed all the test cases.
And if I have passed all the private test cases... Or, the public test cases as you see here;
whatever you see – given to you. So, we have two kinds of test cases: one is called
106
public test case; and another is called private test case. The public test cases are shown to
you; the private test cases are not shown to you. When I submit; it actually evaluates
against a private test cases, which are not shown to you.
And let me see whether I have got 100. So, I seem to have 100 on 100. So, in which case,
this program seems to be correct.
107
(Refer Slide Time: 12:50)
So, one subtle thing that I want to show here is this other notion of... What if I put an
extra back slash n or things like that here. So, let us say I put a back slash n, which is
extra new line; this is something that we seem to do many times. So, let us say I put a
back slash n and I compile and run.
108
We get... We would get what is called the presentation error. So, this is something that
many of you posted on the forum that, your output is correct; but there is something
called presentation error. So, actually it clearly says what a presentation error is. So, if
the expected output is 7; and instead we have printed 7 back slash n.
So, what it really says is just get rid of the back slash n. And it seems like many of you
are doing this. You want to get rid of the back slash n. So, instead of removing it from the
printf, you seem to be doing this – put a back slash b, which stands for back space.
109
(Refer Slide Time: 13:45)
So, let us say I did that; I do a compile and run on that; I get this really bizarre error. It
says could not compile at this time. Try later. So, this back slash b is not something that
is supported by the compiler to be displayed properly. And the compilation actually fails
here. So, it may sound bizarre to you, instead of giving it to you as an error; the
compilation itself seems to be failing. But technically, it is not the compiler which fails;
it is just that it is a bizarre way of giving this error out.
110
(Refer Slide Time: 14:18)
So, instead, I am not going to murkier on with this; what I am going to do is I am just
going to comment this line and get rid of the back slash n. So, once I get rid of the back
slash n, things a fine.
So, I compile and run it. I passed all the test cases; I submit. And this is OK.
111
(Refer Slide Time: 14:40)
So, I would get 100 on 100. So, one nice thing that this program has is if the number is...
So, this is the test case that I did not talk to you about.
So, let us say I give you this number. I test it with 3; it is a single digit number; which
means you should not to any more processing. So, it should exit with 3 itself. And that is
112
something that, the program automatically takes care of.
So, look at this. If the very first time you get N; itself is less than or equal to 9; then, you
do not execute this while loop; you directly print the number. So, clearly, this program
will work correctly only if you have numbers, which are positive. For negative numbers,
it will print the negative number as it is; and digital root is not defined for negative
number. So, for any number that is positive, it is finding out the correct digital sum. So,
this is just a small video that I wanted to show you on how to solve this problem and how
to write code. And I am hoping that, this will help you in breaking your barrier and being
able to translate ideas into code. We are hoping to also actually show such sessions for
the subsequent weeks. So, we will take some of these slightly difficult problems and
show you how to solve the problem and also how to write programs with it.
So, thank you very much. And I wish you all the best for this course. I am sure that, you
are enjoying this course. There are a few main points that you see with respect to
presentation and so on. So, overall, I hope that, you are enjoying the course.
113
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module - 8A
Lecture – 09
What is an array? Why are they needed?
Example of array: Find average temperature for a year
Memory point of view: Array initialization
Example: Find hottest day of a year
Multi-dimensional arrays
Welcome to lecture three. So, in the last two lectures, we saw basics of C programming;
and we also saw basic notions of variables and so on. We looked at control structures, we
also looked at the notion of loops. So, in this lecture, we will see something really
important that C provides us, and that is the notion of arrays.
So, we have seen basic data types like integers, float pointing numbers and characters.
And these are basic data types. So, you can get numbers or letters using that. But
sometimes we also need a logical collection of these values; it is not that, just one value
is sufficient; we need a logical collection of values. And this is where arrays come in. So,
arrays are what are called aggregate data types. So, it is an aggregate data type of a
specific ((Refer Time: 01:03). It aggregates data of the same type of elements. You can
also aggregate data of different types together; we will see that later. These are called
114
structures. We will see them later. But arrays are aggregate elements of the same data
type. So, there are lots of examples, where you need this. One primary thing is these
arrays are usually fixed size and they are also sequentially indexed. So, we will see all
these things as we go along.
So, arrays are logical collections of the same type. For instance, it could be list of marks
of a student, it could be temperature that you are recording over a year or you want to
save matrices and do operations on matrices, and so on. So, these are all examples of
arrays. And on these arrays, you want to do operations like find minimum, find
maximum or you want to order all of the elements in such a way that, the names of
students come in alphabetical order or you may want to search for a particular
temperature of the day and so on. So, these are common operations. You want to be able
to read entries into an array; you want to do operations on an array, and so on. And that
is exactly what we will do in this lecture.
115
(Refer Slide Time: 02:14)
So, let us do a little thought experiment. I want to find out average temperature of the
year. So, I have 365 days. And if I did not have support for these aggregate data types
called arrays, my program would look something like this. So, I show only the code
segment here. Let us see what it has. So, there is first of all 365 days; I will need 365
variables; float, temperature 1, temperature 2, and so on up till temperature 365. I will
need all these declarations. So, even though I showed dot dot dot, you really need so
many declarations. And then I am going to find out the sum and the average – find the
sum and therefore, find the average. So, one thing that I have to do is take all the
variables one at a time and read through them. So, I have so many declarations and then I
go on and scan all of them.
And finally, I need a huge expression, which takes each of these variables and add them
up and give sum. So, what we have is something really cumbersome. So, we have 365
variable names; we will have to scan each one of them; and we will have 365 lines in
which we will scan them. So, that is what you will do in this block here. And finally, this
one, where you are adding up all the values is also rather cumbersome, because if you
forget something, you have no way to go back and check which one is missed out. So,
this is not a nice way to do things. And programming languages give support for the
same data type; temperature being floating point and have many of those aggregated into
something called an array. So, let us see the same thing done using arrays.
116
(Refer Slide Time: 03:59)
It is much more elegant if we use arrays. So, the first thing you will notice is that, there is
float temp of 365. So, what you see here is you see that, there is a declaration here;
which goes like open bracket 365 close bracket. All it says is I do not want a single
floating point number; I need 365 floating point numbers. And… So, that has to be
allocated somewhere. As before, we have float sum equals 0 and average. And now, I
want to scan the elements one-by-one. This whole thing becomes very nice and simple;
you have seen the for loop before. For i equals 0; i less than 365 i plus plus; scanf
ampersand temp of i. So, what this loop does is it scans the elements one-by-one and it
stores it in what is called temp of i. We will see what temp of i means in a little while.
But, the first thing I want you to do observe is that, let us become much more elegant.
Instead of 365 lines that you had earlier, now, you have three lines of code, which is
scanning 365 elements.
The other thing that we have very nice is just the addition itself. So, as before, we iterate
over all the elements from i equal to 0 to i equals 364 – both inclusive; and we add the
temperature to sum. So, you add one temperature at a time and the result goes back to
sum. At the end of this loop, you have the sum, which is the sum of all the temperatures
over 365 days. So, if you divide by 365, you get average. So, one thing that you have to
notice here is that, the sum is actually initialized to 0 here. Therefore, you start with 0 as
the temperature and then you add on temperature of each of the days to give
temperatures of 365 days; and then you divide by 365. So, the nice thing about this
117
whole thing is it fits into one screen. So, if I want 5 years, all I have to do is ensure that, I
iterate over 365 into 5 iterations. So, that piece of code is still going to be something very
very small. So, I do not have to declare so many variables; and the program does not get
clumsy; and there is less scope for errors when you have arrays than when you have
individual variables.
So, let us go and look at arrays in more detail now. The first thing is – as before, we need
to declare arrays before we use them. So, the syntax is as follows. So, you start with type
and then you give an array name and then you give number of elements. So, just like this
– you specify the type; you specify the array name; and you want the number of
elements. So, in this case, marks is an array of size 7 and they are all integers. So, the
key thing is – remember – this is an aggregate data type of type integer here. Similarly,
in this line, we have temperature, which is an array of 365 elements; and the values that
it can contain are floating point values. So, one important thing that you have to
remember is that, when you say int marks of 7 for example, you get something, which is
contiguous in nature; as in, these are locations, which are continuous in your memory.
And the individual elements of the array can be indexed as marks of 0, marks of 1, and
so on up till marks of 6. So, anything which is of the form marks of i, where 0 less than
or equal to i, less than or equal to 6 is valid.
118
(Refer Slide Time: 07:50)
Let us see what happens from the memory point of view. So, earlier, we saw what
happens to variables from the memory point of view. Let us see what happens to arrays
from a memory point of view. So, when you see a declaration like int marks of 7, what
you are really seeing is an array. So, you see memory locations here; I am showing only
a segment of the memory. And instead of one variable called marks, we are going to
have 7 variables named marks of 0 to marks of 6. So, this is not a mistake; it is marks of
0 to six, and not 1 to 7. So, some languages start indexing arrays at 1; but C indices start
at 0. So, you can think of it as 7 variables namely, marks of 0 to marks of 6.
And as we did earlier, if you do not have any initialization, the values in the array are
unknown. You should assume that, these values are unknown. And this could get laid out
anywhere in the memory just like variables do. The only thing is that, these are going to
be 7 contiguous locations. So, for instance, let us assume that, marks of 0 was allocated
address 2731; then marks of 1 would be at address 2732 and so on; marks of 6 would be
at address 2737. So, this is what I meant by arrays are going to have contiguous set of
locations.
So, as I said, each element can be thought of as a variable. Just like individual variables,
they start out to uninitialized. One nice thing is once you have declared an array; just like
in variables, you can assign values to these variables. So, we saw that, we can have left-
hand side variable name, right-hand side value or expressions for variables. The same
119
thing applies for arrays. So, in the left-hand side, you have an individual variable –
marks of 3; and on the right-hand side, we have a value 36. So, if you do this in your
program, then the value will go to 36. So, at this point, you know the contents of the
location marks of 3. As before, we can use ampersand to get the location of the memory.
So, for instance, if I want let us say address of marks of 2, then I do ampersand of marks
of 2; and ampersand of marks of 2 would give me 2733 as the value; ampersand of marks
of 1 would give me 2732 as the value, and so on. So, the addresses are going from 2731
down to 2737 in increasing values. So, they are contiguous and they are increasing from
0 to array index 6.
So, let us revisit the example that we did earlier. So, we have float temp of 365; say we
have an array of type float and 365 values. At this point, we do not care about where it is
getting laid out in the memory, because that is something that, your run time system
should do. So, as a programmer, we do not care about where it is in the memory location;
just like for variables, we do not care where they are in the memory. Then this is
something that, I did not draw attention to earlier. Now, let us see what it is doing. If it is
an individual variable, we saw that scanf takes the format and the address of a variable;
the same thing is happening here. We have the format here and we have the address of a
variable; only that, the variable is not an individual variable; it is actually an array
location. So, temp of i is temp of 0, temp of 1, temp of 2, so on till temp of 364. And
when finally, when you look at this loop, it runs from 0 to 364; so it actually runs 365
120
times. So, your array is indexed from 0 to 364; the loop also runs from 0 to 364. We are
actually scanning 365 elements. And in this loop, we are adding all the 365 elements into
sum and you average at the end of it. So, it is as simple as that. So, from arrays, we get
contiguous locations and we have a same data type; but we have many elements of the
same data type one after the other.
There are various ways to initialize arrays; you can do something like this. Let us say I
want marks of 7 students and I could have it as int marks of 7 and read it from the user.
Or, sometimes I know what these values are. So, I could initialize it right away; I could
do this. So, int marks of 7. So, I am declaring marks to be an integer array of 7 elements.
And you can specify the list of values on the right-hand side using a curly braces. So, in
this case, marks of 0 would be 22; marks of 2 would be 75; marks of 6 would be 45 and
so on. So, the locations on the left side can be seen as 0 to 7; and the values can be seen
from left to right. So, you get a one-to-one mapping from left side to the right side. So,
when you do this, let us say these are locations 0 to 6 and the values are 22 for 0, 15 for
1, and so on till 45 for marks of 6. It is not just that you can initialize and leave it at that;
you can change the values just like you do for other variables. So, you can do something
of this effect. Even though you have initialized, mark of 3 to 56, we can do some
assignment like this – marks of 3 equal to 36; and that will change the value to 36. So,
we saw that, the value changed from 56 to 36.
121
(Refer Slide Time: 13:35)
So, there are a few fine points that you have to remember. Array indices always start at 0
in C. If you come from other programming languages, some languages start at 1. So, be
aware of this. This is a common mistake and lot of people get trapped in this thing that,
the indices start at 1 and not at 0. So, in C, they do start at 0. And when you do marks of
7, marks of 0 to 6 – all are valid; marks of 7 is invalid as well as marks of minus 1 or
minus 2 and so on. So, you will never have indices, which are negative; nor, you will
have an index, which is greater than and equal to the declaration that you had. So, for
example, in the float that you showed earlier, so we have 365. So, temp of 365 would be
invalid, because we have only temp of 0 to temp of 364.
122
(Refer Slide Time: 14:35)
So, I want to talk about another small example, which is along the lines of finding the
hottest day. So, I have read 365 temperatures into an array called temp; but I want to find
out the maximum temperature or which day was the hottest day. So, what I want the user
to print is print the day in which the day was the hottest as well as a temperature of that
day. So, as before, we have float temp of 365; and I have two arrays – two variables
namely, hottest day and i. I am going to use hottest day to find out which day is the
hottest. And I am going to use i to iterate over all the locations. So, initially, what I am
going to assume is day 0 is the hottest. So, let us say this is January 1; I assume that,
January 1 is the hottest and it is day 0. So, temp of hottest day, which is temp of 0 goes
to max.
Now, I run a loop from 1 to 364. So, this goes from January 2 till December 31. And
what I am going to do is I am going to see if the current temperature is less than the…
The current maximum temperature that I have in record is less than temperature of day i.
So, if it is less, then max is that I have is not the actual max; some other day became
hotter. So, I have to update it. So, I have hottest day equal to i updated. I not only update
the day in which the temperature was very high; I also record the actual temperature,
because that is what you are actually tracking. So, what this loop really does is this. So,
you go from day 1 to day 364. So, we start at day 0; we go from day 1 to day 364. And if
some other day becomes hotter, we update it and we move forward.
123
And since I have to find out the hottest day in the year, I have to go all the way till 364
days or day 0 to day 364 – 365 days. At the end of it, hottest day will have the index of
the day and max will contain the actual temperature. So, I can print that, the hottest day
was day number hottest day with temperature max. So, this is a very simple loop. I did
not show code for reading in temperature of the 365 days, but we only see the code for
finding out the maximum. So, this is the very simple and common example of how
arrays are used. I want to find out the maximum. So, it could be marks of 100 students or
so. And I want to find out which student got the highest mark. So, I may have to just
iterate over this and find out the largest value. So, we start with day 0 being the hottest.
And if i-th day is hotter than the current record, update it.
There are also multidimensional arrays that are possible; so not everything in practical
uses 1-dimensional. So, even though I have… So, I have things like matrices and so on.
Naturally, they are multidimensional or in this, matrices are actually 2-dimensional; you
could also have 3-dimensional structures and so on. So, C gives you flexibility to have
arrays of multiple dimensions. So, for instance, let us see something of this kind. If I
have a declaration int A square bracket 4 square bracket 3; it means that, we have four
rows numbered 0, 1, 2, 3. So, that comes from this. And number of columns being 3
numbered from 0 to 2. So, we have four rows by three columns.
124
I can also have something, which is 3-dimensional. So, in this case, I have a floating
point array called B. And there are three dimensions to it. So, you can think of it as x, y
and z dimension. So, in the x dimension, there is 2; you can think of it as two planes:
plane 0, and plane 1. In each plane, I have four rows and three columns. That is what you
see here. So, in each plane, I have four rows and three columns. So, this comes in very
handy for handling matrices and graphics and so on. We will look at these examples in a
little later. In a little while, we will see examples of multidimensional arrays. So, at the
end of this module, what we have is basic notion of arrays; how we can use the arrays,
how we can index them, and what happens inside memory. So, in the next two modules,
we will see more details about arrays.
125
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module - 8B
Lecture – 10
Contents
Rcode segments for reading into and printing out of arrays
Example: Fibonacci unmbers
Example: Using arrays as counters
In this module, we look at how to work with one dimensional array. And I am going to
show more detailed examples and also write small programs for some sample problems.
So, let us look at this piece of code. What this piece of code does is actually read values.
So, it says enter value of element number; percentage d. You are reading some values
into values of i and you are printing them. So, it is a fairly simple program. So, I am
reading values and I am printing the values.
So, one thing that happens in real world is that, many times you do not know what is the
number of things that you want ahead of time. So, we want to be able to write generic
programs; which means, I do not want a program to be of fixed size. So, let us say in the
126
previous example, temperature, I declared it to be 365. What if I had a leap year? In
which case, I need 366 days and not 365 days. So, I need a mechanism by which I want
this in a generic fashion.
And that is where C comes in very handy with something called hash define. So as of
now, so see this lines, line 2; hash define N 6. So, what this does is where ever you see a
symbol N, it is going to replace that with the value 6. So, now if I go and look at this
loop; let us see how to make sense of this loop. For i equal to 0, i less than N, i plus plus.
Where ever you see N, the C precompiler or pre-processor will replace that with 6. So,
this loop will run from 0 to 6. So, what this loop will do is read seven elements;
numbered values of 0 to values of 6. And this loop also runs on same N. So, it will print
values of 0 to values of 6. So, we have declared the array with values of n; n is not a
variable. So, n takes the value 6 here. So where ever you see n, assume that it is number
6 there. So, what you are essentially doing is it is equivalent to say int values of 6 and
you have a for loop running from i equal to 0, i less than 6, i plus plus and so on. So, we
have indices that go from 0 to 5. I am sorry, 0 to 5 and not 0 to 6. And this loop also runs
from 0 to 5. So, we have 6 locations numbered 0 to 5.
So, this generic value is very useful, but one small thing that you have to be careful about
is that if I change this n to let us say 10, I expect 10 values; numbered 0 to 9. But if I
change, the source code is not enough. Remember, once we have a change in a source
code we have to save it, compile it and then run it. So, whenever you have a change in
the source program you have to save, compile, and run. If you want to change the value
of n, it cannot be a single change in a source program and which will automatically
reflect in the binary to be different. So, whenever there is a change in source program,
remember you have to save compile and run it.
127
(Refer Slide Time: 03:36)
So, from now on we will try and use generics as much as possible. So, the first exercise
that we are going to look at is the notion of Fibonacci numbers. So, Fibonacci numbers
are very interesting they come in various places later. So, Fibonacci number goes like
this. The first Fibonacci number is 0, the next number is one. And from there on, the
Fibonacci numbers will go in this fashion. You take the previous 2, add them and that
gives you the next Fibonacci number. So, 0 plus 1 is 1. Then the next Fibonacci number
is take one. And so take the previous 2, add them, then next number is 2. Then again you
take the 2 previous ones, add them; it is 3 and so on. So, the Fibonacci numbers go in the
sequence 0, 1, 1, 2, 3, 5, 8, 11, sorry, 5, 8, 13 and so on. This is the Fibonacci sequence.
So, let us say I want to write a program to do exactly this. So, till now we were scanning
the values from the user. Now, we have a program in which it will actually fill in the
values. So, we are going to generate the first n Fibonacci numbers. So as before, we have
a hash define N. So, what we want is we want the first 10 Fibonacci numbers starting
from 0. So, we declare an array called fib of N. So, this will declare 10 memory locations
and you can address them as fib of 0 to fib of n. We already know the value of fib of 0
and fib of one. And what the loop is going to do is it is going to run from 2 to 9. So, you
have i less than N; which means i less than 10. So, it runs from 2 to 9. So, i equal to 0
and i equal to one are already taken care of. When you run from 2 to 9, you get another 8
128
values. So, this is; overall you are filling up 10 values of fib.
And as I showed earlier, we have to take a particular index i and we look at location i
minus one and we look at location i minus 2. When we add these 2, we get fib of i; that is
exactly what this line is doing. We take fib of i minus one and fib of i minus 2, add that
and put it in fib of i. So, the first time when i equals 2, fib of 2 will get updated with 0
plus 1; which is one. Then you go back to the loop, you check whether 2 is less than 10.
Yes, 2 is less than 10, so you increment; you get 3. So then you are ready to; you are now
ready to do. Fib of 3 is fib of 2 plus fib of one which is one plus 1 giving 2 and so on. So,
what you are having is a loop that runs from 2 to 9. And it is taking the previous 2 values
and adding them and storing them in a fib of i. So, at the end of the loop you will have
numbers of this form; so 0, 1, 1, 2, 3, 5, 8, 13, 21 and 34. So, that gives us the first 10
values of Fibonacci series. So, you will see the array fib filled up from 0 to 34 and you
can print them here.
If I want the first hundred numbers, let us say, all I have to do is I do not have to go and
touch the program anymore. I go and change the hash define. I, now write hash define N
hundred; this changes N to hundred. All I have to do is save it and compile it. I do not
have to go and touch these other lines; where is the N. This is very useful. So, usually
what we do is we keep the values which are very small, so that we can test the programs.
And then maybe I want something some examples where N is very large. I test it with
very small values to ensure that it works fine and then I go and change it to some other
larger value.
We will see a similar example later. But once we have this, we can now move onto other
exercises. In this exercise, the crucial point is we did not have fib of i filled up with
values. It starts out with uninitialized values and slowly you fill them up one after the
other from fib of 2 to fib of 9.
129
(Refer Slide Time: 08: 10)
So, now let us move to another example. Let us say you are taking this course. And
about, let us say thousand students are taking this course. And I want to find out how you
students rate the class. I give a mechanism for you to rate the class. You can rate the class
from one to 5. And at the end of the course I may want to see how the rating of this
course was spread. So, each student is supposed to give a rating 1, 2, 3 4 or 5; one being
bad and 5 being good let us say. And at the end of it I want to see that how many people
said good, how many people said ok, how many people said bad and so on. I want that.
So, this is something that is very common. And what I want is I want to count the
number of times each rating is given. So, maybe I want to see that, “Oh! There are 10
people who gave very good rating; hundred people who gave good and so on”. So, I
want to see numbers like that. Let us see how to write a program to do that.
130
(Refer Slide Time: 09:12)
So, I am going to maintain an array called rating counters. That is the array name that I
am going to use. And I know that the valid ratings are one to 5. So, I am going to have 1,
2, 3, 4 and 5. I have 5 locations. And rating counters of i is going to maintain the record
of how many students rated the course as i. So, 5 students gave rating one. I would
expect rating counters of one to be containing the value 5. So one thing is, since the
rating are only 1 to 5, however array indices start from 0. What I am going to do is I am
going to declare an array of size 6 and I will not use the location. Therefore, even though
my arrays actually is 6 elements, I want only 5 of those. I am using part of the array. I am
not using the whole array. So, this makes the programming slightly more easier. Let us
see how to write this program.
So, let us say there are only 5 students. And the 5 students gave the rating 5; 1, 2, 1 and 5
right. So, let us say these are the rating that were given by the students. And what I
would like to do is go one user at a time. So, the first user gave 5; so, I would want this
one to be a count of one. Then the second user gave one; I want this to be a count of one.
The third user gave 2; I want this to be a count of one. Also, the 4th user gave one; which
means, now there are 2 people who gave rating one. I want this to be 2 and so on. In
general, what I want is as soon as I get a rating, I want to do a plus plus on the array
location, which contains the response. If I get response as let say 5, I want to go to
131
location 5 and increment the current value. So, however one thing you have to pay
attention to is since we are doing increment, you are going to do a plus plus. We should
ensure that all of these entries are set up at 0. If we start with the unknown values, you
may end up with the counter, which are incorrect. So, we assume that all the counters are
initialized to 0. We start from there.
So, let us see how a program of this kind will work. So, I have in rating counters of 6. As
I said earlier, I am going to accommodate 6 locations. But, my actual entries that I am
concerned about are rating counters of one to rating counters of 5. So, even though I
have initialized this to 0, I am not really going to touch the rating counters of 0 at all. I
am going to touch the rating counters of one to rating counters of 5. So, I have this loop
which runs from i equal to one up to thousand; including thousand. For i equal to 1; i less
than or equal to 1000 plus plus i. So, at this point we are scanning thousand responses.
And for each user or each student, we are going to take a response. And we are first of all
going to check if the response is less than one or if it is greater than 5, these are invalid
responses.
So, I said the ratings have to be from one to 5 as integers. So, if anything less than one or
if is greater than 5, it is a bad response. We do not record it. So, we will record only valid
132
responses. And if it is valid response, you are going to increment the corresponding
counter. So, remember there are 5 counters; rating counter of one to the rating counters
of 5. And based on the response, you index that particular counter and increment it. So,
again if you want to break it down, you can think of it as plus plus on a variable. And
which variable are you incrementing? You are incrementing the variable for which you
got the response.
So, if the first user gave one as the response, rating counters of one is going to get
incremented. If the 500 th user gave rating of 5, i would be 500. And rating counters of 5
will be incremented by one. And once you are done with this loop you can now print the
rating. So, we print rating and the number of responses. So, we run from one to 5, both
inclusive, and we print the 2 values. What are we printing? We are printing the rating i
and print the count.
So, at the end of this if I scan thousand inputs, the output would look something like this.
Response of one came from fifteen people, response of 2 came from 85 people, response
of 3 came from 100 people, response of 4 came from 500 people and response of 5 may
be came from 300 right.
So in this case, all the user let us say gave valid responses; 5 people gave 3 hundred, 4
people gave 5 hundred and so on. The summation of this will be thousand. So, that is the
quick way to check whether your program is correct or not. If let us say 5 people gave
invalid responses, then the count will not be thousand. The addition of this would give
you 995 and not 100. So, I suggest that you take this program and write it in the edited
that I showed you in the last lecture and ensure that this actually works.
133
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 8C
Lecture – 11
Example: Find prime numbers
So, now let us move on to another program. And in this program what I am going to do is
I am going to post the problem and actually show piece of code running inside the
software, running inside the IDE. So, the problem is find all the prime numbers that are
less than or equal to N.
So, I am going to give a N. And I want you to find out all the prime numbers that are less
than N. So clearly, this is something that you want the computer to do. You want the
program to give you this. It is not something that is going to come from the user and it is
not something that is as simple as Fibonacci; where you just have to add 2 elements. This
requires a little more work. It requires some bit of logic to find out all the prime numbers
that are less than or equal to N.
So, let us start with some very simple observations. So, we know that 2 is the only prime
134
number that is even. So, 2 is the smallest prime number. And we also know that is the
only even number that is prime. All the other even numbers are not prime, because 2 is a
factor. Three is the smallest odd prime number. And from now on, we know that 2 and 3
are prime numbers. We will have to start checking from 4 onwards, if they are prime
number or not. So, 4 is not a prime number because it is a multiple of 2 it is an even
number. Five is a prime number. 6is not because it is even; 7 is a prime number and 8 is
not because it is even. 9, even though it is a odd number is not a prime number, because
it is 3 times 3 and so on. So, what we are going to do is we are; so I am going to layout a
technique to solve this problem. So, let us see how to do this using paper and pencil first.
And then we will move on and see how to write a program for this.
So, I am going to assume that N equals 10. So, I want all the prime numbers that are less
than or equal to 10. If 10 is a prime number I want to use, I want to declare that 10 is a
prime number also. So, 10 is not. But whatever N I take, I want to print all prime
numbers that are less than or equal to N. So, this is the program to find out prime
numbers less than or equal to N.
So, since I start with N equals to 10, so let us make use of the observations. So, what
could I have? I could have 2, 3, 4, 5, 6, 7, 8, 9 and 10. I already know that 2 is a prime
135
number and 3 is a prime number. I also know that all the even numbers are not prime. I
know that all the even numbers are not prime. So, I am going to start with 2 and 3. And I
have all the odd numbers that are less than or equal to N. So, in this case it is 2, 3, 5, 7
and 9. And of these, I already know that 2 is a prime number and 3 is a prime number.
Let us see how to find out if 5 is prime or not. So, one thing we are going to do is we are
going to assume that 5 is a prime number initially, until we have a proof that 5 is not
prime.
So, in this case 5 is guilty of being prime, until it is proven otherwise. So, what are the
different things that we can do to check whether 5 is prime or not. Of course, I can go
from; if 5 is not a prime, then one of these numbers 2, 3 or 4, we should be able to divide
5 by that and get no remainder. So, let us do that. Let us say I do 5 divided by 2; the
remainder is one. I do 5 divided by 3; the remainder is 2. I do 5 divided by 4; the
remainder is one. So, all these numbers which are less than 5, I divide them into 5. It
leaves the remainder. So 2, 3 and 4 are not factors of 5. So, 5 is actually prime.
But, one thing we have to observe is that if you look at 4, right, 4 is already 2 times 2.
So, if I want to check whether 5 is divisible by 4 or not, right, let us assume that some
number m is divisible by 4. m is actually divisible by 4. But, if m is divisible by 4 it is
also actually divisible by 2. The reason is 4 is actually 2 times 2. So, I do not have to
really check whether m is divisible by 4 or not, if I have already checked if m is divisible
by 2. So for 5, I am going to check only if it is divisible by 2 or 3; I will not check if it is
divisible by 4. So, I go and check whether 5 is divisible by 2; it is not. Is 5 divisible by 3;
it is not. I started with the assumption that 5 is prime. So, it is not changing. So, 5 is
indeed prime.
Let us do the same thing for 7. So, what I am going to do is I am going to; so as, for 7
also I can start from 2, check for 3, check for 4, check for 5, 6 and so on. But, I really do
not have to check for 4 and 6 divisibility, because if for 4 I already know 2 is a factor, for
6 I already know that 2 and 3 are factors. So, therefore I do not have to check 7 divisible
by 6 or 4.
So, to put it more succinctly what we are checking is, for any number N you are going to
136
check only if it is divisible by other prime numbers that are strictly less than N or not.
For 7, the list of prime numbers that are less than 7 or 2, 3 and 5, I do not have to check
other numbers like 4 and 6 and so on. We will only check prime numbers that are less
than 7. So, what are the prime numbers that are less than 7? They are 2, 3 and 5. So,
again as before I will assume that 7 is actually prime, until it is proven otherwise… So, I
will go and check whether it is divisible by 2; it is not. I can check whether 7 is divisible
by 3; it is not. Is 7 divisible by 5; it is not. So, you have checked all the prime numbers
that are less than 7. So, at this point 7 is actually guilty of being prime and it does not
change.
Let us do this little thing for 9. I start with number. So, what are the prime numbers that
are less than 9? They are 2, 3, 5 and 7. I will assume that 9 is a prime number to start
with. I check whether it is divisible by 2; it is not. I can check whether it is divisible by 3
or not. In this case, 9 is actually divisible by 3. So, it is divisible. It is not divisible by 5
and it is not divisible by 7. So, I started with the assumption that 9 is actually prime,
however 9 was divisible by 3. The moment it is divisible by one prime number, even one
prime number, less than the N that I am looking at, then this assumption that this number
is prime is not true anymore. So, I have to change that assumption. So, 9 was assumed to
be prime and it is not prime anymore, because it is divisible by 3.
So, in summary what I am going to do is I am going to start with number 2 and 3. I will
assume that they are already prime and I am going to check. For given N, I am going to
check only odd numbers starting from 5, 7 and so on up till N. So, if N is odd I will
include N also; if N is even I will go only till N minus one. And for each of these
numbers, I am going to look at all the prime numbers that are less than that number and
see if that number divides into this current number under consideration or not. And I do
this till end.
137
(Refer Slide Time: 08:43)
So, let us see a small program segment that I have written to do that. So, in the process I
also want to show you some features of the IDE that I am using which is called Dev C
plus plus. So as before, I start with hash include stdio dot h, because I want to be able to
print the prime numbers later. And I also have hash define N equals 10. So, I define N to
be 10. I want to print all the prime numbers starting at 2 up to 10; whatever prime
numbers are there I want to find them out. So, in the worst case all of them could be
prime numbers. So, I have an array called primes. So, I have this array called primes.
And what it is going to keep is all the prime numbers that are less than or equal to N.
And you are actually allocating N numbers or N locations for prime numbers. So
upfront, if I give you a number 100 you do not know how many prime numbers are there,
I am assuming that all hundred could be prime numbers and I assign or I create an array
of size hundred. So, they are of course going to get index from 0 to 99.
So, I look at this line; primes of 0 is 2. So, the first prime number is 2. I put that at
location 0. Primes of one is 3. So, the next prime number is 3. So, I already have 2
locations filled up; namely location 0 and location 1. I can start at location 2 and start
filling up the prime numbers.
138
(Refer Slide Time: 10:26)
So, the array that we are going to maintain; so this is the setup. We are going to maintain
an array. And this array is going to be called primes. It is going to have locations from 0
to N minus one. Location 0 is going to contain 2; location one is going to contain 3. And
prime index is a variable that I am going to use to index into the array called primes. So,
that is prime index.
And p is an iterator that is going to run from 5, 7 and so on. So, if N is fifteen it will go
till fifteen; 5, 7, up to fifteen. So, that is what this loop does. So, p equal to 5; p less than
or equal to N. So, the nice thing that I have done here is I have incremented p by 2
already. So, if I start with 5, if I go up to N in increments of 2 and starting with 5 and
then it will be 7, 9 and so on. I am looking only at odd numbers. I will never have even
numbers.
So, I also want to show you something else. So, on the left side if you see there are line
numbers. So, the line number hash include gets line number one; this for loop is starting
at line number 12; this for loop is starting at line number 14 and so on. So, I do not know
if you have been paying attention so far when I showed the IDE. So, this for loop starts
at this line and ends at this line. And we can see a thin line running from f to close
bracket.
139
Similarly, this if statement here; you can see a thin line running from here to here. And
for this for loop it is running from somewhere. You do not see the end of it. So, this is
something that many IDE s give you for tracking source code and being able to write
code easily.
So, you see the line numbers on the left side. You also see what is called the gutter. So,
gutter is a small gully on the left side. So, on the gutter you see the line numbers. You
also see small rectangles on the left side. So, these are called foldables. So, if you see this
for loop, right, I can click on this line number 14 and it folded. After 4teen you see line
number 19 now. So, line numbers 14 up to 8een are now hidden. If I click on this minus
symbol next to 19, it again folded. So, I can click on it again and it will expand. So, I can
click to expand or fold. So, let us assume that these are folded now. So, if you do that
what the IDE does is it just shows you opening and closing braces without the statements
inside.
So, let us see what the loops starting at twelve and ending at line number twenty 3 is
supposed to do. We are going to start with p equals 5. I will assume that 5 is prime and
then I will check whether it is prime or not. Then I will either write it into the array or
not. Then I will assume that 7 is prime. I will check whether it is actually prime or not, I
will register it if it is prime, then I move on to 9 and so on up till N is over.
So, the loop running from line number twelve to twenty 3 is only iterating over all the
odd numbers. For each odd number, we assume that it is prime. So this, we are going to
track by using a variable called prime. So, you can think of this as a question. Is 5 prime?
And one means it is prime and 0 is usually used for false. So, one is usually used for true
and 0 is usually used for false. So, is 5 prime; I will assume that 5 is actually prime and
have to run a loop to actually verify if it is prime or not.
So, let us look at the loop from line number 4teen to 8een. So, in line number 4teen you
have a loop for i equals 1; i less than primeIndex; i plus plus. So, if p is divisible by sum
p of i, then p is not prime. So, we are checking that in line number 16 to 17. So, if p is
actually divisible by a prime number that is less than it, that is what you have in line
number 16, then your is Prime becomes 0. You start with is 5 prime; yes. And this if
140
condition checks whether that has to be changed or not. And when this for loop is over,
you come and check whether your assumption is true or not. If your assumption still
holds true; if the current p is not divisible by any prime number less than it, then it is still
prime and you update it, you increment the prime index by one. Otherwise, you do not
increment the prime index, you do not record the value and so on. And line number
twenty 4 is just a loop which prints all the prime numbers.
So, we have this setup now. I want to run it, compile it, run it and show what it does. So,
see that N is equal to 10. I am going to compile it. So, I see that there are no errors and I
run it. You can see that I printed values 2, 3, 5 and 7. So, it does not have any number
which is not a prime number. So, as I said earlier I want to go and change this. Let us I
want to print all the numbers from 2 to fifteen which are prime. So, the first thing I did is
I edited it and then I saved it. So, let us say I change it to 15. You can see that there is a
star on the top, which says it is not saved yet. The first thing I do is save. Let us say I do
not compile it, I just run it. I saved it, but I did not compile it. So, it is still executable
that I have; still holding the whole value of N which is 10. So, it is printing only prime
numbers up to 10. So, to correct that what I have to do is I have to compile it once more.
I compile it once more. Now the compilation is over, now I run it. You see that the print
is 2, 3, 5, 7, 11 and 13. So, I have all the prime numbers from 2 to thirteen included in
this.
Then let us say I want to change this number and I want the first hundred numbers. Not,
the first hundred numbers and whatever is prime in the first hundred integers I want to
print those. I change N to hundred, I save it, I compile it first. So, I compile it and then I
run it. You can see that numbers are starting from 2 it goes from 3, 5, 7, 11 and so on up
till 97. So, this is what I was talking about earlier. Usually you test your program with
small values and once you are comfortable about that, then you go ahead and put larger
values.
So, let us say I want thousand; up to thousand I want all the all the prime numbers to be
printed. So, again I change it to thousand, I compile it first, then I run it. And now you
can see that I have a screen full of prime numbers. I start with 2 and the last prime
number less than thousand seems to be 997. So, you can go and verify that the number
141
before that is 991 and so on. You can go and verify that. So, one thing that I want you to
observe is that the line numbers are a very useful thing. So, use them when you want to
discuss programs and so on. And you can also use this collapse and expand. If you do not
want to see all the code you collapse and expand. So, when I want to see what the outer
most loop is doing, I collapse everything else inside. So, now this is very clear. So, I am I
iterating over all the odd numbers less than or equal to N. right. And if I want to see how
I am actually checking whether a number is prime or not, I expand this loop on line
number 14 and I see that I am checking the current p against all the prime numbers that
are less than this. And finally, if the assumption is still true, right, I record it; otherwise, I
do not record it. So, if isPrime is 0, you see that there is no else clause. So, we are not
recording it and I print everything and return 0.
So, we wrote a small program which printed all the prime numbers less than or equal to
N. So, at line number twelve we had this outer most loop running which is iterating over
all the odd numbers. At line number 14 we had this primality check.
142
(Refer Slide Time: 19:38)
And line number 19 goes and verifies our initial assumption whether it is actually prime
or not. So, there are lot of things it you can do to this program. You can make this
program more efficient.
So, for example, when we check fifteen we would have check 2, 3, 5, 7 9 and so on, if
143
you did not account for just the prime numbers. If you check only the prime numbers,
you would have still checked 2, 3, 5, 7, 11 and 13 also to check for 15. But, there is a
small mathematical property that if you are looking at a prime number it is enough to
check prime numbers that are less than or equal to square root of N. You do not even
have to go to the last prime number that is less than N. So, you can make that change.
The other thing is if p is divisible by some prime number, the loop will still check and
keep running. So, for example, if you have 9 you would have checked it against 2. When
you check against 3, you already know that it is not prime; 9 is not prime. You still go
ahead and check it with 5 and 7. What is the point? We already know that with 3, 9 is not
a prime anymore because 3 is a factor of 9. So, you can use these 2 tricks. Go only till
prime numbers that are square; less than square root of N. And at some point, if your
assumption is violated why even bother looking at all the entries. So, you can break right
then and there and say that the number is not prime any more. So, you can combine each
of these techniques. So, you can have this each of these techniques in place or it even
combine them both to give you a better program.
So, this code segment can be modified to do these 2 changes. So, do not go till all the
prime numbers less than N; go only till square root. And even in that, at some point if we
find out that a number is composite do not go any further, you can break out of the loop.
So, you may have to make changes in 2 places. So, you have to make changes here, so
that you do not run everywhere. And at some point, once you get if isPrime; so if isPrime
become 0, which means it is already a composite number. You do not run the loop any
more. You can stop and do something about it. So, I give you these 2 things as exercises;
go and do this.
144
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 12
Lecture - 12
Module Debugging: Demo Module
Contents
Demo of debugging
Hello all. In this small demo of the debugging environment, I want to show you some
specific features in debugging and how things work with debuggers. So, we will go back
to this program, where we were looking at prime numbers. Just to recollect, we declare
the 2 and 3 will be prime numbers. And for the other prime numbers, we will actually go
and evaluate based on this small logic that we discussed. If some… For a number p, we
are going to check against all the prime numbers that are less than it. And if it is even
divisible by one of them, we will declare that, that it is composite; otherwise at the end of
checking all the prime numbers, if the number is still not divisible, then we will say that,
p is prime. So, let us switch to the programming environment.
145
(Refer Slide Time: 00:59)
And the only modification that we have is I have defined N to be 10, so that we can
quickly see what is happening. So, one aspect of this debugging is essentially that, if
there is a small error in the program, how do we find it out? So, I can always go and run
the program multiple times or I can put printf statements in multiple locations and track.
But, that is not the best thing to do. So, if the programs get very large, then the printf's
can be very annoying. So, instead, many ID is including del C plus plus; give a very nice
146
mechanism for debugging. And that is what we are going to do. So, there are two things
that we are going to do when we have a debugger. The first thing that we are going to do
is set of border called break points. So, generally, what happens is your program starts
executing at line number 6 let us say.
And it executes till line number 27. And you do not get to see any of it; you only see the
program running and you should get to see the final result.
147
But what if I want to see what is the effect of each of these lines? So, I will do single
stepping and I can take one line at a time and show the effect of each of these lines; and I
will also set up something called a break point, so that I can see how things should run
up to the break point. So, let me illustrate those. So, what I am going to do is I am going
to set up a break point at line number 8 by pressing on key F4. The moment I press that,
you can see that, the background of that line changes to red and there is also a small tick
mark on line number 8. I am also going to set up a break point on line number 12. So, I
have set it up for break points at eight and 12. So, what is going to happen is – when the
program is run, it will not run all the way; first, it will break just before line number 8
and after that if I continue running the program, it will break at line number 12 and so
on. So, remember line number 12 is inside the loop. So, it will break; every time, it will
give us a chance to inspect values at line number 12. So, to actually look at the values,
we need a few other things.
So, I am going to click on debug, so that the debugging is turned on. And I am going to
add watch on a few variables.
148
(Refer Slide Time: 03:20)
So, what are the variables of interest? I want to see what primes is.
And if you see that right; so primes seems to have all these values, which we really do
not want. And this is the first thing that you should be observing. So, there are values of
primes. And since primes is uninitialized, remember we are in… We have still not
executed the program; we are just in line number 8. So, we do not know what values
must be there for primes. Let us also watch p; which is the value that we want. We watch
i. Let us watch prime index. And finally, let us also watch isPrime. So, I want to show
149
how these things change as we run the program. So, watch the left side; you will see all
these variables that are there.
And let see what happens now. So, as of now, I am going to do F7, which is single step.
So, it will go one line at a time. So, primes of 0 is now 2 and primes of 1 is 3. So, as I
keep pressing F7, it goes one line at a time and whatever line is to be executed will be
highlighted. So, as of now, line numbers 8 and 9 have executed. So, 2 and 3 went to the
first entries in primes. So, now, I go to line number 10; I execute it. So, prime index
became 2. So, I am going to start filling up prime numbers from location 2 onwards. So,
as of now, 2 and 3 are correct; I am going to start filling up from location 2 onwards.
150
(Refer Slide Time: 05:03)
And what is the value now? p is 5; I am going to check whether 5 is prime or not. I
assume that, it is prime to start with. So, now, the whole program is ready for running.
We have various things; 2 and 3 are already in place; I know p equal to 5; I have to start
checking from 1 onwards and see if it is divisible or not, and if so we are going to see if
this is Prime changes.
So, let us start moving. i is 1. So, the first… I am at line number 15; p at this point is 5;
primes of i. So, primes of 1 is 3. So, this line is checking if 5 percentage 3 is 0 or not. It
151
is not 0. So, this line will not execute. And let us look at prime index; prime index
became 2.
And i is also 2. So, you go and check if this is prime or not. The flag has not changed.
So, 5 is a prime number; you record that. So, if… Once I finish line number 19, you will
see that, this location – this 53 will change to 5.
152
(Refer Slide time: 06:28)
And prime index is 3. So, now, we are ready to see whether the next odd number, which
is 7 is prime or not. So, let us continue now.
So, we are ready to check whether 7 is prime or not. We go back and assert 7 is prime.
And I am going to check from 1 to i less than prime index. So, prime index is 3. So, I am
going to check 7 against 3 and 5.
153
(Refer Slide time: 06:56)
Then, you check 7 against 5; it is also not 0. So, its prime does not change. So, its prime
remains at 1.
154
(Refer Slide Time: 07:12)
So, primes of prime index is – you can record 7; you will see a change here; it changes to
7.
And prime index will change to 4. So, we are trying to find out the next prime number.
Then…
155
(Refer Slide time: 07:22)
Now, we are checking 9. We assume that, 9 is prime to start with. Then I am going to
check against all the numbers from 3 to 7; I am going to check against 9.
156
(Refer Slide Time: 07:44)
So, isPrime become 0. As I said earlier, we are checking still against 5 and 7.
157
(Refer Slide Time: 07:55)
And the key thing is isPrime is 0 at this point. So, since isPrime is 0, this block of code
from 18 to 20 will not execute; and we are going to now go and check the next number.
So, p is 11. So, 11 is actually greater than 10. So, at this point, you exit out of this loop
from 12 to 22. Now, ready to print all the prime numbers. So, at this point, you can see
that, prime index is 4; which means the first four entries starting from 0-th location till
third location have valid prime numbers. So, that is what we are going to do.
158
(Refer Slide Time: 08:35)
So, print 2.
159
(Refer Slide Time: 08:39)
Print 3
Print 5.
160
(Refer Slide Time: 08:41)
Print 7.
161
(Refer Slide Time: 08:50)
And once this is all over, you can stop the execution. So, this program is actually correct.
I can stop the execution and so on.
162
(Refer Slide Time: 09:03)
So, what you are really seeing is this break point gives you an opportunity to start from a
particular. So, let us say these break points 8 and 12. If I start at 8, it actually gives me an
opportunity to run till 12 without executing the lines in between. Even though I was
doing single stepping, I actually showed every single statement and how it was
executing. You do not really have to do that. So, let me quickly demonstrate that.
163
(Refer Slide Time: 09:34)
Again, I am watching all these things. You can see that, all these prime numbers and so
on are invalid.
164
(Refer Slide Time: 09:48)
Then, I can press continue; it will take me to the next break point. So, at this point, it
went from line number 8 to 12 directly without showing each of these steps. So, now,
since the next break point is 12 and it is inside the loop from line number 11 to 22; if I
click on continue, we will do everything that is required for the loop and go to the next
time when the p itself is changing.
165
(Refer Slide Time: 10:19)
p changes to 9 and so on. So, all the things that were happening here; all the work got
done; it is not that, the work did not get done; the work got done; but you are waiting at
line number 12 and seeing what is happening.
So, at this point prime is… You are still checking for 9. And the whole thing got done
now.
166
(Refer Slide Time: 10:43)
You can go and see that, the program is executed. And if I… We have run till the end of
the program. So, at this point, you can stop the execution. And all the values from 2, 3 up
to 7 are already in the primes array. So, you can use the debugger for debugging various
programs. This is a very effective tool. I suggest that you get used to this debugging, so
that you can check all your programs once before you go and do your home works on the
left side. And this is a very useful utility. So, I cannot emphasize this more. You have a
good handle of how the debugger is used. And you do not have to print screen – fulls of
debugging statements; instead, use the debugger effectively. So, the key trick will be in
finding out what variables you want to watch and where to set your break points. So, I
knew this ahead of times. So, I had set break points at line number 12 and line number 8.
But, you have to be careful about where you are setting the break points. It does not
make sense to set break points at every line.
167
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module - 9A
Lecture - 13
Content
Multi-dimensional arrays: two-dimensional and more
Matrices,matrix operations
Welcome to lecture 4. We have a bunch of modules, which are all related to arrays and
what are called pointers. So, the first module is about multidimensional arrays.
So, multidimensional arrays – they appear many times in the form of tables. So, I am
sure you have seen spreadsheets or matrices and so on. These are all very common things
in several engineering disciplines. And you can think of them as two-dimensional array.
So, you have one dimension, which is the set of rows; another dimension which is the set
of columns. And you can have rows and columns, which form a table. This could be a 2D
array. You could also have arrays, which are more than 2 dimensions. For instance, if you
look at graphics, 3D graphics will require x, y and z. So, you have 3 dimensions and so
on. So, we need a mechanism by which we can not only store, but also be able to access
168
them and manipulate them as variables and so on.
So, let us look at what a 2D array would be. So, in this example, what we have is we
have an array called A, which says int A of 4 comma 2. So, that is the declaration. So,
just like what we have for basic variables, int A of 4, 2 tells you that, you want 8 integers
arranged as 4 rows and 2 columns. And as with 1D array, one thing that you will have to
remember is that, you have 4 rows numbered 0, 1, 2 and 3; and 2 columns numbered 0
and 1. So, the rows get numbered from 0 and the columns get numbered from 0 as well.
This is something that you have to remember. This has to be drilled into you. And the
storage in the memory is actually what is called row major order. So, what I mean by that
is as follows. So, even though you have 2 dimensions as a pictorial representation here,
remember – memory is just a sequence of memory locations; it is actually 1-dimensional.
And the way things are going to be stored is as follows. You take the first row, that is, the
0-th and you take the first column, that is, the 0-th column. The entry A of 0, 0 goes into
one location. Then A of 0 comma 1, which is the next column in the same row, gets into
the next location. And then you can think of this as folding down. And you have the first
row and the 0-th column that goes into the next location and so on. So, you can see that,
the order in which things are listed are… You have start with 0, 0; then you have 0, 1;
169
and then you have 1, 0 and 1, 1 and so on up till 3, 1. So, you have 8 memory locations
of the type integer and they are all contiguous. So, this is called row major order. You can
initialize 2D arrays as you did for 1D arrays also. So, for example, here we have a 2D
array called B, which has 2 rows and 3 columns. So, this set of set braces tell you that,
you want an array. And within this, you have two such curly braces – two sets of curly
braces and list of values there. So, the 0-th row will contain 4, 5 and 6; and the 1-th row
will contain 0, 3 and 5. So, we have two different things and the entries are again going
to be laid out in row major order.
You can go and design or use arrays, which are more than 2 dimensions. So, for example,
here we have float B of 2, 4, 3. So, you can now think of this as two planes; each plane
having 4 rows and 3 columns. So, you have the 0-th plane and the 1-th plane. So, I will
call it 1-th just, so that I can say i-th later. This is the 0-th plane and the 1-th plane. And
within that, I have 0-th row, 1-th row, 2-th row and 3-th row. And similarly, I have 0-th
column, 1-th column and so on. So, essentially, you can access i-th row of this plane by
accessing B of 0 comma i. And if I want to access i-th row and j-th column of this plane,
I can say B of 0 comma i comma j. If I want to access something in here, I have to use B
of 1 comma i comma j and so on. And see you can actually have dimensions more than
three also; but for most practical purposes, you will need 1D, 2D, and 3D arrays.
170
(Refer Slide Time: 05:09)
So, let us look at the most common use of 2D arrays. These are usually for matrices. Let
us look at a matrix called M; let it have m rows and n columns. So, the rows are going to
be numbered from 0 to m minus 1; and columns are going to be numbered from 0 to n
minus 1. So, you have m rows and n columns. And if you want to access the i-th row and
j-th column – element in the i-th row and j-th column, you access it as M of i comma j.
So, m of i comma j is i-th row and j-th column. Just as you have for arrays, i should be
less than m and it starts from 0; j should be less than n and it starts from 0 as well. So,
this is just an extension of what you did for 1D arrays.
171
(Refer Slide Time: 06:05)
So, let us write a small program in which you are going to fill up values and print a 2D
array just to show you how access to an array works.
So, I have this small program written up. So, let us look at this program here. So, I have
M, which is defined to be 3 and N, which is defined to be 4. And what I have is I have int
172
A of M comma N. So, this is going to allocate an array A of 3 rows by 4 columns. So,
total of 12 elements. And you can access an individual element using A of i comma j. So,
we have this loop running from 9 to 13, which is going to fill up the array; and the loop
running from line number 14 to 20, which is going to print the array. So, in this case,
these array is the 2-dimensional array is being filled up in this form. So, A of i comma j
is going to have the value i plus j. So, A of 0, 0 will have 0; 0, 1 will have 1; 0, 2 will
have 2 and so on. So, the 0-th row will have 0, 1, 2, 3 as its elements. Row number 1 will
have 1, 2, 3, 4 as it is elements and so on. And we want to be able to print it. So, that is
there in this loop. You can see that, it is a nested for loop that is used here. So, this loop
starting at line number 9, ending at 13 is the outer loop; and this loop is iterating over the
rows. And this is the inner loop and that is iterating over the columns. And this you are
accessing A of i comma j. So, you are accessing each element in a row and you go to the
next row and so on. And you have the same order in which you are printing this.
173
(Refer Slide Time: 08:26)
And as I mentioned earlier, there is row 0, which has 0, 1, 2, 3; row 1, which has 1, 2, 3,
4; row 2, which has 2, 3, 4, 5 and so on. So, the basic idea is that, you can access these
arrays as though you are accessing 1D array, except that, you have one dimension, which
174
is called the major dimension; and one, which is called the minor dimension. So, the
rows are the major dimensions and columns are the minor dimensions. If you have a 3D
array, the plane would be the major dimension; row will be the intermediate dimension;
and columns will be the minor dimension.
So, what can we do with these 2D matrices? Let us see what we can do with it. So, I am
going to talk about how to write a small program to do matrix multiplication. So, I am
going to assume that, there is an array; there is a 2D array or a matrix called A. So, we
have a matrix called A. And we have another matrix called B. Let us assume that, A has
aRows number of rows and aCols number of columns. And let us assume that, B has
bRows number of rows and bCols number of columns. So, if you are going to multiply A
with B, we are doing A times B; the number of columns of A and the number of rows of
B should match; otherwise, the matrix product would be incompatible. And how are we
going to do this computation? So, you can see that, the number of rows of C are the same
as the number of rows of A; and the number of columns of C is the same as the number
of columns of B. So, that is the first thing that you have to do, you have to see. You can
see that, this size is the same as this size; and this size is the same as this size.
So, given that, how do we now find out the actual entries of the final matrix C? So, the
175
way you do that is as follows. See you take one element from A and you take a
corresponding element from B and you multiply that. So, I took one element from A; I
took a corresponding element from B. So, let us say that, I picked the i-th row from A
and I picked the j-th column from B. So, i-th row will have aCols number of elements
and j-th column will also have bRows equal to a columns number of elements. So, this
and this dimension should match. So, the number of entries in the rows here and the
number of entries in this column here should be the same.
So, what I am going to do is I am going to take one pair at a time like this; multiply these
two elements and put it in the location here. Then I am going to multiply these two
elements; so in the column 1 here and row 1 here. And this has to be added to the
location at C and so on. So, I can proceed taking one pair of elements at a time – one
element from A and another element from B. And I have to keep adding it to the
elements in c of i comma j. So, essentially, the bottom line is – if you take the i-th row of
A and the j-th column of B, you get the ij-th entry of C. And you do this by iterating over
all the elements in the i-th row of A and j-th column of B simultaneously; you have to do
this together and take one pair at a time and multiply; add it to c of i comma j. So, this is
the basic logic.
176
So, let us see how to write a program for this. So, as before, we have three matrices. Let
us assume that, these three matrices can accommodate up to 10 cross 10 elements. So,
each of these matrices can take up to 100 elements. However, I am going to use not all
the 100 elements; I may not want all the hundred elements to be filled up; I allow for a
row and a set of rows and columns, that is, lesser than 10. I am going to take it from the
user. So, the user is going to give aRows and aCols, which is the number of rows of A
and the number of columns of A. So, clearly, A rows and A columns should both be less
than or equal to 10. And what I am going do is I am going to iterate over the number of
rows in A and the number of columns in A, and fill up A of i comma j. Similarly, I iterate
over the number of rows of… So, I scan the number of rows from the user and columns
from the user; I iterate over the set of values and end up with B filled up. And once I
have A and B filled up, I am ready to do matrix multiplication.
So, the key thing is the number of rows in C is the same as the number of rows in A; and
the number of columns in C is the same as the number of columns in B. So, let us see
how this program would run. So, you would need these two loops: the i-th loop and j-th
loop. So, the outermost loop and the intermediate loop will iterate over all the elements c
of i. j. So, this is evident from looking at this. So, if I want i comma j-th entry, I will take
this row vector here and this column vector here; I will do a dot product essentially and I
177
will put it here. Then I have to go and fill up this; then I have to go and fill up this; and
so on. So, I will start from this location and I am going to start filling up elements in this
order. So, this is the order in which we are going to fill up elements. So, these two loops
take care of that; i equal to 0 to less than cRows, j equal to 0 j less than c columns – will
iterate over all the entries of c of i, j. So, the first thing we do is initialize c of i, j to 0.
And once you have that, then we are now ready to do the actual dot product. Actual dot
product is done here. I am going to look at i comma k-th entry of A and k comma j-th
entry of B and I multiply that; add it to the current value of c of i, j. So, that is what the
plus equal-to does. You take i comma k-th value of a, k comma j-th value of b; multiply
it and add it to the current value of C. And when you do that, you see that, c of i comma j
is actually accumulating the value; you have initialized to 0 and it is accumulating the
value. At the end of this, c of i, j will get the final dot product of these two vectors. And
you are iterating over all the values of i comma j that are permissible for C. So, at the end
of it, you have all the entries in C filled up. So, this is the basic program to do matrix
multiplication. And this loop is printing the entries. So, this is a fairly simple program.
So, the key thing for 2D arrays is that, you have to remember always rows followed by
columns or planes followed by rows followed by columns. And you use square bracket to
access the row or column; and all the numbering for the planes, for the rows, and
columns start with 0. So, all the basic rules related to 1D array also applies here. So, if
you the array A is of size m cross n, the largest entry you can access is A of m minus 1, n
minus 1.
178
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 9B
Lecture – 14
What is a pointer?
Pointer variables, their declaration and their contents
Example of pointers and illustration on IDE
Declaring pointer does not allocate memory
In this module, what we are going to look at is very interesting aspect that C and several
other programming languages give you, which is not just accessing values that are stored
in variables, but also getting access to the memory locations or the addresses.
So, what is a pointer? So, to do that let us remember what happens, when we declare a
variable called k. So, if I do int k, k is the name of a memory location that can hold a
single value at a time. So, for instance if I have int k, k could be a given array location
100 and at some point of time, you could have k equals 38 and the value 38 will get into
this location called k. So, from the programs perspective, we are going to use k which is
the name of the variable, internally it is stored in location 100 and the value that is stored
is 38.
179
So, that is the concept of a variable. Now, what is a pointer? A pointer is essentially a
variable and in this example, we have this thing called int star p. So, the key thing to note
here is this thing called star, it is not int p, it is int star p. So, remember star is not a valid
character in a variable name for C. So, this star is reserved for something specific. In this
case, p is supposed to be a pointer to an integer. So, let us see what this does.
If I declare something called int star p, this p can contain memory locations instead of the
actual value. So, let us see what that means, so p is a variable as we had before which
means, your compiler is going to allocate some location for it. So, let us say p as a
variable is allocated location 200 and when you say int star p, it means it is a pointer. If I
put int star p equals to 100, the value 100 gets into this location 200 and if I do star p,
star p is an operation that you are doing. The meaning of that is, you take location 100
see, what that is pointing to. So, I take the value 100, instead of reading it as a value I
read that as an address and in that address, what is the value that is present? So, the value
that is present is 38, so p is an integer pointer. What that means is, the value that is stored
that you are going to reference to is actually an integer, p is just pointing to it, p does not
contain the value 38, but p contains the address in which 38 is stored. So, one way to
look at this is as follows.
So, let us say we have, so in our department we have a HOD and so the Head of the
Department. So, it is actually a person, so let us assume that there are people and there
are several people who could be HOD’s. But, at any point of time, there is only a single
180
person who is the head of the department. Let us say, you as a student walk in and you
want to find out, who the HOD is. So, of course you could go and ask each one of these
faculty members who the HOD is. But, the notion of HOD is always just one person.
Let us say I have, this small marker which says, the current HOD is person professor Z.
So, let us assume that there are three professors, X, Y and Z and you come in and ask
who is the current HOD and I say that the current HOD is Z. So, that way from outside,
you do not have to know who the current HOD is. You ask, who the current HOD is and
you get pointed to professor Z. At some point of time in the future, professor Z steps
down as HOD and let us say professor X takes of the headship, then the pointer points to
X.
So, again let us say somebody else walks in and ask the same question, who is the
current HOD? This pointer, can now point to X and not Z. So, that way what you are
actually getting is not the actual value, which is X, Y or Z, but you are getting the pointer
to the actual value. So, this is very useful for instance, this is something that we have in
our department. So, in our department we have an email address called
[email protected].
So, this [email protected] is a permanent email address, but this in turn points to
different people at different times. So, for instance our current HOD in year 2013, his
email address is pointed to now. When his turn comes to give up the position, let us say
in 2015 somebody else becomes the HOD, let us say professor Y becomes the HOD.
Then, I can remove the pointer pointing to professor X and I can instead make that
pointer point to professor Y and this way, you always will email head at CSE and the
internal pointer will forward it to Professor X or Y or Z, according to who the current
HOD is.
So, this is a very useful concept and it can come in very, very handy, we will see
examples of this later. But, as of now just remember that p is just like any other variable.
It gets a memory location, but the value in that is not a integer or a floating point value or
a character, it is going to be a memory address. And this memory address, you can think
of it as pointing to some location and when you do star p, you are looking at the address
and what it is pointing to and from there, what is the value that you get.
181
So, just like any other variable p can hold only one address at a time, but you can point to
different addresses at different times. So, just like variables can have different values at
different times, a pointer variable can have different addresses at different points of
times.
So, let us say I change the pointer value from 100 to 250. I change p from taking the
value 100 to 250, then it can point to a new location 250 and star p would now be 84 and
not 38.
182
So, to understand this, it may be useful to know this concept of what is called l value and
r value. Let us say, I am given a variable k, the l value refers to the address of the
memory location. So, l value is used on the left side, so whenever I say k, in turn it is
actually a specific memory location, that is the l value and the r value is the actual value
that you are going to put in. So, if I say k equals x plus y, then x plus y is going to be a
value and the value of this is going to be stored in k. So, the value of x plus y is the r
value and the address of k is the l value. So, pointers are actually allowing you to
manipulate the l value. You always have expressions on the right side, you get the r
value always, but pointers are mechanism by which you actually can get access to the l
value also.
So, pointers are themselves variables that store the address of the memory location and
the memory required by a pointer depends upon the size of the memory in the machine.
So, for instance let us say I have a pointer of size 8 bits or 1 byte, it can only point to 256
different locations. But, if I have a pointer of 2 bytes in size, it can point to 64000 or 64K
locations, so that is 65536 locations. If it is four bytes, it can point to 4 gigabyte locations
or 2 power 32 locations and so on.
183
(Refer Slide Time: 09:22)
So, let us see how to declare a pointer variable. You precede the name, so p is the
variable name and you precede a variable name by this mark called asterisk or a star
right and before that you have the data type. So, there are three things that go with the
pointer variable, you have the data type, you have star and you have the variable name.
So, p is the name of the variable and the star symbol there, informs the complier that we
want a pointer variable, it should not be an integer p, it should be a pointer p which
means, you are going to store addresses in them and not integer values and the compiler
will set aside, how many ever bytes you want to store the address. And this thing int
before that says, we intend to use this pointer to point to integer values. So, you could
have floating point values and so on, in which case you need a floating point pointer.
184
(Refer Slide Time: 10:28)
So, let us see what the contents of the pointer variables are. So, there is a small piece of
code here on the left side. So, int k equals 38 which means, there is a variable called k,
whose values is going to be 38 and you have int star p and this int star p, so I have int k
equals 38 which means I have an integer variable called k, int star p is a pointer variable
called p and then we are going to do some operations later. So, let us see what this
means.
So, int k equals 38, what does that do? The compiler sees that k is an integer of data type
integer. So, k is a variable of data type integer, so it gives you a memory location. In this
case, I am assuming that it is 100 from the program side, we are going to call it k and the
value that is stored is 38. So, this is something that you already know. Now, let us look at
the next line int star p. So, p is a variable name, so every variable gets a memory
location, so p gets a memory location. In this case, I am assuming that it is 200, but what
type is p, p is of the type int star or it is a pointer to integer. So, int star p is a pointer to
integer as of now, I do not have anything to initialize here, so initially it is a unknown
value for p, next statement says p equals ampersand of k. So, the meaning of ampersand
of k is give the address of k, not the contents of k, give the address of k and what is the
address of k, it is 100.
So, when you say p equals ampersand k, p gets the value 100. Remember, it is not 38 it
gets 100 at this point, you can imagine that p is pointing to the address of k and when
you say star p equals 5, it means take p which is at location 200. It is supposed to be a
185
pointer which is 100 and star p is the contents pointed to by 100, which is now 38. It says
change it to 5, so star p is 38, change it to 5, so let us do this once more.
So, p is at location 200, when you say star p equals 5, you first go to location 200 and
find out, what is the value there. The value is 100 and star p means the value that is
pointed 2 by p, which is currently 38. So, you take 38 and you want star p to be 5 which
means, this 38 should be change to 5. So, it is not changing this 100 to 5, it changing this
38 to 5. So, if the pointer is still pointing to location at which k is available.
So, at this point if we go and print k, k will actually have the value 5 and not 38, because
k is address 100 and you used p to manipulate the value at address 100.
So, I have a small program here which shows this in more detail. So, there are two
integers, a and b, a currently holds the value 10 and b currently holds the value 5 and this
int star ip is a pointer. So, ip is an integer pointer and currently it is uninitialized, so at
this point you can imagine that a and b are two locations, so I am going to do a small
diagram on the right side. So, a is some location, let us assume that it is 100 and a equals
10, we will put 10 at location 100, b is a another variable let us say this is at location 105
and it is going to take a value 5 and ip is a pointer variable.
Let us say, this is at location 211 and since there is no initialization, there is unknown
value here, it is unknown. Let us look at the first statement here, ip equals ampersand of
186
a. So, ampersand of a is the address of a, so what is the address of a? It is 100. So, this
will contain the value 100, then let us look at the printf statement here. So, it says print a
print, print ip and print star ip. These are three things, we are going to print.
So, what would print a give us? It will give us the value 10 itself, because all the
variables when you access them, you directly get the value and what you get for ip. So,
here we are also printing ip. What would be the value of ip? Ip should give you 100,
because 100 is the address stored in ip and star ip is the contents of the address that is
stored in ip, that is the way to interpret it. Star ip is give me the contents of the address
that is stored in ip.
So, the address that is stored in ip is 100, the contents of 100 is 10. So, this printf
statement is expected to print 10. It is expected to print 10 here, it is expected to print
100 and it is expected to print 10 here, as well. Then, let us look at this line, the next line
says i star ip equals. So, star ip equals 4, let us see what that does, so star ip, what is star
ip? So, it says change the contents of the address that is pointed by ip to 4.
So, ip is still pointing to 100, the contents of that should be is actually 10 now, it says
change that to 4. So, contents of the integer pointer pointed by ip should be changed to 4.
So, you go here, you look at 100, 100 is pointing to a now. So, 100 is pointing to a and
you want to change that to 4. So, this printf statement, so since you have changed that to
4, you would expect star ip to be 4, but what happens because of that is, since a is in
location 100, this a equals percentage d.
This is supposed to print the value of a, this will now be 4, ip is still address 100 you
want 100 there, ip has not changed. So, this is still be 100 and star ip is now take the
address contained in ip, chase that pointer find out the value that pointer is 4. So, this
will also print 4, so star ip and a contain the same value, so, so far so good. Now, let us
see what happens, because of this statement. We have ip equals ampersand of b, we are
now changing the value of ip itself.
So, ip still resides at location 211 and you have ip equals ampersand of b. So, instead of
100 now you will have ampersand of b that is 105. So, this will be 105, so you over
wrote 100 with 105. Now, if you take a step back and think about, what star ip must be?
So, let us look at star ip, star ip is take the contents of p which is 105 and find out the
187
value that is stored in location 105 that should be 5. So, b is already 5, ip will now have
105 and star ip is the contents of the address 105 that is 5.
So, one thing that is new in this program is, we have used this format specifier called
percentage p instead of percentage d. So, percentage d is meant for integers and
percentage p is meant for printing the pointer data types.
So, let us see a small program which is essentially going to run this thing. So, I am going
to look at pointers exercise.
188
So, I have the same program here, so instead of a and b, it is called count and number
here. So, count equals 10 and number equals 5 and you have these things here. So, let us
compile it and run it. So, I want you to pay attention to what is happening here. So,
initially count equals 10, so look at this program here, so let us look at this line number.
So, this line is saying print count, print ip and print star ip. So, count is 10, ip is some
zeros, followed by 2, 3, fe, 5, 4.
So, this is actually an address, do not worry about why it is printing f and so on. So, it is
printing something in hexadecimal format. So, that is the address of count and star ip is
10, as we expected. So, because ip is ampersand of count, then we have star ip equals 4,
which means change the value of the address that you already have, change the contents
to 4. So, in the second line we can see, count is also 4, ip is the same address and star ip
is 4 and in line number 15, we changed ip to point to number instead and numbers
address is some other address, it is 2, 3, fe, 5, 0 and not 5, 4.
So, you can see that ip changed to 2, 3, fe, 5, 0. The value is 5 and through star ip, you
actually get the same value 5. So, instead of a and b this program has count and number.
So, hopefully this explanation and the program there, helps you in understanding what is
happening.
So, one minor point and this is something that many novice programmers in C mess up is
the actual notion of memory allocated to values. So, declaring a pointer does not allocate
189
memory for the value, so let me explain what I mean by that. Let us say, I have int star p,
so I have a declaration called int star p. What this is supposed to do is, I am going to
have a variable called p, which is of the data type integer pointer. Let us say that is at
location 200. Since, I have no initialization I have no known value of p. So, that is what
you see here.
So, here you see that there is a dash dash, there is no initialization and let us say, I do this
star p equals 4. What is this supposed to do? This is an hither to unknown value and this
star ip is saying take the address stored in p, chase that pointer and initializes the value to
or change the value to 4. But, since this is unknown, you are saying take this unknown,
go to some memory location I do not know which and change that to 4.
So, this is pointing to some unknown address and you want this unknown address to
have the value 4, this is not a legal operation in C. So, at some point you have to take p
and put a valid address in it and only then, you will be able to access star p. So, let us see
what the sequence of things are, ampersand of p is 200, because p is given a memory
location, compiler will do that because it is a variable, you will get location 200.
However p itself is unknown, so we access the variable p, you get the contents of this
location 200. This is unknown and therefore, star p is illegal. So, this brings us to the end
of module 2 and in module 3 and 4, we will look at various other subtleties related to
pointers. We will not only see pointers as a problem of it is own, but we will also see,
how to use pointers and access arrays and what are the different issues that come up,
when we use pointer to access arrays. So, you have reached the end of the module 2, we
will see these things in modules 3 and 4.
Thank you.
190
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 9C
Lecture - 15
Contents of a pointer variable
Dereferencing operator, Null pointer
Pointer types, Typecasting and Examples
Pointers and arrays, Pointer arithmetic
In this module, we will see a little bit more of details on pointers itself.
So, let us go back to the code that we saw earlier. So, we have int k equals to 38, int star
p and p is ampersand of k, star p equals 5. So, one thing that I did not emphasize earlier
is this notion of asterisk, what is it used for. So, you can see that it is used in two places,
in one place as int star p and the other place, we have star p equals 5. So, they are related
to pointers, this asterisk symbol is used in two connotations. First time when you declare
a pointer, you say int star p and that point you are saying that p is a variable of type
pointer to integer. And later when you say star p equals to 5, the meaning is slightly
different. What you are doing is, you are doing what is called dereferencing the pointer.
So, at this point this is not telling the compiler to allocate space for the variable p or
anything like that. Here, actually we are dealing with the r value of p.
So, one small thing that you will see in the course as well as elsewhere is that, we usually
191
say p is pointing to k. So, in this case p is ampersand of k. So, instead of using this
technical term, the p equals ampersand of k, we usually say that p points to k or p is
pointing to k and so, on.
So, let us look at a dereferencing operator. So, it is the asterisk symbol and when you say
star p equals to 7, it takes the value 7 and copies it to the address pointed to by p. So, we
saw this, if p points to k, then the above statement will actually change the contents of
the k also to 7. And this dereferencing is something that you will see as a technical term,
that is used later in the lecture also.
192
There is a special pointer called the null pointer and generally we use pointers to point to
some specific variable. So, if I have int star p, then I did p equals ampersand k in that
case, p started pointing to k. But, there are several cases where you explicitly want the
pointer, not to point at anything at all. And C provides you a special way of doing that
and that is called the null pointer. So, one key thing about the null pointer is that no other
valid pointer. So, by that I mean, no other pointer that is actually storing some value or
which is going to get point, which is an address of a variable and so, on, will ever
compare equal to a null pointer. So, let us see what it means.
So, first of all C gives something called a predefined constant called null and this is
defined in stdio dot h. So, just like printf and scanf, this constant null is also defined in
stdio dot h. So, generally it is a good practice to check whether a pointer is null or not,
before you use it. And we will see this in lot more detail, when you do what is called
dynamic memory allocation. But, let us look at this code for right now.
So, what we have is, we have hash include stdio dot h and we have int star ip equals null.
So, this is generally considered a very good programming practice, you not only said,
you want a pointer variable of type integer pointer. The name is ip and the pointer is of
an integer type, you also said here, that it is not pointing to any valid location. So, if you
do this, then we never run into this problem of ip pointing at an unknown location and
what happens in this unknown location and so, on.
So, the moment you have null, it means that it is not valid location and you will probably
193
come back and change it later. So, you may do ip equals address of something later and
this is what I was talking about earlier. So, this tip says, if ip is not equal to null printf
star ip or if ip printf star ip and so, on. So, what this does is, it actually checks whether ip
is null or not. If I know that it was initialized to null and at this point if ip is not equal to
null, then there was a valid assignment ip that happen here.
If there is no valid assignment, ip will still remain at null and you will not print the value.
So, this is a check that is done usually in practice. Again, we will see how this is useful
when we do, what is called dynamic memory allocation. So, we have not done that yet,,
but when we do malloc, we will use this template later.
So, another settle thing with C is that, it provides a pointer of type void. So, void is,. So,
you declare it by saying void star v ptr and this void pointer is actually a generic pointer.
What I mean by that is, it can be made to point at anything. So, far we saw integers, star
pointer,. So, you could also have float star, ptr 1 and so, on. But, this void star v ptr is a
generic pointer.
You can make it point to an integer, you can make it point to a floating point, you can
make it to point a character or you can actually make it point to a pointer itself and so,
on. So, this is another thing that comes very handy and this is just for you to notice now,
we will see this in detail later.
194
(Refer Slide Time: 06:17)
So, let us look at a small code segment using pointers. So, here we have two integer
variables m and k and we have a variable ptr, which is actually of the type, integer
pointer. So, one thing you can notice is that in this single line, we have done a few
things, we have declared and initialized two variables m and k, we have also declared a
pointer variable of integer pointer type. So, you can combine declarations in one line and
this is the way to declare both integer variables and pointers to integers in the same line.
So, in this case ptr is made to point at k. So, ptr equals ampersand k is made to point,. So,
you are making ptr to point at k. So, let us look at these four lines of code,. So, the first
line says m has the value percentage d and is stored at percentage p and we give two
parameters as inputs to printf. So, the first thing you notice is, this percentage p is a
format specifier for pointers. So, just like we have percentage d for integers, percentage f
for floating point and so, on.
For pointers, it is recommended that you use percentage p and. So, let us see what
happens in this line. You have percentage d as a specifier that will attach it to m. So, here
you want m printed as percentage d format, which means print m as an integer and the
second argument is void star ampersand of m. So, this is the case of what is called
typecasting. So, ampersand of m is of pointer type and it is an integer point type, you
typecast that into what is called void star.
So, you can take a pointer which is of integer type and make it a generic pointer. So, if
you look at the data type of void star ampersand m, it is actually a void pointer now, it is
195
not an integer pointer anymore and you are printing that using a format specifier for you
called percentage p. So, in the second line k has the value percentage d and is stored at
location, whatever is the address of k. So, I already showed something very similar.
So, ptr itself is a variable which means ptr is given space or it is given some memory. So,
it is given a memory location. So, ptr has the value percentage p and stored at percentage
p. So, in this case this one, the first argument itself is a pointer and the second argument
is taking the address of ptr and casting that to void star. And finally, the last line says the
value of the integer pointed to by ptr is percentage d.
So, star ptr as I said is dereferencing ptr. So, see here, you are not declaring ptr. So, that
has already happened in this line, here you are doing dereferencing of ptr. So, this is what
I was pointing to earlier. So, there are two ways, in which you can use the star associated
with the pointer. One is for declaration, the other one we are seeing here, which useful
for dereferencing. So, it is getting the r value of k and it will print the value of k.
So, the key thing to notice in this slide is, there is percentage p specifier for pointers and
we did what is called typecasting. We took integer pointer ampersand m and we
converted that to void pointer using this bracket void star. So, I suggest that you actually
take this piece of program and tried it out to understand, what is happening there.
So, one key use of pointers is that it is something that is used for manipulating arrays.
So, in C there is a very, very strong relationship between pointers and arrays and without
pointers you cannot do a few things in C, when you actually want to use arrays. So, we
196
will see this again in more detail, when we look at functions. So, you take it for now that
there is a strong relationship between pointers and arrays and anything that you do using
array subscripts can actually be done using pointers. So, pointers in that sense, is more
powerful and more generic way of dealing with either contiguous set of locations or
something that is not contiguous.
So, let us see the small example, we have int a of 12 as I said earlier, int a of 12 will
declares space for a and let us say that is the top set of boxes that you are seeing. And we
know that a of 0 is the very first location, a of 1 is the second location and so, on and a of
11 would be the last location. So, indexes go from 0 to 11, 12 is not a valid index for a.
So, if I have int star ptr, this will give me a memory location called ptr.
And if I do either of these two, if I say ptr equals ampersand of a of 0 or ptr of a, then ptr
starts pointing here. So, let us see, what really happens here. So, let us look at this
statement first. So, if I look at this statement, ptr is ampersand of, a of 0. So, let us see
what this does. So, a of 0 is a value, it is not a pointer, a of 0 is actually a value,. So, we
have seen this in array so, far. But, the moment you put ampersand in front of it, it means
do not get a value that is stored in a of 0, instead give me the pointer to the 0th element.
So, the left side is asking for a pointer and the right side actually gives a pointer. So, this
is valid. So, what is this really doing is, ptr begins to point at the 0th element of a. You
can also achieve the same thing by a slighter shortcut, which says ptr equals a. So, this is
also something that you will see, a good programmers doing. So, experienced
197
programmers do not use this format, instead they use ptr equals a. So, what this is doing
is, ptr is now going to contain the address of the 0th element of a.
So, in some sense the name of the array a is actually only a synonym to the address of
the 0th element. So, when we say a, a is an integer array of size 12. But, the variable a is
actually just a synonym for the address of the 0th element of this array. Let us see, what
all these means.
So, if I do int star ptr equals a. So, in this piece of code, you are declaring a integer
pointer called ptr. So, this says we are declaring an integer pointer and the variable name
is ptr and right when we are declaring, we are also initializing it to a. So, if a is, this array
that we have here, then ptr starts pointing at the 0th location. So, this is something that
comes from the using a as a synonym for the address.
But, the nice thing you can do with ptr is that you can do something like this, ptr is ptr
plus 1. So, let us see what this means, on the right side, we actually have a pointer
variable and we are adding 1 to it. So, what; that means, is, instead of making ptr point at
a of 0, now you actually pointed to the next element after the current one. So, at this
point ptr was pointing at a of 0. So, you can see that here. But, the moment you do ptr is
ptr plus 1, it is starts pointing to the next element.
So, clearly you can see that if you do ptr is, if you do another ptr equals ptr plus 1, it will
start pointing to the 2th location and so, on,. So, it is starts with 0th. So, like as I said, I
abuse this, abuse English language I say 0th, 1th and 2th and so, on. So, right now it is
198
pointing at 0th location and if you do ptr is ptr plus 1, it will starts pointing at the 1th
location. And if you do one more ptr equals ptr plus 1 and it is starts pointing at 2th
location and so, on.
So, we can use like arithmetic, like you would do on integers and so, on. But, there are
certain rules, we will see this in a little more detail in a little wide. So, one thing I would
want you to think about is, what does star ptr equals star ptr plus 1 do? So, clearly there
is a difference between saying ptr equals ptr plus 1 versus star ptr equals star ptr plus 1.
So, take some time and think about it. So, it will be useful to think about, what this
statement ptr equals ptr plus 1 does versus star ptr equals star ptr plus 1 does?
Take a while to think about it. So, let us go back to arrays,. So, in some sense arrays I
said are less flexible than pointers. So, arrays are what are actually called constant
pointers.
So, let us look at piece of code on the left side, we have int a of 10 and we have int star
pa. So, pa is an integer pointer and when we said pa equals a, now pa starts pointing at
the 0th location of a. And if you do pa plus plus, we are incrementing pa, what it does is,
it makes pa point at a of 1 instead. So, this is actually ok to do because, pointers are
variables and here you did one assignment to pa and you change the assignment to pa
here, which is perfectly fine for pa, because pa is a variable. Let us do something which
is slightly different here.
On the right side in the red box, we have int a of 10, we have int star pa and we say a
199
equals pa. So, the first thing is that a equals pa is not permitted at all, so, this is not
permitted. The reason is that when we do a equals pa, a is supposed to point at some 10
locations and we are now asking a to point at some other location, as indicated by pa. So,
this is not allowed nor is a plus plus allowed. So, even though a is the synonym for the
0th location, it is not valid to take the variable name, that is used for the array and do any
kind of arithmetic on it.
You cannot do a plus plus, you cannot do a equals pa or anything of that sort. So, to
summarize a cannot appear in the left side of any expression. So, a plus plus is actually
just a equals a plus 1. So, a cannot appear in the left side of any manipulation at all of
any expressions at all, whereas, pa being a variable, can appear freely on the left side. So,
the variable a is actually an array and it is called a constant pointer. So, what I mean by
the constant pointer is that a of 10 is actually a set of 10 locations. So, we have 10
locations and a is actually pointing to a of 0, technically and it cannot point at anything
other than a of 0. That is what I mean by constant pointer, a can point at a of 0 and
nothing else, the moment using int a of 10.
200
Programming Data Structures, Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 16
Solution: Second Week Programming Assignment
Hello, welcome students. So, in this session solving problems, I thought I will show you
the solution to this problem on printing the matrix in a spiral. So, given a matrix and we
have to traverse it in a spiral order and print the elements. So, bear with me for today, I
have a terrible sore throat.
So, let us say here from the Horses mouth, today you are going to here from the Horse
itself. So, let us look at this matrix here, it is a 4 cross 4 matrix which is given as a public
test case for you. And when we say traverse this in spiral manner, what we going to do is
we want to go in this order. So, we want to traverse the top row first and after the top
row, we traverse the right most column, we traverse the bottom most row and traverse
the left most column.
At this point, we are done with all the outer layer and now we are supposed to go to the
inner layer and do things inside and so, on. So, I am going to define something called
peeling. So, think of it as what you do with an onion. So, you take an onion and you
remove one layer completely, what you are done with is, you have removed one layer
and you still left with an onion which is similar to what you had earlier. Just like that, if
201
you take the elements from the top row, then strip of elements in the right, strip of
elements in the bottom and strip of elements on the left.
If you start with a rectangle, you would be left with another rectangle. In this case, you
would be left with a rectangle 4, 3, 2 and 8, if you remove the outer once. So, you peel
one layer and you are left with another rectangle and on this rectangle, again I can start at
the left top and move one step at a time and print all these elements. So, now I am done
with one layer. So, of course, this arrow must not be there, I am done with another layer
inside and so, on. So, if I keep peeling one layer after the another, I will be left with the
sub problem which is similar to the original problem itself. So, this is a nice structure to
this whole setup. So, now the question is, how long do I keep peeling and what are the
corner cases?
So, to show that I have a small grid, which is 10 rows cross 7 columns here, I want to
see, how many times we can actually do this operation of going on the top, traversing on
the right, being in the bottom and then again traversing on the left. How many times we
can do that? So, I just follow my marker color carefully. So, I start with the left most cell
on the top, I do one traversal. Then, in some sense I turn right, then turn right and then go
all the way here.
So, that takes care of one layer of peel. So, that is my green line here is the first peel.
Once I am done with that I am left with a sub problem, which is not a 10 cross 7
anymore. So, let us go and count what we have. So, we have 1, 2, 3, 4, 5, 6, 7, 8 rows
202
and 1, 2, 3, 4, 5 columns. So, this is not a surprise, because we started with some m cross
n which is 10 cross 7 and from there we remove 2 rows and 2 columns. So, now we start
with a 8 cross 5.
So, now let us peel the outer most layer of the 8 cross 5. So, that is the second peel, once
is we remove it, as you can expect, we will have 6 rows and 3 columns. So, there will be
the third layer. So, let us look at the third layer, we will have 6 rows and 3 columns. So,
and then again I can peel once more. So, that is the third peel and once the third peel is
over, now it looks like I have a corner case. So, I do not have a proper rectangle
anymore, it is not 2 rows or 2 columns anymore. In fact, I have just 1 row here. So, I will
mark that with this color. So, I have just 1 column here that I need to traverse. So, in all
the other once, I can start at the left top of the rectangle and move right towards the right
here and then move bottom, move left and move up. So, essentially what we are doing is,
on the top most row, we traverse from left to right. Then, on the right most column, it
traverse from top to bottom. On the bottom most column, you traverse from right to left
and in the left most column, you traverse from bottom to top.
So, and this is the process that you keep repeating, only that what is your top, right,
bottom and left keeps changing, as you go along. So, initially your top is row number 0
and the bottom is row number 9, initially, but as we go along… So, and in that case, the
left would be 0 and the right would be 6, because we have a 10 cross 7 system, that will
be a first layer. Once you have that your top will become. So, this is the left, after one
round of peeling. This is the right, after one round of peeling. This line will be the top,
after one round of peeling and this will be the bottom of after one round of peeling. So,
and we are going to do this repeatedly. So, I am going to take this and see how to
translate that into our program. So, to do that, I am opening up my local editor.
203
(Refer Slide Time: 06:13)
So, what I am going to do is as before, I will go and scan the matrix first and what are the
things that I need. So, your problem specification said, you should support something for
a 5 cross 5 matrix. So, I am going to declare int A of 5 comma 5 and we need to scan
coordinates of the rectangle, what is the size of the rectangle. I am going to assume that
rows is M and columns is N and I am going to declare two variables called int and j, int i
and j will declare other things as we go along.
So, for now what I am going to do is, for i equals 0, i less than M, i plus plus, for j equals
0, j less than N, j plus plus scanf one integer which is A of i comma j. So, before doing
that I need M and N itself. So, I will do that outside, scanf ampersand M and ampersand
N. So, assuming that I have read the number of rows and columns and both of them
being less than equal to 5. So, when I am going to read M cross N matrix inside the
system. So, I want to imitate, what I did earlier in the graphics.
204
(Refer Slide Time: 07:56)
So, I know that the top most row is actually row number 0 and bottom most row is row
M minus 1. So, I have the top and bottom and I need the left and right, I will start with
left most being the 0th column and right most being the N minus 1th column. So, this
takes care of C style of indexing. So, only that I have been declare top, bottom and so,
on. I will declare that now, in top, bottom, right and left. Now, what I am going to do. So,
let us write code for one layer, peeling one layer let us write code for that.
So, what I am going to do is, I am going to start at the top and I am going to traverse
from left to right so, from left to right, one element at a time.
205
(Refer Slide Time: 08:55)
Here what I am going to do is, I am going to access A of i which is the row number and
right most column, I am going to do something on that later. So, now I am taking care of
traversing on the top and traversing on the right. Then, what I am going to do is, I am
going to start from right minus 1 and go up to left. But, I am going in the reverse
direction. So, I am going do i minus minus and in this case, we are accessing A of bottom
comma i.
And finally, i will go from bottom minus 1, then i want to go up to top plus 1, i minus
minus. Again, I am going to access A of i comma left. So, you can see what is happening
here. So, this part line numbers 19, 20 and 21, you are accessing A of top comma i, but i
goes from left to right. Then, line numbers 22, 23 and 24 is accessing A of i comma right.
So, since right is fixed, you are accessing a column and which rows are you accessing,
you are accessing from top plus 1th row to the bottom most row.
And line numbers 25, 26 and 27, you are accessing the bottom most row, but you are
206
going from right side to left side. You are starting with right minus 1, because you have
already touch the right bottom cell and you go greater than equal to left and finally, go
from bottom minus 1 greater than equal to top plus 1 and i minus minus. So, the first two
loops move in the left and bottom direction and the second, the bottom two loops move
in the left and above direction.
So, this is what we have. Now, we have to do a few things. So, one thing I am going to
do first is, I am not going to assume that this is printed directly, because. So, many of
you run into problems with presentation errors. I am going to show a technique of, how
to avoid presentation errors. So, instead of actually printing right here, what I am going
to do is, I am going to store in a matrix called B that I access A.
So, I am going to declare B and other things appropriately, what I am going to do is, I am
going to B of cnt plus plus. So, count is a variable I am going to run and I am going to
ensure that this records what I visited are not printed and I am going to do that on all of
them. So, I do that on all of them. So, now all I need is, I should ensure that B is also
declared properly. So, ((Refer Time: 13:37)) A is of size 25, I could not visit more than
25 elements.
So, I am going to keep B of 25 and since count is a variable that is counting the elements,
I am going to put in count equals 0. So, now what I have is I have peeled exactly one
layer. So, I move from left to right, top plus 1 to bottom, right minus 1 to left and bottom
minus 1 to top plus 1 and at the end of this, I would have peeled exactly one layer.
((Refer Time: 14:09)) So, let us go back to this picture, let us see what we would do after
peeling one layer.
So, ((Refer Time: 14:18)) in this picture after we peeled one layer, top can actually being
incremented by 1, because this was the earlier top. Now, the top most row is this row and
bottom can be reduced by one layer. So, this was earlier bottom and now it is going up.
This will be the row which is bottom. Similarly, left moves to the right side by one
column and the right can move to the left by one column,. So, I will put that in.
207
(Refer Slide Time: 14:47)
So, top can move up, it is can move down by one step, bottom can move up by one step,
left can move to the right by one step and right can move to the left by one step. Now,
the second question arises, this takes care of one peeling, but we need to see how many
times we can peel?
So, let us go back to the picture and if I give you a general m cross n, how many times
can you really peel? So, let us see after the first peel, before the first peel you have m
cross n. After the first peel, what do you get? You will have a m minus 2 cross n minus 2
rectangle system. After the second peel, you will have m minus 4 cross n minus 4,
because you remove two rows from here and you remove two columns from there and
208
we can keep doing this, but how many times can you keep doing this?
So, I am going to ensure that this process of peeling,. So, the code that you are seeing is
for peeling from line number 22 to line number 35 is, what you are seeing for peeling
accept that I have not said how many times you can peel.
So, I am going to declare another variable called count 1 and I am going to say count 1
must be less than or equal to both M by 2 and N by 2. What this takes care of is, it
ensures that peeling happens exactly either M by 2 times or N by 2 times, whichever is
lesser.
209
(Refer Slide Time: 17:04)
So, now this takes care of peeling various things. But, then sometimes we are left with
the corner case. So, let us see one of this corner cases.
For example, favor 3 cross 3, favor 3 cross 3 system like this. So, M equals 3 and N
equals 3. So, I said we can peel at most 3 by 2 times or 3 by 2 times, whichever is
smaller. So, 3 by 2 happens to be 1 which is an integer division, 3 by 2 happens to be 1
also. Minimum of 1 comma 1 is 1 time. So, it can be peeled exactly 1 time. So, you do
this, you can peel it exactly 1 time. So, let us take another small example which is the 3
cross 4 systems.
210
So, the claim is that you can either peel it, minimum of 1 time or 2 times, whichever is
smaller. So, minimum of 1 comma 2 is 1 itself. So, I can peel it exactly once. Let us
verify that claim,. So, I have 3 rows and 4 columns. How many times can I peel it
completely? I will start from here, go up here and so, on. These two cells are part of
inner one which I cannot peel. So, I peeled it once. So, in general I said we can peel it a
minimum of M by 2 times or N by 2 times and that is exactly, what this checked us.
So, cnt1 is less than or equal to M by 2 and cnt1 is less than or equal to N by 2. So, of
course, I need to declare cnt1 itself.
211
So, I am going to declare it here, cnt1 is also equal to 0. So, now I have the various
things, accept for the fact I have not taken care of what the corner cases, the final case.
So, before I do that let me take one more example.
Let me take a 4 cross 4 system. So, this is the 4 cross 4 system, if I start peeling, I will
start from here and end up here with one peel and the second peel would start from here
and end up here. In this case, it does not leave anything to be traversed at all, actually it
traverses everything. So, in general if you have a M cross N which are both even, what
would happen is, you can actually do peels which is minimum of M by 2 times or N by 2
times, you will actually touch all the elements.
But, if one of them is odd, let us say M is odd and N is even for example, 4 cross 3, then
you would peel, but you would be left with either 1 row or 1 column. So, we saw that
example here. In a 3 cross 4 system, it is 3 rows and 4 columns, 3 is odd we can do one
peeling. But, then we will be left with 1 row. But, if you do a 4 cross 3 system, you will
be left with 1 column and if we start with something like a 3 cross 3 system, where both
are odd, both M and N are odd, you will traverse the exactly once and then you will be
left with one element in between.
212
(Refer Slide Time: 20:21)
So, I am going to use this idea and write the rest of the program. So, at the end of it, if
you are done with everything and let us say, both your top and bottom co inside and left
and right co inside, it means that you are left with exactly one element. So, I am going to
do, I am going to traverse that,. So, A of left. So, what this does is, it takes care of
touching that exactly one element that you left out. Else, let us say top is still less than
bottom, it means you have a column that you have to traverse from top to bottom, but it
is just a one single column and which is that column, it is either left or right.
In fact, left and right should both be same at this point of time. So, what I am going to do
is, I am going to traverse from the top up to the bottom and what is the element that I am
going to access, I am going to access A of row which is i and column which is either left
or right, it does not matter. At this point, actually left should be equal to right. Else, if we
get this condition that left is actually less than right, which means they have not
coincided at yet.
Now, you are actually ready to go from left most column to the right most column, but it
is just one single row and which row is that, it is either the bottom or the top row. In fact,
both should be the same at this point of time. So, I am going to traverse that A of, I am
going to take top, but even if you put bottom here, it is not wrong, it do that and this
point at the end of line 51 now, the whole traversal is over.
213
(Refer Slide Time: 22:22)
So, final thing that we need to do is, just we able to print. So, what I am going to do is, I
am going to start from i equals 0 which is the 0th element of B and I am going to traverse
overall. There are M times N minus 1 elements, I am going to print all of them with the
space,. So, percentage d B of i. So, this takes care of printing M cross N elements for the
last element, we do not want a space. So, I will just printed directly and this is my whole
program.
214
(Refer Slide Time: 23:14)
So, there is a small problem here. So, this time there is no compilation error.
Let me run it, I am going to test it on some of the public test cases. The first public test
case is this, this 3 comma 3 system and it has 0, 1, 2 and so, on. So, there is a small
problem, I think I forgot to put a scanf with ampersand. Let me go and check.
215
(Refer Slide Time: 23:45)
So, this problem scanf was without the ampersand, it was a mistake. So, let me save it
and compile it once more.
I am running it now. So, 0, 1, 2 and 7, 8, 3 and finally, 6, 5, 4 and this seems to give the
output 0, 1, 2, 3, 4, 5, 6, 7, 8 which is what I expected.
216
(Refer Slide Time: 24:10)
Let us try it on some of the corner cases, what if I have is only a 1 cross 1 system and
that is the last test case 1 cross 1, the output is supposed to be 20. So, this seems to be.
So, let me take this and put it in the system.
So, as before I have this saved in my setup like last week. So, I have opened it up, I just
copied and pasted in it.
217
(Refer Slide Time: 24:34)
So, I will save and compile and run. So, let me see hopefully this is.
So, all the four test cases are passed, which is a good think to get.
218
(Refer Slide Time: 24:52)
219
(Refer Slide Time: 25:09)
So, the key thing that I did was not just traversal.
I want you to look at, what I did with B. So, instead of printing A as similar as I saw the
elements, I start it collecting them in B.
220
(Refer Slide Time: 25:22)
If you noticed, I start it collecting them in B right from line 24 onwards, I start it
collecting them in B.
So, at the end of all the traversal my B, the array B would have M times n elements,
index from 0 to m into M minus 1.
221
(Refer Slide Time: 25:35)
What I did here is, I printed elements from 0 to M into N minus 2 with a space and
printed the M into N minus 1th element without the space, because that is the last one.
222
(Refer Slide Time: 25:49)
So, I did not do any printing before this. So, this is a nice way to do things, because you
are done with earlier work, you just have to concentrate on printing now, you just have to
do this one step. So, this is a common way in which you can solve several presentation
problems that you been seeing. So, far. Always go and record, what your output must be
and take care of the printing, as the last thing in the program. So, this is something that
did not touch the logic of the program ever.
So, I believe that many of you had trouble in putting the logic for printing appropriately
inside the loop itself. But, I wanted to show that you can do all of this without worrying
about the logic. All I did was I saved all of them in an array called B and finally, I printed
them outside. So, this is the technique that you can use for many problems in week 3
onwards, I hope you actually adopt this technique from now on.
So, thank you very much and I hope that you enjoy this session as much as I did. So,
there are lots of interesting things that happened here, I did some mathematical
derivations to show that, you can only run M by 2 times or N by 2 times, whichever is
smaller and I will leave it you to think, how do I know for sure that either I have only
one row left or one column left or exactly one element left. So, go and think about that.
So, I knew that ahead of time, because of the way of doing things. So, maybe you should
go and practice yourself on, how to prove something like that and proceed with it.
So, thank you very much and see you next week, bye bye.
223
Programming, Data structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 9D
Lecture - 17
Accessing arrays using pointers
Illustrations on IDE
Pre and post increment, sizeof, pointer arithmetic
Comparing pointers, Pointer subtraction in a string
So, let us look at this small piece of code here. So, we have int myArray equals this. So,
let us think about, what it really is doing? So, int myArray there is a left and right square
bracket equals a list of values. So, what this does is, it takes the set of values from the
right side. So, we have 1, 23, 17, 4, minus 5 and 100, there are six values. So, int
myArray is actually an array, which can take six values. So, the size of myArray is six
values, then we have star ptr and i.
So, instead of explicitly saying myArray should be an array of size 6, we let the
compiler, figure out how many values are there on the right side? Based on that
appropriately take the size for myArray. So, in this case the size of myArray is 6. So,
following that we have star ptr and i. So, what we have in this line is, we have two
integers. So, one integer called i, one integer array called myArray and one integer
224
pointer called ptr.
So, that is what you have in line 1 and in the next line we have ptr equals ampersand of
myArray of 0. So, ptr is now pointing at 0th location of myArray. You can also replace
this with ptr equals myArray, that is also valid and it does the same thing. So, let us look
at this loop here. So, printf myArray of percentage d equals percentage d. So, it is saying
that print myArray of some integer equals some integer i comma myArray of i.
So, I am going to call this line a and in the next line, we have contents in address ptr plus
percentage d equals percentage d and for the first percentage d, we give i and for the
second percentage d, we gives star of ptr plus i as the variable to be printed. So, let us see
what this means. So, in the first line, it is straight forward, you are printing the index of
the array as well as the value contained in the index for i equals 0 through 5.
So, myArray has 6 values and it goes from 0 to 5 and the next line contains an address
ptr plus. So, if you see the loop, it will take I will take 0, 1, 2, 3 and so, on. You will see
contents of ptr plus 0, contents of ptr plus 1, contents of ptr plus 2 and so, on, all the way
up to ptr plus 5. So, that is what you are seeing on this. And star of ptr plus i means, take
ptr plus i, which means move it by appropriate number of locations from the base and
dereference it using the star operator and I am going to call this, line B. So, I will tell you
quickly, why I want the lines to be named A and B. But, let us take this program and run
it.
So, I have already taken this program and copied it into my editor.
225
(Refer Slide Time: 03:44)
So, let us see what is happening in the outputs. So, let us take some time to see, what is
printed in the output. So, the first line is myArray of 0 equals 1, contents in address ptr
plus 0 equals 1 that is what it says. So, let us see myArray in the program. So, myArray
is 1, 23, 17, 4, minus 5 and 100. So, myArray of 0 is indeed 1 and ptr plus 0 which
means the contents of ptr plus 0 is star of ptr plus 0. So, ptr plus 0 is ptr itself and star of
ptr is pointing to 1. So, it is printing 1.
Then, myArray of 1 is 23 and ptr plus 1 is supposed to point at the location one step
226
away from ptr. So, remember ptr is only pointing at myArray of 0. So, one element away
from that is a of 1. So, ptr plus 1 is the address of a of 1 and when you do star of ptr plus
1, you are asking for the value contained in 1th location of a, which means you are
asking for the value of a of 1 and this goes on. So, ptr plus 5 is asking for the value. So,
star of ptr plus 5 will get the value, which is at a of 5 and it happens to be 100 and that is
what you see here.
So, let us do a small change to the program and I want to show that myArray is
something that cannot change. So, let us say I do this, myArray plus plus. So, what I
want myArray to do is, point to 23. So, I want myArray of 0 to be 23, myArray of 1 to be
17 and so, on. Therefore, I am trying to increment myArray by 1. So, I do not, it will not
point to 1 anymore, it is supposed to be pointing at 23. So, let us try and compile this.
Let us try and compile that and let us look at this, error that happens. In fact, the
compiler starts and points error at line number 13. It says, l value is required as
increment operand. So, it is not a very useful error to C, but many compilers for C
actually give you errors, that are slightly cryptic. In this case, what it is saying is. So, let
us change this a little bit, like I said, this is equivalent to saying myArray is myArray
plus 1.
So, let us compile this, you still see the same error. On the right side, it says error
incompatible types, when assigning to type int of 6 from type int star. So, on the right
side it is doing myArray plus 1, which means you are asking for one location away from
227
myArray and in the left side, you are assigning it to myArray. Even this, it gives a
compilation error. So, based on what you write, so, it is changing the error.
But, the basic point is, it is not letting you change, what myArray should be pointing to.
Whereas, if I had done this, let us say I did ptr equals myArray, if I did that... So, let us
look at line number 13. So, if I compile it, it is technically not a problem. Because, even
in this line, ptr equals myArray of 0 is actually equivalent to this line, ptr equals
myArray.
So, I said these are lines A and B, let us see why this is useful. So, I want to use it, to
illustrate something now. So, both plus plus ptr and ptr plus plus are actually equivalent
to saying ptr plus 1, you are incrementing ptr by 1 though at different times. So, there is a
difference between saying plus plus ptr and ptr plus plus, plus plus ptr is called the pre
increment operator. So, it means increment the value of ptr and then use it and ptr plus
plus is post increment operator. Use the current value of ptr and then later increment the
value of ptr.
But, I want you to do this little exercise. So, change the line B. So, if you look at line B,
it saying contents in address ptr plus percentage d is i comma star of ptr plus i. So,
change that to star ptr plus plus and run it again and once more change that line to star
plus plus ptr and run it once more.
So, you take this piece of program which is in this slide, but make changes to line B,
using either this line or this line, compile and run it and try and reason why, whatever is
228
happening, why it is happening? So, try a reason about that. So, let us move forward,
there is another useful thing in C, which is called the sizeof operator.
So, it is not directly relevant to pointers, but this is something that I want to talk about
now and again use this sizeof operator to explain a few things later. So, in this program
we have three integers called int size, chr size and float size, flt size and we are going to
do this, int size is size of int, chr size is size of char and flt size is size of float. So, as the
name might indicate to you, you are looking at the size of an integer or size of a
character and size of a floating point and so, on.
What is it mean by asking for sizeof an integer? So, integer is actually a set of values and
this can be. So, if you go and look at the real world, the set of integers is unbounded in
size. So, you have all the way from minus infinity to plus infinity, it is not something that
a C language supports to. So, you cannot store every possible integer that is out there. C
instead restricts it to be at least 4 bytes in size.
So, if you say sizeof int, it is actually giving you the number of bytes that is use to
represent an integer. Similarly, sizeof character will give you the number of bytes
required to store a character and sizeof float is going to give you number of bytes for
storing a floating pointing number and so, on. So, these things can vary across different
machines and so, on. But, the basic point is whenever you do sizeof int, for the machine
that you are running on, it will tell you how many bytes are required to store an integer.
So, in most modern machines the sizeof integer is either 4 bytes or it can be even 8 bytes.
229
So, let us look at a few things related to pointer arithmetic. So, I am going back to
pointer arithmetic, you can do a lot of things with pointers and arithmetic related to
pointers.
So, you can assign between pointers of the same type, you can do addition and
subtraction between a pointer and an integer. We already saw this example, we did ptr
plus 1 and ptr plus plus and so, on. So, ptr was of type pointer and plus 1 is actually
adding an integer 1 to ptr. So,; however, C allows you to do that, you can compare two
pointers that point to elements of the same array, you can even subtract two pointers. We
will see a small example at the end of this module and you can assign and compare. So,
these are several things that you can do with pointers.
230
(Refer Slide Time: 12:05)
Let us see each one, one after the other, the first one is increment decrement. So, if p is a
pointer to type T. So, there is actually no data type called T in C. So, I am using this as a
template. Let us say, I have a data type called T and p is a pointer to T. So, T could be an
integer, character, floating point, it could be anything p plus plus will increases the value
of p by sizeof T steps.
So, I already mentioned this, if you say sizeof data type, it tells you, what is the number
of bytes required to store the data type. So, when you do p plus plus, it is not
incrementing p to point to the next byte, it is going to make p point to the next valid
element of the data type T. So, let us say my integer is actually 4 bytes. So, I have an
integer called a and let us say, it is occupying 4 bytes, this is not an array of size 5.
So, I am drawing a memory footprint and let us say variable a is stored in 4 bytes. Let us
say a location 100, 101, 102 and 103 and let us say location 104, 105, 106 and 107 are
the next set of locations. If ptr is made to point a and if you do ptr plus plus, ptr will not
point to 101, because the data type ptr is pointing to is integer, the next valid integer is
four steps away from 100. So, ptr plus plus will make it point to the integer at location
104.
So, let us look go back to the slide. So, if we have data type T, tab of n and if you have a
declaration T star p. So, you can substitute T with int, char, float whatever you want. So,
T itself is not a valid data type and let us say int i equals 4, if I do p equals ampersand of
tab of i, so, i is 4. So, ampersand of tab of 4 will make me point to the 4th entry in tab.
231
So, remember the entry start at 0. So, we are talking about 0th, 1th, 2th, 3th and 4th.
So, the ith entry is the 4th entry, starting from 0. So, p is going to point at the 4th entry in
tab and when you do p plus plus, it actually starts pointing at the next entry in tab. So,
this is an example of pointer arithmetic, where you mix pointer and integer operations on
it.
You can also do add and subtract from pointers, you do not have to do just increment and
decrement, you can also add. So, in this example what we have done is, we have made p
point at the 0th location of tab and we are adding 5 to it, which means it will start
pointing to tab of 5.
232
(Refer Slide Time: 15:30)
You can compare pointers, if p and q are pointers of the same, if they are pointing to
members of the same array, then you can do things like equal to equal to, not equal to,
less than andso, on. So, let us say I have an integer array and. So, in this case I am
drawing an array, not memory, I am drawing an array. Let us say, I have an array a and
let us say this is a of 0 and this is a of 4. If I make p point at this location and if I make
pointer q point at this location, then I can actually ask the question is p less than q.
So, the meaning of that is p pointing to a location, that is earlier in the array than what q
is pointing to, in this case p is less than q is actually true. So, you can also do is p not
equal to q. So, in this case p is actually not equal to q, p is pointing to the 0th location, q
is pointing to a of 4. So, p is actually not equal to q. So, you can meaningfully compare
pointers which are pointing to the same array. You can ask is this is the same, is this
earlier than the other pointer, is it later than the other pointer and so, on.
So, you can make pointers point at different locations in an array. You can ask the
questions, whether one pointer is lesser than the other and so, on and we can also
compare it for inequality with zero, you can ask the question, is p equal to null? So, let us
say p is pointing to a of 0 and I can ask the question, if p equal to equal to null? If I ask
this question, this will actually evaluate to false, because p is actually pointing to a of 0.
So, you can do comparisons of this kind.
233
(Refer Slide Time: 17:20)
So, given a string it tries to find out, what is the length of the string? So, let us see what a
string is in C. I will anyway do this in more detail later, but a string in C is actually just a
sequence of characters followed by a special character called back slash 0. For instants,
let us say I have a string called hello, it will be stored in this form, you have h e l l o. So,
this is letter o and on top of that in C, there is a special character called back slash 0. So,
this case is actually 0 or what is called the null character.
So, a string called hello, it supposed to be of length 5, but in reality it actually takes 6
bytes. So, let us I want to find out what is the length of this string? So, I can keep
checking starting from the 0th location of the string, I can keep checking for one
character after the other and I will look for this special character called back slash 0. The
moment I see a back slash 0, it means that this is the end of the string and this is the
convention used in C.
So, like I said we again see this in more detail later, but as of now let us look at this
example. So, let us I want to find out what the length of the string is. So, I have written a
234
small function which does that. So, character star s and character star p. So, at this point
p is initialized to s. So, s is a pointer to a character array that is what you see here. So, s
is pointing to a character array and character star p equals s. So, p also points at the 0th
location of s. So, s is the string for which we want to find out the length.
So, there is a while loop here that keeps checking, if the contents of the memory location
pointed by p is set back slash 0 or not. So, there is a special character back slash 0, I am
going to start from location h. So, where h is stored, all the way till I see a back slash 0.
So, initially p is pointing at h and star p is not equal to back slash 0, we do a p plus plus.
So, p will start pointing here.
So, p has already moved by one location, it has started pointing at s of 1, s of 1 is e and it
is not back slash 0. So, the while loop will continue and start pointing at l and this will
keep happening and it will stop, when p points at back slash 0. So, let us assume that this
is at location 100, this is at 101 and so, on, this will be at location 105. So, when the
while loop will stop, when star p is equal to 0. When the star p equal to 0? When p is 1
naught 5, star p is back slash 0 and which means when p equals 105, the loop will stop.
So, at that point, if you go and ask for p minus s. So, p is 105 and s is memory location
100, 105 minus 100 is 5, that actually gives us the correct length of hello. So, this is a
very neat and beautiful trick to show with pointer arithmetic. So, let us not worry about,
what this written statement is and what is this strange looking int strlen and so, on is
later. But, as of now I just want you to pay attention to the while loop.
So, it iterated over the elements of s and it is stopped at back slash 0 at that point, p got
value 105 and if you do p minus s, it actually gives you 105 minus 100 which is 5. So,
when you do arithmetic over pointers, if you do subtraction of pointers, you actually get
an integer back and this is something that is very handy. We will see use cases for this in
a lot more detail, later. So, we have now come to the end of this module. In the
subsequent modules, we will see how to use pointers for manipulating arrays and we will
also see how pointers are useful in the context of order called functions.
So, we will start looking at functions and when we visit functions, we will revisit how
pointers are handled, when we pass them on to functions.
235
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module -13a
Lecture - 18
What is s string?
How C stores a string? Character vs string
String initialization, Printing strings, Examples
Reading strings
Welcome to this module. In this module, we are going to learn something called Strings.
This is something that happens in the real world very often, name, your name and my
name, colleges name, things like that are all strings. C does not actually give you direct
support for strings and this is why I wanted to have one small lecture on what strings are
and how to manipulate strings, how to read, how to print and so, on.
So, a string is essentially a sequence of characters and the way C handles this is, it
handles it as a sequence of characters stored in an array and it is not just the characters
that you want, it also has a special symbol back slash 0 or the null character. So, if you
see this picture here, we have these 5 characters h e l l and o. So, you can see that this h e
l l and o are the 5 letters that I need. But, there is an extra character called back slash 0
and the whole 6 characters is called a string.
So, we have a string which actually contains 5 letters and the beginning of the string is h,
236
the end of the string is actually back slash 0 which is a special character that C uses. In a
little while, we will see why that special character is needed.
Let us say, I have an array str of length 11, you can see that in this example. The string
even though it has 11 bytes of storage allocated to it. It only has a contents good space
day, followed by this special character back slash 0. So, remember that is going to be part
of every string. There are 2 characters or 2 bytes that are not used by the program. So,
this character str of 11, it has 4 characters g o o d, a space that is the 5th character. Then,
6th, 7th, 8th are characters of day, then 9th character back slash 0. So, there are 2
characters that this string could take. So, this array can take two more characters, but the
string ends at back slash 0. The other things, the last 2 bytes are not part of the string.
237
(Refer Slide Time: 02:47)
So, let us see the basic difference between what a character is and what a string is. So, let
us I say that there is a character H. So, let us say there is a character called H, it is a
single byte representation. So, it stores H and nothing else, whereas, if you store string
H, it actually requires 2 bytes. So, it stores the letter H followed by back slash 0. So, this
is why I said, every string has at least this character back slash 0. So, even an empty
string, a string that has no valid characters at all, will still need, back slash 0 or 1 byte to
store it.
238
a character array of size 2 and equals within double quotes a means, the right side is a
string expression. And in the string expression, remember a is a character and because it
is a string, back slash 0 is always part of it. So, this will require 2 bytes of storage.
On the other hand if you do it, character s2 equals within single quote a, a is a single
character and they require only 1 byte. So, s2 will allocate only 1 byte and you will store
the letter a in it. So, this character s2 is a single character, it is not an array and s1
however, is an array of size 2 and it can store a as well as back slash 0. So, there is a
difference between the storage that is given to both of these.
So, let us take a few other examples. Let say, there is this message1 and message2 I have
in this example here. So, message1 is declared as an array of size 12 and we have
initialized this to the string called hello world. So, hello space world, if you look at the
length of it, hello requires 5 characters and world requires 5 characters. So, that is 10,
plus space requires 1 character. So, that is 11 bytes.
On the left side, we have allocated 12, the reason for message1 of size 12, the message1
being size 12 is that. Hello and world requires 5 bytes, each plus space is 1 byte, 11 bytes
and remembered there is always an implicit back slash 0. You do not have to put it
explicitly back slash 0 is something that if you have as a string, back slash 0 is assumed
to be added automatically. So, the internal representation of message1 would look like,
what you see here.
So, you see hello space w o r l d followed by back slash 0. So, it is actually using all the
239
12 bytes that is allocated to message1. So, it is not that, you always want to initialize it
and. So, declare an array and initialize it. Sometimes, you want to read things from the
user. For instants, let us say I want to read the message from the user. So, I have
message2 which is of size 12 and I scan the message. So, the specifier for scanning a
string is percentage s.
So, we have seen how to scan integers and characters and so, on, before. Percentage s is
a specifier for scanning a string. So, s stands for string. So, message2 and let us say, I
typed hello as input. Then, H e l l o will go as the first 5 characters in message2 and I
said C automatically puts in a back slash 0 at the end of the strings, if you scan them
from the user. So, back slash 0 is the 6 character. So, that happens in the 5th location.
So, 0th location has H, 1th location has e, 2th location has l and so, on, the 5th location
would have back slash 0. And in this case, there are only 6 bytes that are required, the
bytes following that even though message2 has space for 12 bytes, it has only 6 valid
bytes that are useful for the string, the other 6 are unknown. We have already seen this,
when we talked about the notion of arrays and what if, you have not initialized them.
So, let us look at another example, where we have initialization on the left side, we see
character star message3. So, message3 is a pointer to a character or it could be a pointer
to a character array, on the right side, we have hello world. So, what happens here is, this
hello world again as I said before requires 12 bytes and if you print message3, it will
print hello world with a space in between. But, message3 here is actually a pointer to an
240
array of characters.
So, one thing that happens is, if you notice the left side, there is no explicit storage
allocated for message3. So, unlike message2 or message1 where you see that, there are
12 bytes allocated, message3 does not have any storage allocated to it. So, the way C
handles this is, the right side what you see as hello world is treated as a constant string.
What; that means, is, this is the string that cannot be change and this string is put into
what is called the read only memory of the process or the program, before the program
starts running.
So, there is a portion of the memory for every program, which is marked as read only
and this hello world as a string is put into the read only portion of the program and
message3 is just a pointer to this read only portion of the program. So, we already saw
the notion of pointers. So, message3 is just a pointer to the beginning of this array, which
has hello followed by space, followed by world, followed by back slash 0.
So, the reason why this is critical is, message3 is not allocated space, you do not have
space allocated for it, it is done in the beginning of the program and you do not even
have control over it explicitly. So, if you do message3 of 1 equals a and since hello world
here is saved in a read only memory location, this changing any contents of message3.
Since, you did not allocate space explicitly for it, would result in undefined behavior.
So, this is the common mistake that programmers make. So, if you allocate space like
what you see in message1 or message2, it is to go and change contents of it, you can read
the characters as characters that you allocated, we saw integer arrays and so, on earlier.
So, this is just character arrays. But, if you did not explicitly allocate a space like in
message3 now, C allocates space for the strings and you cannot touch it, you cannot
change the contents of it.
241
(Refer Slide Time: 10:06)
So, let us look at a small example. In this case, we have character star a1 which is hello
World. So, what we have is, we have a pointer to a constant string. So, what I mean by
that is, the string itself is constant, it cannot change and a1 is a pointer to it. Character a2
of square brackets is hello world. What this does is it allocates space for a2 as much as it
is required. So, let us look at the right side, the right side requires 11 characters for hello
space world plus remember 1 character for back slash 0. So, a2 size will be 12 bytes.
So, a1 is a pointer to constant string and a2 is a constant pointer to string. So, let us see
what this difference is. So, a1 is a pointer and it is pointing to a constant string. What;
that means, is, you cannot change the string, whereas, character a2 is a constant pointer.
What; that means, is, you cannot change it, you cannot make it point to something else,
but wherever it is pointing to the contents of it can be changed. So, I will explain this in a
little while, but before that let us look at line 4.
So, I want to show the distinction between, how a1 is handle verses how a2 is handled by
C. So, if I go and use this sizeof operator, size of is supposed to tell me, the size in the
242
number of bytes of the data type that is passed to it. So, if you look at size of a1, a1 is a
pointer to character and a pointer to character will have the same size as pointer to an
integer and so, on. So, size of all the data types could be different, but size of the pointers
in a specific machine will all be the same.
So, size of a1 will be the size of the pointer. So, in my machine the size of pointers is all
8 bytes. So, size of a1 will print 8, whereas, size of a2. So, a2 being a constant pointer to
a string is actually going to look at, not the size of the pointer itself, but the size of what
is pointer to. In this case a2 is pointing to hello world that requires 12 bytes. So, size of
a2 would be 12. So, if I took this program and run it on my machine, I would get 8 for
the size of a1 and 12 for the size of a2.
So, this distinction happens because, even though pointers and arrays are treated as
something which can be interchanged, it is not always true. So, this is a classical
example of a place, where pointers and arrays do not really mean the same thing. Now, I
said a1 is a pointer to a constant string, therefore, if you make a1 of 1 equals u, you are
trying to change a constant string. The string is supposed to be not changing through the
program, but you are making a1 of 1 is u, that will result in undefined behavior.
So, you may have a1 even changing to h e l l o, but you cannot always expect that this
will work. Sometimes, when you run the program, this can even result in an error. Let us
look at the next line, a1 equals a2. So, a1 is a pointer, you can make it point to anything
else. So, here a1. So, if you look at a1, a1 is a pointer, it is pointing to something which
is constant, but it itself can point to something else. So, in this place we are making a1
point to a2.
So, this is acceptable, this is not a problem. However, changing the contents of a1 is not
acceptable; a1 can start pointing to something else. So, if you see what happens after this
printf statement, you will see that a2. So, a1 is pointing to a2. So, it will still print hello
world, because a2 is also hello world. Let us look at the last line, a2 equals a3. So, this is
where this notion of constant pointer comes through. So, you can change the contents of
a2.
243
error in this line.
So, again I will read this. So, a1 is a pointer to a constant string, you can make a1 point
to something else, but you cannot change the contents of a1, a2 is a constant pointer to a
string, you can change the contents of a2, but you cannot make a2 point to anything else.
So, let us go and look at, how strings can be read? We saw how strings can be printed.
Let us see, how strings can be read. So, for example, scanf takes this percentage s as a
specifier and you can give a pointer to the character array as a parameter to it. So, let us
assume that there are two strings, A underscore string and E underscore string and let us
assume that both of them have 80 characters in them, which means they can take 79 valid
characters and you have to reserve at least 1 byte for the back slash 0.
So, let us say I end up typing something which is 79 characters. The 79 characters will
go into location 0 to 78 and automatically back slash 0 will be put in as the 79th
character. So, let us say I did printf, enter some words in a string, I prompt you and I
scanf, A string and E string and if I print it. So, that is what the program is doing. So, this
line is prompting the user to type something. This line is expecting the user to type
something in the keyboard. And finally, it is printing whatever is read in the keyboard.
And let us say when this program run, I type this line, this is a test. Let us say, I type that,
this space, is space, a space, test, followed by a dot. So, what happens is that this scanf,
percentage s starts from a particular location and keeps reading letters, till it finds a white
space. So, in this case A string, right when scanf works, it starts with t and then it reads h,
244
reads i, reads s and it reads space. The moment it is reads space, it says that.
So, scanf the way it works is, space is not scanned into the A string, it stops here, it puts t
h i s followed by back slash 0 into A string. And subsequently, when you are reading E
string what happens is, it ignores the space, goes to the next location which is not a
space, which is i here and it starts reading from there, till it finds another space. In this
case, this space in after i s. So, it keeps reading till it finds it is space. So, it found the
characters i and s.
So, A string in the memory, it would have t h i s and back slash 0. This will be the 5
letters in A string and if you look at E underscore string, the variable name E underscore
string, it would have i s followed by back slash 0. So, it will have 3 characters, even
though, you have space for 80 characters, it has only 3 characters of which only two are
really valid, these how we read strings.
245
Programming Data Structures, Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 13b
Lecture - 19
Functions to handle strings; Length, copy, concatenate
Lexicographic ordering
String comparison
String function in library C
There are functions to handle strings, so we will see the notion of functions itself in little
a detail later.
So, the primary thing is string is not a basic data type. So, you have characters, integers,
floating points and so on, they are all valid data types and valid basic data types, whereas
the notion of a string is not a valid data type. So, there are other languages like C plus
plus, java and so on, where string is actually part of the basic data type. Unfortunately in
C, it is not. So, what it requires is you have to do something called a derived data type.
So, when I said a string is an array of characters, so it is actually an aggregate data type
of characters, you have several elements which are all of the same type. So, it is an
aggregate type where you have more than one element of the same type. And to
246
manipulate various things with functions, we will look at what are called string functions
later.
So, the notion of functions itself is not too big, so the idea of functions is if you do
something repeatedly, instead of you doing it every time, you typing the program every
time, you type the program into what is called a function and you call the function once
in a while. So, just like printf and scanf and so on, these are functions that you are
calling, you did not a write printf and scanf. So, we will see the notion of how to write
functions, how to specify them and so on later.
But, as of now various operations is related to strings, we will see what functions are out
there that C provides. So, the typical functions on strings are finding out the length of the
string, let us say I give my name, I want to find out the length of the string. So, I need
there are functions which can go and count the characters in my name and tell me the
number of bytes required. Given two strings, I can ask whether the two strings are equal.
What I mean by that is, so two strings are said to be equal, if it has the same sequence of
characters.
And we can ask a question like is something a sub string of the other or I have two
strings, I want to attach one string to the other and so on. So, there are functions for each
one of these, these functions are available and given to you in a library called string
library to invoke functions or these methods from the string library, you need to include
what is called string dot h. So, just like for printf and scanf you included stdio dot h for
doing various string operations, we will need string dot h. So, one thing we are going to
do in this class initially is that, we will write these functions ourselves or we will write
these programs ourselves and then, I will finally show you how to use the C library calls.
247
(Refer Slide Time: 03:01)
So, let us say I give you a string and I want you to find out the length of the string. So, in
this example we have string A and let us say string A has Hello space world followed by
a back slash 0 and I want to find out the length of the string. So, what is the length of the
string? I will start from location which contains H and have to keep checking till I find a
back slash 0. So, when I find a back slash 0, I know that it is the end of the string,
because that is how C understands strings.
So, anything which is a valid sequence of characters ending with a back slash 0 is a
string. So, you go and concentrate on this segment of the program, it is starts with i
equals to 0, we have i which is 0 and A of i is h to begin with. So, it is starts looking at
that and it keeps adding, so you can see that in the loop, we are incrementing i by 1,
every time I see a non null character. So, the null character is back slash 0, every time I
see a non null character, I will increment i by 1.
So, i starts with 0, the moment I see H, i is changed to 1, now I look at e, then e is a non
null character, i is changed to 2, I look at l and so on. So, if we keep looking at it, I will
see that the length of Hello world is 11, so you have... So, when i equals 11 you have
back slash 0, so remember this is A of 0 and this location is actually A of 11. When i
equals 11, this is the 12th time we are running the loop. So, i equals 11, it is a back slash
0 character.
248
So, what you actually have is length of this valid letters, so the array containing all these
valid letters excluding back slash 0, so that is the length of the string. So, Hello world
has 11 characters in it, so remember there is a space in between. So, what we have is we
have i equals to 0 and we have a loop that does it, so do not worry about this notion of
return and string length and so on, we will see that in a little while.
So, this is actually a function which takes A which is a pointer to the array and it finds
out the length and it returns the length. So, we will see this notion of passing A and
returning the values and so on in a lecture on functions later. So, the basic idea is what I
want you to grasp.
Let us look at another function in which I want to copy the contents of array A to B. Let
us say, array A is of some length and array B is of something else and I want to copy
array A to array B and the way to do that is, so I want to go and look at size of this array.
So, what I want to do is I do not care about what is present in B, B could be actually of a
size which is different from A. I also do not care about what are the contents of B already
in place. I want to copy byte by byte or character by character, the contents of A into
contents of B.
249
the null character. So, that is what this program segment does, so int N1 is string length
of N1. So, remember I wrote this thing called string length, if I past Hello back slash 0 to
it, it will tell me that the length of the string is 5 and we have k equals to 0, k less than
N1, k plus plus. So, it will go from 0, 1, 2, 3, 4 and you will keep copying contents of
location A to B.
So, at the end of this you actually have to make B of k equals back slash 0 or you could
actually change this to equal to. So, this only copies all the valid characters, we still need
to do the last one, so we need to write B of k equals back slash 0. This is one way to do it
or you change this condition to equal to, either one of them will take care of copying the
string including back slash 0. So, one thing that this segment does is it ignores whatever
was stored in B earlier.
So, there is a certain problem here, let us say A had only 4 bytes, so let us say A was all
these bytes whatever number of bytes, but B does not have enough space to copy the
contents of A. This will be a problem, because you are going to start looking at B and
when I start looking at B, I am going to look for back slash 0 as a possibility. And for
whatever reason, let us say I did not have any of this space, I would have copied H e l
and l, but I did not have enough space for o and back slash 0.
This should be a disaster, because my array has lesser space than what it really needs.
This is not something that you have to handle it explicitly, if B has lesser space than A,
what is it mean to say copy the contents of A to B, you have to be careful about that.
250
(Refer Slide Time: 08:28)
Then, let us look at this function called string concatenation or the idea of concatenation.
Let us say I have this string called world and I have a string called Hello and I want to
make a single string Hello World. So, that is called concatenation, what I really want is I
want to copy W to this back slash 0, o to the next location or to the location after that and
so on, including back slash 0 of B to be copied here. The results I expect is Hello which
were the contents of A followed by the contents of B, which is World followed by back
slash 0 which says that this is the termination of A.
So, I am trying to concatenate B to A and as a result I want this, so you can see how this
is supposed to be done. So, clearly A must have space for copying B to the end of it, so if
A had lesser space than the size of B plus A included, there is a problem. So, in this
example it is not a problem, but if B were something much larger, then concatenating B
with A, you will run out of space in A. So, in this example however, it is not a problem.
Once we did the concatenation, so up till here is what you got from A, from here to here
is what you got from B and this is the extra character that you have to add to the end to
ensure that when you now treat A as a string from left to right, it terminates with B. So, I
am going to leave this as an exercise for you to go and write a program on your own.
251
(Refer Slide Time: 10:12)
Finally, let us look at the notion of ordering strings and so this is something that we do in
our daily life. So, if I go and pickup dictionary I may want to go and look at what all the
order in which the words are occurring. So, in this slide I am looking at names of people
or words, so let us look at the word Badri and word Devendra. So, clearly if I go and
arrange them in dictionary, the word Badri will come before the word Devendra on the
dictionary. So, I will say that word Badri coming before word Devendra, I will use the
notation less than for it.
So, Badri comes before Devendra is expressed as Badri less than Devendra. Let us look
at this example, Janak is less than Janaki, so Janak has only five characters whereas
Janaki has six characters, so clearly if you go and look at dictionary, all the smaller
words will appear before all the bigger words. So, in this case Janak is a smaller word
than Janaki, so even though the first five characters of both these are same, Janak has one
less character than Janaki. So, therefore, we will say that Janak is less than Janaki.
Then, if you look at this example, Shiva is less than Shivendra, because if you go and
look at the fifth character, it is A here, the fifth character here is E. So, clearly A will
come before E in the dictionary, so Shiva is less than Shivendra. Then, Seeta is less than
Sita, so Seeta is less than Sita, so we are not looking at the size of the string alone, we are
also looking at dictionary ordering. So, clearly Seeta will come in the dictionary before
252
Sita. Even though the length of Sita is only four, Seeta is of length five, in English
dictionary you would see Seeta before Sita.
Then, there is something peculiar about characters and languages. So, one thing that is
true in Machines is that all the upper case letters in an alphabetical order appear actually
before all the lower case letters. So, this is a property of how the machine treats various
characters. All the upper case letters are supposed to be lesser than all the lower case
letters and with in the upper case letters A is less than B and so on up to Z and within the
lower case letters, a is lesser than b and so on up to z.
But, capital A is actually less than lower case a, capital Z is less than lower case a and
lower case z and so on. Because of that if I go and look at this example, capital B Badri
is actually less than small case b badri. So, in dictionaries you do not really have make a
distinction between upper case and lower case, but in programming, strings can be
difference. So, lower case could be different from upper case, so we have to be careful
about that.
As a final example, Bad is actually less than Badri with capital B, because Bad has three
characters and Badri has five characters. So, this is similar to this Janaki and Janak
example. So, in fact Bad is also less than Badri, so that is also true, because Bad is
actually three characters in length and so Bad starts with the capital B and whereas badri
starts with lower case b, so this is also true, so this is called lexicographic ordering.
So, just remember that it is just like dictionary ordering, with the one extra addition that
lower case letters are considered to be coming later than upper case letters. So, we will
also have to deal with blanks and other things for example. So, let us look at this
example Bill Clinton and Bill Gates.
253
(Refer Slide Time: 14:16)
So, if you go and arrange them in order, Bill Clinton should actually come before Bill
Gates, so the space itself should not matter, so C comes before G. Therefore, if I go and
look at character by character from the left side of this and character by character from
the left side of this, so B and B match, i and i match, l and l match, l and l match, space
and space matches, C is actually less than G, therefore Bill Clinton is less than Bill
Gates.
So, I am not making any comment over their personalities, so in terms of a string Bill
Clinton is less than Bill Gates. Then, let us say we have this example Ram Subramanian
and Ram Subramanium. So, here up till i the characters are the same, whereas when we
look at this character a and this u, a is less than u therefore, the string Ram Subramanian
is less than the string Ram Subramanium. And finally, if we go and look at this example,
Ram space Subramanian verses Rama space Awasthi.
So, Ram space Subramanian is supposed to be lexicographically earlier than Rama space
Awasthi. The reason is that if you go and look at the first three, they are matching Ram
and Ram, the fourth character is a space whereas the fourth character is a and in storage
space gets a code 32 and that comes before the code for a, which is actually 97. So, the
code for lower case a is 97, so code for space is 32, so since space comes before any
valid letter in the sequence of characters, Ram space Subramaian is less than Rama space
Awasthi.
254
(Refer Slide Time: 16:05)
So, there is a small program that I have written here which goes and looks at comparing
two strings str compare. I am assuming that there are two strings A and B, so this is
supposed to be a comma here and let us assume that there are two integers N1 and N2.
N1 is supposed to be the length of A and N2 is supposed to be the length of B. So, I want
to find out, if when I compare A and B, whether A is less than B or A equal to B or
whether A is greater than B.
So, let us look at the examples on the right side, if A is Hello and B is Hello, then A is
actually equal to B. So, for this example this one, this condition A is actually equal to B.
In this example, the second one A equals Hell and B equals Hello, so the set of
characters first four characters are the same, but A does not have the fifth character,
whereas B is a longer string. So, therefore we claim that A is less than B, like I said
small words will come in the dictionary before bigger words.
And if A is Hello and B is Hell, A is actually greater than B, then in this example the
both are of the same length, the length is not matter, but if you look at the second
character there is e here and there is u here. Therefore, in the dictionary Bell will come
before Bull, therefore string A is before B and in this example, Hull is and Hello, Hull
will come in the dictionary after Hello, so we have A greater than B. So, these are some
examples I want to be able to handle all such cases.
255
So, let us starts with the first one A equals B. When will I have two strings to be equal?
All the characters of A must be the same as the characters of B and the length of A
should be equal to the length of B also. In fact every character including back slash 0
should be matched from A to B. If all the characters including back slash 0 are exactly
the same, then in the order for A and B, then A is supposed to be equal to B.
Let us see, how to detect conditions A less than B. What are the two cases in which A is
less than B? I have matching characters for A and B, but A ran out of characters, so in
this case Hell has only four characters and Hello has five valid characters. I ran out of
characters in A to compare with B, in that case A is less than B or I do not ran out of
characters, but I am still in the middle of checking characters, but I see that some
character in the middle is actually less than some character at the end. In this case also
we have A is less than B.
So, these are the two conditions or examples for which A is less than B and for
everything else, we already considered A equals B and I showed you, when A is less
than B, in all the other cases A is supposed to be greater than B. So, this program that
you see on the left side is supposed to take care of printing whether A equals B or A less
than B or A greater than B based on these checks. So, let us see what this loop here is
supposed to be doing.
So, k is initially initialized to 0, we are starting with the 0th character of both A and B
and if both the characters of A and B match and if I have not exhausted A and I have not
exhausted B, then I can go and look for one more character. So, if I keep doing this, k
will keep incrementing. So, if I start with let us say Hello and Hell, k is supposed to point
to the 0th character of both. So, let us say this is my A and this is my B, so k initially
starts with pointing to H, these both are same I increment k.
So, k now, so A of k is e and B of k is e, these two are same. Then, I increment k, now A
of 2 is l and B of 2 is l. So, as long as things are same I have to keep moving and I will
stop, when either the condition that the characters are same is violated or I ran out of
characters in either A or B, so that is what these three conditions are doing. So, now let
us go and see there are three ways in which you could have come out of this loop.
Either, you violate this condition or you violate this condition or you violate this
condition, you could have come out because of violation of any of these. If A of k equals
256
B of k for all the characters that you saw so far, then at some point you are N1, you
would have hid N1 the end of A and you would have hid N2 which is the end of B. So, if
N1 is actually equal to N2 and if k equals N1 which means you have looking for equality
in A and B, then A and B are equal.
So, that is the example we have on the right side Hello and A equals Hello and B equals
Hello are actually the same, else you ran out of characters in A, but the first set of
characters N1 characters in A and B are the same. But, you ran out of characters in A,
there are more characters in B to process which means you have a word which is shorter
than the other word and so for example, Hell is shorter than Hello and Hell is a prefix of
Hello. In this case A is less than B.
If you ran out of characters in B and if you still have characters in A and if the first N2
positions matched, then you print A is greater than B. So, we have taking care of three
cases where we hid in some sense back slash 0 So, in this case we hid back slash 0 on
both of them with the same time, in this case we hid only on A of 0, in this case we have
hid only on B of 0, none of these need to be true. For example, in Bell and Bull, I do not
have to go to the end of the array to see that Bell is less than Bull.
As soon as I see the 1th location, I see that e is less than u therefore, A is less than B, so
that is what you are doing here. I did not run out of characters in either A or B, however
so there are still more characters to process in A and B, but however I found something
lexicographically different. So, A of k is less than B of k means A is less than B, in this
case Bell is less than Bull or I started looking at H in both of these, I see that in the next
location u and e, u is actually greater than e. So, Hull should come after Hello and
therefore, A is greater than B.
So, this is the way in which you can write the program for string comparison. So, we
looked at three programs, one to find out the length of the string, one to concatenate and
one to copy and finally, we now looked at comparing two strings. So, one thing that I did
not show you is for concatenation, I did not show you how to write the program. So, we
can keep writing all these things every time, but it is a laborious thing. So, for integers
and floating point and so on, we had plus and minus and so on which are basic
operations.
257
However for strings, it may be useful to have some of these things given by the
programming language. Unfortunately C does not give that, but C gives it you as a
library. So, these are all built in string functions. If you do hash include string dot h, it
has several built in functions.
So, for example you have a function called strlen. If you pass character array to it, it will
find out the length of the string excluding the length of the null character and return an
integer. So, strlen is a function which takes a character array as a parameter. So, it takes
pointer to a character array and returns the length of the string. So, you will see the
notation and meaning of this in a subsequent video on functions, so you can treat these as
a inputs that is given and this is the output.
So, the input is of type string and the output is of type integer, strlen is the name of the
function. It takes a character array or pointer to a character array as an input. So, it has a
program like what we saw for the length inside which actually goes and looks at every
single character and finds out back slash 0. It reports the length and returns an integer.
There is a function called strcpy, so you take two parameters character star destination
and character star source.
So, if you give strcpy of A comma B, it will copy contents of B in to A. So, clearly
destination must have a size which is at least as large as the size of source. Finally, there
is another function called strcat. So, again as before we have destination and source, so it
258
will take source and concatenate it to destination. So, destination must have space at least
as big as the original string that you had plus the new string that you have concatenating.
So, these are things available from as built in functions from C itself and we can write it
on our own, but we are prone to making mistakes, instead we use libraries given by C to
do this. So, these are three basic things that you may want to use. So, there is another
function called strcmp which can also come in handy.
Strcmp takes two parameters as inputs which are both pointer to arrays and it returns an
integer. So, we wrote a program which printed things on the screen as A less than B, A
equal to B and A greater than B, instead it returns an integer. So, if both s1 and s2 are
equal strings which means, if I gave Hello and Hello as s1 and s2, then both strings are
equal, you will get 0 as the result. Instead of printing on the screen it gives you an
integer, you can go and look at the integer value and decide, whether they are equal or
not.
You get 1, if the first string is lexicographically greater than the second string which
means, in the dictionary if the word pointed to by s1 will come later than s2, then you
will get 1. You will get minus 1 as the return from strcmp, if s1 will be in the dictionary
order before s2. So, you get three values 0, 1 and minus 1 depending on whether A
equals B and whether A greater than B or whether A is less than B. There is also another
function called strncmp.
259
What it does is, instead of comparing all the characters including back slash 0. It will
compare only the first n characters of two strings. So, if I have string s1 let us say, this is
of size 10 and let us say s2 is an array of size 15 and if I specify size as 4, strcmp will
compare only the first four characters, it will not go till the end of s1 or s2, it will go only
till the first four characters and it will give you a comparison of the first four characters
of A and B. It will still returns 0, 1 and minus 1 by comparing only the first n characters.
So, this brings me to the conclusion of strings, this is something that can come in quit
handy for you. So, later you will see programming exercises which are actually on
strings. So, I suggest that instead of writing things on your own, you go and use the
functions that are given in the string library. All you have to do is do hash include string
dot h and just like printf, scanf and so on, you call functions by passing appropriate
parameters. So, how to pass parameters and so on will probably become clear, once you
go through the video for functions.
So, thank you very much and see you in the next lecture.
260
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 10A
Lecture - 20
What is a function? Why use functions
Example: power (base, n)
This lecture on functions, this a very important aspect of C that one has to learn and it
also makes a programming much more easier and fun.
So, this kind of thing happens in the mathematical world often. So, I want to find out f of
x and so on. So, let us say I as a programmer have very carefully written a program to do
square root. And let us say, you do not know how to do it. So, what I do is, I write the
program for square root and I put it in what is called a function, I give that as a black box
261
to you and you have to give it inputs I will compute square root for you and I will give
you outputs.
So, the program or the sub program that I wrote will compute the square root for the
inputs and you will get output. So, this is the notion of a function, so you may not want
to know the internal details of square root. For all practical purposes, you can think of
this square root as a black box that is given to you. And as long as it is done correctly,
you can give inputs, you can expect it to give outputs and you can use that in your
calculations. So, this notion of putting things in a black box is nice, because what
happens inside the function can be hidden from what happens outside it.
So, what happens inside the black box is not of concern of course, you have to be
careful. So, if I say that I given you a program to write square root and let us say that sub
program is incorrect, then you are in soup. So, you have to be careful, if the functions are
written carefully and implemented correctly, then it let’s you take a big task and divide
that into smaller sub tasks. And we have seen several examples of functions and there are
three things that I can immediately point you to main for instance, it says for function.
Even though, we will talk about main in a little more detail, later. The two functions that
we have been using repeatedly are printf and scanf, printf was used to print things on the
screen and scanf was used to scan things of a keyboard. So, these two are functions, so
clearly you did not write printf and scanf. These were written by someone else and you
262
are using it as a black box. You did not worry about, how things go to the screen nor
where you worry about how the keyboard, the key press that is done is taken to the
internal variables.
So, this kind of hiding the functionality of what a function does is very useful. Because,
you do not have to worry about the inner details of functions, as long as the interface is
clean. So, you can think of functions as giving inputs and getting outputs from there.
So, let us see what outputs to the function means. So, not all functions actually give
outputs, so there are functions which compute something and or expected to return a
value. For example, if I am looking for square root, so I want square root of x, x is the
value that square root will take and in return, I should expect a value. Because, it has to
do some work and return a value. Similarly, there is a mathematical function called pow
which stands for power.
Given two floating point values m comma n, it can calculate m raise to the power of n.
So, clearly m raise to the power of n is a very useful thing in various aspects and I expect
a value more importantly. So, if I give m comma n it can do the action, but I want to use
that. Let us say, I want to do a squared plus b squared, I can do power of a comma 2, but
that a squared should be returned as a value. So, there are functions which return values.
263
There are also functions which do not return any values, explicitly or at least not return
any useful values, but perform actions. For example, printf is a function, does something
to do on the screen. It does not return a value to the program, where printf is called. So,
at least it does not return a useful value for all practical purposes. So, you can write
functions which does a sequence of actions or you can write functions which does a
sequence of actions, compute something and also returns a value.
So, the notion of functions is essentially equivalent to outsourcing. Let us say, you are a
busy programmer, you are doing something, you do not want to spend time on writing
square root function, you outsource it to me, I write the square root function. So, I am
good with numerical methods, I go and write the square root function and I give it to you
and that way, you can outsource your work. So, you can take a big problem, divide that
into smaller problems and compose this smaller sub task together.
This is very useful, when you want to build large programs. You can build on top of
what you have done earlier or you can also build on top of what others have done. So,
this is a very nice thing about using functions. So, most languages give you, what is
called a library a C also has a very rich set of libraries. And very common things, the
most common things the most programmers would need are all put in the library.
So, imagine every single user or every single programmer having to know, how to write
things on the screen, having to know how to read from the key board and so on. Instead,
264
C as a language gives you printf and scanf. Similarly, there are several mathematical
functions including pow and square root and so on, which are all packaged in what is
called a library and as engineers, you will actually either develop these libraries for
others or you may actually end up using libraries that others have written.
So, one thing that you get from using functions is what is called modular programming.
So, you take a task, divide that into sub task as sub programs and the sub programs can
either be things that do some work or things that do some work and return a value. So, if
you have been exposed to languages like Pascal, in Pascal they called them procedures
and functions. Procedures do not return values, whereas functions return values.
In C and C++, everything is called a function, if a particular function does not return any
value, it does not have a special name for it. It facilitates modular programming, you can
take this sub program or the function that you have and you can invoke it at several
points. So, I will show a simple example later, on how this becomes modular and as I
said, it hides the implementation and if you want to make any change to the program, it
makes it much more easier.
265
(Refer Slide Time: 07:40)
So, what kind of functions are there in C? There are several functions available in C for
string manipulation, this is a very common thing. How do you take a sequence of
characters forming a string and manipulates strings? So, we will look at strings in a later
lecture. There are also several mathematical functions that are available as standard
libraries in C. So, if you are coming from other engineering disciplines, so you develop
your own things.
For example, Mechanical Engineers, Civil Engineers, Aerospace Engineers and so on,
may use finite element methods, very often and there are libraries that are available. Not
a standard libraries, but there are vendors who provide these libraries. Many of these
libraries are usually domain specific. So, what I mean by that is, so somebody use a
specialist in an area requires a specific kind of computation. This may not be useful to
people outside the domain.
For example, finite element methods are useful to certain class of engineers, whereas
somebody in Electrical Engineering or Computer Science may not really need finite
element methods or libraries doing that in any of the regular routine job. So, as a
programmer what you will do is, you will learn to use existing libraries and you may also
identify scope for building new libraries and you may even make money by building
these libraries and selling them later.
266
(Refer Slide Time: 08:50)
So, let me show you a small piece of code in which I am doing this basic mathematical
function, raising x power y. So, let us say we have a number called base and we have a
number n and we want to raise base to the power n. So, we will assume that n is an
integer greater than equal to 0 and base is any integer, it can be negative, 0 or positive.
And there is a small code in this slide which achieves what we want to do, so raising
base power n.
So, if you see that there are two integers i and p and p is actually initialized to 1. This is
important here and you have a for loop which runs 1 less than or equal to i plus plus. So,
this loop runs n times and we take p, multiply it with base n times. So, the first iteration
you would get one times base which is base power 1. The second time, you will get base
into base which is base squared and third time, p will have base squared into base, you
will get base cubed and so on and you will get base power n at the end of the loop.
So, clearly if n is negative then you have a small issue. So if n is negative, then any
integer raise to a negative power will be a fractional value and this is not a program
which takes care of that. However, if n is 0 then this loop will never execute and p is 1 is
actually correct. So, raising in base to the power 0 is 1, it is correct and for anything
which is greater than 0, it will run as many times as n is and at the end p will be base
power n. So, this is a simple iterative structure, in which we have calculated base power
n.
267
(Refer Slide Time: 10:51)
Let us look at the motivation for functions. Let us say, a programmer needs 3 power 5
and minus 4 power 3 in the same program. So, as a programmer one could write this, so I
have i and p and base and n. These are supposed to be four variables which take care of
base, n, i and p. So, i is for running the loop, p is for collecting the product and base and
n are the two numbers. You are doing base power n, so the base and n are the two
integers and the result is supposed to be gathered in num1 and num2.
In this case, you are ready to do minus 4 power 3 and again you have repeated the same
loop that you have earlier. So, you look at this, this loop here and this loop here are
exactly the same, the result is accumulated in p and you copy the value of p to num2. So,
this is the correct program, as in num1 and num2 would get 3 power 5 and minus 4
power 3, but let us look at a few things. First of all, you have duplicated effort.
So, there is a for loop that is written here, there is also a for loop that is written here. Let
us say for some reason I made a small mistake here, this mistake could also come here.
268
Even, if we have written the program correctly, it is just that the same task that I am
doing, I see it in my program over and over and over, that is one thing. Two, this is
something very suttle and you may not catch it if you are not careful.
So, even though this code, this for loop is repeated. Let us say, I even let you do that and
let us say, it is not a problem right now. Let us look at base and n, anyway these are
things that are supposed to change. So, I have setup base and I have setup n, so this is,
but let us look at p. So, I have p equals 1 and then we do the set of computations, it gives
us 3 power 5. If I forget to do this p equals 1 here, what can happen is I have 3 power 5
that goes to p and then let us say, I forget to do p equals 1.
I have base equals minus 4 and 3, n equals 3. If I forget to initialize p to 1, then p will
start with 3 power 5 and to that you are going to multiply minus 4, 3 times. So, you are
not really calculating minus 4 power 3 anymore and the result would have been for num2
3 power 5 into minus 4 power 3, this is a disaster. So, every time you want to do this,
you also have to remember that p has to be initialized to 1.
Of course, you would have done, change the base and number, because you are
computing for something new, but you may forget initializing p to 1 and this can be a
recipe for disaster. So, you have to be careful and this is not a very good way of doing it,
it is definitely sounds clumsy and we have to change this.
269
It would be really nice to do something like this, would it not? So, int i, num1, num2, 3
integers, num1 is power of 3 comma 4, num2 is power of minus 4 comma 3. So, this is
nice for multiple reasons. One, is definitely more readable, instead of, that screen full of
code that you saw earlier, you now have three lines of code, int i, num1, num2, it is for
declarations and num1 and num2 gets 3 power 4 and minus 4 power 3.
This looks almost like writing mathematical equations on a piece of paper. So, you have
taken 3 power 4, put it to num1, minus 4 power 3, put it to num2 and so on. It is
definitely more readable, it is less error prone. Because, if somebody you have
essentially outsource the work of doing this power, it is computing power to someone
else or some other functionality, you have made a sub task which is supposed to be
computing power of base comma n and when you do that, you are not going to run into
this problem.
So, every time the function is going to be called, will assume that all the initializations
are properly taken care of, the computation is done carefully and you actually get work
done and you get the return value. So, if power is a function if it takes these two things
and if it returns a value, then you have essentially delegated the duty of computation to
either someone else or to some other region of the code. Definitely, this looks much less
clumsy.
So, the only thing is somebody still has to write, this power of base comma n. So, you
are outsourcing it, but at the end somebody has to write it.
270
(Refer Slide Time: 16:10)
So, let us see how this power of base comma n can be return up as a function. I said,
when you use it, you can think of it is a black box. So, power is a black box, you get base
and n as inputs. If you give these as inputs, you are supposed to get some result back,
which is supposed to be base power n. So, let us start with this black box, so what I am
going to do is, I define body. So, I have this body with this left and right set of flower
braces and I have a name for a function called power. This is the name that I am going to
use for power.
It takes two integers as parameters, int base and int n. This takes care of the inputs and I
am supposed to be getting a result back and whatever is return from a function, is what
you have here. So, the way to look at this line is the function is called power, it takes two
inputs base and n and it returns an integer. So, the return does not have a name, it just has
this type. You take two integers as inputs, one integer is called base, another integer is
called n and we get an integer as a result.
So, that is what you have so far, so this is what the black box setup is. So, clearly you
have to write the program for it, we did this earlier, so you already had it. So, p equals 1,
for i equals 1, i less than or equal to n, i plus plus p equals p times base. So, this actually
does the computation. So, we already have this program written up and let us say, I just
did cut and paste of this into this block of code. So, I have this, having this is not
sufficient.
271
So, if I look at this as a sequence of steps, I have variable p variable i, but these variables
are never declared. So, there is no declaration for p and i and I mentioned earlier that
every variable has to be declared, so that you can get a location in memory. So, p and i
are not declared yet, so let us do that first. So, int i comma p, you have that, so this is not
the end of it, you have declared int i and p at the end of this for loop, p will actually have
the final result.
But, if you look at this function, there is a variable i, there is a variable p, there are also
these two inputs that you took base and n. So, I want a result back, but which one of
these variables is the result. No way, I compile and I can figure out, which one of these
things should be returned as a result. So, for that we use this keyword called return, so
this return, return is a keyword. So, it is a reserved word in the C language and what it
does is, after various variables that and various calculations that you have done, the final
result is stored in p, please take that and return it to, whoever called it.
So, if we have base comma n as an inputs, there are lots of local variables inside power.
There are variables like p, i and so on and of these, you are expecting p to be returned
and return p takes care of that.
So, essentially if you look at the whole program, the whole function this is how it is, int
power of int base comma int n. So, from by looking at this I know that it is expecting two
inputs and both are supposed to be integers and it is going to give one output, which is
272
also an integer. And which integer does it return? It is going to return p, after doing this
set of calculations. So, now let us see how this is nice, so since p equals 1 is an
initialization done here.
The key thing is every time this function is going to be called, this variable p is going to
get initialized to 1. So, therefore you do not have to remember to initialize p every time,
you have given the task to somebody else. So, you have delegated the task and that task
or the subtask here called power, is supposed to take care of whatever is required to
compute power of base comma n. So, to put this whole thing in one package, let us see a
complete program which uses the power function.
So, what you see on the right side is the function, you have int power, int base and int n
and we have written this function and let us see, how this can be used. So, we have two
integers, num1 and num2 and I said, would it not be nice to have num1 is power of 3
comma 5 and num2 is power of minus 4 comma 3, we have exactly that.
So, this is an actual program, we have these set of lines followed by these set of lines, let
us say. So, even though it is all supposed to be in one place, one after the other I am
showing it right and left. So, let us say I have these set of lines followed by this set of
lines. So, let us see, so we have num1 is power of 3 comma 5, num2 is power of minus 4
comma 3. So, at this point power of 3 comma 5, so that would be 243, I would expect
273
num1 to have 243 and power of minus 4 comma 3 is minus 64, negative 64. So, that
supposed to be num2.
So, once I have computed it, I can actually print it. So, in this case it is only printing, you
may want to use it in other calculations, later also and we have completely delegated the
work of power. So, this whole thing is very clean and readable, so in this context I want
to give some names. So, at this point we say that the power function is being called at
this point and this point and power is a function here, so this is called the caller.
So, the main function here is called the caller. The caller calls power function twice and
power is the callee. So, you have a caller which is main and you have a callee, which is
power and the caller can call callee, multiple times. So, that is what I said earlier, so you
may have some task which is repeatedly done. So, as long as you have written up the
callee function and it is cleaned up, it is correct and you have verified it and so on, you
can call it as many times as you want in any caller. So, we will see more details of
functions in the subsequent modules.
274
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian institute of Technology, Madras
Module - 10B
Lecture - 21
Function definition, function prototype, function invocation or call
Control flow, calling inside a loop
In the previous module, we saw the basics of functions and how it is nice and how it
makes it readable and so, on. In this module, we will go and look at this notion of
functions and lots of gory detail. So, what is a function?
Function is essentially a part of your program. So, one thing that you have to be careful
about is, it cannot be part of any other function. A function cannot be part of any other
function. So, in the previous module we saw that there was a function called main, which
is the caller and there was a function called power which was the callee. We cannot go
and take the program for power and completely put it within the caller.
So, a function cannot be completely written up inside another function. So, there are
some languages which allow you to do that. C does not allow you to do it, I mentioned
this earlier that main itself it is the main function. So, whenever you have multiple sub
tasks, somebody has to know, what is the very first task that you have to start with. So,
usually it is the main. So, you start with main, main will have a sequence of steps, if
there is any delegation of duty that you want from main, the main program should have
275
that, the main function should have the delegation of duty.
So, we had delegation of duty to power from the main function. So, execution starts from
the main function, the control flows starts there, from there the control flow can move to
one function or another and return back and so, on. So, every time you call a new
function from a caller, you are essentially transferring the control to the callee. So, with
the function call you usually pass some parameters. So, in our case we passed base and n
and these parameters are used within the function.
You compute some value, the values return to the caller and the caller could use it in
some other computation. The caller can also choose to ignore the value that is produced.
In fact, printf to be technically correct, actually produces a return value. Only that,
generally we choose to ignore the return value from prinf. It does some work, it prints
things on the screen, it actually returns a value also, only that we usually do not care
about the return value. It is not useful for most programmers. Let us look at the template
of a function.
We have a function name and we have all the argument declarations and we have a return
type and you may want more declarations inside your program itself. You have further
declarations in statements and finally, you have a return expression. So, the function
name identifies the name by which you are going to have this sub task call. It may have a
set of arguments, the arguments will have their names and the order in which the
arguments have to be given.
276
So, for example, power of base comma n, if you pass 3 comma 5, we will do 3 power 5.
Instead, if you say power of 5 comma 3, it will actually rise 5 to 3, you will get 125 as
the result. So, you have to be careful in figuring out, what the arguments are and in what
order the arguments are passed and you have various declarations that are within the
functions. For example, we needed int i and int p which where useful in running the loop
and so, on. And finally, we have a return expression, return p earlier and there is a return
type in our power function, it was an integer.
So, one thing that you have to remember is, in the body of a function, it is a basic block
of code and in the body of the function, you cannot have other function declarations. So,
it is simply not allowed to have other functions which are completely declared and
described within another function and the arguments essentially found the inputs, you
need both the types and the order. Sometimes, the return expression is optional.
So, the keyword return can take an expression, it can also not take any expression and
just say return or if you do not even have return, your program will come to the final
closing brace of the sub task and automatically, it will go back to the caller. So, invoking
of a function is usually done using this function name and you have the set of all the
parameters that are passed.
277
(Refer Slide Time: 04:43)
So, there is this notion of what is called a prototype of a function. So, we will see this in
a little while. So, I had that in a program, but I did not go into the regions earlier, but we
will see what a prototype is. A prototype is essentially defines the number of parameters
and the type of each parameter, it also defines the return value of a function. So, a
prototype essentially gives you a black box description. So, when every function has a
prototype followed by the actual definition of the function.
So, the prototype just tells you what is the black box description, what are the inputs and
what are the outputs and so, on and some where you have to write the program to do the
function itself and that is the function implementation. So, the prototype is a necessary
thing, the compilers usually check, whether a prototype is present or not and if we give a
prototype, the compiler can actually indicate warnings, if you missed up something.
Let us say, you have return a function and it takes three parameters and let us say
everywhere you called it, you have only two parameters that are passed. The compiler
can spot it and tell you that this function expects three parameters and you have to passed
only two. So, the prototype here is for power is as follows, int power of int comma int.
So, in this place we are actually not named the parameters at all, power does take two
parameters, but the names are not mentioned.
So, a compiler when it looks at this prototype knows that power is supposed to be a
function which takes two integers, returns another integer. At this point, the compiler
would not know, whether base is the first parameter and n is the second parameter or n is
278
the first parameter and base is the second parameter. Actually, he does not care at this
point of time. However, later we actually put the variables base and n here and write a
program appropriately. Typically, function prototypes are given in the beginning of the
program.
So, let us see this, I am showing the same piece of code that we had earlier. We have this
power function. So, what you see on the right side is called the function definition or
function description and what you see in this line here, on the left side is called the
function prototype. So, we have function prototype and function definition. So, the
function prototype by itself is not very interesting, but it is necessary to tell the compiler
as well as tell the other programmers that power actually expects two variables and it
will return only an integer, it is not going to return a character, it is not going to return a
floating point value and so, on.
So, that anyone who uses the function, knows what they are getting into. So, this is
called the function prototype and this is called the function invocation. So, whenever we
call the function, we call that function invocation or calling the function. So, earlier I
said, I want to touch up on what is called the control flow. I said, flows the program,
flow starts with the main program and whenever it calls a function, it goes there and so,
on. So, I want to make that slightly more clear now.
279
(Refer Slide Time: 08:00)
So, let us look at this piece of program and let us see, how this function or how this
program is going to get executed. So, as I said earlier, all executions start with main, you
start with main. So, at this point you have two declarations, num1 and num2. So, as with
any variables, the compiler would have allocated memory for num1 and num2. So, I am
ready to execute this line, num1 is power of 3 comma 5. At that point what happens is,
instead of getting to the next line here which is what happens usually.
So, when we looked at programs. So, for, for loops and other things, we always recent
how this sequence of code that you see on the screen, how will it get executed internally.
So, if you have no loops or branches you start from line 1, you go to line 2, line 3, line 4
up to line n and you return. If you have if then else branches, either if condition is true or
else condition is true and only one of them works, if we have a for loop you have a
repeated body and so, on, all those were examples of control flow. This is one another
example of control flow.
So, at this point if you are in line number 2 within main, the control gets transferred to
line number 1 of power and then you go and actually do this sequence of operations and
once this sequence of operations is done, you return back to this line. When you return
back what happens is, you have computed the value power of 3 comma 5, it should have
computed number 243, as the result and that is assigned to num 1.
Now, you are again back in the caller, in the caller, you will by default go to the next
line. So, the next line is also a function invocation or a function call, again the control is
280
transferred to the callee. In the callee, it goes through the sequence of code here, at the
end of it you return back to the caller. In this case, minus 4 comma 3 would have
computed minus 64, that value is assigned to num2. So, it returns to this location and
then you are ready to do printing of both these numbers.
So, the control flow for this program is, you start with main, at this line you call this side.
So, remember for every assignment operation, you evaluate the right side and you
assigned it to the left side. So, there is evaluation on the right side. So, power of 3
comma 5 would transferred the control to the power function and you do the sequence of
steps, the control comes back here, you do this assignment, the control falls to the next
line by default. Again the next line on the right side it makes a function call, you do this
you come back here, do the assignment and you have a printf followed by another printf
and so, on.
In fact, technically printf is another function. So, when you come here, printf is a
function which takes two parameters, a format and the value here. These two are the
parameters that are passed to printf. In fact, what is hidden is what is happening inside
printf.
So, the control actually gets transferred to printf, printf does whatever it has do to print
things on the screen, it comes back to this line and the next line, it moves to the caller,
next line again it is a call to printf, you go to the internals of printf, whatever it does print
on the screen it does it, comes back here and then it comes here and comes to this line
with a return and the last line in the main function is return 0, it returns 0.
So, if you notice main is a function, this is the function name that is the return value and
this is the return statement and main is a function which has not taken any parameters.
So, there are ways in which you can take inputs to main also, we will probably cover that
in a later class. So, main is a function and. So, is power and. So, is printf and scanf and
so, on.
281
(Refer Slide Time: 12:17)
So, I want to show a slightly different example, where we use the same power function
inside a loop. So, we have int power int, int. So, this says that this is the prototype, which
takes two integers and returns an integer and what we have done is, inside main we have
a loop for int i equal 0, i less than 20, i plus plus. You are printing i, 3 power i and minus
4 power i. So, we have this function for computing power. So, this is the slightly more
complicated control flow. So, we start with here. Main, the first line after that is a for
loop. The for loop will initialize i equal to 0, it will check if i is less than 20 or not. So,
when it is 0, it is actually less than 20 and it comes to this line. So, when you go to printf,
it actually requires i, 3 power i and minus 4 power i. Only if you know the values, you
can print them. So, when you see power of 3 comma i, it actually makes a function call
to 3 power i. It makes a function called 3 power i, it computes the result, you get the
result back and then you have power of minus 4 power i, this power function computes
the result, it comes back.
So, you have i which comes from the loop, 3 power i would have come from the power
function, minus 4 power i would have come from the power function, printf has all the
values that it requires, it will print things on the screen and then the control comes to this
location. At this point, it says it is the time to check, go to the end of this for loop which
is i plus plus. So, you are supposed to do the post loop function which is i plus plus.
Check again, whether i is less than 20, so, i would have become 1.
It is still less than 20, again it comes to this line which has printf. At this point, it makes
two function calls, get it is arguments, prints things on the screen, comes back to the end
282
of this for statement, at that point you do increment of i again and so, on. So, is the
slightly more complicated control flow. So, you have loops and you have various
function calls here.
So, let us take this example where i equals 3 is done. So, let us say at some point, i would
have been 3 in the loop, let us see how that would have done, how that would have
happen. So, when i equals 3, the printf would see that there is a call to power. So, power
of 3 comma i, at that point that transfer goes to this segment and since you are returning
3 power i which is in this case 3 power 3, which is 27, the result comes back and that is a
temporary place holder. So, 27 comes back, you have i which is already present and you
have 27. So, you have two parameters ready, printf still needs one more parameter for it
to print.
283
(Refer Slide Time: 15:23)
So, at that point, you see that it is power of minus 4 comma i, that gets evaluated. The
result is minus 64 that comes back as a value to the printf. So, at this point you have 3,
27 and minus 64, printf has everything that it needs to print. So, printf function will be
called, at that point you will print 3 comma 27. So, 3 space 27 space minus 64 on the
screen and then you are ready to go to the next iteration in i.
284
Programming, Data Structures and Algorithms
Prof. Shanker Balachandran
Deportment of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 10C
Lecture - 22
Function arguments and local variables
Automatic local variables
Scope of local variables
Call by value versus call by reference
Returning multiple values from a function
We saw the notion of arguments, these are inputs to the black box called the function.
When we talk about arguments, there are two kinds of arguments, this terminology called
formal arguments or a formal parameter. So, our formal parameter is a name that is used
inside the function body to refer to the arguments and the function call is actually made
with actual arguments or actual parameters. So, there is a name that you used for things
within the function and you may have names, which have different in the caller.
So, the caller may have names with the parameters which are different from, what is in
the callee. So, the actual arguments or actually values that are assigned to the
corresponding formal parameters, this actual argument could be a constant or a variable
or even a complicated in elaborate expression.
285
(Refer Slide Time: 01:12)
So, in our printf instead of giving the integers directly here, instead of giving the integers
directly, we actually put an expression that is permissible. And the actual argument is
evaluated and the value of that is copied to the corresponding formal parameter for the
function. So, we will see this in a little more detail with the next example.
So, let us say I have a small program which calculates the triangular number, we saw the
notion of triangular number in one of the earlier lecturers. So, I have a main function and
the main function has int main void. So, what this means is, main is a function which
does not take any parameters, you say that by using the keyword called void. So, void
means you are not using any input, you are not taking any input and you are returning
286
integer.
So, calculate triangular number 10. So, this is a function call and the function description
is here. So, this program is actually missing the function prototype, there should be an
function prototype which says, void calculate triangular number of int. So, anyway, So,
we have a function call calculate triangular number of 10. So, this 10 is an actual
argument, because in the caller you have the value 10.
This is the formal argument, we call it by the name n, the formal argument for calculate
triangular number is n and i and triangularNb are two local variables. So, these are two
local variables, i is used for the iteration and triangularNb is used for the summation. So,
this is a formal argument. So, n is a formal parameter, 10, 20 and 50 are actual
parameters and i and Nb are local variables. So, this is a distinction that we have to keep
in mind.
So, even though there is one formal argument, the actual arguments are actually
changing. So, you notice this piece of code, actual parameter is 10 at this function call,
the actual parameter is 20 for this function call and the actual parameter is 50 for this
function call and the formal argument is always by the name n.
So, let us see a little bit about arguments and local variables. So, the variables are
actually defined inside a function. So, you go and look at this, i and triangularNb are
actually defined within the function, same thing in power. We had i and p which were
designed inside the function, we call them what are called automatic local variables. We
287
call them automatic local variables, the reason we call them automatic local variables is
that they are automatically created by the compiler, each time the function is called.
So, the key thing to notice is that they are created, each time the function is called. The
values that you have for these variables are local, that is why we called it local. So, these
are variables, they are automatically created, but they are local to the functions. So, the
value of a local variable can be accessed only within the function, in which the variable
is defined. So, for example, from this function, I cannot directly use triangularNb or i.
So, in fact, this one is only printing the triangular number on the screen. Let us say, I
wanted that triangular number, it is not that I want to print it on the screen, let us say I
want to use the triangular number. Then, this function cannot be used directly, because it
is calculating triangular number that is printed on the screen, but triangular number is a
local variable, it is printed on the screen and that is it. You have not returned
triangularNb here, therefore, we cannot see the value first of all and there is no way to
refer to triangularNb from the main function.
So, the values of the local variables are all local to the function, you can access it only
within the function and if an initial values given to the variable, every time the function
is called, it is going to be assigned. So, this is particularly useful. For example, let us say
calculate triangular number of 10. So, that is the first function call, you calculate
triangularNb, at the end of this, the value would have been 55. So, we are adding 1, 2, 3,
4 and so, on upto 10, that is 55. So, let us say triangularNb is 55.
The next time I calculate the triangular number, since I outsourced it. I assume that you
are going to take care of the initialization. So, when the function comes in next time,
since triangularNb is again initialized to 0. So, the value 55 is completely lost. In fact,
the triangularNb is not even present, once the first call returns before the second call, the
triangularNb and i are not accessible any more.
So, you have 20, when you go back here, triangularNb is again created, i is again created,
you initialize triangularNb to 0, you do the computation, the result is printed on the
screen and when you return from the function, i and triangularNb can be thought of as,
something that vanishes also automatically. So, automatic variables are both created and
destroyed automatically. So, the formal parameters are a slightly different.
So, let us look at this variable n. So, from the point of view of this function n is also a
local variable, it gets created every time the function is called and n gets destroyed, every
288
time you return from the function, the variable called n is destroyed also. So, formal
parameters essentially behave like local parameters, that completely private to the
function.
So, let us look at how this sequence works. So, I have the main program, the control flow
starts there, you are ready to calculate triangular number of 10. So, at this point n, i, and
triangularNb are all created. This 10, the value of 10 is actually copied to n. So, think of
each of these boxes has separate memory locations. So, 10 is already in some memory
location, 20 is in some memory location, 50 is in some other memory location.
The value 10 is actually copied to n and then you go through whatever is supposed to
happen and that happens and then you return the value or you print the things on the
screen and you return to the column at that point. So, if you carefully watch the
animation n, i, and triangularNb evolve vanished. So, there is no way to access n or i or
triangularNb anymore. So, then the function goes to the second main function, now
moves to the second function call.
At that point, again it goes to the first line of calculate triangular number, again you
would create n, i and triangularNb. The value 20 gets copied and triangularNb would get
the initial value of 0, i anyway gets initialized in the loop, i becomes 1. Whatever work
you have do there that gets done and you return the control back to the main function and
finally, you have the last call and the same thing happens once more. So, this is
something that you have to remember that, every time a function is called, you would
289
create the variables which are local to the function as well as the formal parameters.
You do whatever work is going to happen there and when you return back from it. All
these local variables and the formal parameters are lost. The only thing that can
potentially be retained is the return value. If the function had a return of a variable that
value gets copied back to the right side of an expression. So, for the power function, we
return p. So, the value of p is comes to the right side of the expression and that can be
useful for assignment to the left side, namely num1 and num2.
If you do not have return of a variable, all the calculations that you have done are
completely lost. All the variables that you declared are completely lost. May be you
printed something on the screen, but you can never use the 10th triangular number in any
calculation, you can only print thing on the screen, with this function that we have. So, in
this context it is good to understand the notion of life time and scope. So, life time... So,
this is something that people get confused about, very often.
So, life time is the period of time, when a memory location is allocated. So, if you go
back to this example that we had earlier ((Refer Time: 10:35)). So, the life time of i
triangularNb and n are these variables are said to be alive, only when this function is
called. Once the function returns, you can treat them as variables that are dead. So, life of
the variable is only between the functions first line and the functions last line. These
variables and the formal parameters are dead otherwise. So, we have the notion of life
time.
290
Scope is slightly different and it is an important concept to know also. So, scope is the
region of program text, where declaration is visible. So, we have not seen this in any
detail earlier, we will see that now. So, all the local variables and formal parameters are
not only alive within the function, their scope is also only within the body of the
function.
So, local variable i in function is different from any other variable i, you declare
anywhere else and formal parameter n is also different from any other n that you are
declared or used anywhere else. So, it is not only that i and n are not alive outside that,
you are free to use i and n in other places, in other functions including the caller.
So, let us see a small example of what the scope is. So, let us first look at the main
function. It has a variable called n which takes the value 5 and you call f1 of 3 f2, which
has no parameter and return 0. So, this is not really doing anything with n, this example
is only there to show you, what the notion of scope is. So, as a control you will start with
int n equals 5. So, 5 gets the value of n and then you have f1 of 3.
So, 3 is the actual parameter, f1 is the function name, you go and look at f1, f1 expects a
floating point called x. So, x is the formal name. Here, you have another declaration
called int n equals 6. So, you may wonder whether this n and this n are actually the same.
So, the answer to that is, they are not. Remember, whenever a function is called, there are
variables that are created. So, when you come inside, you create a new variable called n
and only this variable is seen in this function.
291
So, here n equal 6 and you called f1 with 3. So, x is 3 and n is 6 and x plus n would be 9.
So, this printf percentage f would print floating point value 9 on the screen. Now, the
function would return back to the caller. In this case you have f2, f2 does not have any
parameter, but when the function gets called, you have float n equals 10. You again
create a new variable by the name n. The life of this variable n is only within this
function.
You just print n equals 10 and when you come back here, you have this return statement.
So, at this point let us say between f1 and f2, if you printed n it would have been 5. After
f2, if you printed n it would have been 5 also. This n and this n are not in the same scope
as this n. This n is local to this main function, this n is local to this f2 and this n is local
to f1. So, x and n are local to f1, they get created, when f1 is called, they get destroyed,
when f1 returns.
Similarly, n gets created when f2 is called, it is destroyed when f2 is returned and these
variable names are essentially local, it does not interfere with what is there in the collar.
So, in this case in fact, the n was a floating point and this was an integer and this was an
integer, for all you care there may be another function, where n is a character or even a
pointer and so, on, it does not matter. So, within the same scope, you cannot have two
declarations for a variable.
But, once you go to different scopes, namely within functions, across functions and so,
on, you are opening a new scope you are allowed to declare new variable names. So, that
is something that you have to keep in mind.
292
(Refer Slide Time: 15:12)
So, one thing that we have done so, far is, we have seen what is called call by value. So,
in C the function arguments are all passed by value, values are the arguments. So, the
values contained in the actual parameters are copied to variables, which are in the formal
parameters. So, we have the actual parameters.
When you call the function, the formal parameters get created and like any variable,
initially they do not have any values and the first thing when you have for a function call
is, automatically the actual parameters are copied to the formal parameters and with the
formal parameters, you do all the local computations, the value gets returned and those
variables are destroyed. So, the modifications to the parameter variables do not affect the
variables in the calling function.
So, we already saw this example here also ((Refer Time: 16:10)). So, n became 10, n
became 6 and so, on, this has nothing to do with this n, first of all and you have passed a
parameter f1 of 3, that has x here. So, this x is not changed in this example, but even if
you changed x, the value 3 will not change. So, there is also something that you can do
which is called call by reference. So, I want to show you the difference between call by
value and call by reference.
When you say call by value, we copy the contents, but when you say call by reference,
we pass the reference or the address of the variables instead of the contents of the value.
So, in fact, you can think of this has passing the r values and this has passing the l values
of the variables.
293
(Refer Slide Time: 16:58)
So, let us see this call by value using an example. We have p equals 1, q equals 2 and r
equals 3 and I have a function call test. So, actually this is a function prototype, this
cannot appear within this function, it has to be outside and we have call s equals test. So,
test equals p q r. So, this is something that should have been outside. So, it cannot appear
within another function, it should have been outside. So, this is correct.
We have a function prototype which takes three integers, p equals 1, q equals 2 and r
equals 3 and this is a fourth variable called s. So, this is the function prototype now. You
pass s equals test. So, you pass p, q and r to test, they are received by the variables a, b
and c. So, a, b and c are local variables. In this example, a is incremented by 1, b is
incremented by 1 and c is incremented by 1.
So, a would start with a copy of p which is 1, but it gets incremented. So, a would be 2, b
gets a copy of q that is 2 and it is incremented. So, b becomes 3, c gets a copy of r which
is 3 and it is incremented so, it is 4. So, a would be 2, b would be 3 and r would be 4, c
would be 4 and you add 2 plus 3 plus 4. So, the result would be 9 and that is what it
returned as s. So, s get's 9, that is something that is probably clear to you by now. But,
what happens is, p, q and r do not change.
294
call by value.
In contrast, there is something called pass by reference. So, in this slightly loaded
example, we have a few things. So, I have two integers, x and y and let us say I want to
find out, what is the quotient of dividing x by y and what is the remainder of dividing x
by y so, these two are integers. So, I do an integer division, I get a quotient and I get a
remainder. So, I want to know both the quotient and the remainder.
So, one thing that you have noticed is that functions cannot return multiple parameters.
So, if you remember the basic prototype, basic description or template of a function, you
can pass more than one inputs to it, but you can only return one output. That is clearly a
restriction for this problem. I want both the quotient and the remainder, but if I write a
function for it, I can only get the quotient or the remainder, but not both.
So, to do that we have a small trick, here we are going to pass what is called passing by
value. So, we have this function called quoRem that stands for quotient and remainder. It
takes two parameters, numerator and denominator. It takes two more parameter, quotient
address and remainder address and they are not integers, they are pointer to integers. So,
we have two pointers that are passed on and two integers that are passed on.
So, num will get a copy of x, den will get a copy of y and quoAdr will get a copy of
ampersand of quo, which means the address of quo is copied to quoAdr and address of
rem is copied to rem of Adr, but then now you have the addresses of quotient and rem.
Now, if I do num by den, you do integer division, the value gets truncated. But, I can
295
assign it to a memory location. In this case, I assign it to the location pointed to by
quoAdr and num percentage den gives the remainder. The remainder is computed and it
is stored in the location address by remAdr.
So, quoAdr and remAdr are both local variables, but these are pointer variables. So,
when you do star quoAdr, you are not changing the pointer, but you are changing what is
pointed to, you have deference quoAdr. So, the num by den, the value is stored in the
location that is pointed to by quoAdr. When you come back here, since quoAdr had a
copy of quo and remAdr had a copy of ampersand of rem, the pointers were copied.
So, at this point we already saw this, when you manipulate things with pointers, the
memory locations are the same. So, quo and rem actually would have appropriate values
from num by den and num percentage den. So, this does two things, we did not really
have to change C, the language C to give us two return parameters or three return
parameters and so, on. With still one parameter, we are able to get things done. Only that
you have to pass pointers instead of passing values.
So, we have passed references to quo and rem, instead of passing the values of quo and
rem. Because, if we are passed quo and rem directly and if we add integers here, you
would get the local variable calculated, but remember when they return, since you have a
void, those values get destroyed you will not see the appropriate values in the local
copies, passing by reference takes care of that.
We will see this passing by reference in a lot more gory detail later, especially in the
context of arrays. So, this brings us to the end of this module. In the next few modules,
we look at more details related to functions.
296
Programming, Data structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science
Indian Institute of Technology, Madras
Module - 10D
Lecture - 23
Example: swap value of two variables
Why pass by value does not work?
Passing parameters by reference
Arrays as function of parameters
Examples: printing arrays, swapping elements
Welcome back. Earlier I promised that we will look at pass by reference in lot more
details. So, in this module, we will look at pass by reference and we will also see the case
for passing arrays parameters to functions and how do you program something like that.
So, let us start with the very basic example. So, let us say I want to exchange the values
of two variables. So, I have two variables, a and b and I want to exchange the values of a
and b. So, one way to do that is as follows. So, a equals b and b equals a. This is a very
common thing that beginners do take a equals b and b equals a. Unfortunately, this is
something that would not work. So, let us take a small example. Let us say a was
(00.58).
Let us say a is a variable. So, in memory it has a value 5. B is a variable and in memory
it has a value 3. Then, if you do a equals b, it takes 3 and overwrites the value. Here it
becomes 3 so, because of this the value of a is lost. So, both a and b will have the same
297
value instead what will actually work is something like this. So, this is a very basic
template for swapping two variables. This is something that you will see very often, and
you will also use it very often to swap two variables. So, this is a very straight forward
way of doing it. So, instead of two variables, just a and b which we already have. So, let
us, say we start with that a equals 5, b equals 3. We also have a new variable called temp
in memory, right.
What we are going to do is since we were losing a, we will take a and copy on to temp,
right and now if I copy b to a, I lose 5 and I have 3. However, I have remembered the
value 5. So, it is not a problem. Now, what I will do is to compute b, I will copy back the
value from 10. So, from temp I copied to b and that becomes 5. So, at the end we have a
equals 3, and b equals 5. So, this is a basic piece of code, right. So, if I want to swap two
variables in the body of a program, I could do this.
So, now I want to take this and write a function instead, right. So, I want to take two
variables and I want to write a function call swap which will exchange the variables. It
looks like it is straight forward. So, we write a function we take two variables a 1 and b 1
and this is the body of code. Let us say I just copied and paste from the previous line.
Instead of a and b, I call them a 1 b 1, right. So, clearly if I start with a 1 b 1, at the end
of this a 1 and b 1 would be swapped, right. So, if a 1 was 3 and b 1 was 5, right. Sorry,
if a 1 was 5 and b 1 was 3 as I had in the example earlier, I would get a 1 to be 3 and b 1
to be 5 so, that is guaranteed. Let us go and look at how the function called would be. So,
I have a equals 5 and b equals 6. I am calling this function swap 1 of a, b and then, I am
298
going to print. So, since this is calling the function, I would expect a and b to be swap. I
would expect the print out to give me 6 and 5. Unfortunately, this one gives the result 5
and 6 itself, right. So, a is 5 and b is 6 before the function call. Even after the function
call, a and b seems to be 5 and 6 itself. So, we need to think about why this is really
happening. So, to do that, we will have to go back to the basic permeation function
parameters in c. So, let us look at what is going to happen.
So, I am in the main function let us say. So, this is for explaining swap 1, right. Swap 1 is
the function name. So, we had a which was 5 and b which was 6, and let us say at some
point I called swap 1. So, when swap 1 comes, you are going to get an activation record
for all the new variables, right. So, what are the new variables? We had a 1, b 1 and
temp. So, a 1 and b 1 were actually formal parameters, right and temp was actually
declared within the program. So, if you go back, we see that temp is a variable that is
declared within the program. A 1 b 1 are actually formal parameters, and the thing with c
is we take whenever I have function call, we copy the formal parameters, right. So, I
make a copy of the formal parameters 5, gets it copied to a 1 because we call swap 1 of
a, b. A gets copy to a 1 and b gets copy to b 1. So, I have 5 and 6.
Now, the function is ready to execute. I have the variables a 1 and b 1, and what is that it
we did that we took 5 and made a copy on temp. So, it got copied and then, we took 6
and then, copied to 5. So, a 1 got 6, and we took temp and copied into b 1 that got 5. So,
clearly at the end of this a 1 and b 1 got 6 and 5. So, I already mentioned this it swaps a 1
b 1. Let us say now you are done with this. The function returns to the caller. What
299
would happen? So, this side is the caller and this side is the callee, right. This is swap 1.
So, when the callee returns, all the local variables get destroyed. We do not have them
anymore. You cannot access them anymore, right and when you came back, you did
really change a and b. No, we made copies of a and b on a 1 b 1. A 1 and b 1 got
swapped, but they do not change a and b, right. So, this is the common rookie mistake
that people make that user call by value here because we called a 1 and b 1 by value. The
pass 5 in 6 within the function it swapped, but when you return from the function, it does
not swap. Now, how do we fix this problem? So, clearly it does not work. You need to
think about something else. We will write this swap function using references..
So, as before we will start with functions. So, we have a equals 5, b equals 6. What I am
going to do is instead of swap 1 and going to write a new function called swap 2. So,
clearly there was a problem. We should not use pass by value. I am going to use pass by
reference. So, the way it is going to work, this follows.
300
(Refer Slide Time: 07:51)
So, I am going to write how to swap using references, ok. I am going to call this function
swap 2. So, let us see how I could do this? I have a, which was 5 and b, which was 6 and
if I had passed values, they would have been a problem. So, instead what I am going to
do is, I am going to take the pointer of a, and pass it on, right. So, let us say I have
pointer of a, and that could have been ampersand of a, right. We already know that and
pointer of b is ampersand of b, right. So, the address of a and address of b, I can always
get it. What I am going to do is I am going to pass that to the function. The function is
going to take two pointers now, and what is going to do is follow, right. So, I have these
two pointers. So, I have a pointer. Now, I am going to call that a 1. So, this actually now
going to point to 5, and I am going to have another pointer called b 1 and that is going to
point to 6. So, I copied ampersand of a into a1 and ampersand of b into b1. So, a 1 and b
1 are not integers. They are pointed to integers.
So, now what I am going to do is, I am going to have another local variable called temp
because you already know that we cannot swap two variables without these extra things,
right. So, there are ways to do that using only two variables, but in the method that we
saw, we declared another variable called temp. Now, what I am going to do is, I want to
swap not the contents of a 1 and b 1. I want to swap that content pointed to a 1 with a
contents pointed to by b 1. So, what is a 1 pointing to it is pointing to 5, and b 1 is
pointing to 6. I want to swap the contents of that in essence I want to change the contents
of a and b. Now, how I am going to do that is, I will keep a copy of the contents pointed
to by a 1. What is a 1 pointing to? It is 5. I will keep a copy of contents of the memory
301
location pointed to by a 1. Then, what I will do is, I will take the contents pointed to by b
1. So, I want to take that content pointed to by b 1 and move it to location pointed by a 1.
So, what is b 1 pointing to? It is pointing to 6. I am going to take that, right and move it
to the location pointed to by a 1.
Where is a 1 pointing? A 1 is pointing to this location. I will move it here and finally, I
still have to restore it. So, I have to get the values of wherever a 1 was pointing that has
to change. So, that has changed. You have to see what contents of b 1 must be. B 1 is
pointing to this location which had a value 6. So, I have to take this value 5 and copy into
that. So, that would change the values of this memory location and when I write it as
before a 1 gets destroyed. So, it was of pointer, but it was a formal parameter. It gets
destroyed. B 1 was a formal parameter. It gets destroyed. Temp is a formal parameter that
gets destroyed. So, I have written from the callee back to the caller, but that point already
a, and b are 6 and 5, right. So, the key thing was instead of passing the values 5 and 6, if
we pass the pointers to a and pointers to b, I can use the de-referencing operator that we
talked about earlier and get back to the values and use that. So, that is what we are going
to do. So, I am going to pass address of a and address of b, and I am going to tired print,
right. So, it is function call and let us think about how the function should look like. So,
swap 2 is not returning any value, right and you have two pointers that have been passed
on. So, we need some mechanism by which we will take to pointers and I do not expect
any return values. I will not have any return. So, the function would look something like
this, right.
So, I have void because it is not returning anything. I have swapped to as the function
name and as I said earlier, we are passing pointers and the way to receive the pointers is
int star a 1 and int star b 1. So, we have two pointers, two integers. One is called a 1 and
one is called b 1. Now, I was talking about a new variable called temp. So, this is
actually local to swap. It is an automatic local variable. What I am going to do is I am
going to take a 1, look at the contents of whatever is pointed to by a 1. So, that is done by
using star, right. So, a 1 is pointer star. A 1 gives the contents of the location pointer to by
a 1, and I am going to keep a copy of that. Then, I am going to take b 1, look at the
contents of the location pointed to by b 1 and make that the value of the contents of the
location pointed by a 1. Finally, I take temp which is a local variable and make a copy of
that on to contents of location pointed by b 1 and when you return back from here, a 1 b
1 and temp are destroyed. So, we met copies of the pointer.
302
So, it is not really destroying a and b, it is only destroying the copies of the pointers that
we had for a and b. It is not even destroying copies of a and b. It is destroying the copies
of the pointers for a and b, and it destroys temp which was a local variable. It does not
matter because by now we are actually changed the contents of 6 and 5. So, a would
become a 6, and b would become a 5. So, at this point if you print f these two things, you
would get 6, 5. So, this is called swap using references. So, what we have actually done
is we have taken two variables, and we want the variables to the values of the variables
to change. We are not going to work on just the copies. We want actual contents of the
variables to change and whenever you have anything of this sort where you want the
contents of some variables to change and if you want a function which is dedicated to
doing that, you have to pass pointers.
So, reiterate that let us say I have some variables and instead of doing the work on the
variables locally, let us say I want to work on it by doing some delegations to a function.
I want to pass on to a function, but I expect this piece of code to actually change the
contents of the variable. If I am going to only print them on the screen, right. It does not
matter. I could have just printed using a function. I do not need pointers, but I want a and
b to exchange their values. The moment you write a function, for that you have to use
pointers to do it. You cannot just copy the contents and do operations on that because
whatever you do on the copies will not reflect on the original variable that you have.
Now, let us look at the larger picture. So, I showed you how to do pass by reference for
this small thing. You also did this earlier for quotient and remainder, right. There are
303
several cases where we need functions to work on arrays, right. So, let us say I give a list
of numbers in an array, and I want you to find out the largest of the numbers or I give
you list of numbers in array. I want to put it in decreasing order or increasing order and
so, on and I want to a function to do that, right. So, I can always go and write the whole
thing in the main programme itself, but if I am going to delegate duty and if I make it
readable, I am going to write function, in that case we will also need to understand how
to pass arrays as parameter to functions, not just individual variable. We also need to
learn how to pass arrays.
So, let us see a simple example. So, we will start with things which are only reading the
arrays, but not changing the contents of the arrays. So, let us look at this simple example
called print 1, right. So, it is a function which is going to take an array and print the
values that is all, nothing more right. So, I have two arrays in the main function a and b.
So, a is an array of size 5, and b is an array of size 7 and this function takes an array and
the number of elements in the array, it prints all of it. So, if you forget this part, right
now, let us look at the body of the loop. The body of the loop if I have n for i equals 0, i
less than n i plus print tab of i, So, I have some one-dimensional table I want to print the
contents of that. I can do it definitely. So, it is not a problem, right. Now, what I want to
point out is the mechanics or how to do this, right. So, if you look at the function call, it
has print 1 a, 5. So, this point you are passing a, which is the variable name that we use
for the array.
What it really does is, it sense of pointer to the first element of a. So, to print 1 the caller
304
sense a pointer to the first element of a, and this on the other side in tab of. So, we have
this int tab of left and right bracket. So, what this does is, it receives the pointer and we
can do tab of i because you have received the pointer and from there if I say i equals 0.
So, it is that pointer plus 0 steps away from it. Then, when i become 1, it is that pointer
plus one more step away from it and then, two steps away from it and so, on. So, that is
what we are going to do, right. So, we get a copy of the pointer to this and then, if you
know how to access the contents of this, I can print it. Then, I will get one step away
from it. So, tab of 1 is one step away from tab of 0. I can look at the contents and print it.
Then, tab of 2 is one step away from that I can print it and so, on. So, this is a one way of
passing arrays to function.
So, what we have is we have two arrays a and b. So, at this point you are passing
pointing to a pointed to a of 0 to print 1. In this line you pass pointer of b. So, you pass
pointer of b of 0 to the print 1. So, local variable is tab is formal parameter and n is also a
formal parameter. So, the formal parameter receives pointer of a of 0 the first time and it
takes n equals 5. So, it will print five entries. The second time this formal parameter tab
takes the value which is the address of b of 0, and it will take n which is 7 and print
seven times, right. So, this is one way of doing..
So, there is another way of doing the same thing. So, in all these examples the main
function, there is no change. You watch out for what is happening in the print alone. So,
this is the second version of print. So, the first change we have is, we have int star tab,
right. So, instead of int tab. So, in the previous example we had int tab of array index like
305
right. So, instead we have int star tab int n. So, this is as it is before and we have int star
pointer ptr. What we are going to do is we are going to take ptr equals tab which means
tab the first invocation of print 2 will have a pointer to a of a of 0. You make a copy of
that to ptr. So, let us look at the sequence of things. So, in print 2 a, 5 when you call this,
the caller passes the pointer to a of 0. So, a of 0, a of 1 and so, on, right. So, let us say
that is the pointer it passes that to tab.
So, you make a copy of that to tab, right and in this loop you have another variable called
ptr which is also a pointer data type, and this pointer data type makes another copy of
that is ptr makes another copy of tab. Now, if you do star ptr where is it going to point?
So, tab had a copy of pointed to a of 0, ptr is copy of tab. So, ptr also points to a of 0,
right to star ptr will print a of 0. Then, we come back and look ptr plus plus. So, since
this is an integer pointer, this is supposed to point to the next valid integer. So, ptr would
have been pointing to a of 0. Now, ptr will point to a of 1, right and then, star ptr will
print a of 1 and we keep doing this till ptr is less than tab plus n, right. So, this is one way
of doing this. Same thing I already talked to you about this notion of arithmetic in tab,
right. So, tab plus n means take n step away from wherever tab is pointing to a of 0. So,
we are looking at a of and if I walked five steps away from it. So, tab would be pointing
to something beyond a of 4. So, as long as you are less than that, you are good. We can
keep printing. So, this is print 2. Let us look at print 3. In print 3, we do not use this ptr
anymore. In fact, if you look at this print 2, all we did was we made up copy of tab to ptr.
So, tab itself is a copy of pointer to a of 0 and we made up copy of that and we use that
for the iteration.
306
(Refer Slide Time: 23:50)
We do not have to do that. Instead what we are going to do is, we have tab which is
pointing to a of 0. So, as before we have a and this is a of 0 and pointed to that is copied
to tab when you call the function. So, tab actually is also going to 0.2 a of 0 and star tab
would be a of 0 first and then, this is actually running a loop n times, right. So, this is not
using any pointer arithmetic for stopping the loop, instead we know that we want to print
n of these variables on the screen. So, just run the loop n times. So, this check is actually
on an integer, right. So, we have i equals 0, i less than and i plus plus will run this loop n
times starting from i equal to zero to i equal to n minus 1, and each time what you doing
is the first time i equals to 0. Star tab will be pointing to a of 0 you print. So, tab is
pointing to a of 0. Star tab will actually print a of 0 and then, you do tab plus plus which
means you are actually pointing to the next location.
The next time when you come around, star tab would be a of 1. You print that and you
increment tab to go here and so, on. So, that increment is happening here. So, there is
pointer arithmetic. This tab plus plus is actually saying where ever you are pointing to
the start pointing to the next location where ever you are. So, it is start with a of 0. Next
time we will point a of 1 and so, on, right and once a is printed, when you come back
here tab gets destroyed and n gets destroyed because these are in the callers. They get
destroyed. I also get destroyed. Now, you start with b, b, 7. At that point you have this
array called b which has 7 entries and you are actually passing the pointer of b to tab. So,
this is in the caller side in the callee side. So, you have tab. It starts with a copy of b of c,
right. So, this is the setup, right. So, we saw three ways of doing this. So, this is one most
307
likely way in which we will do things. This print f print 3 is the most likely way in which
you will actually do things, right.
So, now, let us go back to now looking at how to use arrays and do some simple things.
So, in this example we have a small function which takes care of swapping, right. So, we
have swap 3. We have two versions of swap. Earlier swap 1 which is actually incorrect,
swap 2 which was taking to pointers and swap the contents of the locations, where the
pointers pointing here. What we are going to do is, we are going to take an array and two
indices i and j. What we want is a of i and a of j to be swapped. So, that is the goal of
this. So, the way to do that is very similar to what we did earlier. We have this new thing
called temp. Temp keeps a copy of array of i. Array of i copies, array of j and array of j
copies temp, and this i claim is correct, right. So, even though it looks like it is very if i
replace array of i by a and array of j by b, it is looking like a swapping local variable, but
you are actually passing the pointer to the local variables. Array of i is not the simple
value, right. It actually goes to what was pointed to by the thing called array. I will take
the contents of that, put int temp, right. So, this is very similar to pass by reference only
that two pointers are pointed to the ith location, pointed to the jth location.
So, you are accessing the contents of the location pointed to by what is happening in the
ith location and j. You can think of it as the index which gives you the pointed to the jth
location of the array. You get that pointer; you access the contents and so, on. So, this is a
very simple way of swapping the contents of two variables in an array. So, we are passed
array by reference. We are actually passing pointers here. We did not copy the contents
308
of arrays on to formal parameter list. You only passed a pointer to the beginning of the
array and if you have i, j, these a 2 indices. So, array of i will go i steps away from the
beginning of array. Array of j will go j steps from the beginning of the array, and you are
swapping the contents of these two locations. So, you get the swap 2 elements. So, some
something like this is very useful if you want to do some things like order the elements in
an array or find out the largest or smallest value in an array and so, on.
So, this brings us to the end of this module as well. So, we will see how to look at using
this swap function to do finding out the nth element in an array or the largest element in
an array, and see how to do swapping.
309
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 10 E
Lecture – 24
Content
Example: factorial of a number
Non-recursive versus recursive functions
Stepping through the recursive calls
Stacks and activation records
Example: Compute power(m, n)
Why and where to use recursion?
Hi, Welcome back. We looked at functions and we looked at a function being called by
the main program and so on. So, there is nothing which actually stops functions being
called by other functions also. But there is an important and interesting class of
functions, which are called recursive functions. So, these are functions which actually
call themselves. And this is actually a natural thing that happens in lots of mathematical
equations and so on. So, it is an interesting thing to learn. So, I want to talk about the
notion of recursion in this module.
So, let us start with a very simple example. Let us look at this notion of factorial of n. So,
n factorial is defined as the product of the first n terms. So, formally it is; n factorial is
310
one times, 2 times, 3 times so on up till n. So, for example, 3 factorial is one times 2
times 3, which is 6; 5 factorial would be one times 2 times 3 times 4 times 5 that is, 120
and so on. So, if you want to write a small program to calculate factorial of a number it is
not very hard. So, you have seen loops and you know how to do it.
So, let us look at this little function here called fact. It takes one parameter n and it has a
result, variable, which is initialized to one and there is a loop iterator called i. So, the
loop iterator runs from one to n; we can see that. And the result is just multiplied by
itself. So, you take one and then multiply with one. So, that is in the result in iteration
one. Then i becomes 2. You take one times 2; that is temporarily stored in result and so
on. So at the end of n iterations, this loop will terminate and the variable result will have
the corresponding factorial. At that point, you are ready to return it. So, this is a fairly
straight forward and simple code.
And so we have seen this notion of function name, the formal parameter n, the return
type int and actual return result and so on. So, one small issue. This is result; can only
accommodate certain values. So, integers in the real world are unbounded, whereas
integers in C programming is bounded to a certain large value. So, if you give a large
enough n, this program may actually give you incorrect result. So, I suggest that you go
and try something like 40 factorial and fifty factorial and so on and see what the result is.
So, you would be surprised at the result that you get, but this is because the integer
variables cannot accommodate results of indefinite size.
311
(Refer Slide Time: 03:14)
So, there is one way in which you can look at this program. In which, this problem you
can look at n factorial as the product of n times n minus 1 factorial. So, this is a very
natural and recursive way of defining n factorial. So, this is something that you might
have seen in your school days. So, n factorial is defined as n times n minus 1 factorial for
appropriate n. So, clearly n cannot be; so if n goes to negative, so this thing keeps going
on forever and so on. So, we will have to be careful about it. But for positive numbers,
for positive integers n, this seems to be working nice. So, let us say I want to take this
idea and convert that to a program as the idea shows.
So, I would like to do something like this; return n times fact of n minus 1. So, whenever
I am going to call this factorial with variable n, I am going to return n times factorial of n
minus 1. So, that seems to faithfully do what this mathematical description wants.
However, as I said we cannot go on doing this indefinitely because at some point let us
say I start with 5, 5 will go down to 4, 4 to 3, 3 to 2, 2 to 1 and so on. And at some point
it will become negative. And then what do we do? So, we have this extra check; if n
equals one, we return one. So, that is the base case for the recursion. Even here we need
a base case, which defines one factorial as one. So, this is clearly very short. And if you
understand how functions can call themselves, it is easy to understand also and it
definitely uses fewer variables.
312
So, the nice thing about that is it becomes very readable. But, the slightly messy thing
here is that so you have fact. And we looked at the control flow of programs. So, if main
call fact, we know what it does. But then there seems to be a call to fact, within fact
itself. So, you need to understand how this is going to happen.
So, let us look at this setup first. So, I have this program on the right side just for clarity
sake. And let see we want to look at. Let us say we want to look at how this is going to
work. So, I want to do factorial of 4. Let us say the main function called fact with 4. So,
this local, this formal parameter n will copy the value 4. So, n is now 4. So, factorial of 4,
you go and check. If n equals one, at this point n equals 4. So, this condition is not true.
So, you have to return n times factorial of n minus 1. However, you have 4; which is n
factorial of n minus 1 is not known yet. So, at this point you have to calculate factorial of
n minus 1 which is factorial of 3 and then do this product 4 times that and only then you
will be able to return the value. So, it seems logical. We have 4 times some value that we
need, but we have not computed that yet. But once it is computed, I can multiply that
with 4 and I will have the result of factorial of 4. I will be able to return the value. Right
So, but now that I have this fact of n minus 1, let us see how to do that. So, fact of 4 in
turn calls fact with the parameter 3. At that point, factorial of 3 again you could. So, n
takes the value 3 now. And this multiplication is pending and this value 4 has to be
313
remembered. So, we will look at that in little more detail later. But remember that this
star, this multiplication, is pending as of now. We cannot do it yet.
So, by that time we get the factorial of 3, we can then multiply it. So, to find out factorial
of 3 we call fact with 3 with in fact itself. And again you check if n equals one, at that
point n equals 3. So, 3 times factorial of 2 is required. So, we want fact of 2. Again at
that point, I should remember that n is 3. And I need to do multiplication. So, there is a
pending calculation and there is a value with which you have to do it. But, right now I
am going to just go and look at how to compute fact of 2. So again if we go one step
further, it says take 2 and multiply with factorial of one. But finally when you call this
with factorial of one, at that point n equals one. And this condition is true if n equals one
return one; which means, you are actually going to return from the function. This else
clause will not to be looked at anyway because n equals one. You are now ready to
return from the function. And what are you returning? You are returning the value one.
So, factorial of one is one. So, we have touched the base case of recursion.
At this point, we know fact of one. So, there was a pending multiplication. It was waiting
on this factorial of one to be calculated. So, we return the value one. And this one, when
you multiply by 2, factorial of 2 is 2. So, what are all the pending things at factorial of 2?
It was remembering 2 and then this product was pending. Once the product is ready,
once the other factor is ready, so you have n times factorial of n minus 1. Let us say n
equals 2. Once factorial of one is ready, you have to compute this product. And that
product, remember, it has to be return. So, now we have computed the product as 2. And
this; once that is computed as 2, you are now ready to return it. And there was this
version of fact where n was 3. It was waiting on factorial of 2 to be return back. So,
factorial of 2 is now 2. You are now ready to do the product, which is 6. And you are
now ready to return it back. So, if we continue doing this, at some point we will go back
to the very first function call that was made to fact.
So, we called with 4. So, 4 times; now I have factorial of 3 ready, which is 6. So, 4 times
6 is twenty 4. We do the product and we are now ready to return the product. So, twenty
4 actually gets return back to the caller of fact of 4. So, this is how this works. So, this
may look like a little bit of magic right now. But, we will see in the next slide in detail
how this actually works.
314
So, the calling version; whenever it has pending work, it will just as though it will
suspend itself and it makes a; so it passes the control to the new function. Once the return
value comes back, it will do the pending computation. But during that time, the caller
needs to remember the values. So, once the return happens, you get a value from there.
You have to remember what computation has to be done and on what value you have to
do this computation. So, this is something we will look at in detail in the next slide.
So, let us see how recursive function calls are actually implemented. So, we already
know with respective functions that all automatic local variables and formal parameters
are created every time we call a function. So, we saw this in an earlier module that every
time a function is called, you have a new avatar of the variables. They get used up during
the function. And when you return from the function, all these actual parameters and
automatic local variables are all destroyed also. So, this is not just true of non-recursive
functions; it is also true of recursive functions. So, even for a functions like fact this
notion is true. Let us see what the implication of that is.
So, whenever you have these automatic variables and formal variables, they are actually
stored in an area called stack. So, this is something which is actually a region in memory.
And every function call will push what is called an activation record on the stack. So, the
activation record contains what are the different variables that are local to the function
and so on. And the activation record gets pushed on to the stack, you pass the control to
315
the caller, passes the control to the callee and the callee does various calculations. And
when a function call returns, the activation record is removed from the top of the stack.
So, solve this; may sound like theoretical.
Let us see pictorially what it is doing. So, let us say I called factorial for n equals 3. So, I
call fact of 3 and the fact as a function creates an activation record. So, at that point we
have n equals 3 and we do not know the result yet. So, this is the state in the beginning.
Now, we call n times fact of n minus 1. So, n is already saved. So, that is there in your
record. The result is unknown. We will have to come and update the result later. But,
now you make a call to fact of 2. So to do that, it actually creates another activation
record with n equals 2. So, remember it is not over writing the value 3 here in the current
activation record, it creates a new record. And for the new record you know the value of
n because fact of 3 called fact of 2. So, n equals 2; that fact of 2 the result is unknown.
So, it is pending. Let us say at some point you called fact of one, you create another
activation record. So, you have this activation record n equals one. And at this point the
result is known. So, this activation record assumes that we go all the way down to 0.
Zero factorial is also one. So, instead of n equal to 1, if you have checked n equal to 0
and return one; this example shows that.
So, n equals 0 returns one. And when that returns n equals one times, whatever return
from the previous activation record, the result was one. So, one times one is saved as the
316
result for fact of one and this returns to its caller. Its caller is expecting to compute its
result. This caller is expecting the result form the callee and it has this variable 2. It has
to multiply the result from the callee and the 2 and put that here.
So, you have that. And now the callee is going to return 2; the caller is waiting with
another variable n which is having a value of 3. It will take that, multiply it and put it in
the result.
317
And finally whoever called fact of 3, will return with the value of 6. So, that is how it
works. So, the basic premise in this thing is you have what are called activation records.
So, the activation records are just copies of all the variables that are local to a function.
If a function calls itself, the activation record is kept and then you create a new activation
record with new variables n and result. And you compute things there and when you
return, you destroy the activation record. And the result, the return value is the only thing
that is passed on to the caller. So, I hope this set up was clear.
So, we will use this notion of recursion in solving this other problem. So, I want to look
at a recursive way of solving power of m, n. You have already done this using an
iterative set up. Let us say I want to think about it recursively.
318
(Refer Slide Time: 16:20)
So, let us do this on a piece of paper. So, what we are going to do is we are going to look
at a recursive way of doing power of m, n. So, we call this base and n earlier. So, I am
going to call it m for now. So, earlier what we did was this. So, to compute m power n
we did something like this. We did m times m times m times so on till m. And this, you
need n of those. So, actually what you are doing is you are doing n minus 1
multiplications. So, this is the key thing. You are actually doing n minus 1
multiplications.
So, let us take a little while and think about whether we really need n minus 1
multiplications. So, there is this nice recursive way of doing power of m, n. So, I will
take a specific example and show how this thing works. So, let us say I want to compute
3 raise to the power of thirteen. So, I could always look at computing 3 power twelve and
then multiply by 3 or 3 power eleven and then multiply by 3 twice and so on. But, one
nice way to do this is take 3 power thirteen and split thirteen into half. So, thirteen by 2 is
6 point 5. Let us look at the smallest integer. So, the integer which is lesser than n by 2.
So, n is thirteen. Let us look at the integer that is just less than n by 2. If n by 2 is an
integer, we will keep that itself. So, what is that? Thirteen by 2 is 6 point 5. The integer
that is smaller than that is 6.
We will start with 6. Let us say I have able to compute 3 power 6. Right. I have to do
some computation. It is not going to come jump right into our lap. We need to compute 3
319
power 6. But then if I have 3 power 6 and I can multiply that with another copy of 3
power 6. And if I now multiply that by 3, this is actually 3 power thirteen. So, this is
correct. So, 3 power 13 is 3 power 6 into 3 power 6 into 3. So, what have we really
done? We still have 2 multiplications. So, and instead of looking at 3 power twelve into
3, 3 power one right, we have it as 3 power 6 into 3 power 6 into 3. So, what is the big
deal? So, what we are going to do is we are going to compute 3 power 6. But, if I am
going to compute 3 power 6, this product, we can think of it as a pending calculation that
we have to do.
So, I am going to compute 3 power 6. Somehow when I get that computed, I still have to
multiply that by something else. So, there is pending computation. So, we will do that
pending computation later. So, we have 3 power 6. Now, how do you compute 3 power
6? So to compute 3 power 6, I am going to write it as 3 power 3 into 3 power 3. So, I
have used the same idea. I have taken 6 divided that into 2 divided that by 2. So, I have 6
by 2 which is 3. And I am going to calculate 3 power 6 as the product of 3 power 3 and 3
power 3. Now, again I do not have 3 power 3. So, I will keep this as a pending
calculation. I am going to compute 3 power 3. So, to do that I will have 3 power one. So,
this exponent here; 3, if I divided that by 2 it is one point 5. I look at the integer smaller
than that; which is one 3 power one into 3 power one and this time the exponent is odd.
So, it is not. So, you have to take care of the fractional part also. So, remember 3 by 2 is
one point 5. If I only do 3 power one by 3 power one, I get 3 squared; not 3 cube. So, I
still need to do a multiplication by 3. And to compute 3 power one I am going to look at
how to do that.
So, we know that any number raise to the power one is n itself. So, m raise to the power
one is m. Therefore, we have 3. So, now this gives us in some sense a very nice and
recursive way of doing it. So, where is the recursion here? So, the recursion comes from
the fact that if I have 3 power 6, I am going to call. So, for computing 3 power thirteen, I
am going to call 3 power 6; to compute 3 power 6, I am going to call 3 power 3; to
compute 3 power 3, I am going to call 3 power one to compute 3 power one. I am like; it
is just any number raise to the one, I do not have to call the same function once more.
Instead I can have this base case that m power one is m. That is what we have used here.
Now, let us say we actually did that. So, to compute 3 power 3 we wanted 3 power one,
but we got that by making another function called factorial. It returned 3. So, we got 3
320
now. Now, what do we do this 3? We got 3 power one. We still have some pending
work. At this point what is the pending work? I have to calculate 3 power one and 3 need
not be calculated. So, 3 power one and 3; we have to take a product of that, multiply that
with the current product that I got, which is 3 itself. But, one nice thing is that instead of
making of function call to compute 3 power one, we just got 3 power 1. We just got 3
power one. While we made a function call for 3 power 1, we got the return result as 3.
Why do not we actually use that right?
So, what happens now is instead of making another call to compute 3 power one, since
we just got the value 3 for 3 power one, I will not use 3 power one. I will instead use the
copy of this; which is 3 itself. Now, I have to do 3 times 3 times 3. The result is twenty
seven. And this 27 gets return back to its caller. So, 3 power 3 becomes 27.
321
(Refer Slide Time: 24:28)
So to do 3 power thirteen, we did 3 power 6 into 3 power 6 into 3. So, that requires 2
multiplications. If I know 3 power 6, I have do only 2 multiplications to do 3 power 6.
We did 3 power 3 into 3 power 3. So, that required only one multiplication. To do 3
power 3, we did 3 power one into 3 power one into 3. So, that required 2 multiplications.
And to do 3 power one, we do not have to do any multiplication. We will use the base
case of recursion that m power one is m. We will use that directly. So, that requires
actually 0 multiplications. So if we add all of the sub, 2 plus 1 plus 2; that is actually 5.
We only did 5 multiplications. I am supposed to doing it as 3 power thirteen. If I had
done it as 3 into 3 and so on, right, this would have required twelve multiplications. So,
clearly 5 multiplications is better than twelve multiplications.
So, I want you to go and think about what is really happening here. If it is instead of
thirteen, if I had use twenty, if I use twenty 5 and so on, I want you to think about what
number of multiplications you will need. If you did it using this way verses what number
of multiplications you would need if you are done something like this. So, go and think
about it. You will also see how to analyze this algorithm and how many steps it takes and
so on in a later lecture. But, let us get back to how to write a program for this because
that is the thing that we wanted now. How do you write a program to compute power of
m, n?
322
(Refer Slide Time: 26:18)
So, I written this simple program here to take care of this. I written a function called
power. It takes base and n as a 2 parameters as it was before. And we have int p. We had
this earlier also. So, instead of writing an iterative way of doing this, we have a recursive
way of doing this. If n equals one, return base. So, this base case takes care of the fact
that m power one is m. right. Otherwise, what I am going to do is I am going to compute
p as power of base, n by 2. And if n is odd, for example, in thirteen n is odd, so I need to
take 3 power 6 and 3 power 6 and multiply that by 3 once more. That is what you get
here. So, this is p times p times num. If n is even, then you have only p times p. For
example, in here for to compute 3 power 6, it is enough to have computed 3 power 3 and
reuse the 3 power 3. You would get 3 power 6. So, one thing that you have to probably
recollect is that n by 2. If n is an integer it will truncate the decimal value, it returns only
an integer. So, thirteen by 2 is only 6. It is not 6 point 5. So, thirteen integer divided by 2
integer is 6. It is not 6 point 5.
So, now let us see how this whole thing would have worked. Power of 3, 13. So, n equals
thirteen. So, this check would not have been true. p would get power of 3 power 6
because n by 2 is 6. You call power of 3 power 3, 6. Power of 3, 6 would transfer control
to power again with n equal to 3, sorry, n equal to 6. So for n equal to 6, n is not equal to
1. That should have called power of 3, 3. Power of 3, 3 would have called power of 3,
one. So when n equals to one, you have this return base. So, it would have just returned
323
3. So, that is going to come back where n was 3. It will come back here. So, you would
have computed 3 power 1.
So, clearly for every recursive function at some point you should not call the function
any more. So, you have if n equals to one you return just the base without calling the
function anymore. However if n is greater than one, you will call the function atleast
once more. So, this is the basic idea behind recursion. So, you can think of it.
As in some sense even for induction, we do this. For proof by induction, we say this is
the base case for induction. And from there on, we keep building things. You can think
of recursion in a similar way. So, there is this base case and then you are building
something on top of it. If you do not have a base case is program would be incorrect. So,
we saw 2 kinds of recursive functions namely factorial and power. And this is a very
powerful setup, because once you know recursion, there are several things that you can
do very easily and you can write programs very easily. You would not have to think
about doing them in an iterative manner.
324
(Refer Slide Time: 30:37)
But, you have to remember a few things about recursion. The first thing is recursion must
end at some point of time. If function f calls itself and it calls itself and so on, it cannot
go on forever. If you do that, then the program is never going to terminate. So, it can be a
problem. So, you should have some condition inside any recursive function to terminate
recursion. You should not call the function once more from the base case. So for that, go
and think about what would happen if the check for n equal to one was not there for
factorial. We just did return n times fact of n minus 1. If you have done that, see where
you will stop.
So, it is not that recursion is a silver bullet. It is not going to be useful every time.
Sometimes loops are very straight forward to use. So, you should see where to use loops
and where to use recursion. So, in general what you have is recursive functions are
actually quite elegant. It is very simple to write. You can take any mathematical formula.
It is usually recursive. You take that and write it down. It is very elegant, but in general it
is less efficient. So, it will recursive functions usually take more time. And if you are not
careful it can take a lot more time than this writing loops to do the same thing. And of
course in recursive functions you have to take care of the base case.
So, there are several other things that you can write using recursion. So, one classical
example is you can do what is called n choose r. So, let us say I want to n choose r.
Recursively, I can use n choose r minus 1 plus n minus 1 choose r minus 1. This is a
325
recursive definition for n choose r. So, out of n objects if you want to choose r objects,
what are the number of ways in which you can choose them? So, this is clearly recursive.
So, n choose r uses n choose r minus 1 plus n minus 1 choose r minus 1. So, all these
different things can be actually done with recursion. So, that brings us to the end of this
module.
326
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module - 2
Lecture - 25
Measuring running time of a program
Size of input Big-O and other notation
Sum rule and product rule
In the previous lecture, what did we talk about? We talked about we did a recap on the
basic building blocks of programs. And then we said how we can use. We kind of,
informally looked at the arrays and recursive data structure. And we looked at some
programs which can operate on these 2 data structures. And already we also looked at
another example of prime classification. And we said how do we know, how efficient the
programs that we write are. And we just did; we informally said why checking up to the
square root of n for prime classification is more efficient; primarily because it runs fewer
times through the loop. That is what we saw. Now, what we will do is in this class we
will look at measuring the running time of a program.
And when we measure the running time of a program, we will see how we can formally
referred, define the running time of a program in a sort of a machine independently.
327
What do you think that governs the running time of the program? Surely, the input to the
program matters.
For example, if I am doing prime classification again, if I am giving a very very large
number, then it is going to take a longer time. Whereas, if I am testing for the number 2
or 3, it is going to be very fast. An other issue could be that it could also be the compiler.
If I have very bad compiler, the compiler does not generate code which is very efficient.
Then, it is possible that there can be difference in the running time of the program. And
of course, finally how good am I using a i7 or am I using an ordinary Pentium machine
and so on and so forth. The MIPS of the machine can also decide how efficient your
program is going to be...
328
(Refer Slide Time: 01:52)
Now, what we would like to do is clearly when I write an efficient algorithm, we should,
you know today you have i5, you have a i6, i7 and so on and so forth. So, can we do in a
kind of a machine independent way is the question. How can you perform complexity
analysis or analyze the running time of a program in a machine independent way. And
sometimes, for example, depends upon the; the another input is we talked about the input
matters. Let me give you an example.
329
(Refer Slide Time: 02:29)
You are looking at the prime classification problem clearly to determine whether number
like 13457 is a prime or not. It is going to take much longer than determining whether
the number like this is prime or not. So, the size matters; the size of the input. An other
situation could be; if for example, if I am looking at sorting n numbers. You have already
done some basic sorting algorithm in the programming course. So, you know that if n is
equal to hundred, then the number of computations required is going to be smaller than n
equal to, let us say, ten thousand for that matter. So, what is interesting is if I take these
sorting of n numbers, let us say I am looking at sorting of ten numbers only.
330
(Refer Slide Time: 03:22)
And let us say these ten numbers are, you know, 15000, 12500, 175, 60, 10000 and let
us say something like this, even if the size of the number is large. On either hand if I
have, you know 10000 numbers to be sorted. And numbers are 2, 4 or they are all single
digit numbers are; you know double digit at most and no more than that; repeated
number so on and so forth. Clearly even if the contents, the numbers are large and in the
other case the numbers are small, clearly we look at sorting program. It simply does not
matter what the value of the number is. But, what simply matters is the number of
elements that you have to solve. Number of elements; if I am sorting, for example, I am
going to use an array for sorting. And it is going to depend upon whether n is hundred or
n is equal to ten thousand. So, clearly what is the fundamental difference here? The size
of the input, rather than the input itself, matters in the sorting program.
331
(Refer Slide Time: 04:39)
So, this is essentially what we are going to look at and how do we define running time.
So when we define running time, running time of an algorithm is defined in terms of n;
where n is either the input of the number or the number of elements in your input like the
sorting program. And this is written as C into n squared. What is C now? For example,
what is it telling me? C is a constant over here.
332
(Refer Slide Time: 05:59)
T of n generally refers to what is called the worst case time complexity. That means;
what do we mean by worst case running time? We are saying maximum over all possible
inputs. That is generally the meaning of T of n. And T average of n is you are looking at
the average over all inputs of size n. And what it means is that generally T of n is little bit
difficult to compute. This will be done in your algorithms course.
333
Next, what we will do is I will just give you a very brief overview of the kind of notation
that I am going to use as part of the data structures course. You will use more formal
notation. They are what are called, what we need as part of this course. They are
something called a Big_ o notation, a Big_omega notation. And there is a small_ o
notation and then there is a small_ omega notation and there is also a Big_ theta notation.
All these details you will learn in the next course on algorithms. So, what I am going to
do is I am just going to talk about the Big_o and Big_ omega notations, which are useful
for the analysis in this particular course. So, what is it mean? T of n is equal to big O of n
square means what? There are constant C and n naught, such that for n greater than or
equal to n naught, T of n is less than or equal to C n squared. What is the meaning of this
now?
Let us take n square and let us take c is equal to one for that matter. Then, what do we
have? Zero, then we have one and let us say this is 0, 1, 2, 3, 4 and so on. Then if this is n
over here, for 0 it is 0; for one it is one; and for 2 it becomes 4. So, this is let us say 0, 1,
2, 3 and 4, for example. It becomes like this. So, basically the curve is not linear, but it is
kind of quadratic; the way this function grows. What is the meaning of it? If I have T of
n what it tells me is that this is like a bound. So this, here I have chosen C equal to one.
What it tells me is when T of n is less than or equal to C n squared, if t is something like
334
this, then it tells me that the running time of the given program is going to be bounded by
this curve. I have taken a special case where C is equal to one. But in general, it can be
any constant C. And it tells me that for a particular n naught, what do we mean? n one
greater than or equal to n naught and a particular C. If this is T of n, then T of n is
guaranteed to run in less than or equal to that C of n squared time.
So, it is a very very important point over here. So T, when the T of n is written as
basically O of f of n, let me give you a few examples.
335
(Refer Slide Time: 09:33)
If I have T of n is equal to n square minus 2 n plus one, then we would write T of n equal
to big O of n squared; because it is possible to find a C and a n naught, such that T of n is
always less than or equal to C and n naught. So, this is the fundamental point of view. In
this particular case, for example, if I take C equal to 2 itself, it gets satisfied. So, this is
the important point about time complexity analysis. There is an other notation called the
omega notation. And it is just the other way round.
So, what it tells me is the T of n here is less than or equal to C n naught over, C n squared
over here. And we have another one. It says T of n is greater than or equal to C n squared
over here. Again, you can find a C one and n naught. So, what we say is that when I am
looking at T of n is equal to some omega of n squared, then what we are saying is it will
always be above this curve and not below that curve. So, that is the meaning of
complexity analysis over here.
336
(Refer Slide Time: 11:03)
There are 2 bounds. This is like the upper bound on the growth rate of the algorithm; as
on increases. And this is the lower bound. There are stricter notations like the theta n
small. And looser notations like the, looser bounds likes the small_ o and small_ omega.
All this you will relearn in your algorithms course. Now, the question is how do we
compute the running time of a program.
337
(Refer Slide Time: 11:39)
I am going to take very very simple ways of doing. There are 2 primary rules called the
sum rule and the product rule. Let us see what these 2 rules are and how do you compute
them in a given particular program. What the sum rule states is if I have a program like
this, let me call this the program P. And this program P is made up of 2 segments P one
and P 2. Then, the sum rule states that the computation of the; what it tells me is the
compute time for program P one is T one of f of n and the compute time for P 2 is T 2 of
f of n. Then, it says the running time for the entire program P is the max of this; max of T
one of n and T 2 of n. This is what the sum rule tells us.
338
(Refer Slide Time: 12:56)
Now there is an other rule, which is called the product rule. And what this product rule
tells us is that if I have a program, what do we mean by?
It says that in the product rule, if P is made up of P one into P 2, what do we mean by
this? That is, a program; for example, if I have for i equal to 0; i less than n; i plus plus.
339
And I have a program segment here. This is P 2, end for. And this is P one. It tells me
that P 2 is executed P one times.
Then the time complexity for this is given by if T one is equal to order of f one of n, T
one of f of n let us say. And P 2 is, sorry, P 2 of f of n and T one is g of n. Then, the
overall time complexity for the program is T of f of n into g of n. That is what it tells us.
Let us look at it with an example in the next lecture.
340
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 03
Lecture - 26
Example of computing time complexity using sum/product rules
Computing time complexity in a recursive function,
Example: Factorial Example: Compute time complexity of sorting and searching
(linear versus binary)
So far what we have done is we talked about two rules for complexity analysis very two
very simple rules; one rule is what is called the sum rule, and other is call the product
rule? What is the sum rules state? It my program P is made up of two program segments;
P 1 and P 2. What do we mean by this have P 1 followed by P 2, and this is my entire
program P, then the time complexity of the program P is max of be time complexities of
P 1 of n comma P 2 of n. We have already saw where n comes from, n comes from the
input to the program, it may be size or the value of the input itself.
Now the second rule is a product rule. What is the product rule state? It is says that if I
have a program P 1 like this, and within this I have a program P 2. What is the meaning
of this? It tells me that P 2 is executed P 1 times, then the product rule the time
complexity is, time complexity that is what is that we are saying the program is made up
341
of P 1 times P 2, and therefore the time complexity of P - P is equal to the product of P 1
of n into P 2 of n, this what we saw in the last lecture. So, we are two rules: one is sum
rule, and other one which is the product rule. Now what we will do is will go through an
example and see how these rules can be applied. So, what do I have there, I have a
simple program let me write just re write it here it.
It simply computes the some of the elements in an array. So, it is says sum equal to 0 and
for i equal to 0, i less than n, i plus plus. We compute sum is equal to sum plus a of phi, a
is the array over here, we computing the some of n elements in the given array a. Now let
us look at this program. Sum is equal to 0 is an assignment statement, and if I look at this
entire program here I would say this is made up of P 1 and P 2. Now when I look at P 2
by itself notice that this is again made up of there is a loop here; that means, this
statement is executed n times. So, here in P 2 to analyze the time complexity of P 2, I
need to apply the product rule. To analyze the time complexity of P 1, I am simply use
the, but some rule. So, to for this one for the example, if I look at this what is this
statement here, i is being initialized, i less than n, i plus plus. So, what ((Refer Time:
04:07)), the worst case I will do three operations; initialization, comparison, and
increment. So, this once time complexity is order one, because if max of all this all of
them are constant time operations, and this is also constant time operation, but what are
342
we seen the P 2 now. P 2 is made up of this segment, that is this for loop is executed, this
executes this statement n times. So that means what, the time complexity is of P 2 is n
times means what, if I look at the if let us say P 2 is made up of two parts; P 2 1 and P 2
2, where P 2 corresponds P 2 1 corresponds to the for block and within that I have this
sum which is computed. That means, what P 2 1 and P 2 2, this is what it corresponds 2;
P 2 2 is executed P 2 1 times. Therefore, now I have to apply the product rule. What is
the product rule tell me now? The product rule tells me that the time complexity of P 2 of
P 2 is the time complexity of P 1, time complexity P 2 1 times the time complexity of P 2
2. So, since the for block executes n times, it is time complexity is n, notice that the
increment is you know i increases by 1 during every iteration. Therefore, this loop
executes n times.
343
of time complexity. So, what have we done? We have used both the sum and the product
rules to compute the time complexity. Let us now this is fine as for as programs with that
are having regular for loops are concerned.
Now, if we looking at something else, let me leave you with this example. Suppose I
have i equal to 1 while i less than n, do I am writing some ((Refer Time: 07:57)) here, i is
equal to i star 2 end while, now again there is of the form, this is program P 1, this is P 2,
again P 2 have the loop, but how many times that this loops executes, basically notice
that i is getting double every time. So, if I say n is equal to, let us say 8 then i equal to the
first time I have i equal to 1, then the second time I have equal to 2, i equal to 4 and the
third time i equal to 8. So, if we look at it, it basically goes through this, this part of the
program is executed only three times, and therefore in terms of n if you look at it; that
means, how many times is a loop executed, it is only executed login times. That means,
now and the cost of this statement over here is order one, this is also order one, but the
loop is executed log n times, therefore the time complexity becomes order of log n. So,
this is how you compute time complexities using the product, and the sum rules.
Now this is all fine as long as there is a loop like this which you can you know all that
we have done is we have kind of un ruled this loops ((Refer Time: 09:19)), how many
344
times if the loop the executed, this is what we are doing here? And multiplying the
number of times the loop is executed with a cost of the operation within the loop, there is
how you defined time complexity over here. Now let us look at another example. This is
slightly more complicated example, what happens when we have recursion, here is an
example with recursion a computing, the factorial of a given number, let me rewrite this
over here is saying. So, is basically like this the int fact over here, and what are we doing
here? Again we can apply the same which but the only difference over here is that, we
need to find out how many times the recursive call is made? And that will decide the cost
of the computation.
So, if you look at it over here. So, what is it that I have we have fact, and we are saying
some if n less than or equal to 1 then we are returning 1; values set to one in that is what
we have written. That means, that is the only statement that is executed in the recursive
function else value equals n star fact of n minus 1, and if here and we are returning value,
this is what the function is return. So, let us look at this recursive function? What is the
various thing that what have been done this statement is very harmless, what do we have
here? We making one comparison here, and the cost of the operation, therefore this will
be atmost order 1; that is then n becomes less than or equal to 1, the cost of the
computation is only order 1, but when n is not equal to1, let say n is greater than 1 then
345
what is that that we have? We have n star fact of n minus 1. That means, what I have fact
of n, now this is calling fact of n minus 1 which is calling fact of n minus 2 and so on.
Let us say I want to compute the factorial of 3, then the fact of three is going to incur;
that means, what is that I am doing, within this now there is one multiplication over here.
The cost of the multiplication is essentially going to be order 1, and the assignment for
that matter is going to call order one here, and fact two again order 1, and so on fact to
one which is again order 1. So that means, what to compute the factorial of 3, I do 3
times order one operations. So, the recursive call essentially tells me that is going to
three times order 1, n is a number over here which is this input, therefore the time
complexity of this is going to be order n. And what we can do is what we have written
there basically if we look at the particular loop here, which as order of 1 plus plus T of n
minus 1. This is the way we write the time complexity for recursive functions. Let see
what we do over here. So, what we do is to give you a nice simple way of doing it. So,
we are saying there is some constant cost over here plus the cost of T of n minus 1,
where this is the cost of the recursion. If n is greater than 1, it is equal to d just one some
other constant cost, if n less than or equal to 1. So in general define at the ith iteration, ith
recursion then T of n and what we done i's i time c plus T of n minus i, n is greater than
n. Then i equal to n minus 1 what is happening, then there is only one c of… So,
basically what happening i cost c of n minus 1 plus d. d is the cost of the last call to the
recursion. And therefore, the time complexity becomes order n. I must one new this is
not a nice algorithm to compute the factorial, because it is two expansive, but never the
less this illustrates the problem of how complexity analysis can be done for recursive
functions. So, this is…
346
(Refer Slide Time: 14:05)
So, basically in when you have recursive function the most important point is that, we
write it we say T of n will take one more example, and a little while and we write T of n
and both on the left hand side, and the right hand side in this particular computational
just C plus T of n minus 1. So, you will find T of n is equal to define in terms of T of n
minus 1 and so on, and you can solve this, you learn more about solving the time
complexity for recursive functions in the next course, an algorithms where it will regress
way of finding the time complexities will be given.
So, we leave it here and now what I want do is, I want to take few more examples on
complex, so this is so that I am sure that your absolutely clear about what is being done
and let us look at this example of sorting and elements. So, I have a set of n elements by
this; 10 7 15 25 32 20 and 3 12, that is given as input to my sorting algorithm and I want
to get this output which is a sorted array.
On the other hand if I am searching what am I doing, I am given sorted array, and I am
let us say I am given the key equal to 12, and output should give me index of the where
this element can be form. So, these let us look at these two problems. So, let us look at
347
sorting. Will take a very simple selection sort algorithm, what the selection sort
algorithm? It is a quite an inefficient version of the selection sort algorithm that I have
written, all it is doing is it take is array over here, and what we does is let us look at this
example again I have an array with something like this. What do I have? I have 10 7 15
25 32 23 and 12.
So, what is it do? When I look at a first loop, let us look at this I will ((Refer Time:
16:09) what is doing, its assigning something call small to this element, i is pointing here
and j is now pointing here. Then what it is do? It goes through this entire list and keeps
on exchanging then the swap function I just use the swap function from the start very
efficient to do it which should actually find the minimum in the array from I plus 1 to n
and replace exchanging the 2 of them. So, what is it do now it comes keep on exchange
this, 7 7 will be exchange then I can 7 is in the beginning here, 7 and 15 is find 7 is
smaller than 15 remain as it is. Then it will compare 7 with 25, 7 with 32 and then comes
with 3 here, and ultimately what we will have it is something like this at the end of the
first loop.
So, I would have here 3, I will have 10 here, I have 15 here, 25, 32, 20, 7 and 12 after the
first pass. So, what are we done one execution, that is one’s I have gone through the
entire array, the most minimum element is that the beginning. Next what we do, we make
this small and this is i, this is j, and then we compare again find the smallest element
smallest element is put he, and this is repeated until we are exhaust the array. Now when
I look at the time complexity this program, this program is of the form P 1 into P 2 into P
3. What is P 3 now? P 3 corresponds to this int this loop over here. So, there are two for
loops; one for loop is inside the another for loop, each for loop is executed n times. So,
now let us look at...
So, the for loop with j as index is inside the for loop with the index i, P through
corresponds to the segment inside the second for loop, P 1 and P 2 corresponds to the i
and j loops respectively. So, that means what do I mean over here going back to this. So,
this corresponds to P 3, this corresponds to P2, and this corresponds to P 1. And we will
P 1 again, there is one there is a some rule small plus this loop. Therefore, the time
complexity become an order n now by now your quite familiar with this, this loop
348
execute n times, the cause of this operation is order one. therefore, this is order n that is
within the for loop, and then this for loop is again execute n times therefore, this total
time complexity this comparison operation cause order one. So, this becomes within this
for example, if I look at the body of the ith for loop, the time complexity of the ith for
loop is basically order means, our body of the ith for loop is order n.
Therefore, order n multiplied again this executed n times, therefore the time complexity
of this becomes order n square which is what i summarized here. The for loop of j index
is inside the for loop with the index i, quickly correspond to the segment inside the
second for loop; P 1 and P 2 corresponds to i and j loops respectively. So, basically you
are executing two for loops, therefore the time complexity is order n square. Now let me
leave you with another function which was a nice is a which is a interesting and efficient
more efficient sorting algorithm then the input permutation is very quite random, and this
is called the quick sort algorithm. The quick sort algorithm you learn on the algorithm
scores and what is interesting over here is, this is a recursive function and what does the
quick sort algorithm do is simply take this an array over here divides it into two parts,
and then recursively sorts the element, and what happens is goes on until that is divides
into two parts and I am divides into two parts, what is it ensure? That elements on the left
side of the middle element as smaller and the elements on the right side of the middle
elements are larger, the first part is will do that. The next part what will do recursively do
this again for this part, and in the best case if you a middle element is actually the median
in the array at every time.
349
(Refer Slide Time: 20:57)
What we will find is that the depth of the recursion notice that in the program quick sort
is being called again and again it may be atmost, so I have two labels like this, next it
will become items of two elements long, finally you will have items which are one
element long. So, the depth of the recursion is only log n. But in each recursive call all
the n elements of process, because you have one part here, this is n by 2 element being
process, this is n by 2 elements being processed, and this is again n by 4 n by 4 and so on
in this particular case. Finally, you have one element being the process, this the best case.
Why do I say called is best case, that is an every recursive call, we assume that the
middle element corresponds to the median element in the array. Then the number of
recursive calls, there are required is at most log n to the base 2, but in each recursive call
one call is doing n by 2 elements here, and other call because the recursion recursive call
to n for example, that is makes two recursive calls over here, both on the left and the
right, therefore always n elements are looked at, therefore every time doing some n
comparison. So, n into log n is the time complexity of this algorithm. So, this have you
do these operations. All assignment statement, expressions, and so on so forth, the
essentially cost order one. So, each recursive call divides the array into this is the best
case that I am talking about, in each call all the n number are compared recursion
terminates when the array size becomes 1. And therefore, the number is a recursive calls
is log n in the best case, and order n log n is a time complexity for the best case of this
350
algorithm, I encourage you to go back and verify this. I also want to verify that the time
complexity only given for the best case is also true for the average case; average case
((Refer Time: 23:04)) about is little more involved, you will find that in the worst case
what can happen is that? The array is divide into two parts such that; one part is the size
n minus 1 and other part is the size 1 and if it happen recursively then the depth becomes
n and you will get a n squared worst case algorithm, I want you to verify this case. Now
let us look at searching, now let us do simple thing is next do linear search. Now this is
again a short of inefficient algorithm and just I assuming that.
I have a set up n elements which are not necessarily sorted, and all I am going to do is I
am going to search through this I have an array like this, and search through the array to
find whether there the whether given element is present or not, this is very straight
forward. So, what is that we are doing over here, this the initializing flag found to 0, and
while less than or equal to n and and not found, if array of i equals k found equals 1,
otherwise return a found. So, in the worst case the time complexity that is if the element
is the last element I am looking for 12 as I have looked here, then linear search
essentially searches through the entire array, and determines whether the element is there
or not, therefore the time complexity can be ordered n, it is a very, very straight forward
algorithm. Now what I am going to do is am going to do one more algorithm ((Refer
Time: 24:40)) which is called binary search, and in binary search that as you assumption
is that we have a sorted array, and in this example what would be my sorted array, this
could let us assume that use the sorting algorithm which I have already have and I am
going to perform search on this search on this.
351
(Refer Slide Time: 25:00)
So, this will be 3 7 10 12 15 25 20 25 and 32. So, let us see this the output that I have.
Now I want a search for this element 12. So, what is done in binary search is somewhat
difference. So, because it is already shorted, all that we need to do is we divide the array
into two, and find out now what is the given, the key is given the difference is I look for
the element key equal to 14. Then I compare with where this key might be present, key
equal to 12 is work it you mean. Now I check whether it can be in the first part of the
array or the second part of the array, then what I do is I search depending upon which
will part it my belong to, then we can do this recursively again again divided into two
parts, that is what that algorithms is doing. So, what is doing is, if key key is greater than
array of middle then it searches from middle plus 1 to upper, otherwise it searches from
lower to middle.
So, what is initially middle now, what is lower now, lower is the starting point of the
array, upper is the end point of the end index should be array. So, it is searches in the
appropriate portion for the key. So, what is happening is every time the search space is
getting reduced into half. So, that I am looking at 12, the next time I am only searching
in this part. Next time I am searching only in this part, and then finally I am searching in
this. So that means what the array size which was 8 long becomes 4, 2 and 1; and when I
((Refer Time: 26:49)) 1, then if there element is equal to the key that I am searching for I
352
have found the element and this the essentially the idea mind research. So, now, if you
look at it, what is the time complexity of it, notice that in the first case I come made one
comparison, every recursive call does only one comparison. Therefore, that is constant
time, therefore the total time complexity of this is, because in the recursion I am running
it up to log n times in to 1, and therefore the time complexity is order of log n to the base
2 for the binary search algorithm. And this is what I have summarize to become.
353
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Lecture – 27
Solution: Third Week Programming Assignment
Welcome to the solution section for week 3. So, in this tutorial section what we will do is
we look at this problem of palindrome checker. So, for the first time we gave a piece of
code that gets automatically appended, and that piece of code was shown to you as
commented section, and we said fill in the code to do the work. So, let us look at the
problem statement first.
So, let us look at a few examples and let us understand what the problem is actually
asking for. So, I am taking some of the things from the public test cases. So, let us take
this input called notation. And what we are asked to do is we are supposed to check if, is
a given this input notation, is supposed to check matching characters at both ends.
So, for example, in this n has a matching n on that side, and o has a matching o on that
side. And the moment you go to t, this does not match any more. And what we are asked
to do is, print the prefix, the longest prefix in the input string itself which has a matching
thing on the other side. So, in this case, the prefix n o has a match on the other side
namely o, n.
354
So, let me take another example which is actually a palindrome. If I have m a d a m,
madam, then m has a matching m, a has a matching a, and d is sitting in the middle. In
this case, the whole string is the prefix itself. So, the whole string has a matching reverse,
matching string at the reverse. So, let us take a few other examples. Let us take examples
where they are not even palindromes. So, for example, there is a public test case says
camera. So, even the very first letter does not match. So, the longest prefix is actually
nothing.
So, what we wanted you to do in this problem was take these cases and print the longest
prefix. So, for this input notation the longest prefix would have been n, o; for this input
madam, the longest prefix would have been m a d a m. And if you look at this input
camera there is no prefix at all, where there is a matching suffix in the reverse. So, in
these cases we ask you to with print number 0. So, this is the deal. And how do we
ensure this?
We gave you this function called print characters. Print characters is supposed to take 2
pointers, 2 character pointers p and q. And if you make p equals null, it will printf 0,
right. So, what this is supposed to do? What you are supposed to do with this is, if you
see that as given string is not a palindrome, you should somehow ensure that p is passed
as null because that is what we are expecting in the output. If the input is not a
palindrome which means not even a single character from the beginning and end is
matching then you just somehow you should ensure that p is passed as null and this
355
function will print 0, otherwise let us see what this function is doing.
So, else while p is less than or equal to q. So, we are assuming that both p and q are
pointers to an array because a string is an array of characters, and p is supposed to give
the starting location and q is supposed to give the ending location for the prefix that you
want to print. So, for example, in if you process notation at the end if you somehow
ensure that p is n and q is o, you will end up printing n o which is the longest prefix.
However, for an example like madam, we should ensure that p should point to this m and
q should point to this m. Somehow you should process and finally ensure that p and q are
these because if you give p as this and q as this and call print characters, then you will
get the whole string m a d a m printed. So, what this does is, if goes p less than or equal
to q print star p and p plus plus.
So, what it is doing is, it is starting from whatever is pointed to by p, it prints that
character. And p plus plus, remember it is a character pointer. So, when you do p plus
plus it goes 1 character at a time till it reaches q. So, it also prints what is pointer to by q
and it stops. So, this is what the print characters is doing. So, I am staring with the
template that we gave you.
So, we gave you this, hash include stdio dot h and hash include string dot h, to do any
string operations. We put the prototype called print characters, character star p and
character star q because it is a function that we are using later we need to say what is the
356
prototype and we have the int main. We put a intentional semicolon followed by a
function called printchars. So, this function call somehow before this null statement we
have to ensure that p and q are appropriately chosen. So, let us go back and look at this,
these inputs once more.
So, let us start with notation. How do we write or how do we solve this problem? So, in
the worst case, right, the whole string could have been a palindrome. So, what I am
going to do is I am going to assume that there is a pointer p that points to the very first
location and q which points to the last valid location. So, remember, you are reading as a
string you would have a null character at the end, but we are not going to use it. So, the
valid characters are 1 step before that, right.
So, if I start with p here and q here, the first thing I will do is I will go and see if there is
a match, right. So, is star p equals star q? So, that is a question. I am asking this question,
is star p equals star q? So, in this case, star p and star q are both equal which means we
have 2 characters at the 2 ends which are matching. So, atleast there is a prefix which is
of non zero length. So, this now we can; so we should record this. So, is somewhere we
know that star p and star q are the same.
Then what we have to do is we will have to go and see if the prefix can be extended. In
this example it can be extended by 1 more character, right. So, if you do p plus plus, p
will start pointing to o; and if you do q minus minus, it will start pointing to this o. And
357
again I can, we can ask this question, is star p equals star q? So, at this point p is pointing
to this o, and q is pointing to the o at the end. So, at this point, is star p equal star q? Yes
indeed. So, this is true and this is true. So, the prefix is extended by 1 more step.
Now, let me extend p to 1 more step further and let q go back 1 more step. So, p and q
will now point to t and i. At this point if we ask this question, is star p equals to star q,
the answer is incorrect. So, it is not true. So, what we want is we have found one position
in which the matching position at the other end does not have a matching character. So, t
is matching position is i on that side, but t is not the same as i. So, at this point, you can
notice that p is stopping 1 step away from the actual prefix. So, p is stopping at t which is
1 step away from the matching prefix.
And what do we want to do in this example? So, you have n o that has to be printed. So,
somehow if I make, I am going to call print characters, right. So, for print characters it
needs 2 pointers, and the first pointer should point to the beginning of the prefix which
should be this and the second pointer should be pointing to the end of the prefix which is
this. However, so beginning of the prefix is easy to get. It is just ampersand of a of 0, or a
itself.
But, the end of the prefix we have p which overstep by 1 step. So, what we need is if we
call print characters with p minus 1 and a, right. So, a, would be the beginning of the
prefix, and p minus 1 would be the end of the prefix. So, somehow if we ensure that if
we can call with, a and p minus 1, our job is done. So, this is for notation.
358
Let us look at another example. So, let us look at m a d a m. Again, will start with these 2
locations; at this point is star p equal to star q? Yes. Then p will, so these are for p and q;
then p moves 1 step and q moves 1 step back; at this point, is star p equal star q? Again,
yes. Now, p points to this location and q would point to this location, actually we have
reached the middle of this string. When we reach the middle of the string, so this
characters would actually be the same, right.
So, in this case, when p equals to q, we can actually stop. And at this point we still need
to say what is the whole prefix, right. So, in this case the prefix is expected to be the
whole thing. So, what we want is we want the first pointer that we are calling to print
characters, should be here, this m; and the second pointer that you pass should be to the
end one, right. So, somehow you should ensure that these 2 pointers to the beginning and
end. So, this is something that we need to do.
So, if something is a full palindrome we need to ensure that the pointers that we pass to
print characters are to the first and last character. If we have something like a prefix, like
in this example n o and o n, we should ensure that the first pointer is to the first location
and the second pointer is to the prefix; in this case, it is o.
359
(Refer Slide Time: 11:28)
So, finally, let us look at one example. Let us say, a b c. If a b c is given as input you will
start with p as this and q as that, and star p is actually not equal to. So, star p equal to star
q, if we ask that question, this is not true. So, you can as well say that there is no prefix.
And for this case, we wanted print characters of. So, when we call print characters we
should somehow ensure that we pass a null pointer here and anything that you want here,
right. It does not matter what you pass because print characters we can see is written in
such a way that if p is null, it is already printing 0.
360
(Refer Slide Time: 12:04
So, now, let us take this and see how we want to write a program for this. So, the first
thing we want is we want to be able to read a and the input specification said at most 99
characters will be in there. So, I will declare an array, A, of size 100 because I want to
accommodate back slash 0 which is the end of the string. So, I want to accommodate
that. And I am going to track, where the end of the string is. So, the length of the string
is, you can use the function called strlen of A, this is a string function available in string
dot h. So, I am going to declare an integer called length which tracks the length of A.
So, if you go and look at this input, let us say notation, so what is the length of the
string? So, let us count it. So, 1, 2, 3, 4, 5, 6, 7, 8, there actually 8 characters which are
valid characters. So, the length of this string is 8. However, it uses 9 locations. This
location would be a of 0, and this location would be a of 7, this null character would be a
of 8.
So, what we are tracking here is just the length of the string. It is not the last valid
position though. So, if we subtract one from length it will give you the last valid position,
right. So, now, it is passing p and q. So, if we notice print characters here, at line number
13 currently, you need to set up p and q. So, we need 2 pointers, character star p and star
q. So, somehow we should ensure that p and q follow the appropriate ones, right.
So, what we, what did we do in the algorithm? We said we will set up p to start at A
which is the beginning of the array. And q should point to the last valid character; so
361
where is the last valid character? It is at length minus 1 position, but it is not the
character A of length minus 1, it should be the ampersand of A of length minus 1. We
want the address of the last valid character. So, I put a ampersand of A of length minus 1.
So, so far, so good.
Now, I want to go and write this program which will ensure that this whole thing is going
to run in a loop. So, as I said before there are 2 conditions that can happen, I am going to
start. So, again if we go and look at this. So, we start with p and q. As long as p and q,
they do not cross each other. So, p is to the left of q and we can keep checking if star p is
the same as star q.
If you look at madam, they did not cross each other, but they touch the same location. So,
we do not want that. So, if it touches we know that it is already a palindrome, right. So, I
am going to write this condition. While p is less than q which means p is still to the left
of q, p has not collided with q nor has crossed over. So, this loop is trying to maintain or
this loop will run only when p is to the left of q, not when p is to the right, not when p is
the same as q.
So, now, if p is to the left of q and star p equals to star q, I said we can move p by 1 step
to the right and q by 1 step to the left, right. Now, this while loop will keep running till
one of the conditions fail; so either p less than q can fail, or star p equals to star q can
fail. If p is less than q, so let us say, if star p is equal to star q failed, it means that at this
point your palindrome is not matching any more. You had a prefix till now, but at this
point you had a character.
For example, in notation, star p would have been t and star q would have been i, and p is
still to left of q, you would have failed that condition, right. So, whenever you have some
prefix, but something else fails at some other location, this star p equal to star q would
fail. However, this p less than q will fail when p is either equal to q or when p becomes
either greater than q. So, we will have to check various conditions. So, let us start with
the first one.
362
(Refer Slide Time: 16:53)
It is possible that if I gave you an input like camera, right, it never goes anywhere. So, p
does not even increment and q does not decrement even 1 step. So, that is the first thing I
am going to check. If p is actually the same as A itself, it means it is not a valid
palindrome. So, even the first characters do not match. In this case, we wanted to print 0,
and we are going to achieve that by the making p equal to null, right. So, once you pass p
equal to null, print characters will take care of printing 0.
So, just to be careful I am also going to make q equals null. So, this is the first check. If,
for in, so the example I gave, right, camera. For camera p would still remain at A,
therefore, we want to print 0. Now, what are the other things that can happen? We can
have p less than q, right. So, else if p is less than q, we know that the while loop failed
because of star p not being equal to star q because; see, look at this point, right; so at this
point p is less than q and star p is equal to star q. If you came out of the while loop and if
p is less than q is still true then star p should not have been equal to star q that is the only
thing that is possible.
So, if that is the case then we know that p over stepped by 1 location like in notation,
right. So, p over stepped, so I need to go till p minus 1th location and print that, but I
have to start from the beginning, right. So, that is what I am doing here. So, when I put q
equals p minus 1, I take 1 step back and say, print only till this matching prefix, but
where should I start it? I should start it at p. So, I should start it at the beginning of the
string. So, that is A.
363
If both these conditions are not true; so p is not equal to A which means p is actually
proceeded somewhat, and star p is still equal to star q. So, which means p is less than
equal to q is not true then what could have happened is you have mechanism by which
you have a complete palindrome and the whole thing collided at, let us say, in madam, p
and q collided at p, right.
So, this could have happened, right. So, if p and q collide then we know that the whole
string is a palindrome which means I should start from the beginning of the string and go
till the end of the string, right. So, if I do that, this would be a palindrome, right. So, now,
I am going to take this.
364
(Refer Slide Time: 19:33)
365
(Refer Slide Time: 19:44)
So, it looks like I did not scan the input at all. So, let me scan the input also. So, I just
declared it and I forgot to scan it. So, scanf percentage s A. Also, it is going to take input
from the user. So, let me compile and run it.
366
(Refer Slide Time: 20:07)
So, at this point I am going to give notation. So, n o t a t i o n, I am sorry. So, I will run
once more, not that it matters, but. So, for notation it prints no which is good.
Camera, it prints 0 which is good. So, let us look at the other public test cases.
367
(Refer Slide Time: 20:37)
And the other input that was given was Malayalam capital. So, let we see what it is. So,
there is m a l a y capital A. So, I am going to give that as an input, A, and it prints till m a
l. So, now, it seems to have taken care of 4 cases. Let me check this test case 2.
368
(Refer Slide Time: 21:08)
So, I will run that now. I never talked about it so far. I put b; it is a single character and it
gives me 0. There is a small problem. So, let us go and see what this problem is. So, if it
is a single character it gives me 0. So, b is a single character and it is a valid palindrome,
right. Let us now run through the code and see what happens. We start with p equals A
which means p will point to b, the letter b; q is ampersand A of length minus 1; so q will
also point to the letter b. And while p is less than q and star p equals to star q, this
condition would have failed because both p and q are the same, right. So, because p is
pointing to the letter b, q is also pointing to the letter b, and this would have failed.
Now, we have this check; if p equals A, in this case p equals A, we put p equal to null
and q equal to null. So, this is a problem because we started with p equal to null and q
equal to null which means it will always declare all the single letter things as not a
palindrome. So, this is a very special corner case that has to be handled. So, it is quite
likely that the if we submitted a program like this, it had passed the 3 or 4 test cases that
are there, but not this test case for the single character. So, I am going to deal with the
single character as a special case.
369
(Refer Slide Time: 22:34)
So, if p equals q, right; what that means is, sorry; let me check, if p equals q what does
that mean? It means p and q are pointing to the same location; we have not even done
any processing so far. If p and q are pointing to the same location it means we have a
problem. And what is that problem? The problem is that this will be declared as a not a
palindrome.
So, if p is not equal to q, it means the input string is at least 2 characters, only then I will
370
do all this work. I am going to do all this work only if p is not equal to q. However, if p is
equal to q, p is pointing to the first location, q is pointing to the last location, so print
characters will automatically take care of it. So, just to be clean I will change the
indentation, so that you know what is happening.
So, all this work will happen only if you have a palindrome which is greater than 1 in
size.
So, let me now save it, compile it, and run it. Now, if I do the input b, it is declared as a
371
palindrome and you get this.
So, I am going to take this code, put it online, and see if I pass the test cases. So, I will
save and compile and run.
So, I get some warning saying data definition has no type in line number 59. So, all these
are supposed to be commented. So, I am not supposed to add any of that; I will remove
all of that. So, remember, it supposed to end with here, and the rest of code is
automatically added. So, I will save and compile and run once more. So, that is a mistake
372
that you might have done. So, I deleted all the things that where beyond it.
And now, that code got automatically appended, and I seem to have passed all the public
test cases. Let me submit this and see if I pass the private test cases also.
So, I seem to get 100 on 100. So, if you go and put the code back you will see that there
are issues with it because again there is a piece of code which is the same thing that gets
appended to it. For running in the id, I had to uncomment and use it. But, when you put it
back in the webs, in the portal, you should ensure that you delete everything that was not
373
supposed to be there. So, the code will look like it is truncated somewhere in the middle,
but that is ok, because there is automatically some piece of code that gets added up.
So, I just wanted to show you this to show a few features – 1, we saw how the string
operations worked and we used plus plus and minus minus, and A plus length minus 1
and so on. These were operations on the strings; that is one thing that I wanted you to
see. The other thing that I wanted you to see is if you putin code from your ide back to
the portal, without deleting appropriate things, you will have a problem. So, I deleted
those and things were ok now.
And the third thing is, when you do corner cases we have to be careful; we have to really
thing through what is happening. So, many times we get these request on the forum that
oh all my test cases pass, something is failing, right. So, in this case if I did not check for
the test case, the single letter b it may look like everything is ok. We could have worried
that inside a private test case and you would have assumed that a public test case has
passed. So, these are the 3 things that I wanted you to show. I am hoping that you are
able to catch some of these mistakes, just like I did when I started programming these
things.
And I am also hopping that you are enjoying the course so far. So, one thing about the
certification is we keep seeing requests that oh can we extended deadlines and so on
because certification is there. So, one small point that I want to make is do not worry
about the certification now; even if you missed 1 or 2 assignments, or 1 or 2 problems in
an assignment, keep doing it. So, it is important for you to take as many as you can and
finish them, and we will worry about the cutoff and other things later.
So, thank you very much and see you next time, bye.
374
Programming, Data Structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 01
Lecture - 28
What is an algorithm?
Example: Fast arithmetic-raise a number to the power n
Raise to power n by direct method
Correctness and efficiency
Raise to power n by repeated squaring
Efficiency of repeated squaring
Welcome to this first lecture of the algorithms course, and I am Narayanaswamy from
IIT, Madras.
So, the course is about designing and analyzing algorithms and which instructive to
actually start off with what an algorithm is. Algorithm is a finite sequence of steps that
halts on all inputs. We are familiar with algorithms. If you think of it, a recipe to cook a
dish is an algorithm. The multiplication, the sequence of steps to multiply 2 numbers is
an algorithm.
And if you look at any program written in any programming language, it is desired that it
encodes an algorithm. I say it is desired, because it is a fairly challenging exercise to
375
ensure that it halts on a finite sequence of steps, halts on finite number of halts in a finite
number of steps on all inputs.
Now, the question that we address in this course is how do you design algorithms, how
do you ensure that algorithms are correct, and how do you we also ensure that it halts on
all inputs in a finite number of steps. And for those of who may already know some
programming it should not have infinite loops for example. And we also address the
issue of minimizing the number of steps on every input. This is not a very well stated
goal. And this is one of the things we will formalize as we go through this course. These
are the main foresight of this course.
Outline of the course is that we are going to have 10 one hour lectures. Every week there
will be 2 of them; and every lecture would be broken into approximately 4 modules each
of 15 minutes. And at the end of each module there will be some exercise for the learner
to go out and try before the next module. The contents of the course are as follows.
During the first week we will focus on fast arithmetic, during the second week we will
work on algorithms for searching and sorting, the third week we will work on greedy
algorithms, during the fourth week we will move slowly towards advanced techniques
for designing algorithms and one of them to address quite a few problems as the dynamic
programming approach to design algorithms.
And finally, we will come to challenging exercises which have means practical
applications like string matching and identifying shortest paths in a network like a road
network or any transportation network for example.
376
(Refer Slide Time: 03:31)
So, let us start off with the contents of the first lecture which is all about fast arithmetic.
Now, in these issues we make some minor assumptions which are often verifiable as
being satisfied by most processors. We assume that multiplication and addition of 2
integers can be done in unit time. And most processes this is not a completely valid
assumption because it is either a 32 bit integer or a 64 bit integer addition, or 128 bit
integer addition or a finite number of bits addition that happens in unit time.
But, for the purposes of understanding the challenge of designing algorithm to do fast
arithmetic we relaxes, sometimes we assume that any 2 integers can be multiplied or
added in unit time. So, now, here is the simplest of exercises which we can do very
efficiently as human beings and let us see how to convert this into a computer program.
To convert it into a computer program we need to ensure that we understand the
underlying mathematics first, we come up with an algorithm, and then you can choose
your favorite programming language to convert the algorithm into a program. This is
how algorithms and programming are very closely connected.
So, whenever you want to solve a problem, when you want to write a program to solve a
problem, we go out and try to design an algorithm. When we try to design an algorithm
we try to go out and understand the kind of inputs that we process and identify the
appropriate mathematical logical steps to that part up that play an important role in the
algorithm. And then ensure that the algorithm is a good algorithm; in the sense, that it
terminates on all inputs, and it uses as few as steps possible; it uses as few resources as
377
possible. These are all the parameters that we will try to quantify as we progress through
this course.
Coming back to fast arithmetic, let us consider the simplest of problems; let us consider
the question where the input to a program is 2 positive integers, x and n. And the goal is
to design an algorithm that will calculate x to the power of n that is the nth power of the
given integer x. Now, let us look at a bit of mathematics, right; very simple mathematics
which all of us are very familiar with, that the definition of x to the power of n is x to the
power of n minus 1 that is n minus 1 to the power of x, multiplied by x.
And there is always a boundary condition that x to the power of n is 1 if n equals 0, right.
So, this requires a bit of correction. There is an error there; this must be 1. So, this is the
number 1 and not the 0. This mathematical definition of x power n gives us a desired
algorithmic idea. And this is implemented in any programming language using a loop
control structure which our programming language it is, we can use a counter control
loop.
As in this case where we use almost c like a syntax where you see a variable called
counter which is of integer type. And we initialize the value which is going to store the
variable value to contain the value 1. And then here is a loop which has an initial value
which sets counted to be equal to 0 and runs for n iterations. At the end of each iteration
it increases counter by 1, and value is multiplied by x; at the end of it value is returned.
378
(Refer Slide Time: 08:19)
Now, this is quite a simple program, and let us run a small example, right. So, for
example, let us say I want to evaluate 4 to the power of 3. So, this is done in 3 iterations;
initially value is equal to 1, and counter takes a range of value is which is 0, 1 and 2.
Now, when control enters the iteration for the first sign counter is equal to 0, and then so
this is the table of values, value becomes 4.
At the end of this counter is incremented by 1, then value becomes 16, and value
becomes 64 in the third iteration that is when counter is equal to 2. At the end of this
counter becomes 3, control exits from the loop. And the final value is returned and that is
64. As you can see, the correctness of this algorithm comes from this formal
mathematical definition which is exactly what is implemented in the loop in the program.
And it is clear that this algorithm is indeed correct. However, we are not interested just in
the correctness, but we are also interested in how many arithmetic operations are
performed.
379
(Refer Slide Time: 10:21)
And that is really the focus of the next part on this small example. As we just mentioned,
the algorithm is correct because of the mathematical formulation of power of a particular
number. As well as efficiency goes, we measure efficiency as the number of arithmetic
operation which had performed. And in this case, there are only multiplications. And it is
quite easy to see that every iteration there is a single multiplication that is performed.
Therefore, for any iterations there are n operations.
So, let us just go back to our program. You look at this. There are n iterations. Every
iteration has an iteration number which is captured by counter. Counter takes a range of
values between 0 to n minus 1. And in every iteration the value is updated by a single
arithmetic operation which is a multiplication. Therefore, the number of multiplications
that are associated with this program is exactly n where n is the power of x that we want
to compute.
There are also a couple of additions which happen. So, where counter gets increased at
every step; that is one addition that happens at every iteration. But, we are not interested
in counting that because that is more a structural exercise, and we are interested just
counting, interested in counting the number of multiplications. Let us test a question as
how to reduce the number of arithmetic operations which are performed in computing
the power.
380
(Refer Slide Time: 12:11)
So, here is the next approach which is very interesting. And this is the technique that you
can use even to compute the higher powers of matrices, not just numbers. So, in this
case, we want to compute the nth power of x, on other word we want to compute x
power n. And the observation that we make is that x power n is given by x square raised
to the power of n minus 1 by 2 if n is odd, and multiplied by x.
So, here is a trick which is recursive repeated squaring. It is repeated squaring of what?
Let us see that now. Remember, that initially we have the value x, and earlier in the first
iteration we computed, in the iteration number 1 we computed the value x square. And
remember that in the next iteration we computed the value x cube, instead we do
something which is slightly cleverer and compute x power 4 which is x square whole
power 2 which is x power 4.
And then we compute, we square the value of x power 4 in the fourth iteration, right.
There is the iteration number 3 that is zero; the first iteration, the second iteration and the
381
third iteration, we compute x power 8. The fourth iteration we square the value computed
the previous iteration. So, we get x power 16, and so on. So, what is the whole idea? The
whole idea is very simple. We compute the square of the previous term; in other words, a
keep term in the sequences x power 2 power k.
So, let us write this term in the kth iteration. We would have computed x power 2 power
k. And for what value of k is; so what we want to compute is x square whole to the
power of n by 2. So, what we do is we compute, we perform this operation of repeated
squaring of the value computed in the previous iteration till 2 power k and n by 2, or 1 in
the sense, at which a point starting from x square we would have of computed x square
to the power of n by 2.
That is, we calculate the value of x power n by repeated squaring, and the termination
condition is when 2 power k where k is the iteration number becomes equal to n by 2,
assuming that we start with x square. For what value of k is 2, for what value of k is this
equation satisfy, that 2 power k becomes equal to n by 2? So, for k equals login to the
base 2 minus 1. For the value k equals login to the base 2 minus 1, k equals n by 2.
Let us do a small exercise here to get an idea as to what we are talking about. Consider
the case when n is a power of 2. So, let us assume that the number that is given is x;
consider the case when the number is x, and we want to compute, see, x to the power of
64. So, let us write down the sequence. We start off with x square, then we compute x
382
power 4, then we compute x power 8, then we compute x power 16, we compute x power
32, and finally we compute x power 64. Observe that at every step the exponent keeps
getting doubled.
The most important concept here is that the exponent doubles at every step, in each
iteration, and we started off with x square, and we wanted x to the 64, x to the power of
64, and observe that the number of iterations is 1, the second iteration, the third iteration,
the fourth iteration and the fifth iteration. And it is very clear that log 64 to the base 2 is 6
because 2 power 6 is 64; minus 1 because we started off with x square, and minus 1, this
is 5. Therefore, there are 5 iterations.
In the zeroth iteration is x square, in the iteration number 1- x power 4, iteration number
2 it is x power 8, iteration number 3 it is x power 16, iteration number 4 it is 35, iteration
number 5 – 64, x power 64 is calculated; at the end of it for powers of 2 in the log n to
the base 2; minus 1 steps; the correct value is calculated; then very importantly n is a
power of 2.
So, this brings us to the exercise here. As a student it is very instructive to write a c
program to implement the repeated squaring algorithm. In particular, the challenge here
is to write down the loop when n is not a power of 2. And this is very interesting. So, let
me give you a hint as to how to go about this. Let us assume that n is, instead of 64, let
us assume that n is 65, and let us write 65 in binary. It will be 1 0 0 0 0 0 followed by 1 is
383
65, right. So, this is contributes the value 1, this is 2 power 0, 2 power 1, 2 power 2, 2
power 3, 2 power 4, 2 power 5, 2 power 6.
So, this is 64 plus 1, in binary this is 65. What we wanted to do is we want to compute x
power 65. And if you write this carefully, this is equal to x power 64 plus 1; this is x
power 64 multiplied by x power 1. And we already know how to handle the case when
the exponent is a power of 2; here also the exponent is a power of 2; recall that 1 is 2
power 0. And we know how to handle this. And this is a hint for you to complete the
algorithm when n is not a power of 2 and implemented in the C programming language.
384
Programming, Data Structures and Algorithms
Prof. N.S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 02
Lecture – 29
Example: Polynomial evaluation by direct method
Polynomial evaluation by Horner's method
Example: Multiplying two n bit numbers by direct method
Multiplying two numbers by Karatsuba's method
Analysis for Karatsuba's method-recurrence equation
Big-O notation
So, let us move ahead to a very closely related topic which is polynomial evaluation
where we need similar tricks to improve the efficiency of evaluating polynomials.
Consider the polynomial which you see here which is p of x, which suggest that the
name of the polynomial is p, and x is a variable. And the polynomial is a n x power n
plus a n minus 1 x to the power of n minus 1, all the way upto a 2 x square plus a 1 x
plus a naught. So, this is a polynomial of degree n which means that the variable x occurs
as x power n; and a 0, a 1 to a n are coefficients.
385
(Refer Slide Time: 01:20)
As you can see there is an example here; the example is a quadratic polynomial. In other
words, a polynomial of, it is a quadratic polynomial, right, which means the polynomial
of degree 2. And the algorithmic exercise is an extensively used algorithmic exercise
though it looks extremely simple. Input a polynomial, given a polynomial p of x as input,
and value v for x; x is given a value v; v challenges to output the value p of v.
So, for example, let us take the given polynomial, and let us say v equals 2, in which
case we have to compute 2 multiplied by 2 square minus 3 multiplied by 2 plus 1. And
this is 8 minus 6 plus 1 which is equal to 1. Observe that there are many ways of
evaluating this polynomial, and the most simple way of evaluating this polynomial is the
following iterative algorithm where you evaluate the formula, evaluate the polynomial
from right to left.
386
(Refer Slide Time: 02:53)
You evaluate the polynomial from right to left; what do you I mean by right to left? You
start off in the ith iteration, for i is equal to 0. You take the value is 0. And this is the
value that you have computed. For, i is equal to 1, you take the value x multiplied by the
value a 1 and perform an addition. In the iteration number 2, you take the value x, you
multiply it by x again which is gives you x square, store this and keep it with you, or
store it in a temporary variable, multiply this by a 2 and add the value that you have
computed so far.
And in iteration number 3, you take the value of x square that has been computed in the
previous iteration, multiply it by x, you compute x cube multiplied by a 3, and perform
an addition; this is what I mean by evaluate from right to left. In other words, you can
think of it as the polynomial being evaluated from the element of least degree to the
element of highest degree; note that a 0 is a constant. The multiplier is x power 0 that is
1, and you evaluate this all the way upto a n x power n.
Let us evaluate this algorithm. Indeed the algorithm is correct because it is quite clear
that in every iteration, one part of the polynomial is being calculated on the given value
v. So, in every iteration you calculate v power i from v power i minus 1, you multiply it
by a i; this cause a total of 2 multiplication operations; then you add the result to the
current value which gives you the value of the polynomial on the degree i polynomial
that is a i x power i all the way upto term a 0.
387
The total number of arithmetic operations that we have performed is 2 n multiplications.
There is in every iteration you perform 2 multiplications and 1 addition. And this counts
for 2 n multiplications plus n minus 1 additions, this is the total of 3 n minus 1 arithmetic
operations which are performed. Let us do see if there is a way of improving this; and
this improvement is a very clever trick which is a very important trick which is called the
Horner's method for evaluating the algorithm.
In other words, p 1 of x is a n x power n minus 1, all the way down upto a 2 x plus a 1.
Observe that p 1 of x is obtained by taking the first n terms of p of x, and taking out the
factor x from them. For example, if you go back and look at p of x, so look at p of x, it
has, the first n terms of x as a factor; you just take that out and consider the remaining
polynomial; and what we get is p 1 of x.
So, we have already seen that the Horner's method is an optimal method for evaluating a
388
given polynomial, given as the set of its coefficients in the sense that it uses exactly the
required number of arithmetic operations. In other words, to evaluate the polynomial of
degree n at a given point for the variable x we need definitely n multiplication operations
followed by n addition operations.
And observe that we are not counting the number of operations which are required to
compute the higher powers of the value v that has been given. What we have just seen is
that the Horner's method uses exactling the minimum number of operations; in other
words, it is a optimal algorithm for evaluating a given polynomial at a given point, or a
given value for the variable x.
So, let us move on and ask some questions about efficient algorithms for the most
simplest looking exercise of multiplying n digit numbers. Here is the exercise. We have
to write a program to multiply 2 n digit numbers. And here, we deviate slightly from the
module that we have been looking at so far when we assume that any 2 numbers can be
multiplied in, any 2 positive integers can be multiplied in unit time. We do not, we
discard that assumption and now explore the challenge of multiplying 2 n digit numbers.
In other words, the 2 numbers have n digits and we want to count the total number of
single digit multiplications that are required to multiply the 2 given numbers. So, let us
look at this example which we see here which is multiplying the numbers 1234 and
5678. Both of them as we can see are 4 digit numbers, and the straight forward high
389
school multiplication approach is to write down 1 2 3 4 5 6 7 8, and now observe that 8
has to be multiplied to each of this 4 digits, and then 7 has to be multiplied with each of
these 4 digits; 6 has to be multiplied with each of the 4 digits; and 5 has to be multiplied
with each of the 4 digits.
In other words, each digit in 1 must be multiplied with all the digits in the other, in the
straight forward multiplication method which we are all very familiar with. And
therefore, if the 2 numbers have n digits each, we essentially are performing n square
multiplication operations, single digit multiplication operations. This example, it is of
course, 16 multiplication operations. The question now is, can we come up with an
algorithm which uses a lesser number of single digit multiplication operations? Let us try
to pose this question very clearly, and that is exactly what has been written as the
question at the end of the slide - can we come up with an algorithm and along with the
algorithm we must write down a formula for the number of multiplication operations
performed by that algorithm.
And that formula must be smaller than n square, for all values of n expect for some finite
numbers of n. I repeat this. The exercise now is to come up with an improved algorithm.
What do we mean by an improved algorithm? We want to come up, or design and come
up with, or design an algorithm; not just design the algorithm, but we also want to
analyse the algorithm and come up with the formula for the number of operations which
are performed by the algorithm.
What property do we desire from such a formula? We desire that the formula must be
smaller than n square at all values of n, except for a few small values of n; meaning,
maybe for the first 10 numbers n, that is for upto 10 digit numbers, maybe n square is
smaller than the formula that we have come up with. But, beyond 10, it must be larger
than the formula that we come up with. In other words, our formula must be smaller, or
the formula associated with our algorithm must be smaller. So, there are 2 tasks here -
one to design the algorithm, and two to analysis the algorithm and write down the
formula for the number of single digit multiplication operations that it performs.
390
(Refer Slide Time: 13:24)
So, we have already seen a very straight forward method; let us do something more
sophisticated and interesting and exciting. And this is the Karatsuba's method. So, let us
just recall the whole exercise. We want to multiply x and y which are 2 n digit integers;
these are 2 n digit integers. And the way we do this is to look at x and y in a slightly
different way. So, what we do is we consider x to be in the form x 1 multiplied by 10 to
the power of n by 2 plus x naught, and y to be y 1 to the power of 10 to the n by 2 plus y
naught.
391
(Refer Slide Time: 14:28)
So, let us do a small exercise. So, let us consider x. Let us consider the x to be equal to 3
4 5 6 and y to be equal to 6 7 8 9 1 4. So, the total number of digits in both these cases is
6. We write x to be 123 multiplied by 10 to the power of 3 plus 456. Clearly this can be
very easily done, right. 6 is divisible 2, right; that gives an exponent value of 3; and
therefore, you can write this as 123 into 1000 plus 456. Similarly, we can write down y
to be equal to 678 multiplied by 1000 plus 914.
What are we interested in? We are interested in the value x multiplied by y, which is this
term multiplied by this term; this is the product of the right hand sides. I hope the
approach is clear. So, the most important thing is that 123 multiplied by 1000, and 678
multiplied by 1000 are very easy to evaluate; and they do not require multiplication
operations. 123 multiplied by 1000 just involves us writing down 123 followed by 3 0s.
Similarly, 678 multiplied by 1000 just requires writing down 678 followed by 3 0s.
If one wants to compute the product of 123000 and 678000, then one only needs to
evaluate 193 multiplied by 678. Once you have computed these, this number we had it
with 6 0s. Let me write this just for the purposes of visualization in this form; that we
just need to evaluate 193 multiplied by 678. Observe that we want to multiply 2 6 digit
numbers, and because we know that we can pad the 6 0s, this is the multiplication of 3
digit numbers. This is the multiplication of 2 3 digit numbers. This is the whole idea that
392
we have reduced the size of the problem.
This is extremely important. We have reduced the size of the multiplication problem in
one step by rewriting the number in this form from a 6 digit multiplication to 3 digit
multiplication. But, of course, there are some tricks in world. It is not so straight forward.
As you can see, there are some more multiplications to be done. So, let us go back to our
slide and look at it in the formal way.
As we have just seen, we have seen that x can be written in this form and y can be
written in this form; we did see this through an example. We saw this through this
example. Now, let us just focus on the values of y naught, y 1, x 1 and x naught. You
have already seen that x naught and y naught are smaller than 10 to the power of n by 2.
Now, let us look at x, y, the product of we are interested in, and see how this can be
rewritten.
This can be rewritten by just expanding this term out. When you try to expand this term
out let us assume that the coefficients that one gets are z 2 multiplied by 10 power n; that
is a product of x 1 and y 1 is written down as z 2. Then there is a term where the
exponent is 10 to the power of n by 2. This is the product of x 1 into y naught plus x
naught into y 1, and then there is protect of x naught and y naught which we have written
as z naught.
We have already seen the multiplication by powers of 10 is adding least significant 0s.
Now, is this going to help in reducing the total number of multiplication operations,
single digit multiplication operations? So, let us just focus on z 2 which we have already
seen that is x 1 multiplied by y 1; z 1 which is x 1 into y naught plus x naught into y 1;
and z naught which is x naught multiplied by y naught.
Most important thing is that this value, this is 10 to the power of n by 2; this is selected
so that in the sub problems there are just n by 2 digits that need to be multiplied; the
number of, in every number that means to be multiplied, there are just n by 2 digits.
Now, let us just look at the second term which is the most important thing. Observe that
as of now there are 4 multiplications to be done, that is z 2; this is a single multiplication;
z 1, it looks here that this is one multiplication and this is the second multiplication and
then this is the fourth multiplication.
However, a small trick of rewriting z 1 into x 1 plus x naught into y 1 plus y naught
minus z 2 minus z naught. Let us see this z 2 is x 1 minus x 1 y 1, z naught is x naught
393
multiplied by y naught, if you evaluate this and subtract z 2 and z naught we get exactly
what we want which is the value of z 1. And now observe that this is a single
multiplication operation. And what we are going to see is that all these multiplication
operations involve just n by 2 digits.
So, let us just go to the example, right. So, remember that we wanted to multiply these 2
6 digit numbers; we wrote the number down as 123 into 1000; how many digits are left
now? Half the digits are left. If you notice here there are n by 2 digits. There are just 3
digits here. Similarly, there are also 3 digits here. Further observe that these 2 numbers
are obtained by looking at the 3 digits of x, the first, the least significant 3 digits of x and
the least 3 digits of y.
Therefore, those 2 values have to be lesser than 1000. They cannot be 1000. They have
to be lesser than 1000 because they involve only 3 digits; same with this term, right.
Therefore, what is the conclusion? The conclusion is that we now have 4 3 digit
numbers. We started off wanting to multiply 2 6 digit numbers; we have broken it down
into 4 3 digit numbers. And going back to our previous slide, we are now wanting to
evaluate, we have now observed that x 1 y 1 y naught y 1, all are n by 2 digits; in that
example there were 3 digits.
Now, let us just look at the reduction in the number of multiplication operations. Both, x
1 and y 1 as we have seen are smaller than 10 to the n by 2; that is they have less than n
by 2 digits; same with y naught and y 1. Secondly, let us just look at the sum of the 2 of
them. So, this is just 2 n by 2 digits 1, n by 2 digit multiplication; z naught which is x
naught into y naught, both are smaller than n by 2. This is again 1 n by 2 multiplication.
We only have to worry about and reason about this multiplication operation. Let us just
observe that x 1 and x naught both are numbers smaller than 10 to the n by 2, 10 to the
power of n by 2. When you add both of them how many digits can you get? So, that is
you have 2 values which are 10 to the power of n by 2, and when you add the 2 of them
how many digits can you have? You can have a maximum of n by 2 digits because both
of them are smaller than n by 2.
So, let us just go back to our example. Observe that 456 and 914 are 3 digit numbers.
When you add both of them, at the most you can get a 4 digit number, right. So, let us
just see this; you can have 999. You can have 999 a 3 digit number, added with another
999 which is another 3 digit number, this is 1998. Observe that it is a 4 digit number.
394
Therefore, the most important point that is been made is that if you add 914 with 678 you
can atmost get another 4 digit number and not a 6 digit number, right. So, that is the most
important thing. So, you do not get a 6 digit number.
So, let us just see what the gain in the arithmetical. The gain in the arithmeticals we have
3 multiplications of n by 2 digit numbers, we have 4 additions and 2 subtractions, we
have 1 multiplication by 10 power n, 1 multiplication by 10 power n by 2, and the last 2
do not are not counted as multiplications, they are just shifting operations or adding 0s,
least significant 0s.
So, let us just go back and calculate this. You see 1 subtraction as second subtraction,
then you see 2 additions here, and then there is a third addition and a fourth addition, and
there are 3 multiplication operations. So, therefore, let us formally analyse the gain in
arithmetic. The running time of the algorithm that we have just written where we want to
calculate the total number of multiplications that is our measure of running time, let us
not count the number of additions and the shift operations, let us just count the number of
multiplications.
The solution for this is given by n to the power of log 3 by 2, the solution for this is
given by the value n to the power of log 3 by 2, and observe that log 3 base 2, n to the
power of log 3 to the base 2, and observe that log 3 base 2 is smaller than 2. Therefore, n
cube into the log 3 base 2 is smaller than n square; and n square is a total number of
single digit multiplications in the simple method.
395
(Refer Slide Time: 29:55)
This bring us to the last module here which is the concept called a big oh notation where
the focus is how do we compare a pair of algorithms. Observe that we have done analysis
of running time of these algorithms by different methods; we use simple methods and
sophisticated methods.
So, let us just look at the Karatsuba's method. We know that the Karatsuba's method uses
10 to the power of log 3 base 2 multiplications, and this is smaller than n square. How
does one capture this? We capture this using the big oh notation which will be the focus
396
of the next lecture.
Thank you.
397
Programming, Data Structures and Algorithms
Prof. N.S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology Madras
Module - 03
Lecture – 30
Searching
Unordered linear search and analysis
Ordered linear search and analysis
Binary search on an ordered array
Binary search illustration and code
Analysis for binary search
So, today let us continue our study of algorithms with an exploration of this area of
searching, as most of us no searching is actually a fairly common word now associated
with computer science, because of search engines which are accessible to most of us.
And let start off with simplest of searching exercises, where we want to search a data
structure for a key which is given as input.
So, in this lecture we are going to look at this issue of searching an array. Of course, one
could consider the problem of searching other data structures. We will come to it as we
progress. So, if you look at the question of searching. Searching is a process that is used
to find the location of a given key or a target among the list of objects. So, when you
search an array, the search algorithm, is expected to return the first element, in the array
398
that contains the given key. In this picture we can see that the target key that is been
given is a key 62, and it occurs in the location 4, and we want to design an algorithm
which efficiently gives us the value 4. The approach to achieve this particular task is
given to us in the coming slide.
So, now let us explore the procedure for the search of the given key 62, which we know
from this visual is at location 4 or it is in the array index within number 4. And it is
important to note that the indices of the arrays are from index 0, all the way up to index
11; that is there are 12 elements in this array, and we want to search for a given key
which is 62. Let us look at the basic steps that have to be done. We start by comparing
with the element which is at index 0, and the element there is 4, the comparison of 62
and 4 definitely results in the fact that they are not equal. And next comparison would be
with the element at index one, which is 21 and so on till we come to the third element for
example, where again a comparison with 62 is made with a value which is their, which is
14. And after 5 iterations they remain 62 is found for the first time in the array index
four, and this is considered as a discovery or a successful search, where the search key
has been found.
Now, it is a act to indeed call such a search procedure and linear search, because in every
399
iteration we queried index in the array, increases by 1. If you plot the indices of the
locations which have been proved or searched, you will find that this plot against
iteration number; that is the x axis been the iteration number, and y axis be the index of
the location search. You will see that this plot is a straight line, and into the natural to call
is a linear search. This is very important to understand, why this is called linear search.
The array indices as searched in a linear fashion. It starts off with 0 then one then 2 and 3
and so on and so forth.
It is considered the running time to search for a key which is seventy 2, which as you can
see does not occur in this array. And there are 12 elements in this array, and we do not
show all the comparisons. This is definitely not necessary. As you can see all the
comparisons will fail in this linear search, till the index value exceeds 11; that is it
becomes 12. At which point if time you have an exit condition; that is you have searched
the array, compared every element is a array with a given key, and you have exceeded the
total number of elements that are there in the array, and therefore, the element is not
present in the array. And the algorithm at this point of time can report that the given key
is not present in the array. It is very important to note that we have observed two of the
exit conditions of this algorithm, when the key is found, and when the key is not found.
The key is not found when you compared it with all the elements in the array. And when
400
the first time the key is found in the array, the algorithm exits. These are two exit
conditions, and this can be encoded.
In the following algorithm, and to make a distinction which something that we are going
to study we call this unordered linear search. The algorithm is linear search, and it is
unordered linear search, because the algorithm does not use any structure of the data
elements which are present in the array. For example, the data elements in the array
could be sorted, but the description of the algorithm does not use that fact, and therefore,
this is called an unordered linear search. In other words unordered linear search is
applicable, when you have searching for a given key in an array, in which you have no
apriory information about the organization of the data elements. Data elements could be
in a sorted order, or they could be in unsorted order.
And we have seen the conditions under which, a straightforward linear search algorithm
will exit, and this is encoded in this pseudo code which is described. It says that while
there are some more elements in the array. If the value is found at the current index then
you return the index of the current location; otherwise you increment the index, and
continue in the loop. At the end if the value is not found at all. A return value of minus
one is given. Of course, if a programs is one has be very careful, and ensure that the
401
array indices are between, or at least as largest 0, and minus one is not an array index.
The programming language like c, this is definitely the case where there the indices start
at 0.
So, this is a snapshot of a c program which is included in the slide, to understand the
complexity, or the amount of time that is spent in executing this particular algorithm
which is unordered linear search. So, the function that we have written here that you see
here is called search function. The arguments to this function is an array, which is called
elements, and the size of the array is given as an additional argument, just to illustrate
this example, and the key, the desired that is being search for in elements is also passed
as an argument search. The for loop there that initializes and indexed to 0 on search as up
to the size of the array, and in every iteration, in every loop the element access at a
particular indexes compared with a given key, and if a successful match is made then the
indexes returned, as the location where the key is present. If the key is not present in the
whole array, which is discovered after the loop has run for as many steps has the size of
the array. The control exits from the loop, and a minus one value is return by the search
function, which informs the calling function that the key has not been found. The use of
this c program mainly, is for us to understand where the effort in computation is, for us to
be able to, say something about the running time of this algorithm.
402
(Refer Slide Time: 08:53)
Here is the analysis, if the key is at index i then clear the i comparisons are executed;
one and every loop. So, the other words, if the key is the index 0, then one comparison is
definitely executed; that is, in the first loop. In the worst case, if the key is not present in
the array, then the loop is executed as many times as a size of the array which we have
said. Therefore, the worst case running time of this algorithm, is order of size of the
array. Note that we have one arithmetic operation also, which increases the value of the
variable called index. A natural question now, is the following; can we reduce the
number of comparisons in a number of arithmetic operations performed by a search
algorithm to find a key in a set? And we also consider cases, in which the data in the
array is, in a sorted order, and then we see if it is possible to design better algorithms,
better in the sense that the number of comparisons in number of arithmetic operations is
reduced. Here is an exercise, at this point of time.
403
(Refer Slide Time: 10:01)
So, what happens if one does a linear search in a linked list. And what is linear search. In
this case, as you can see linear search involves incrementing the array index, starting
from the smallest index value to the largest possible index value in the array, and
searching or comparing for the presents of a key. So, it is important as an exercise to
understand, what linear search in a linked list is.
404
That is an exercise, and let us move ahead, and explore this question of, what order linear
searches. In other words what is linear search when the data is ordered in the given array.
In other words the array contains the data elements in say sorted order. Let us say in this
case as you can see it is in ascending order, and how much time does it take, or how good
in algorithm can be design, to be able to find a target key in this particular array. And of
course, if the key is not found in the array, we should return a value minus one, and we
make assumptions as we have been making so far that all the data items are in the range,
sorry all the indices are at least as large as 0.
So, one of the properties of ordered linear search, is that linear search can stop
immediately, when it has passed the possible position of the search value. For example, if
you see the slide, if the queried value is the value 8, then one can perform a linear search
up to the value 5, up to the value ten which is found in the location index by the number
5; that is the element a of 5 in this array a, and we find the ten is larger than the queried
value 8, and we already know that the array sorted in ascending order. Therefore, we are
not going to ever find 8 after the array index 5, because 10 is the value which is sitting in
that location. So, this is one way in which we can use the fact that, the array, the data
elements in the array are in sorted order.
405
So, this is exactly the algorithm that is implemented. Observe that there is one another
check, which is there, which is run first inside the while loop. If the value is, at the
current indexes greater, then the value that we are searching for, then the value win not
be found, and you can return a minus one immediately. Now while this is one way of
using the fact that, the elements of the array are ordered. In this case in ascending order,
and this is indeed the c code for this.
406
(Refer Slide Time: 13:14)
Let us perform an analysis, what is the worst case running time of linear search on
ordered data. In the worst case, as we can construct by an example, no matter what the
array is. If one considered the target key to be a value which is larger than the element,
which is present in the largest index. In this example consider the key 18. 18 is larger
than 17 which is a value, which is present in the index 7. Therefore, an execution of
linear search to look for 18 in the array, will compare 18 with each of the 8 elements
which are present in the array, and it is a same for loop as we have seen in the previous
slide. Therefore, there is an arithmetic operation every iteration in the worst case, and
there is also a comparison that happens in the worst case. Therefore, in the worst case,
the running time is, the order of the number of elements in the array. Therefore, there is
really no change in the worst case analysis. Therefore, how does when use the fact that
the array is ordered, to get better algorithms? Is it possible at all?
407
(Refer Slide Time: 14:30)
That is the focus of the next search algorithm that we explore, which is very well known
as the binary search approach, and we will see why this is called binary search approach.
And here the most important principle, is that the search key does not have to search, or
does not have to be compared with every element in the array. In other words, by making
certain comparisons, we can deliciously discard, certain parts of the array from the effort
that we have to put into compare the given key with the elements. For example, if the
given key is 18. If we end up checking, comparing 18 with the element 5 which is
present at the array location three. If 18 is present in the array, then it could be present
only among the indices 4 5 6 and 7. And we can see that, the search region is kind of
reduced by half or approximately by half. This is exactly what we are going to encode
into a alternate procedure. And let us look at one run of this algorithm.
408
(Refer Slide Time: 15:44)
So, a is the array here, and the target key is 22 and there are 12 elements in this array. As
you can see 22 is present in this array, it is in the array location index by the value is 6.
So, the algorithm is very simple. It keeps track of three values which are called; first,
mid, and last. First and last are extremely important. They keep track of the sub array
that we want to search. The sub array that we want to search is, in this example, the un-
shaded part; the shaded part, is the part that we do not want to search. So, let see this run
of this algorithm. Initially, first is the value 0, last is the largest array index which is 11,
and mid is the midpoint, which is the first plus last divided by 2, and we take the floor of
the division. In this case the floor would be 5. So, 11 plus 0 divided by 2 is 5 and a half,
and we take the value 5. A comparison of the given key is made with the data item which
is located at the array index 5, which is 21, and 22 is greater than 21, and because the
array is sorted in ascending order. It is clear that 22 must be present, only in the array
indices 6 through 11, and definitely is not present in the array indices 0 through 5.
The array indices 0 through 5 are now shaded in grey, and first and last are now used to
encode, the first and last index values of the relevant part of this array a, which is now 6
to 11 and the midpoint is now. You can do the calculation is 8 is 6 plus 11 by 2 which is
17 by 2 is floor of it is 8. And the algorithm repeats this step of comparison, comparing
22 with 62 which is a value in the array index 8. And of course, 22 is smaller than 62 and
409
therefore, 22 cannot be present in the indices 9 10 and 11. It is not present in the array
index 8, as our comparison shown. Therefore, it can only be present among the array
indices 6 and 7; that is among the array indices first and mid minus 1. This is most
important thing that it is present in the array indices first and mid minus 1 in this case.
And as you can see now, first and last have become 6 and 7 respectively and mid is now
6; 22 is successfully found and the algorithm terminates reporting the index of the
location where 22 has been found which in this case is 6.
Why do see the condition and which this algorithm exits and reports that the key is not
present. In this case, the target value is 11, and as you can see 11 is not present in the
array, and again the gray shaded part is the irrelevant part of the array for the search
algorithm, and the unshaded part is the relevant part. First and last as usual end code
have, do encode the relevant part of the array, keeps track of the indices, initially it is 0
and 11. A comparison is with mid, 11 is smaller than 21; therefore if 11 is to be found, it
can be found only to the left of the index 5 that is among the array indices 0 to 4, which
is now captured by the modified value of last, which has now become 4. Mid is now
recalculated in the next iteration to be 2, a comparison is made with a value which is
sitting in the array index 2 which is 8. 8 indeed is not larger than 11; therefore, 11 has to
be to a right of 8 if it is at all present in the array.
410
Therefore, now in this case, 11 should be present if at all, in the array indices last and
mid minus 1. Sorry last and mid plus 1. So, mid was the value 2, and now observe that
the value of first is now 3; that is, mid plus 1 and last. As you can see that in one more
query it is discovered that, the search key 11 is not present in the array, and at
termination condition you can see that, first has become larger than last, and this is a
termination condition for the algorithm. So, this is very important the termination
condition for the algorithm is, when first exceeds the value of last. At which point return
you can report that the key has not been found, and the key is found, the value of mid.
The location where the key has been found will be reported as a return value of the
algorithm. These are the two invariants, which precisely map the presence or absence of
a key from the array.
So, in generic terms, binary search is a paradine of solving the problem by what is called
a divide and conquer strategy. In this divide and conquer strategy, the search space, in
this case the array is repeatedly divided into smaller and smaller portions with the
guarantee that, the search value would be present in the region that is been search that
every level or in every iteration. As we have seen in with every comparison, the size of
the array has been reduced by a factor of 2. In other words the array size become smaller
and smaller is halved in every iteration, and this kind of gives us a clear handle on, an
411
understanding of the binary search algorithm.
Let us see the pseudo code. And the pseudo code is very important, because it tells us
how the first mid and last have to be modified. So, initially first and last are set to take
the values of the boundary of the array. The mid value is calculated. The search value or
the key is compared with the element in the mid value. If it is in the present the mid
element is returned as a value. If it is not present, a comparison is mid whether the value
was. If indeed the mid value does not contain, but first is more than last then the value is
returned. First is at least as large as last then the return value is minus 1, same that the
key has not been found. Otherwise if the value is smaller than that in the middle element,
last is now make to take the index which is mid element position minus 1; and if value is
larger, than first is taken to be the mid element plus 1. That is, in other words one would
visualize the search to be moving either to the left or to the right of the midpoint in the
array, provided the key is not found.
412
(Refer Slide Time: 23:41)
Here is the pseudo code for this function which is binary search. It is actually cut and
paste from a c program. Right now the arguments are as usual element an array which
contains sorted set of elements. The size of the array is given, and key is also given as
part of the input. And first is taken to be the value 0, and last is taken to be the value size
minus 1. And you can check the integer division which is middle takes the value of first
plus last divided by 2, and whatever comparisons we have discussed so far are made. The
value of this particular piece of code is that it gives us an idea as to the number of
operations there are perform in a run of this particular algorithm.
413
(Refer Slide Time: 24:38)
So, let us use go through the example of binary search in this case where the array has
the elements 8 elements in ascending order and the search key is 14. 14 at present in the
array index 6, and one can see the conditions and which the algorithm exits. So, the first
value that is searched, is the array index 3. And array index by the value 3 which is 0
plus 7 divided by 2 on the floor of it gives you the value 3, and the search value is 14 and
clearly 14 is to the right of 5, and first is the one whose value is updated to take the value
4; last remains unchanged. In a next iteration mid look becomes 5 and 7 plus 4 divided
by 2, and this search succeeds by computing the mid value 6. As you can see the order of
the elements in the array, guides the choice of the array locations which are propped by
the algorithm.
414
(Refer Slide Time: 25:49)
So, here is an unsuccessful binary search, and the unsuccessful binary search can
terminate in to conditions where first and last are the same, and the middle value does
not contain the search key. This is very important. There are three cases here; the first
case we have already seen, which is successful search. Here is an unsuccessful search,
where the exit condition is because the key is not present, and first and last of the same
value. right here 8 is a search key, and it is clear that, the sequence of searchers finally,
queries the element 4, which has the value 7, but first and last take the same value at this
point of time, and because 7 is not equal to 8 and there is nothing else to be searched, the
search returns a failure.
415
(Refer Slide Time: 26:42)
Here is a case where first exceeds the value last, in the next iteration, and the key is not
present here. So, this can also checked.
The focus now is to analyse binary search, which is the algorithm that gives analysis we
postponed, to the end. Let us look at this analysis of binary search, where our goal is to
416
estimate the total number of comparisons of the binary search algorithm. So, let us write
the formula for binary search by t of n; where t stands for time, and n stands for the array
size. For the moment let us assume that n is a power of 2. This simplifies the analysis,
and therefore, we use n to be a power of 2. Let us look at the case when t of n, for n is
equal to 2 . So, let us just focused on the array, it just has two elements , and let us
assume that the elements are 1 and 3 in sorted order, and let us count the number of steps
that it takes to check the query element . So, this is the array index with 0. This array
index by 1. And it is clear that a queried element, can be resolved for whether it is
present or not in the array, in two comparisons. The maximum of two comparisons. So,
for example, what could happen is, that if the search is for a value half, then the array
index which should be compared is, half would be compared with 1, half is compared
with 1, and then the first mid and last are updated, and half will not be found, so that
takes only one comparison. If the value is three then in two comparisons it could be
found. And if the value is larger than 3 then again it would be found, that if the value is
absent in at most two comparisons. Therefore, t of n for n is equal to 2 is written as t of 2
which is just two comparisons.
So, let us considered the case for n is a power of 2, and let us look at the array indices.
The array indices are a of 0; that is, sorry the indices are 0 to 2 power k minus 1, and the
value of the array index; that is taken to be relevant, is the value 2 power k minus 1
divided by 2, which will be 2 power k minus 1 minus 1; that is the floor of this is 2
power k minus 1 minus 1, which would be the value that would be compared. Therefore,
as you can see, after the first comparison the relevant part of the array would be the
range a of 0, to a of 2 power k minus 1 minus 1, or a of 2 power k minus 1, to a of 2
power k minus 1 there are only two possibilities, for the rest of the search space, after
one comparison. So after the first comparison with a given key, these are the two range
of values. As you can see that the range of values is now down by a factor of 2.
We can write this. We can encode this using the following recurrence, which is t of n is t
of n by 2. The time taken to search for the key and an array of size n by 2 plus 1 and the
boundary condition is given by t of 2 is equal to 2, and the solution for this recurrence is
t of n is order of log n. Indeed it is log n to the base 2, but the constants in the order take
care of it. So, this is the analysis of binary search, and observe that we have use the fact
417
of the array is sorted, to come up with an algorithm with just use as order of log n
comparisons, as suppose to ordered search which in the worst case, was using linear
number of comparisons, or order of the size of array number of comparisons. And in the
case when the array is unordered, we were already using the linear number of
comparisons which are unavoidable. So, with this we stop this discussion on search and
we will continue on the next lecture.
418
Programming Data Structures, Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 4
Lecture - 31
Content
Finding minimum and maximum in an array
Simultaneous min and max in an array: analysis
Example of simultaneous min and max
And compare the current minimum to the new data; that is entered into the sub problem.
In other words in the second iteration, we consider the second element and compare it
with the minimum, which was the first element. If indeed the second element is smaller
than the current minimum, then it indeed becomes the minimum among the array, which
consist of the elements a of 1 and a of 2. So, this is an iterative procedure, where one
scans the array from the element index by the smallest value up to the element index by
the largest value in this case 1 to n, and start of with the, an estimate of the minimum
value to be the first element and update this estimate by a comparison, by one
comparison in each iteration. It is very clear that we will have to perform n minus 1
comparisons. It is not possible to perform anything less n minus 1 comparisons in this
approach, unless we know something else about the array. Therefore, these algorithms
419
that we have just discussed, is an optimal algorithm with respect to the number of
comparisons. The number of comparisons is you need n minus 1 comparison in the
algorithm above, performs exactly n minus 1 comparison; therefore, this is an optimal
algorithm.
it is the very interesting exercise to ask, if one thing compute, both the minimum and the
maximum elements in the array by efficiently, but more importantly by in a simultaneous
fashion; that is we want the algorithm at every point of time to keep estimates of both the
min and max in the array, and update both this estimates, and finally, conclude that min
and max of min found. Here is one way of doing it. Use n minus 1 comparison for each
of thing that is can the elements from the first element to the last element.
Assume that the first element is both the minimum and the maximum, and compare the
current minimum and the maximum in every iteration with, the current element. This is
two comparisons for element, there are n minus 1 elements that there are compared
against the minimum and the maximum values, and therefore, refer from 2 n and minus 2
comparisons. We can interested in coming up with a better algorithm which uses strictly
smaller than 2 n minus 2 comparisons, and we presents an algorithm which uses at most
3 n by 2 comparisons. So, let us just understand how this can be done. In this straight
420
forward approach, which is listed in the first item. We have estimates of min and max
and we compare both min and max, with the next element in the array; that is in the ith
iteration we compare the ith element with both min and max, to check if the ith element
replaces the minimum value or the maximum value. Of course, one can use the fact that,
we have computed two values which are min and max, and if we compare min and max
with a pair of values, then min is to be compared only with the smaller of the two, and
max needs to be compared only with the larger of the two, and therefore, you can
compute the min and max among these four elements with 3 comparisons. This is the
whole idea.
So, the whole idea is, to process the elements in the array in pairs and maintain the
minimum and the maximum values, in each iteration that has been calculated so far, and
compare the minimum element with the smaller element of a pair, and compare the
maximum to the maximum element in the pair. Observe that the inner pair; the smaller
element can be identified with one comparison, and after that we count only two
comparisons therefore, among four elements we are able to compute the minimum and
maximum, using just 3 comparisons, and this is the trick that we generalise, to reduce the
total number of comparisons .
421
So, here is the algorithm which is described. Initially the values, when n is odd min and
max are taken to be the first element, and when n is even min and max are taken to be the
first two elements. This takes one comparison; min is taken to be the smaller of the first
two elements, and max is taken to be the larger of the first two elements. This is done at
the cost of one comparison.
And then based on this algorithm that we have a outlined, there will be a total of n by 2
comparisons, or if n is odd, there will be a n minus 1 by 2 comparisons. And if n is even,
there will be n minus 2 by 2 comparisons. Comparison pairs an each of them requires 3
comparisons to identify or update the minimum and maximum. Therefore, the total
numbers of comparisons which are made, are 3 times n minus 1 by 2 plus the first
comparison and 3 times n minus 2 by 2 plus the first comparison. This is the total
number of comparisons which are made. So, this is the whole idea. Let us run it on as
single example, where there are five elements in the array. The array has elements 2 7 1 3
and 4, and we just illustrate have this simultaneous min and max calculation happens.
Initially, because n is odd min and max are taken to be 2.
Then we compare the elements, by considering the pairs one and 7, and the pair 3 and 4.
In one iteration, we consider the pair one and 7; one is smaller than 7, this involves one
422
comparison. And after that one is compared with a current minimum, and 7 is compared
with the current maximum. As you can see this is sufficient for us, to very easily extract
the minimum and maximum among the elements 2 7 and 1. Now min and max are
updated to be one and 7 respectively. They are different from the earlier estimates, which
was both 2. And we have now use 3, addition 3 comparisons. Then 3 and four are
brought into the whole exercise. Now 3 is compared with the current minimum, because
it is a smaller of the 2, and four is compared with the current maximum, because it is
larger of 3 and four. Already one comparison is used to identify which of 3 and four is
smaller; therefore, we use three more comparisons, and total of six comparisons are used
which is three times n minus 1 by 2 comparisons
Similarly, when n is even, we use one more comparison to identify this smaller of the
first two elements, and subsequently we have three times n minus 2 by 2 comparison. In
this case it is very easy to see that there are 7 comparisons that have been made. So, this
is the whole idea, let us run it on a single example, where there are five elements in the
array. The array has elements 2 7 1 3 and 4, and we just illustrate have this simultaneous
min and max calculation happens. Initially because n is odd, min and max are taken to be
two then... We compare the elements by considering the pairs one and 7, and the pair
three and four. In one iteration, we consider the pair 1 and 7. One is smaller than 7. This
423
involves one comparison, and after that one is compared with current minimum and 7 is
compared with the current maximum. As you can see this is sufficient for us, to very
easily extract the minimum and maximum, among the elements 2 7 and 1. Now min and
max are updated to be 1 and 7 respectively. They are different from the earlier estimate,
which was both two, and we have now use three addition, three comparisons. Then three
and four are brought into the whole exercise.
Now there is compared with the current minimum, because it is a smaller of the two, and
four is compared with the current maximum, because it is large of three and four.
Already one comparison is used up to identify which of three and four is smaller;
therefore, we use three more comparisons, and total of six comparisons are use, which is
three times n minus 1 by 2 comparisons. Similarly when n is even, we use one more
comparison to identify this smaller of the first two elements, and subsequently we have
three times n minus 2 by 2 comparisons. In this case it is very easy to see that there are 7
comparisons that have been made.
424
Programming, Data Structures and Algorithms
Prof. N.S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 05
Lecture - 32
Sorting
Insertion sort-Correctness and running time analysis
Merge sort Correctness and running time analysis
So this lecture is on algorithms for sorting, and the focus of the lecture is going to be not
just in the algorithms, but the sequence of steps that go into arguing that the algorithm is
indeed correct and analyzing the running time.
Another simplest algorithms to sort a given data set, and in this case we assume that the
input is given in an array of size n, with the elements are a of 1 to a of n. The data type of
the elements in the array is not so important for the rest of the presentation, as long as it
is clear that there is a very clear order between every pair of elements. The output of
sorting algorithm is to sort the array in ascending order, and insertion sort is a very
simple and popular algorithm to understand the challenge of sorting. The approach that is
taken by insertion sort, is the following. It is an iterative approach, which means there is
425
a loop that is running n times. Recall that n is a number of elements in the array that need
to be sorted, and at the end of every iteration. Let us say at the end of the ith iteration, the
invariant that is maintain by the algorithms, is that the elements form the first i elements
in the array; that is elements a of 1 to a of i are sorted in ascending order. Further in the
ith iteration, the elements a of i is inserted into the correct location, among the elements a
of 1 to a of i minus 1, and thus this invariant is maintain one iteration after another. This
kind of guaranty is the correctness of the insertion and sort algorithm, to sort an array of
n elements.
Of course is challenge here, is to understand how the insertion happens. Recall that in the
previous slide we mentioned that, in the ith iteration the elements a of i is inserted into
the correct location, so that a of 1 to a of i is sorted. Given that a of 1 to a of i minus 1 is
sorted when the control enters the ith iteration. So, how does one insert a of i. One can
look at the step by step procedure. We said first j to be a temporary variable, and then
whenever j takes the value one you exit. We will see what the motivation for this
condition is, and as long as j is more than one; a comparison is made of the elements a of
j and a of j minus 1.
In other words, the elements which are in the array locations j and j minus 1 are
426
compared, and if a of j is smaller than the a of j minus 1. Then there is an exchange that
is effective, between the elements and the location j and j minus 1. And now j is
decremented by one and then we repeat this procedure all over again. Indeed it is also
possible that the elements at the jth location is at least as large as elements at the j minus
one th location in which case, it is clear that a of j is the correct place, given that the rest
of the elements are in sorted order. At this point of time the control exit's, and now we
can report that the elements a of 1 to a of i are in sorted order.
In other words we achieve the goal of inserting a of i into the correct position, which is
look at this pictorially, this is taken from corner license and drive is standard text book.
And let us look at the starting array, which is array index by the letter a and observe that,
there are six elements in this array which sort out as 5 2 4 6 1 and 3, observe that in the
first step. In our cases would be the second step the way of presented the algorithm, the
elements two and five are exchanged. In the array index by the letter b, observe that the
first two elements are in sorted order, and the elements four is not being inserted into it is
correct position, and this is required a single comparison with four and five, but of
course, the comparison with two should also happen.
Therefore, it actually requires two comparison. Similarly if you look at six, six is larger
427
than five and this one comparison keep six exactly where it is, and then observe that one
is no inserted into the correct position after four exchanges, six first exchanged with one,
then five with one then the value four with one, and finally, the value two is exchanged
with one. Similarly three now is move to it is inserted into it is correct place in the figure
index by the letter e. At the end of this whole procedure we find that the array is in sorted
order. This is essentially the idea behind the insertion sort, and one can even imagine this
as a very natural way of arranging a deck of cards
How does one guarantee the correctness of the insertion sort algorithm. The way to
guarantee the correctness of the sorting algorithms, is to know argue that in the ith
iteration the elements a of i will move to it is correct position, among the elements a of a
of one to a of 1 minus 1 within i comparisons. In other words we prove this by induction.
We observe that at the end of the ith iteration the elements a of 1 to a of i minus 1 are
sorted in, a of i are sorted in ascending order. We prove by this induction on the value of i
observe then when i equal to 1 which is the base case, the array just cons containing a of
1 is just in sorted order. So, let us assume that a of 1 to a of i minus 1 are sorted in
ascending order, and let us complete the induction step. So the hypothesis now is a of 1
to a of i minus 1 are sorted in ascending order. Now a of i let us assume that a of i is now
inserted between the elements a of j and a of j plus 1. So, let us look at the indices, that j
428
can take. J can take a value between 1 and i minus 2, and where ever it is inserted. On
termination it is clear that a of i is at least as large as a of j and a of i is less than or equal
to a of j plus 1. In other words, as the algorithm progresses, the prefix of the array which
is sorted, it is length keep increasing, iteration by iteration. Consequently at the end of
the nth iteration it is clear that the elements a of 1 to a of n occur in sorted order.
Therefore, insertion sort is indeed correct in the senses that, we terminate s and
termination the array is in sorted order.
The analysis are the running time is based on the observation that for the elements a of i
to be inserted in this correct location in the worst case, which is need 2 times i minus one
comparisons, we use comparison as a measure of the running time. We count the total
number of comparisons that need to be made, to ensure that i is inserted into the correct
place. And here we place an upper bound. In other words we do our worst case analysis
of the number of comparisons which are required. And observe that if the elements, the
worst case is the case where the elements a of i should eventually enter the location a of
1. In other words in the elements a of 1 to a of i a of i turns out to be the smallest
elements, in this sub array of consecutive elements.
Then a of i indeed has to be compared with each of the elements; that is i minus 1 all the
429
way have to, the element index set i minus 1 up to the element index by y by 1 .Further
in each of this iterations if you notice the code, that we have written, there is a
comparison of whether the indexes is equal to 1 or not. So, that is why we get 2 into i
minus 1 comparisons. Therefore, the total number of comparisons made, is one
comparison for the first element, and subsequently it is an even number of comparisons
up to the nth element. Therefore, the total number of comparisons that is made is given
by summation 1 plus 2 plus four plus six and so on up to 2 time n minus 1. Here is the
small exercise which is in the toy exercise, calculate this exactly and verify that it is of
the order of n square. So, this brings to end the discussion of insertion sort, and it is
instructive to see on what input insertion sort performs the most number of comparisons.
This is also left as a small exercise to the student.
Natural question now, is that can one reduce the number of comparison made by our
sorting algorithm, from order of n square to something significantly smaller than n
square. We now show an algorithm which uses a divide and conquer pharadine, recall
that in the last lecture, where we studied binary search. We encounter the divide and
conquer paradine, where in that case we were searching for a particular element in a
sorted array, and in an every iteration the region of the array which is sorted, became
smaller by a fraction of two. In this case we do something similar we create two sub
430
problems of almost equal size.
Sort the two sub problems recursively, and then merge the sorted arrays. So, this is the
whole idea, this is the divide and conquer paradine, and this is what merge sort does. The
sequence of steps are as follows; for the goal of sorting an array, whose indices are in the
interval p to q, where p is smaller than q. Initially the first time when merge sort would
be called, the value of p would be 1 and q would be n; that is we want a sort the array of
size n. The algorithm takes a middle element in the given array. In this case it computes
the index, given by the formula r which is the floor of p plus q by 2. In other words, the
range p plus q is taken which is the sum of the two elements divide by two gives a
middle index in the array, and we take the floor just in case the p plus q is odd. If r is not
same as p. In other words, if the array has more than one element, or more than three
elements. If the array has more than three elements, then merge sort of p comma r is
called followed by a recursive call to merge sort of r plus 1 to q.
Observe that here is a boundary condition of what happens when r is equal to p. Observe
that when r is equal to p, there at most two elements in the array. And there is no need to
recursively sort the elements of size one. Now the two arrays a of p to r and a of r plus
one to q are now sorted recursively, using merge sort itself, at the end of which the array
a of p comma r and a of r plus 1 comma q are sorted arrays. We call a function called
merge which we will shortly discussed. The output of merge is should take these to
sorted arrays, and insert them into third array, which is called b here. Peak b contains all
the elements of both the arrays in a sorted in ascending order. b is now copied into the
array p comma q which is now sorted, and this is description of the recursive algorithm.
The natural question now, is how many comparisons are made by this merge sort
algorithm.
431
(Refer Slide Time: 14:54)
Before we go there, let us we look at a key step, right which place in important role in a
merge sort algorithm, which is a time taken to merge to sorted arrays. Let us look it as
independent exercises, not just in the contacts of merge sort, but just the question of; the
input consisting of two sorted arrays and the output should be the third array which
contains, all the elements of two arrays in sorted order. This operation is called the
merging operation of the two sorted arrays a and b. So, let us look at the time taken to
merge the two arrays a and b into a sorted array c. The idea is quiet intuitive. So, initially
x and y are taken to be one. They are smallest elements in the two arrays respectively.
And z is the first element, the index of the first element in the array c. The first step is a
following which is very important, this is a beginning of an iteration. If z exceeds the
total number of elements in the array, in the two arrays, then you exit merge has been
completed you written the value of c.
The algorithm returns the value c. In a second step, the smallest of the two elements a of
x and b of y are identified, and the smallest of the two elements is now assigned, to be
the element present in the array index z in the array c. This is iteratively done right as
follows; a of x is compared with b of y. The smaller the two elements as you can see, is
assigned to c of z. z is an incremented and depending on where the smaller element came
from, whether it came from a or b, the indices x and y are incremented, the indices y and
432
x are incremented. And for example, if the smaller element came from the array y array
b, then the element assigned to c of z is b of y and y is now incremented and z is also
incremented. If is smaller elements came from the array a, then x and z are incremented
and the array element a of x is copied into the array index z in c. Now we will argue that
this indeed, merges the two arrays and we will also count the total number of
comparisons made.
At this point of time, before we go into that argument, let us does look at the execution of
the merge sort algorithm, and again this image is taken from the text book or by (18:26),
and let us look at the run of this algorithm. The leaf levels in this tree tell us the initial
array. The initial array has had eight elements, and the elements are 5 2 4 7 1 3 2 and 6,
and observed that in this view every intermediate array that you see as you view this in a
bottom of fashion, is a sorted array. And the parent of two arrays is obtain by merging the
contains of two sorted arrays. Let us look at the leaf level, let us look at, the elements
five and two, individually they are in sorted order, and they are merged to result in the
parent array which has an elements two and five. If you look at the sibling, the right
sibling of the array 2 5, it is 4 7 which is obtained by merging the two arrays, which have
just a single element 4 and 7 respectively. Now 2 5 and 4 7 as you can see, is merged it to
give the array 2 4 5 and 7. And eventually the whole sorted sequence obtains at the root
433
of this recursion tree. It is important to observe that this data structure is present, only in
the analysis of the algorithm, and the algorithm indeed does not run in this order. It runs
in a top down order as oppose to the analysis which is happening or which is being
presented in a bottom up fashion.
Let us look at the correctness analysis of the merging step. The merging step is indeed
correct. So, let us just observe a primary invariant which says that for every z in the
range; 1 to l plus m, after the zth zth iteration c of z satisfies some nice properties. c of z
is smaller than all the values in the array a among the region x to l, among the indices x
to l and it is smaller than all the values in the array b among the indices y to m.
Therefore, it follows that c of 1 to z all the values are, at most each values in the array
indices x to l in a, and the array indices y to m in the array b. The third property that is
the guaranteed by the algorithm is that, the array c is sorted for every z. So, is it
remembered that z is the iteration number. To prove the above claim, it is sufficient. If
we prove the above claim, this prove that the merge algorithm is indeed correct, because
when you take the value z is equal to l plus m, the array c of 1 to z would be sorted; that
is the whole array is sorted, because of the range of value is that z takes from 1 to l plus
m.
434
(Refer Slide Time: 21:56)
Continue in the correctness analysis, we set up the proof by induction. Let us assume that
z is equal to 1, and in this case the single ton array. The array containing just one element
is sorted. It is indeed that smallest element in the arrays a and b; therefore, our claim is
correct, in this base case. Let us assume, and let us make the induction hypothesis that
the claim is indeed true for a value z minus 1 ,and let us prove this for z this would
complete the induction step. So, the first claim is that c of z, is the smallest elements in a
of x and among a of x and b of y. This is indeed correct, because if you look at the steps
in the algorithm, we choose a minimum of the elements a of x and b of y and assigned it
into the value c of z, and therefore, c of z is indeed the minimum.
Since a and b are sorted, it is clear that c of z indeed smaller than the elements in the
array location x to l in the array a, and the elements in the array locations y to m in the
array b. By induction we know that c of z minus 1, is indeed s smaller than or equal to c
of z. In other words we know that by induction that c of z minus 1 is smaller than or
equal to all the elements in the arrays a and b put together, and we have taken a minimum
of all those elements and put them into c of z. Therefore, c of z minus 1 is smaller than or
equal to c of z. Consequently it follows that c of 1 to z is in the sorted order, given that c
of 1 to z minus 1 was in the sorted order by the induction hypothesis. This proves the
induction step; and therefore, we have proved that merge in the succeed, that after l plus
435
m iterations the whole array c is in the sorted order, and therefore, the merge algorithm
indeed succeed.
Let us now analyze the running time of merge. It is very important to get an
understanding of how much time it takes for merge to run, it is crucial to understand,
how does one count the running time, and in this case we count the total number of
comparisons. The most important observation that we make, is that every time a
comparison is affected, the size of the array c keeps increasing by exactly one. As a
consequent of this observation, after l plus m comparisons there will be l plus m
elements in the array c, and therefore, the total number of comparisons used, is a sum
total of a total number of array elements in two arrays a and b. Therefore, the number of
comparisons used by the merge algorithm is l plus m. In other words it is just linear in
the size of, the number of elements in the two arrays put together.
436
(Refer Slide Time: 25:21)
This gives us a handle to analyze a running time a merge sort. Again here we count the
total number of comparison made by merge sort. Let t of n denote the number of
comparisons made by merge sort on an n element array. For the sake of simplicity, just
for the argument let us assume that n is a power of two for some value k; that is n is
equal to two power k. Now t of n is given by the formula, t of n is two times t of n by
two plus n, and t of two is just equal to 1.
437
(Refer Slide Time: 26:28)
Let us understand this formula. This is called a recurrence equation, and two times t of n
by two, is the number of comparisons on the recursive sub programs, to sort the recursive
sub problems; that is, the sub problem consisting of one half of the array, and the second
sub problem consisting of the second half of the array. The second term which is n, is the
total number of comparisons made by merge which we just very recently analyze. A
solution to this recurrence equation, gives us a solution to the number of comparisons
made by the merge sort algorithm, and this will be completed in the next lecture.
438
Programming, Data Structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 06
Lecture – 33
Merge sort-Completion of running time analysis
Counting sort-Sorting in linear time (in some cases): Stable sorting
Radix sort-Correctness and analysis
So, we now study the analysis of merge sort and also learn a new algorithm for sorting;
which under special cases is better than merge sort called radix sort.
439
(Refer Slide Time: 01:14)
440
The recursion tree for the recurrence is T of n is equal 2 times T of n by 2 plus n. This is
a recurrence. And the recursion tree is constructed as follows. At the root of the tree, is
the function that we want to compute, which is T of n; its two children are T of n by 2
and T of n by 2. Now, the root is now replaced by the value n, which is added to the sum
of T of n by 2 and T of n by 2. The tree is further expanded using the same root; where, n
by 2 replaces the element T of n by 2; and its two children become the values T of n by 4
and T of n by 4. One branch of this whole recursion tree right up to the leaf – one can
note that, the values are in the range n, n by 2, n by 4 all the way down to 1. In other
words, as the depth of the tree increases from the root, the size of the sub problems being
solved falls by a ratio of 2 – falls by a fraction of 2. It becomes n, n by 2, and then n by 4
and goes down all the way to 1. Therefore, the height of this tree is log n to the base 2.
The contribution to the whole recursion, which is essentially the summation of the values
at all the nodes now; at the root, the contribution is n, because the value is just n. At the
level 1 – depth 1, the contribution is again n, because there are two nodes of value n by
2. At the next level 2, there are two nodes, four nodes of value n by 4; and the
contribution is a value n and so on all the way up to the leaves. And it is clear that, the
number of leaves is exactly equal to n, because every leaf node contributes a value 1.
And therefore, the number of leaf nodes is n. Therefore, the contribution from leaf level
is also n. Consequently, the solution to this recurrence is n log n.
441
Now, we have placed in upper bound, the total number of comparisons made by merge
sort; which is an algorithm, which uses the divide and concur approach. And clearly, n
log n grows more slowly than n square. This is very easily visible to us; observe the n log
n and the n square; the common term is n and log n and n; n is exponentially larger than
log n. And therefore, n log n grows much more slowly than n square. Therefore, merge
sort has a better asymptotic running time than the algorithm insertion sort. We go
forward with the very natural question of can we beat the n log n bound?
It is indeed possible to sort linear time when the data that we are sorting has some very
special properties. In this case, we study an algorithm called counting sort; counting sort
is a very special algorithm; it does not have any comparisons among the elements. The
input array is an n element array, which contains the values to be sorted. In this case, we
assume that, the values come from a range 1, 2 to k. And we make an assumption that,
the values are all numbers. The output is presented in an array B, which is also an n
element array and the output is sorted. The algorithm uses an auxiliary array called C,
which has k elements. Recall that, k is a total number of distinct elements that once C is
in the input array from the range 1 to k and C is an array of size k elements; in essence,
every value, which is in the array A can be an index into the array C. And this is an idea
that we use to obtain a linear time sorting algorithm in this special case.
442
(Refer Slide Time: 06:52)
So, let us just look at counting sort. Counting sort basically has four loops. The first loop
is the initialization loop, where the count array or the array C is initialized to take the
value 0. In the next iteration, in the next for loop, we count the number of occurrences of
each element of the array A by making one pass of the array A. Observe what the for
loop does. The statement inside the for loop; say for example, the first element – C of A
of j; if the first element is a 1, then the array index 1 in the array C is incremented by 1.
So, every time we see a value in the array A, the corresponding index location –
corresponding location in C is indexed by the value is incremented by 1. At end of the
first for loop, every array location – in particular, the i-th array location counts the
number of occurrences of the key i in array A.
In the third for loop, the consecutive elements of the array C are added to ensure that, the
array element i keeps track of the number of elements of value less than or equal to i in
the given array A. And finally, these count information are used to rearrange and obtain
the sorted array B, which is as follows. The key at the element j – the key at the index j
in array A is moved to its rank in array B. In other words, if the key at array index j has
four occurrences in the array A, then C of A of j would be 4; and the value A of j would
be kept in the array location B of 4. And then the number of occurrences of the value at
A of j at the index j is reduced by 1.
443
(Refer Slide Time: 09:42)
Let us run one example of this algorithm; the array A has the values 4, 1, 3, 4, 3 in the
five locations.
And let us see how the initial array C looks like. It is initialized to 0. And since the
distinct keys are in the range – 1 to 4 in the array A, the array size C has only four
elements; array C has only 4 elements to keep track of the counts of the values 1, 2, 3
and 4 respectively.
444
(Refer Slide Time: 10:23)
The first element in an array A is the value of 4 and the second loop increments C of 4.
Then the value C of 1 is incremented by 1. Then the value C of 3 is incremented by 1.
Then the value C of 4 is incremented by 1 and it becomes 2 now. And the value C of 3 is
incremented by 1 now, which becomes 2. It is easy to observe the following invariant
that, the sum of the array elements C is the total number of elements in the array A. In
this case, it is 5.
445
In the next iteration, we modify and store C. And let us see how we do this. Observe that,
we now keep track of the number of elements, whose value is less than or equal to 2 in
the array A and in the location C of 2; that is obtained by adding C of 1 and C of 2 to get
the value 1, which is a count of the number of elements, whose value is less than or equal
to 2. In the next iteration, the number of elements is less than or equal to the value 3 is
kept track off. And we can see that, the value should be 3 and let us see this. That is what
it becomes. And then the number of elements of value less than or equal to 4 is in the
five elements; all of them are of value less than or equal to 4. And C of 4 is now updated
to the value 5.
Now, comes the phase when the sorted elements are output. And this is a loop that runs
in the reverse order. Let us look at the last element in the array A; it is a value 3. Now,
there three occurrences of elements less than or equal to 3 in the array A, that is, the
element 1, 3 and 3; now, the element 3 should definitely be in the third position in the
sorted array. And therefore, 3 is now placed into the third position. And the number of
relevant occurrences of 3 is now reduced by… The number of elements smaller than 3 is
now reduced by 1. Then 4 is considered. The number of elements of value is less than or
equal to 4 is 5. And therefore, this 4 should occur in the fifth location in the sorted array.
In other words, 4 is the largest element and it must occur in the fifth location in the
sorted array and then the value of C of 4 is reduced by 1. Next element considered as 3;
and the number of elements less than or equal to 3 in the given array A, which has not
446
been considered so far is 2. And therefore, three goes into the second location. And then
naturally, 1 goes into the first location. And the number of elements less than or equal to
1 is reduced to 0. And then the last element is considered 4. And the number of elements
less than or equal to 4 in the sorted array – the array index is given by 4. And 4 goes into
the fourth location and the algorithm terminates when the loop comes to an end.
One of the most interesting properties of this algorithm is that, counting sort is a stable
sorting algorithm. What is the meaning of a stable sorting algorithm? If two elements
have the same value; if two array indices have the same value; then in the sorted array,
the order in which they occur is preserved. For example, if you see the two occurrences
of 4 in the array indices 1 and 4 respectively, observe that, the occurrence of 4 in the
array location 1 in A goes to B of 4. And the occurrence of 4 in the fourth location goes
to B of 5. Same is true with 3. Therefore, not only is the array sorted, but interestingly,
the array also maintains a certain stability. In other words, it guarantees that, the input
order among elements of the same value is respected in the sorted output. It is interesting
to ask this question as to which other algorithm that we have seen have this property.
447
(Refer Slide Time: 16:12)
Based on this algorithm, we present this approach to sort numbers, which is called the
radix sort. The idea of the radix sort is to sort numbers digit-by-digit. And the idea is
used to sort the numbers based on the first digit and use a stable sorting algorithm to sort
the numbers based on the first digit. In other words, we will come up with an approach,
where we just sort the first digit of all the numbers and we will use a stable sorting
algorithm to do this and iterate over all the digits.
448
This is very nicely pictorially represented. So, let us consider the numbers, which are
given to be sorted, which are the numbers 329, 457, 657, 839, 436, 720, and 355. Let us
consider the least significant digit in each number and visualize this as the array that
should be sorted by counting sort, which is the stable sorting algorithm that we have just
seen. On sorting the array consisting of the least significant digits, the sorted array would
look like this, which is natural. The least significant digits are when they are sorted,
occur in the order 0, 5, 6, 7, 7, and 9, 9. And because counting sort was a stable sorting
algorithm, observe that, the remaining two digits have also been moved to the
appropriate location along with the least significant digit. For example, the number 720
has the smallest least significant digit among these. And however, it is a second largest
number; but at the end of the first call to counting sort on the least significant digits, 720
occupies the first position and the whole number 720 goes to the first place and 0’s at the
correct position.
Now, the second least significant digit is picked up; and now, this array is sorted again
using counting sort; and the remaining numbers in some sense are typed to the digit that
is being sorted. And as a consequence of using a stable sorting algorithm, one can
observe that, if you focus just on the numbers in the first two digits, they occur in the
sorted order. And this is an inductive invariant that will be used to argue the correctness
of the radix sort algorithm. Finally, we pick up the third digit and perform a stable
sorting. And in this case, we use a counting sort, which we have just discussed. And now
we can observe that, all the numbers are in sorted order. And observe that, the stability of
counting sort is very crucial in the correctness of the algorithm. And when a certain array
is being sorted, the remaining numbers are visualized as being typed to this particular
number.
449
(Refer Slide Time: 19:20)
So, the correctness of radix sort is argued by induction on the digit position. So, clearly,
after the first call to counting sort; that is, after sorting the least significant digit, the
number, whose lower order digits – the array of least significant digits are sorted. So, let
us assume that, the numbers are sorted by their lower order t minus 1 digits after sorting
the first t minus 1 applying counting sort on the first t minus 1 digits.
Now, when we sort on digit t… Now, when we sorted on digit t, the arrays is now sorted;
it is a stable sorting algorithm. And let us look at two elements, which are in the opposite
order. In other words, 720 and 329 – the first two elements observe that – they have
exchanged their places; and 329 is smaller than 720; and 720 has gone to its correct
location in the sorted array. Therefore, the numbers that digit that differ in digit t are
correctly sorted. And two numbers, which have the same digit – two indices, which have
the same value at digit t are also correctly sorted, because we are using a stable sorting
algorithm. Therefore, we have the correct order among the t digits. And this completes
the inductive argument.
450
(Refer Slide Time: 21:30)
Let us complete the analysis of counting sort. The loop 1, which was initializing the
count array performed k operations, because every… there are k indices and each of
them was initialized to 0. Loop 2 performs a computation by looking at every element of
the input array A exactly once and then increasing an appropriate index in the count
array C by 1. So, this takes n time, that is, it takes a constant number of arithmetic
operations per stack. And loop 3 then gets upper bounds on the values at most on the
number of terms, whose values are at most each index in C. And this again takes k time.
And loop 4, which writes out the output array B also takes n time. And therefore, the
total time taken is order of n plus k. In this case, it is 2n plus 2k arithmetic operations.
Now, if the key set – the smallest and the largest value differ by a linear amount by at
most n; then counting sort takes order of n time. It is also clear that, counting sort does
not perform any arithmetic, does not perform any comparison operations; and it is
completely an arithmetic operation based sorting algorithm and it takes linear time in
some special cases.
451
Programming, Data structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 34
Algorithms
i-th Smallest
Order Statistics
Today's lecture we are going to look at algorithms for finding the ith smallest number in
a given data set. This speciality about the problem that we are going to look at is that is a
recursive algorithm, and it is very efficient compare to the most simplest algorithm to
answer this question. The problem and question is given n distinct numbers an imagine
to be presented in an array, the goal of the problem is to find the ith smallest element
which is also given us what are the input? The value i is also given us part of the input.
In other words we want to find an array element, which is larger than exactly i minus 1
elements in the given datas. One natural approach is solve the question is to sort the
given n distinct elements in ascending order, and 1 can use an algorithm like merge sort
which is known to run in order of n log n time, and then we return the ith element in the
sorted array.
452
(Refer Slide Time: 01:23)
This definitely does solve the problem the focus of this lecture is to see if we can design
better selection algorithms, in other words our aim is to design selection algorithms
which run in time order of n in the worst case. The main idea behind the algorithm that
we are going to look at is the concept of median. Let us just recall the definition of the
median. Median is the middle rank element in a given set of numbers. If there are n
distinct odd numbers in the dataset then the middle rank elements is the unique element.
If n is indeed even then there are 2 medians; the elements ranked n by 2 and the elements
ranked n 1 by 2. If n is odd - this is the single element, and if n is even - this is the n by 2
th ranked element.
453
(Refer Slide Time: 02:32)
The idea for identifying the ith rank element is that we use the power of recursion, and
given the elements in an array, we aim to partition the elements of the array into 2 parts,
based on an index r, the elements a of 1 to a of r; that is the first r elements in the array r
of values smaller than the r plus 1th element in the array. And the remaining elements
that is the elements with the indices r plus 2 to n or of value more than a of r plus 1.
Indeed if r has the value i then we have indeed found the ith ranked element. This is very
clear, because the first i minus 1 elements are smaller than the ith element, and therefore
a of r is the ith ranked element for r is equal to i. We are going to do this for some
carefully chosen r, so that we can get our desired efficient algorithm. The way to do is
this if r is not equal to i then we ensure that we have recursively smaller sub problems to
solve. So, that we can get to the ith ranked element as quickly as possible.
454
(Refer Slide Time: 03:59)
So, now we setup the recursion. If indeed i is smaller than r then we find the ith ranked
element in the set a of 1 to a of r. This is indeed the most natural thing r is larger than i
and all the elements below r, all the elements whose indices the range 1 to r in the array a
or indeed smaller than r. And therefore, the ith ranked element would also have an index
smaller than r, therefore its natural to search for the elements in the range a of 1 to a of r,
the ith ranked element in the range a of 1 to a of r, i is more than r clear that we look for
the i minus rth ranked element in the range a of r plus 2 to n. In either case it is clear that
we have smaller recursive sub problems, but to get are efficient running time we will see
that it is desirable to have the 2 parts in the partition to be of almost same size, and we
will try to ensure this.
455
(Refer Slide Time: 05:17)
So, let us go to the design of the whole algorithm, we refer to this algorithm as a select
procedure; the procedure has 2 arguments; ((Refer Time: 05:29)) array a, and a rank i.
The output of this function is to return the value of the ith ranked element in the array a.
Select A of i is the a of recursive function. Let us recall recursive function it is a function
which makes calls to itself with of course, different parameters.
456
So, what we now do i, we try to partition, we come up with a procedure to partition the
array based on an index r. To do this what we do is to take the n elements and divided
into n by 5 groups, the n by 5 groups are the elements with index 1 to 5, the elements a of
6 to a of 11 and so on upto a of m minus 5 to a of n. It is important to notice here that the
last group could be of values smaller than 5. For example, if n is not a multiple of 5, then
clearly the last group will be smaller than 5. In each of these groups we find the median
element, we call that an each group there are 5 elements except for the last one, for the
purpose of this discussion, let us assumed with the last one also has 5 elements right.
And we pick the median element each of these n by 5 groups, and for this we can use
insertion sort and sort the 5 elements and pick the median. In other words this will be the
third ranked element in each group. What we do is, we visualize these median elements
from each of these groups in an array m.
Now, we here is the first use of recursion. We look at the array m and ask for the median
of the n by 5 elements in the array M. So, the recursive call is described here, if there are
m elements in the array, then the recursive call is select on the array m, the median
element which we know is floor of m plus 1 by 2, let the returned value be x. Now what
we do is the partition the array A into 2 parts around the element x. We will see how to
457
do this, but before that let us also analyse how good this partition is. What do we mean
by a good partition we analyse, what properties of this partition are there which could
give us a linear time algorithm to find the ith ranked element.
So, let us see how good this partition is, let us make some observation. Let us ask in the
array A how many elements are greater than x. Recall that x comes from the array M and
indeed it is a median element of the array M, M has n by 5 elements in it. Therefore, x
has n by 10 elements larger than it; that is half the elements in the array m or larger than
x, x being the median element in the array M. Let us also recall that ((Refer Time:
09:05)) each element in M is a median element in those n by 5 groups, and there are 3
elements which are at least as larger each element of M in the array A. I repeat this, for
each element in the array M, there are definitely 3 elements in the array A, which are at
least as larger as that particular element. Therefore, there will be at least 3 n by 10
elements, which are at least larger than x in A.
Therefore, at least half the groups have 3 elements smaller than or equal to x. This is
except for the group containing x which has 2 elements, and the last group which may
contain only 1 element. Therefore, the number of elements which are smaller than x and
A is at least 3 n by 10 minus 6.
458
(Refer Slide Time: 09:57)
So, let us completes the discussion of how good this partition is using a symmetric
argument, the number of values atmost x in A is also atleast 3 n by 10 minus 6, as a
consequence of this argument it follows that the 2 arrays - A of 1 to A of r and A of r plus
1 to A of n, after the partition step have size at most 7 by 7 n by 10 plus 6, this is because
the array has a total of n elements, and therefore the number of elements in the 2
partition 2 arrays A of 1 to A of r and A of r plus n to n is a tmost 7 n by 10 plus 6.
Therefore, by making a recursive select call on one of these two arrays, we get a running
time which is given here which is T of n the time taken to find the ith ranked element in
the given array A is equal to T of n by 5 which is used to find a median element in the
array M plus T of 7 n by 10 plus 6, which is the time taken to solve the recursive sub
problems plus the time taken to create the partition, which is also a linear time procedure.
Finally, if the array has a single element to find the ith ranked element is extremely easy
right. So, it just is. So, T of 1 is taken to be i. It is easy to see the T of n is order of n for
the recurrence, we do not evaluate the recurrence here, but this is a recurrence which
evaluates to order of n.
459
(Refer Slide Time: 11:38)
What are the remaining steps of the algorithm? We partition the input array around the
element x to do this the following steps have to be done, we identify the position of x in
the array A. This can be done an order of n time by scanning the element, scanning the
array A for the element x. And then we perform a partition procedure linear time partition
procedure which identifies an index r, such that A of r as x and the elements 1 to r minus
1, the elements in the indices 1 to r minus 1 smaller than x, and the element in the indices
1 to r plus 1 smaller than x, and the elements in the indices r plus 1 to n r more than x.
This is exactly what we want? There are r minus 1 elements on the low side of the
partition, and n minus r elements in the high side of the partition. So, the algorithm is
almost complete now, if i is equal to k then we return the value x, otherwise we use
select recursively and the recursive calls are made as follows, we find the ith smallest
element in the low side, if i is smaller than r and we find the i minus r smallest element in
the i side, if i is more than r.
460
(Refer Slide Time: 13:00)
Let us run through an example of this algorithm. In the given array of 28 elements, we
want to find the eleventh ranked element. In the first step we divide the array into 5
groups of 5 elements each which may counts for 25 elements, and 1 group the last group
of 3 elements.
461
We sort that groups and then find the median element in the 6 groups, and the median
elements are marked in red. Observe that in the group of size 5, the median element is
the third element, and the last group the median element is the second element. In the
third step we find the median of the medians, and this is our array A, this is our array M
and one can see that there are 6 elements and the median element is the elements 17.
We partition the array around 17. And in the first part we have all the elements which are
smaller than 17, and 17 is the eleventh ranked element, and the algorithm terminates and
returns the value 17 as the eleventh ranked element. If for example, we want to find the 6
th smallest element, then we would after recurse our search in the first part which is the
value which are smaller than 17. And similarly if we want to find an element of rank 15,
then we would have to find the element of the rank 4, in the second partition. This
completes the description of the algorithm to find the highest smallest element in a given
array A.
462
Programming, Data Structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 35
Algorithms Discussion Sorting Words
So, the exercise involves a collection of words as given as input, the first step that we
will perform is that we will read the words, and store them in an appropriate data
structure. In our case it will be that the data structure will be an appropriate array. Then
you will choose a sorting algorithm to sort these words, and we will indeed use a
comparison based sorting algorithm today, and it is important that if you use a
comparison based sorting algorithm, then we should have the ability to compare two
given words.
463
(Refer Slide Time: 01:28)
Let us for example choose the insertion sort, and we all know the pseudo code to sort and
sort an array which has integers. Here what we do is we look at the array from the first
element to the last element, and in the second while loop we sort the first i elements.
Recall this we have already study this during the insertion sort lectures, the main
invariant of insertion sort is that if you look at the iteration number i, then the first i
elements of the array will be sorted at the end of iteration number i. And this is really the
pseudo code. Most important think to notice here is there is a comparison made between
the elements of the array A, the j minus one th index element in the jth index element,
and if you assume that it is an integer A, then conceptually there is very clear
understanding a what is the result of a comparison between two integers.
This is highlighted in blue, and if you wanted to use exactly the same insertion sort idea
to sort a given list of words, then we must have a clear understanding of how to compare
two words and which of given two words are is the larger among the given two words,
and whether they are equal and so on and so forth. We need to be able to compare two
words. In particular whatever we are going to discuss really does not matter, what the
elements of the array are as long as, they are of a single type. So, therefore, we imagine a
function call compare, and the result of compare will basically tellers which of the two
elements is a larger of the 2. So, let us assume that they elements are A of j minus 1, and
A of j. The result of compare tells us that whether as swap must be performed or not. If
the result is more than 0 then definitely a swap must be performed, essentially saying
464
that A of j is smaller than A of j minus 1 saying that the two elements must be
exchanged, and the result is equal to 0 then no swap is to be effected, because a of j
minus 1 is smaller than a of j. So, therefore, what we are going to look at is the
implementation of a compare function when the elements of the array are words.
So, let us now look at this small function which is used to compare two strings. Let us
recall at s 1 and s 2 are pointers to character arrays, and they are terminated with the null
symbol each of them. So, that the contents of both these arrays are consider to be
character strings. The output of this function is a 0, if the strings s 1 and s 2 are identical.
If s 1 has higher dictionary order then s 2, in other words in the lexicographic ordering or
in the dictionary ordering, if s 1 occurs after s 2 then the return value of this function will
be a positive value, and if s 1 has a lexicographically lower rank then s 2; that is s 1
occurs or the string s 1 occurs before string s 2 in the lexicographic ordering or in the
dictionary ordering, then the return value will be negative.
Therefore, the way of using this function in our sorting algorithm will be that, if s 1 is
lexicographically later then s 2 in the dictionary order, then the return value will be
greater than 0, and we will effect a swap that is how we are proposing to use the compare
function in our sorting algorithm. So, let us just go through this function it is a nice and
tricky implementation using function pointers, and the fact that characters can be
compared by performing arithmetic on their corresponding integer values. It is very
465
important to remember that the integer value corresponding to a character is it is unique
integer code, which is often call the ASCII code use a programming in the C
programming language. So, let us look at the compare function. What it does is that there
is a while loop which iterates till s 1 reaches the end of string, and the control remains
inside the loop as long as the character under process in the string s 1, and the character
under process in the string s 2 are identical.
Let us understand this. If the end of string of s 1 is reached or if the character pointed to
by s 1 and the character pointed to by s 2 are different, then control will exit from this
loop. As long as these conditions both are satisfied, inside the loop the pointer value is
just incremented by one unit, in this case by just the address of 1 byte. When control
exits from the loop, the check that is performed is to check, if what s 1 and s 2 are
pointing to or one and the same or is one smaller than the other or is one larger than the
other, and the way is affected is by subtracting the integer value associated with the
corresponding on corresponding characters.
Indeed if they are equal and the control is excited; it means that both the strings have
come to the come to their ends, and therefore the strings are indeed equal and the return
value would be 0. If the return is because of s 1 reaching the end of string, and s 2 not
being the end of string, then it means that all of s 1 is contained inside s 2, and the value
that would it be return is that s 1 is smaller than s 2 in the lexicographic ordering. And if
s 1 the value at the location that is pointed to by s 1 is smaller than the value at the
location pointed to by s 2, then this subtraction will return in negative value saying that
the s 1 is smaller than s 2. Indeed if it is going to return a positive value then it means
that the string s 1 in the dictionary order occurs after the string s 2, and therefore we will
use it to exchange to effect an exchange or effect a swap in our usage.
466
(Refer Slide Time: 09:15)
So, first we create an array of words, it is a two-dimensional array. The number of rows
is max words, which is a constant as given in the input, and the length of the word - the
length of the longest word is given and we use one more location and this is only to
ensure that we will we will store the words, as strings we will use one more letter or one
more character position to store a null. So, now we read the number of words that need to
be sorted, this is what the first scanf does and then the for loop here reads the strings one
after the other, the words one after the other and source it in the array words.
It is very important here to note that if the words are of length more than word length,
then the way we of this program is unpredictable, therefore it is extremely important to
ensure that the words that are given as input are only words of exactly word length, the
last letter will be used to store a null. Then we have an initialization step of another array
call order, and this is the very crucial array which is what we base are insertion sort
algorithm on. So, order is another array, there are as many cells in this array, as there are
the number of words or the maximum number of words, and all of them i initialize to 0.
Then as we read the input words, the order of the ith word is initialize to i, this is just a
initialization step and most importantly observe what we are doing, we reading the input
words from the input and storing at into the array words. And we are giving a initial
ranking which good be wrong which will be wrong on most inputs like unless the words
are already sorted, and the ith word is given the order i. And it is this order that will be
changed when you perform a insertion sort.
467
(Refer Slide Time: 11:57)
So, now we go to the sorting algorithm itself. For the usage of space appropriately with a
int use the word use the space appropriately, we have taken a bit of shortcut; comp stands
for the compare function which we talked about earlier, w letter is a shortcut for the
words array, and o letter is the shortcut for the order array that we have defined earlier .
So, now what we do again like in insertion sort; there are two loops. The outer loop runs
over the indices 1 to n, and what we do is we compare the first at the end of this while
loop we will guarantee that the prefix of the elements, that is if you look at the ith
iteration of the for loop at the end of this while loop, the first i elements will definitely
half the order values in ascending order. So, let us look at this comparison in the ith
iteration j is set to i, then while j is more than 0 and here j is decremented end at the end
of this. We compare the word who is order is j minus 1, and the word who is order is j,
and if the comparison gives you value more than 0 which means they must be
exchanged, then we swap the words order of j and order of j minus 1. We swap the
values of order of j and order of j minus 1, then we decrement j minus 1.
468
(Refer Slide Time: 13:53)
So, let us understand this algorithm. Initially we have a current ranking of the n words, at
the end of the first iteration the invariant as we know is that the word with order value
one is sorted; that is in this case of first word is sorted, indeed every single word is
sorted. After the second iteration, the order values are the first two elements are in
increasing order, and after the ith iteration the order values of the first i elements are in
increasing order, consequently after the nth iteration all the elements are sorted.
Therefore, this is an algorithm to sort a given list of words observe that we have use
insertion sort in a very clever way using this order data structure or the order array which
whose values are change, and using the comparison function of two words.
Thank you.
469
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module -11A
Lecture - 36
What is a structure?
Example: Points in a plane
Accessing members, More examples
Nested structures: point, line, triangle, rectangle
Defining new data types: typedef
Hello, welcome back all of you. I hope you have been enjoying the lecture. So, far and
you have been putting in some time to work on the home work exercises as well. So, in
this lecture we are going to look at what are called structures. So, we looked at one kind
of aggregate data type called arrays earlier and in this class we are going look at what are
called structures.
So, structures as arrays are a collection of one or more variables. So, you have one or
more variables,, but as opposed to arrays structures can have different types group
together. So, if we look at an array right it is an aggregate type of the same data type. So,
I can have an array of integers or array of floating point numbers and so, on, where as
structure let us you aggregate data which are even of different data types.
So, let us take for example, point in a two dimensional plane, a two dimensional plane
will have an x coordinate and a y coordinate. And you want to keep this group together,
470
because that is what a point is and one way to do that is as follows. So, you have a
something called a point which we call a struct. So, struct is a keyword in C, we say that
point is a data type which has two things, some integer called x and another integer
called y from now on point can be treated us a new data type that you created in your
program.
So, this is something that is absolutely new for your setup so, far. So, far we have been
using all the data types that are already in place, like int and float and character and so,
on,, but for the first time we are seeing, how to create your own data types. So, we have
created a new data type called point which has x and y has two members inside it. So, by
doing this you get a mechanism for defining compound data types as of now, you do not
have a storage this is like saying I have an integer. So, only we need do something like
int a comma int b and so, on, you have variables of the names a and b just by having int
in it you do not have a storage space. Similarly, when we have the basic data type called
struct point, we are saying that this structure is going to have two things an integer called
x and an integer called y, that is group together. And this collection is useful instead of
looking at as a two different integers, this collection is going to be for a representing a
point.
So, let us look at this a point in 2D is now two integers, there are different ways to
declare structure variables. So, let us look at this top part, you have a struct point int x int
y and we have these braces closing it followed by point 1 and point 2. So, the way it
interpret this is point 1 and point 2 are two variables of the data type called struct point
471
and the struct point data type has two integers in it x and y. So, this is one way to do it.
Or if you have already defined only the structure without doing declarations for the
variables, we can declare only this part saying that my point is a data type containing two
integers, you could leave it at that and then come back and say that I want two variables,
point 1 and point 2 they are both of the types struct point. So, if you look at this line it is
quiet similar to what we have done for integers and floats and so, on.
So, on the left side you start with the data type and then you have a comma separated list
of variable names, only that the data type has, is keyword struct in front of it and in name
that we have assign to the data type. So, at this point the storage for point 1 and point 2
are allocated. So, point 1 will have two integers and point 2 will also have two integers,
there is one way to initialize this, that is in the last line here struct point point 1 is 3
comma 2. So, at this point you are saying that point 1 is of data type struct point.
And since there are two members x and y, the two members will get the values 3 and 2
respectively. So, that is one way to say that point 1 should have it is x as 3 and y as 2. So,
this is also very similar to what we have for basic data types, where we say int x equal to
5 for instance would mean, you are not only declaring a variable called x, you also have
the storage declare for it plus the initialization of the value to 5.
So, let us see how to access the individual elements x and y are called members and
referring to the members is done with what is called the dot operator or the period. So,
point 1 is a variable, point 1 dot x will give you the x coordinate and point 2 dot y will
472
give you the y coordinate of point 2 and so, on. So, let us see this small piece of code, let
us say I want to print point 1 on the screen. So, printf point 1 percentage d percentage d
print point 1 dot x and point 1 dot y.
So, this statement will look at point 1 dot x the x member of point 1 and print it as an
integer and look at the y member of point 1 and print it as an integer. So, that is what this
line does, it is not just you can use them for printing. So, you have read the values of
point 1 and point y, you can also go and change the contents of point 1, you can also
write to it. So, point 1 dot x equals 0, point 1 dot y equals minus 10.
So, this one changes the value that you currently have two a point called 0 comma minus
10. So, it could have been something else before and now you could change it to 0
comma minus 10. So, one small thing that you have to watch out for is that, you cannot
access point dot x and point dot y, remember point is a data type point, the data type does
not mean that you already have storage, only when you have a variable of the certain
data type you are allocated storage.
So, you can only use instances of the structures, namely the variables it you have used
and not the original data type itself. So, point 1 dot x is point dot x is not point 2 dot y is
point dot y is not, it should also know that point 1 dot x and point 2 dot x are actually
two different variables, they have two separate memory locations and they do not get
mixed up.
So, let us see various other examples of structures. So, one classical example is of a
473
student who has a student id and as a educational instantiation am I want to track, what is
the age of the student is the student male or female and what is the students CGPA and
so, on. So, this is one logical group of things, I have the student id and along with the
that I have an integer called age, I have a character I would probably put m or f
depending on whether the student is male or female and the CGPA is usually floating
point number. So, I am going to keep it as a double CGPA.
So, this is the logical collection of things, instead of keeping them as four separate
integers if I have it as a structure. So, this collection has a meaning. So, it is all the
information about a student and we call this information here student info. Similarly, if I
am going to look at a date, I have three things that are that make a date, the day, the
month and the year, we need all these three.
Again I could of kept it as three separate integers,, but putting all of them as one logical
unit make sense. Because, then I can look at is today this date or setup today's date to be
this and so, on, instead of dealing with three separate integers which have no relationship
to each other.
Finally, let us look at another example called bank account. So, this is a suppose to have
details of a bank account. I of course,, have the name of the person who has the bank
account, in this case you have name which is a character array of 15 bytes. So, the name
can be up to 15 bytes, then there is an integer account number in this case account
number is expected to be integral. And the balance that you have in the account is
474
suppose to be a double value and am I want to track the birthday of my customers and
that in turn is a structure.
So, this is what I was talking about earlier that you could actually mix and match data
types of different kinds and through them into a structure. So, in this one we have a
character array of size 15 and integer, a double and within a structure we have another
structure called date or birthday. So, there is a member called birthday whose data type is
struct date. So, date I already mention has three members. So, in some sense we have a
nested structures here. So, we have a structure called structure date within this structure
called bank account.
So, this kind of nesting is really useful. So, let see another example here let say we have
a rectangle and I want the rectangle to be a data type by itself,, but the rectangle is
specified by the left bottom point and the right top point. So, I have two different points
that define the rectangle, namely the left bottom point and the right top point and
rectangle itself is a data type. So, this is useful for example, I want to go and draw the
rectangle I will pass this structure called rectangle to the draw function if.
So, let us look at this struct rectangle. So, it says that we want a new data type called
rectangle and this in turn consist of two members, namely pt1 and pt2, pt1 is struct point
data type and pt2 is also struct point data type. And somewhere we have to remember
and know that pt1 stands for left bottom corner and pt2 stands for right top corner. Now,
if we want to access something in this nested structure you could do this.
475
So, rect1 is a variable of type rectangle, you can see the declaration here the data type is
in the top and rect1 is the variable of the type rectangle. So, rect1 dot pt1 dot x refers to
the variable rect1 it has two members pt1 and pt2 and the pt1 member has x as one of its
member. So, you are looking at member of a member and you want that to be set to 4 and
rect1 dot pt1 dot y to be set to 5. So, this sets of the rectangle to have the left bottom
point as 4 comma 5. You could also do this, rect1 dot pt1 is 4 comma 5, we already saw
this example, only that we have a nested structure case. So, rect1 dot pt1 the x member
takes the value 4 and the y member takes the value 5.
This notion of nest nested structures is really useful to construct a lot of things, let us do
this example one more time, similar nested structures. So, we have a struct point which
has two members x and y and I have a variable p of the data type point, then let us say I
have a stuct line which takes two points. So, a line in a two dimensional plane is defined
with respect to two points and I have these two points, this blue line here is line l and line
l has p 1 and p 2.
So, this point here for line l which is p 1 is x coordinate is l dot p 1 dot x and the y
coordinate is l dot p 1 dot y, this point here is p 2 of l it is x coordinate is l dot p 2 dot x
and its y coordinate is l dot p 2 dot y. Similarly, I can make a triangle and I have to
specify three points for it. So, the data type is called triangle and the variable is called t.
So, t dot p 1 dot x and t dot p 1 dot y is one of the corners of the triangle and there are
two other corners namely p 2 and p 3.
476
(Refer Slide Time: 12:44)
So, let say I want to setup point p to have 4 comma 11, line l 1 to have 2 comma 7 going
to 10 comma 9 and triangle with these three as coordinates, how do we do it. So, this is
how do it we first say that there is struct point p, we already have struct line l and struct
triangle t, it is assume that these declarations for p l t are in place and now we have
variables p l and t. So, to make this point p to be 4 comma 11, we can say p dot x is 4 and
p dot y is 11 and for making the line 2 comma 7 to 10 comma 9 l dot p 1 dot x is 2 and l
dot p 1 dot y is 7 takes care of this point l dot p 2 dot x equals 10 and l dot p 2 dot y
equals 9 takes care of this point.
So, we have two points and for each point we are given the x coordinate and y
coordinate. And finally, for the triangle will we need three points and for each point we
give the x and y coordinate.
477
(Refer Slide Time: 13:55)
So, this defining new data types is a very useful and powerful thing and sometimes it
gets very tedious to say that it struct rectangle and struct pt and so, on and C
programming language gives you this short cut called typedef. So, typedef is use to
create new data types for example, let say I have an integer and the meaning that I want
to attach the integer is age. So, you do not want to accidentally mix it with something
which is of type let say volume or something which is of the type date and so, on.
So, this is age and it is an integer, if I just say it is int I could accidentally use this
variables somewhere else. But then, now what I am going to do is, I am going to say age
is typedef to be an integer. So, what I mean by that is, wherever I see age as a data type
intern it is actually just an integer. Now, if I declare age my age equals 99 it is clear that
my age is of age data type and I do not want to mix it with things like, volume or length
and so, on, it is actually about age.
So, this combination of doing typedef is particularly useful in structures. So, for
example, I could. So, we were using this struct point struct point and so, on repeatedly.
Now, I am going to use a short cut, which says typedef struct point point type. So, what
this does is it defines a new data type called point type, which is actually a struct of type
point. So, it is a structure of type point and the nick name for that is point type.
From now on I can avoid saying struct point point 1 and so, on and instead I can say
point type point 1, point 2. So, in the last line here we can see that this is very similar to
what we have do for basic variables, we put the variables data type first and then a
478
comma separated list. Only the variables data type does not have this extra thing called
struct followed by the structure name, instead it has this nick name called point type. So,
this is actually equivalent to writing struct point point 1 and point 2. So, it avoids typing
this struct point every time.
479
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 11B
Lecture - 37
More on Structures
Contents
Operations on structures
Functions and structures
Example: Screen and Centre point
Example: Point inside a rectangle?
Arrays of Structures
Structures and assignment operator
There are several things that you can do as Operations on Structures. Structures can be
copied by using assignment statement, that is a very common thing, you want to copy, let
us say one students record to another student or you want to copy the date to another date
and so on, you can do those things. You can put an ampersand before a structure variable
and you can get the address of the structure. So, remember structure has in turn has
records or members and these members in turn may have other members and so on. All
of them get packed as one unit in the memory back to back. If I put ampersand of
structure variable, I would get the address of the structure itself. You can take structures
and pass them on to functions, we can also make structures return from functions. You
480
can pass the entire structure, you can pass only the components or you can even pass a
pointer to the structure, just like you would do for variables.
For a variable, I could have transferred the variable or I could have passed on a pointer,
just like that you could do it here also. The only thing is you cannot compare structures
directly with each other. So, if I have int a comma b, I could do if a equals b; however, if
I have struct point a comma b, I cannot do is if a equal to b as a check. This is the
something that the compiler will catch and give us an error. So, this is probably one
major difference between basic data types and user defined data types. You cannot use
equality operator or less than, greater than and so on to compare two structures with each
other.
Let us see for an example, how structures can be passed on to functions. So, let us say I
want to write a small function which is trying to find out, if a point that is passed on to it,
is it the origin. So, origin is the point 0 comma 0, I want to find out, if the point that is
passed on to it is actually the origin. So, the function is called is origin and instead of
passing two integers, I am going to pass the data type called pointType and the local
variable name or the formal name is called pt.
So, we have talked about formal names and so on earlier, the formal name is called pt.
So, now this is the variable pt, and for pt you have pt dot x and pt dot y, because it is of
the type pointType. So, you are checking if pt dot x is 0 and pt dot y is 0, in which case x
481
and y coordinate are 0 comma 0 respectively, yes it is the origin, I return true else I will
return false. So, this is the program here, let us see how this little piece of code would
work.
So, let us say I have a point already in place and I call it is origin of my pt. So, let us say
this is inside your main function. You are setup my pt to be let us say 5 comma 4, you
want to find out is 5 comma 4 origin or not. So, when you make this function call, what
happens is remember, we can see that this is actually passing by value, functions and you
have something which is passed on by value. So, pt is something which is local and it has
two things namely x and y, and my pt is in the caller and caller has two things x and y.
And when you see this function call what happens is, just like what happens for basic
variables. The value of the structure is copied to here, what I mean by that is, the value of
the x member is copied to the x member of pt, and value of the y member of my pt is
copied to y member of pt. So, at this point pt has copies of the x and y members of my
pt. Now, you can do this check is pt dot x is 0 and pt dot y is 0, so you are going to go
and check these values and you return 1 or 0.
So, the keep thing to notice is that if you make a change at pt, it is not going to reflected
my pt, because you made a copy of my pt to pt. The reason being, this is the function call
where everything is pass by value, we are not passing the reference to my pt.
482
Let us look at another example, I want to make a new point with the given x comma y
values. So, given a x comma y value, I give the x coordinate and y coordinate and I want
a new data type of the type pointType. So, this function we are going to call MakePoint,
it takes two integers x and y and it returns a data type pointType. So, internally we have
a declaration called pointType temp. So, this is local to MakePoint and you change the x
member to the integer that you got and the y member to the integer y that you got.
So, let us see how this sequence would have work, you would have received, let us say 5
comma 10 as the two integers. Now, if you say pointType temp, you have temp which is
a structure and it is supposed to get two members x and y. Now, we say temp dot x
equals x, so I said x was 5, y was 10, these are the two values that we have passed. So,
temp dot x equals x would copy the contents of x into temp dot x. So, temp dot x
becomes 5 and temp dot y equals y, copies the content of y into temp dot y, so this
becomes 10.
So, at this point your temp has the value 5 for x and 10 for y and just like any other data
type, you can take this and return it, that is what we have in this piece of code. So, again
the key thing to notice is that we had copies of integers x and y and these two integers x
and y, there is no confusion of this x and y with respect to temp dot x and temp dot y.
Temp is a structure and temp dot x is a member within the structure, whereas x is a free
variable, it is of the data type integer. So, temp dot x is a qualification to get to the
member of this structure called pointType and in this case, it is of the particular in the
variable temp. Whereas, this x is a free variable of the type integer, it is not part of the
structure.
483
(Refer Slide Time: 06:58)
So, let us see a few other examples, so let us say I have some declarations of this type.
So, I want to represent this screen that you see here as a rectangle, so I assume that the
rectangle is already declared as a data type before. So, I have struct rect screen and I
have done a type def for point, so I do pointType middle. So, middle is going to be a
point and I want to find out given a screen, what is the middle point in the screen? So, we
want these things.
So, to find out the middle point, so let us say I start with 0 comma 0 which is the left
bottom of my screen. When I call MakePoint of 0 comma 0, this is going to return a
structure. So, the return data type here is a structure and on the left side, we have screen
which is a rectangle, but rectangle dot pt1 is a point. So, on the right side you have a
point and the left side you have a point, you have matching data types, everything checks
out.
So, we have already mark the rectangles left bottom as 0 comma 0. I want the rectangles
right top to be 1920 comma 1080. This is the resolution of my screen, I want the right
top to be 1920 comma 1080. So, again when I call MakePoint, this is going to return a
point. So, the return data type for this is a point and I want this to be setup as the right
top coordinate for screen, which is a rectangle. So, these two lines take care of setting up
the rectangle with the appropriate left bottom and the right top points.
484
And finally, I want to find out what is the middle of the screen. So, maybe it is
somewhere here, for my screen, I want to find out what this point is, so to do that I take
the bottom coordinate of the screen and the top coordinate of the screen, I add that and
divide by 2 and I take the left coordinate of the screen and the right most coordinate of
the screen, I add them up and divide by 2 that would give me the center of the screen.
So, these two things are integers and MakePoint takes two integers and returns a data
type called point. Left side is already of the data type point and the right side expression
gives the data type point. So, both the sides are of the same data type, so if we assume
that these declarations are already in place, this code using the structure will given a
screen it will tell me, what is the middle point of the screen is.
So, let us say I want to give you a point and I want to find out, is this point inside this
screen or not. So, I am going to write a function called IsPtInRect. So, you can think of it
as a question I am asking you, is this point inside the rectangle. And I am going to pass
the rectangle as r and going to pass the point as p. So, I could ask this about any such
point, it could be something outside the screen or inside the screen, I do not know.
I want to find out, if it is outside I want to get false and if it is inside the triangle, I want
to get true. So, I have let us say this as my rectangle and I could give you point p as this
or point p as this or point p as this and so on, it could be of any of these things. Now,
485
how do we find out, whether this is point is actually within the triangle shown here. So, I
want to find out, whether this point is actually within the triangle.
So, to do that if I know up front that the x coordinate of the point is to the right of the
beginning of the screen and the x coordinate is to the left of the right side of the screen,
then the point is somewhere in between the left and right side of the triangle. And if it
also happens to be above the bottom of the triangle and below the top of the triangle, if
all these four conditions are satisfied, then the point is within the triangle.
So, this point is not within the triangle, even though it is within the wide range of the
triangle, it is not in the x range. So, it is to the left of my left most point, so this point is
not a point in the triangle, so or this point, this point and so on, these would not be in the
triangle. For all these points, you would get false and points like this I have started with,
so point like this and so on will get true.
So, this is a small piece of code which actually takes two difference structures, one
which is a rectangle and one which is a point and remember rectangle r has pt1 and pt1
has x member and a y member. So, pt1 if it is the left bottom of point, it will have it is x
and y coordinates and we are comparing that with the coordinates passed on by variable
p.
486
So, just like basic data types, you can also do a few other interesting things with
structures. For instants, let us say I want an array of 10 points, may be I want to define a
polygon which has 10 sides, I could do it using this. So, struct point int x comma y and
pointArray of 10. So, what this gives us is, we define a structure called point with two
members x and y. We do not want just one variable of the type point, we want 10
variables of the type point.
The variables are going to be called pointArray of 0, pointArray of 1 and so on, all the
way up to pointArray of 9. So, you could also do it the way, it is given here, you can just
stop with the structure description that is given here on the top and then ask for
pointType pointArray of 10. So, this is also a valid way of asking for point, so in this
case I assumed that there is typedef struct point is given as pointType.
And finally, even if you have arrays, you can initialized them. So, this is an array of
structures on the right side, so pointArray is an array of structures. So, you can think of it
as I have an array and each one is a structure and the structure has x and y. So, this is
pointArray of 0 comma x and this is pointArray of 0 comma y, this is pointArray of 1
comma x and this is pointArray of 1 comma y, this is pointArray of 2 comma x, this is
pointArray of 2 comma y and so on. So, in this case the pointArray has three points, so
the x will get 1, the y will get 2 at pointArray of 0. Then, pointArray of 1 dot x is 2,
pointArray of 1 dot y is 3, and finally this would be 3 comma 4. So, this is the set up that
we have for array of structures.
487
(Refer Slide Time: 14:09)
You can access the individual members using arrays as given below. So, you can say
pointArray of 0 will be a structure. Given that structure, you can access dot x and get the
x member or you could even use pointArray of i to get the ith structure or ith member in
the array and in that you want the x coordinate and y coordinate to take the values 5 and
5. You can also do read of these values, pointArray of 0 dot x and pointArray of 0 dot y
can be used us as they are given here and printed and so on.
488
So, finally structures can be assigned to each other, we have been seeing this for a while
now. So, if I did MakePoint 4 comma 4, MakePoint is a function which is going to return
a point which it is x as 4 and y as 4. And since the right side is a structure of the data
type point and I should have the left side also to be of the same data type. In this case,
newPoint is also of the same data type. So, what it would do is, whatever is return from
here, it does member wise copy. It copies each member on the right side to the
corresponding members on the left side. So, you can assign structures to each other.
So, this is the useful thing, because you want them to be treated as basic data types. So,
this brings us to the end of this module on basic structures, and we will see how we can
do a few other things with structures by passing them onto functions and so on in more
detail in the next module.
489
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 11C
Lecture - 38
Using Structures and Pointers to Structures
Contents
Example: Complex numbers and Operators
Pointers to Structures
Precedence and association
Welcome to this module on Structures. In this module, we are going to look at how do
we use structures and how do we use Pointers to Structures. Because this is something
that comes in handy in many places, even in the course later you will be looking at the
notion of linked list and so on. And for that you need not just structures, but also pointers
to structures.
So, in this lecture we will go and look at this basic data structure, basic structure called
complex numbers. So, C does not give you a data type for complex numbers, there are a
few languages which give you that, but C does not provide you that and one way to get
complex numbers is by faking it. What we will do is, we will define a structure called
490
complex and the complex structure it has two fields or two members, namely real and
imaginary.
So, we are going to keep this as our complex number. So, complex numbers is going to
have a real number, and an imaginary number, and from now on complex is a data type
that I can use. I can take this data type, pass it on to functions, pass them back from
functions and so on. So, I am go to write a function call sum, which takes two complex
numbers m and n and returns a complex number. And we are also going to write a
function called product, which again takes two complex numbers and returns a complex
number. So, this in some sense is similar to passing two basic data types and getting a
value out of it. So, C does not prevent you from passing structures to or back from a
function.
So, let us look at how this might be done. So, in the first line here, we do declaration for
4 complex numbers - a, b, c and d. And we are going to take the complex number c as the
sum of the complex numbers a and b, and the complex number d is supposed to be the
product of the complex numbers a and b. So, we first go ahead and scan the real and
imaginary parts of a and b, and once that is done, I would like to do something like this.
c equals sum of a comma b, it should add up the corresponding real values and put it as,
491
c is a real value, take the imaginary values of b, add it up and put it to the imaginary
value of c and so on. And I may also want to do things like product of a comma b, print
sum and product. So, this is something that I want to do, let us go and see how the sum
function and product function are going to get return up.
So, the sum function is supposed to take two complex numbers m and n. So, inside we
have a local variable called complex p, so p is a local variable. The p’s real value gets m
dot real and n dot real added up and p’s imaginary value gets m dot imaginary and n dot
imaginary added up, and you return that to the caller. So, if you notice p is return to the
call are here, so you can see that here, p is return to the caller.
If you go back to the caller, the caller is expecting something of the type complex
numbers, the left side is the complex number, so everything is good there. Similarly, if I
want the product, so we know that product of two complex numbers is given as follows.
So, the real part comes from the product of the real part minus product of the imaginary
part, and the imaginary part is the product of the real and imaginary combinations added
up together.
So, this gives you the real part and this is the imaginary part. Again we have a local
492
variable of the type p, and you compute what should go into p dot real and p dot
imaginary and you return p. So, you are returning a complex number and when you
return on this side the products complex number. So, this the complex number that you
got in return, the real part will get copy to d is real part, and the imaginary part that you
return will get copy to the d is imaginary part. So, this is the way to pass on structure
variables to functions and get back structures from functions. So, as of now we did not
know have a mechanism I did not show you a mechanism to pass on pointers to it.
So, we will do that now, let us look at this basic thing. So, what we have is you are going
to have pointType pt1, so pointType point1 and star ptr. So, let us take this a little slowly,
so we already know pointType is defined to be struct point. So, point1 is a variable of the
data type struct point, and star ptr on the other hand is a pointer to the structure of the
type point. So, or it is a pointer to pointType data type, so here this is like a basic variable
and this is only a pointer.
So, when we say point 1, we actually allocate two members x and y and give it to point
1, whereas when we say point type star ptr, we do not do an allocation for a structure. We
just allocate space for a pointer that can point to a structure, we do not allocate a
structure yet. So, point 1 is make point of 3 comma 4, this will give you a point whose x
493
coordinate is 3 and y coordinate is 4. Now, you can do things that you did on basic data
types.
So, ptr is ampersand point1, takes the address of the structure point1 and assigns it to ptr.
Remember, it is not taking the address of the members of point1, it is taking the address
of the whole structure point1 and it is passing that as the value to ptr. So, at this point ptr
is going to point to the structure. So, let us say point1 is of this type, so this is point1, it
has some x and y coordinate and this whole thing is going to sit inside some memory, it
is going to sit somewhere. When we say ptr, ptr is also going to get one memory location
and as of now, it is not pointing anywhere. The moment you say ptr is ampersand of
point1, it would start pointing to the structure here. Actually, technically it points to the
first byte of the first member of the structure, it goes to that location. Now, we want to
know how to dereference this, like we did for pointers in basic data types and the way to
do that is, use what is called as star operator.
So, in star operators, so let us see what this is doing. So, printf percentage d, percentage
d star of ptr dot x, so ptr is of the data type pointer to a structure. If I do star ptr, I get
structure, and therefore if I do structure dot x, I get to a member. So, this thing what you
seen in the blue circle here is correct, because you started with ptr which is a pointer to a
structure, when you do star ptr it actually dereferences the pointer and gets the structure
and you are looking at structure dot x. So, it looking at the member x and member y here
respectively.
So, the parenthesis here is actually crucial. So, thus star and parenthesis have different
presidents. Therefore, you have to put this whole thing inside, the star and ptr should be
within the parenthesis, but this is sometimes very laborious. So, C gives you another
shortcut to look at the member that is pointed to by a pointer of a structure, shall I repeat
that. I want to get to a member that is pointed to by pointer of a structure. So, the pointer
of a structure is ptr here and I want to get this member called x.
So, one way to do that is use ptr arrow x. So, it is not an arrow symbol, you do not see a
arrow symbol on the keyboard, it is actually a dash followed by the greater than symbol
or a minus sign followed by the greater than symbol. So, it is two characters, so when
494
you say ptr arrow x, if you see something like that what it means is, ptr is expected to be
of a pointer data type. It is not just any pointer, it is a pointer to a structure and when we
use arrow, you actually get to the member.
So, ptr arrow x takes you to the x member of the structure which ptr is pointing to and ptr
arrow y will take you to the y member of the structure that ptr is pointing to. So, this is
one way to refer to the contents pointed by ptr, you can always do star ptr dot x, but be
caution that you have to use parenthesis around star ptr.
So, now let us look at the notion of a precedence and association. If I have a combination
of various operators, what gets precedence and how do I interpret, do I have left to right
association or right to left association for the combination of operators, we will look at
that. So, both dot and arrow are actually at the top of precedence hierarchy. So, if you
have an expression which has a dot and an arrow along with plus and minus and so on,
dot and arrow gets highest precedence over everything else.
So, let us see a small example here, we have a rectangle called r and we have a pointer to
a rectangle which is called rp and rp is made to point to rectangular or itself. So,
remember rectangle r is supposed to have, so let us say this is the space allocate for
495
rectangle r. Rectangle r itself had something called pt1 and something called pt2 and pt1
in turn had x and y, pt2 in turn had x and y. So, this should be the layout for r, r is a
variable, struct rect is the data type.
So, this is the layout for r not for rect and rp, the moment you say struct rect star rp, rp
will get a memory location which is supposed to point somewhere and what is it
pointing, it is pointing to ampersand r which means it is pointing to the first byte of the
first member of r. So, it is actually going to point to the address where pt 1’s x is present
inside the rectangle r. So, if I say r dot pt1 dot x, I am referring to the pt 1’s x member.
I can also say, rp arrow pt1 dot x, so in this case the way to interpret that is rp arrow pt1
is a structure and you want the member x of that structure. So, this is where the
precedence and association comes in, both arrow and dot are of the same precedence and
they go from left to right. So, therefore the way to read that is first do dereferencing with
respect to rp, that gives us a structure and that structure dot x gives us the member.
So, what this does not do is, it is not interpreted us, I will first take pt1 dot x. So, pt1 dot
x what is the data type for it? Pt 1 is a structure of the point data type and pt1 dot x is
actually an integer and if I say rp arrow something, there is no rp arrow integer. Because,
rp is a pointer to a rectangle and rectangle has only points inside, it does not have
integers inside. So, the association went from left to right and the precedence also went
from left to right. This is the correct interpretation, it is not interpreted as rp arrow pt1
dot x.
And you can also put arrows if you are in doubt, if you do not really know what it is
doing and if you want to be sure, what it is supposed to do, you can always put arrows
around them. So, in this case you can always put parenthesis around them, in this case
you put parenthesis, so that r dot pt1 is supposed to give a structure of the type point.
And when you do dot x, it gives you the x member of that point. And similarly rp arrow
pt1 will give you a structure of the data type point and it is now to take the data type to
do a dot x. Whereas, it should have been incorrect to do rp arrow pt1 dot x.
496
(Refer Slide Time: 13:39)
So, this brings us to the end of module on the structures and how to use pointers along
with it. There is also lot of things that you have to be careful, when you use operators
like plus and minus and so on when especially, when you have the dot operator and the
precedence of and the arrow operators. So, remember that dot and arrow has higher
precedence over various other things like plus and plus plus and minus and so on, just
keep that in mind. And when you are in doubt, always put parenthesis around the
structures that you want. So, this brings us to the end of module and thanks for watching.
497
Programming Data Structures, Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module - 11D
Lecture - 39
Dynamic Memory Allocation
Contents
Why allocate memory on demand?
Allocation of memory space in programs
Static versus dynamic allocation
Malloc ( ) and its use
Deallocation of memory space
Free ( ) and its use
Example: Create_date
Pitfall: Allocation inside functions
Incorrect and correct returns of pointers from functions
Allocating and freeing multi_dimensional arrays
Hello all, welcome back to this lecture. In this lecture, we are going to look at what is
called Dynamic Memory Allocation. So, in all this time so far what we have been
looking at is, we know the size of the array is or we know the number of different
integers we want or the number of floating points, we want and so on, and that is not
always something that you get. So, many times you may want to ask the user, how big
the number of elements you want and based on that you want to allocate arrays of an
appropriate size and such.
So, in this lecture module what we are going to look at is the notion of dynamic memory
allocation. It is so fundamental to various programming languages to be able to allocate
memory only on demand. Only if the program needs it, you allocate memory and
whenever you do not need it that memory has to be reclaim back and so on. So, to come
to this lecture, you should have a reasonable understanding of what pointers are... And if
you have not gone through the lecture on pointers, so I strongly suggest that you go and
review the lectures on pointers, before you come back here.
498
(Refer Slide Time: 01:22)
So, let us go back to the very first set of things that I talked about, the notion of
allocation of space. So, whenever we declare a variable of any data type, there is
allocation of space done to that. So, you take the amount of memory that is there and you
go and reserve a certain sequence of bytes and say that this is useful for this variable and
no other variable can use this location. This is what we have been doing so far.
Whenever I have int x or float y and so on, we assume that there is a particular set of
bytes, it is already allocate by the compiler for you to go and use. So, sometimes when
you look at these data types, there are two questions that one could ask, and these two are
not the same, they are related, but they are not the same. When do we know the size to
allocate? So, if I ask you int, if I have int comma int x, do we know the size that we have
to allocate, yes if I specify int x I know that I have to reserve space for an integer.
So, generally if you know the size that has to be allocated, which means if you know
statically how much has to be allocated, you would also do the allocation statically.
499
However, if you do not know the size of the array that you want and so on, you have to
wait till the program starts running. When the program starts running, you may have to
ask the user how much you want. Based on that you need a mechanism by which you
make reservation for only as many as bytes as the programmer wanted. So, that is called
dynamic memory allocation. And let us see, how the process goes.
So, let us see this example, character c. The moment a compiler looks at it, it knows that
it means only one byte, because characters required only one byte. However, if you have
a declaration of size int array of 10, so you need an integer is 4 bytes typically and that
into 10 times, you need at least 40 bytes. If the size is known, it is generally also good to
go and allocate the space. As soon as the program starts running, you will have 41 bytes
allocated, 1 byte for character c and another 40 bytes for array of 0 to array of 9.
However, sometimes we do not know how much memory to allocate. So, if have int star
array, we may not know how big the array is going to be. So, it is a pointer to an integer
right now. So, is it pointer to just one integer, is it a pointer to 10 integers, 100 integers, 1
million integers, we do not know that yet. But all we know is, at this point of time we
know that it is a pointer to an integer. So, how do we use this?
So, int star array if I have that I could make array point to something that is already
allocated. So, I may have something like this. So let us say I had a declaration of this
form int b of 10. So, b is already allocated it is space for 10 into 4, 40 bytes at least and I
500
could make array point to b. So, I have b which is of 10 integers and I could make array
point to that. So, when you make it point to that it will point to the 0th location of b.
So, in this case allocation is already done, but there are also places, when the declaration
is not done. In which case, you have to go and declare or you have to go and claim
memory, you have to allocate new memory and claim that space to be yours right now.
So, this is something that you probably remember from an earlier lecture. So, when I said
something like int star p, p is just a pointer variable, it does not allocate an integer.
It only allocates a pointer which can point to some integer, but the integer itself is not
allocated. So, int star array may have to be pointed to something that is already allocated,
but if it is already allocated, I should know the size. So, this gets in to a loop, this gets in
to a problem of find, knowing the size ahead of the time. So, what we want is a
mechanism by which the size may not be known and during the run time of the program
and the program already starts running, only then you get to know the size and you want
to allocate size is appropriately.
So, to do that there is a function called malloc or m alloc as some people would call it.
So, malloc is a function in C and it can take an integer as it is input and it returns a
pointer as it is output and this function in this library file called stdlib dot h. So, it is
already given as a library to you, you do not have go and write your version of malloc.
501
So, malloc is a function that is already defined in stdlib for you. What it does is as
follows.
You pass a size to it, you pass a number of bytes that you want. Let us say I want 10
integers, each integers is of 4 bytes, I will pass 40 which is 10 times 4 to malloc. What
malloc would do is, it would go and scan the memory that is there, find out if there is an
unused space. If there is a contiguous chunk of unused space of 40 bytes, it will claim
that as a space that you want now and return it to you. So, if I have a huge memory, it is
possible that some portions of the memory are already taken and some portions are not.
When malloc is called, let us say I called it with a 40 bytes, I want 40 bytes to be
allocated. We will go and looked at the first free space and ask, is there 40 bytes in this
space? If the answer is no, it would go and look at the next white space wherever there is
no allocation done, is there 40 bytes there. Once, it finds out some free space with 40
bytes which are continuous, it will return a pointer to the caller. So, in this case in this
line here, the caller is at this point.
The caller called malloc, malloc would return a pointer and what pointer does it return. It
actually returns what is called a void pointer. So, it returns something called a void
pointer which means, the data type is not important, I given you space. And then, it is up
to you to take that and typecast it to a integer pointer or a character pointer and so on. So,
in this case we ask for 10 integers 40 bytes, you will get a pointer to 40 bytes.
So, you make that a data type int star, it now means you have a pointer which is a integer
pointer data type. On the left side, you have an integer pointer data type and once you
typecast, the right side is also an integer pointer data type, these two are matching. So,
you can copy the value of this to the left side. So, now how do we ask for 40 bytes? We
did not ask for 40 bytes explicitly, instead we said give me number of items into size of
an integer.
So, this is something which is a common thing that you will see in malloc. Instead of
actually specifying the number of bytes, you say the number of elements that you want in
to size of each element. In this case, num items is the number of items that we want and
what we want, we want integers of so many count. So, sizeof is an operator in C, if you
pass a variable to it, it will return the number of bytes required. We have already seen
this before.
502
So, this void star in this case is typecasting to int star and this is the way to allocate
memory. So, again in summary malloc is a function which is in stdlib, if you pass the
number of bytes to it, it will go and scan the memory and find out if there is an unused
space of so many bytes. And once it finds out, it will tells that this location is free for you
to use, go ahead and use it and you use it as a contiguous space of memory and
contiguous space of memory is usually arrays. So, you get a pointer to an array.
So, let us look at this small example here, so we have two pointers namely, star i and star
array. So, i is a pointer of integer type and array is also a pointer to integers, so in this
line here, we have i equals int star malloc of sizeof int. So, we are claiming space only
for one integer and i is going to point to it. So, i is a pointer to an integer, whereas here
we had, as for number of items into sizeof int, you are asking for more than one integer
depending on num items.
You get a pointer to so many bytes which can occupy num items integers and you get a
pointer to that, that goes into array. So, both i and array are pointers to integers, only that
i is pointing only to one integer, whereas array is pointing to a series of locations, in this
case num items location. So, array is pointing to num items integers, so you can do star i
equals 3, so that will change the contents of the memory location pointed to by i to 3 and
you can do things like array of 3 is 5.
503
So, array of 3 is 5 is valid, because if num items is greater than 4, then array of 3 is valid.
So, array of 3 will point to the third location in the array and that is changed to 5. I and
array are inter changeable just, because I have 1 integer allocated here and 4 integers or 5
integers allocated here, it does it mean that i and array are incompatible. If we go back to
the definition, both are integer pointers. So, i and array are interchangeable.
So, arrays can be thought of as pointers to the 0th element, we have been doing this for a
long time now. Whenever I have an array, I have been saying that we can abuse the
notion of an array and say that is actually pointed to the 0th location. So, I could also
point to an array as well. So, in fact I can do i equals array and i would starts pointing to
the array, which array was pointing to earlier and these things can be changed over time.
Both the variables, i and array can changed over course of the program.
So, one thing that we have to be careful about is I said, if malloc goes and finds out the
chunk of space, if that is free it will return the pointer to the chunk of space. What if
malloc does not find any free space? You wrote a program which is supposed to process
a lot of data and you want more memory allocated to the program, you called malloc.
Malloc went and looked at the memory and what of there was no more memory
available.
In this case, actually malloc does not return a valid location in the memory, it returns this
special case called NULL. We have seen this before, this NULL in capital letters is a
504
special symbol, this cannot be any valid pointer. Because, it is not any valid pointer and
malloc if it returns that you know that there is some error that happen. Malloc try to find
out your 40 bytes or 100 bytes or 1 million bytes or whatever and if it was not able to
find out contiguous chunk of space of so many bytes, it returns with NULL.
So, it is your duty to go and check, if malloc return NULL or not. If it returns NULL, it
means some problem happened in memory allocation, you can print it to the user that
you actually run out of the memory and you can do exit. So, this exit of one is again a
function call, which indicates some kind of an error. So, the moments some problem
happens, you may want to exit from the program and say, I did not have enough space, I
could not have continued running the program.
But however if a is not NULL, you will not print anything on the screen, you will not
call exit. So, a is successfully pointing to some allocation that you did and you can use
the a you want it. So, this is something that you are keep in mind. Even though, I am not
going to show this thing in all the subsequent code. So, I will not have malloc followed
by this check every time, it is something that you have to do every time. You have to go
and check, whenever you do a malloc, it is possible that there is something that malloc
can not allocate. Therefore, you have to check whether it return NULL or not.
It is also not enough to just allocate memory, you also have to think about what is called
deallocation of memory. So, let us go and look at this piece of code here. I have a
505
function f which has int y and array of 10. These are local variables within the function f
and so I know this from functions that since is a local variables, they became alive only
when the function is called and their automatically killed and returned. When the
function returns, they are automatically destroyed. So, we talked about this, when we
delete with functions.
So, local variables are good, because when you call the function they became alive and
when you return from the function, they became dead. So, when you go to the end of this
line f here, y this is an integer is de allocated, array which is an array of 10 integers is
also de allocated. You cannot refer to y or you cannot refer to array, the variable array
anywhere outside f. This is something that we know already. So, all automatic variables
are created automatically and destroyed automatically, that is why they are called
automatic variables.
Whereas, if you do call something with malloc, you are getting a pointer to the memory
that was pointed to and it is your duty now, you claim the memory you wanted some
memory and you have to free it out. So, it has to be done using this function called free
and forgetting to de allocate. So, let us say I claim some memory and I do not have used
for it anymore, I have to de allocate it and for that I will use free. So, for example here
int star a is malloc, so many bytes as num item integers demands and I used that.
506
If I do not have any use for it, I should go and free up a. So, if you do not do that what
might happen is that this chunk that you allocated may remain as both. Somebody
claimed it and somebody made a reservation and it is still been used, that is how the
complier would treated. So, this is like you go to a hotel, let us say you go to a restaurant
you make a phone call and you reserve a table. You reserved the table, but you never
turned up.
Then, the restaurant would have to know at some point, whether somebody is going to
turn up and use the table or not. And if you are not going to call, if you are not go to the
restaurant, it is good for you to call the restaurant and tell them, I even though I called, I
made a reservation I do not want it any more. So, that is equivalent to free or you
actually went to the restaurant, you had your meal you return back and that point it is not
reserved anymore. So, you have to say that this table is not reserved any more, it has to
be freed again.
So, this is the equivalent or analogy that I can give you for malloc and free. Whenever
you claim some space, it is your duty to go back and tell the system to reclaim it. You do
not need this anymore. So, once you free it, you cannot use a anymore below this line.
So, once I have freed a I do not have space reserved for a anymore, a is not pointing to
anything. Therefore, I cannot say a of i equals 5 and so on, i cannot do that.
I should either go and do new allocation or I should make a point to something that is
already allocated. So, I could do something like a equals b, if b was declared as int b of
10 or I could do new allocation as it was given here. So, you must not use a free pointer,
unless it is re assigned or re allocated. So, keep this in mind.
507
(Refer Slide Time: 19:19)
So, space allocated by malloc is freed when the program terminates. So, if you do not
free of memory ever, so you allocate some memory, you use it. But let us say you do not
do free and you keep doing that. So, slowly and steadily your memory will get used up,
at some point of time your memory might become full. So, in any of these cases if you
do not do explicit memory de allocation, when the program exits, your program is done
with it is work, you do not need to claim this space anymore. Automatically, everything
gets de allocated by your operating system. So, you can wait for your program to
terminate and free up all memory that your program claim. So, some people do that, but
that is not a good practice. So, the good practice is assume when your program does not
need some memory as well deallocated, because your own program may need more
memory for doing some more work later.
508
(Refer Slide Time: 20:20)
So, let us look at a small function called create date. It is going to take three integers
month, day and year and it is supposed to return a date. So, let us say I did this, Date star
d. So, d is a pointer of the type Date and remember, it does not allocate three things for
month, day and year. It only declares a pointer to type Date. So, you have a space for a
pointer, but the actual members are not allocated yet and let us say you did this.
D is month, it is supposed to be the month that you passed on, d is day it is supposed to
be the day that you passed on and d is year, it is supposed to be the year that you passed
on and return the pointer d, let us say we did this. So, this should be wrong, because d is
not allocated any memory. So, the correct way to do that is this follows. D is a pointer,
first allocates space and how much space do you want to allocate?
I want to allocate space for one Date type and what does the Date type have? It has one
month field, one day field and one year field, we need three things. So, when you see
sizeof Date, sizeof Date is three integers, one for month, one for day and one for year.
And when you do malloc of sizeof Date that returns a pointer that is assigned to d. So,
now d is actually pointing to an allocated space. Once it is pointing to an allocated space,
it is to do d arrow month, d arrow day and d arrow year.
And when you return d, you are actually returning a pointer to the reserved space. So, the
reserved spaces still exist and you are returning the pointer to the reserved space. So, the
function on the right side is correct, whereas the function on the left side is not. Because,
509
you did not allocate space on the left side, you explicitly allocated space on the right
side. So, one thing we have to do is even though the space is explicitly allocated,
remember create date is not responsible for de allocating it. You are not explicitly de
allocating it yet. You are only explicitly allocated it. It is somebody’s job to come and de
allocated later, we will see who is this somebody must be later.
Let us see a small thing here. So, Date star today, today is create date 9, 1, 2005 and do
return. So, I want to do let us say 9th of January 2005. I created a variable called today.
So, today is a pointer to the data type called Date, create date in turn returns a pointer to
data type called date. I used today and then I returned it. So, at this point create date
allocated some memory and you got that in today. So, let us look at the sequence of
things.
So, inside this function called foo, so today is a local variable and what is today, it is
actually a pointer. It is supposed to point to a Date data type. And when you call create
date, so inside create date your allocated space one for date, one for month and one for
year, you allocated space for the structure, you return the pointer to this. So, at this point
your today field is pointing to the location that you gave for the structure. And let us say,
we use today and we return, we came to the end of it.
And this point we return from foo, what would happen now? Since, today is a local
variable within foo, today gets destroyed. It is an automatic variable, the automatic
510
variable gets destroyed. So, whoever called foo cannot access today, because today is a
local variable within foo. And because today is lost, you also lost a pointer to some
reserved space that you had. You reserved space for a structure, but today which was
holding the pointer to the structure was destroyed.
Therefore, you lost the trail to go back to the memory that you allocated, so this is the
problem. So, whenever you have something that is allocated from a function and if you
put that in the local variable and if you did not explicitly de allocated, that thing is still
going to look around. This structure that you had is still going to look around and you
will have no way to free it, this will get freed only when the program gets done. So, you
have to be careful about it.
So, there are various possible solutions and the cleanest solution is, let us say, I did
create date. I did whatever I want to do with Date, I know that I do not need date
anymore and I am returning from the function as well free today. So, that when you say
free today, the function will go and looked at the space allocated are reserved for it and
remove the reservation. So, that space can be used by the program, some other part of the
program. So, this free takes care of that and when you return it, you already taken care of
whatever allocation you have done, you have removed your reservation.
511
(Refer Slide Time: 25:53)
So, now I am going to show you a sequence of various things and I want to argue about,
why something is correct or why something is incorrect. So, let us look at the function on
the left side. So, int star f, so f is a function and i is a local variable within the function
and you are returning address to the local variable. So, that is what this int star does. So,
int star means you are returning a pointer to an integer. Is this something that you can
do?
This is not possible, because when the function gets return i is a local variable, i gets
destroyed, it is not correct to track the address of i anymore. So, this is not correct. It is
de allocated i, you cannot track it anymore. Let us look at this function on the right side,
it is also supposed to return an integer star, it is returning a pointer to an integer. So, here
you have a local variable called array, which is an array of 10 integers and you return
array which means, you are returning the location of the 0th element of array.
But again this return array is supposed to return a pointer to the 0th location. But since
array is local to make array, array gets destroyed when you return from make array. So,
you are having a pointer that is actually pointing to a location that you cannot claim to be
yours anymore. So, this is a problem, so if you do this, sometimes it may look like this
whole thing is actually working. So, this might work, because when you return back
from this make array, may be the space is still containing all the values, nobody
reclaimed it.
512
So, when you go back to that location, you still see the values. But if you are unfortunate,
this memory location that you did not want anymore got used up somewhere else and
now you are actually accessing something that is not supposed to be used by you. So, the
space if it is reallocated this becomes unpredictable, but if the space is not reallocated to
anyone else, it might work. In general, it should not rely on the space to be unallocated.
You should not believe that I am hoping that it is unallocated, let me go and use it. That
is not correct. You should treat, you should be conservative and you should say that I
will assume that it is going to be reallocated. Therefore, I will free it up here and I will
not pass pointers back to local variables.
So, if you want to really pass pointers to local variables or anything that you allocate
inside, you first of all do dynamic allocation and you pass on a pointer. This is correct.
Similarly, here you did dynamic allocation, so these 10 integers are not going to be de
allocated when the function returns and because of that it is to return a pointer to that. So,
you allocate with malloc and return the pointer, so this is malloc.
513
(Refer Slide Time: 28:53)
So, now let us go and look at, how to allocate multidimensional arrays. It is not just that
we looked for single dimensional arrays or array of integers, array of characters and so
on. Many times you want to build matrixes or 3D matrixes and so on, how do we do it.
So, we starts with int star star p. So, the meaning of that is p is a pointer to a pointer to an
integer. So, let us see what that means. So, p is not pointing to an integer, p in term is
pointing to another pointer which is in turn pointing to some integer. So, that is the
meaning of int star star p.
So, you half M pointers, we do that first and then each pointer variable, you now make
an allocation of N integers. So, you allocate M pointers to integers and then, you allocate
N integers at a time and this is supposed to give you N integers. So, each row you get
from malloc and you have M rows that gives you M cross N matrix. So, let us break this
down, the first thing we have done is a malloc of sizeof int star. So, sizeof int star is
sizeof integer pointer, not sizeof integer it is sizeof integer pointer and we want M
integer pointers.
514
Let us say you got them, you got M integer pointers and they are not going to point to
anything, because malloc does not initialize. So, what you get in return? Malloc will
return a pointer to 10, let us say 10 rows. We will get pointers to an array of pointers and
these are pointing to 10 integer arrays. So, an int star star tells you that the left side is
expecting a pointer to a pointer. It is not a pointer to an integer. It is pointer to a pointer
to an integer.
And once you have that you have all these pointers, which are ready to now point to the
ten different rows, but you have not allocated space for the rows yet and that is done
inside a loop. So, this loop goes from i equals 0 to M minus 1 and you allocating one row
at a time and each row is of size n integers. So, that is what you have here. So, this is the
very basic way in which M rows by N columns is allocated. At the end, you actually
have M and N elements.
In fact, once you have this you can use p of i j to access the elements. So, just like you
would access array of i comma j, two dimensional array of i comma j, p is a pointer to a
pointer to an integer. You can use p of i comma j to get to the basic elements. So, it
would be incorrect to access p of i j, if you have not done this or if you have not done
this. You should have done both these mallocs, so you should have malloc for 10
pointers to integers and you should have allocated for each pointer to point to a row of N
numbers. So, that will give us 10 rows by N columns.
515
So, let us look at how to de allocate multidimensional arrays. So, right now you have let
us say 10 pointers pointing to one row each and each row, it is of size N elements. So,
you have M of these and you have N of those. What you do is, you first go and do free of
p of i and you iterate i from 0 to M minus 1. So, when you say free p of i, you start with
p of 0, free p of 0 will de allocate that. Free p of 1 will de allocate this row, p of 2 being
free will de allocate this row and so on, free p of M minus 1 will de allocate this row.
So, you have done with de allocating all the integers, but you still have all these pointers
that you allocated and the way you allocated that was p was pointing to all these 10
things as though, they were an array of pointers. And when you do free of p, it also de
allocates all the pointers. At this point, the pointer p is not pointing to anything valid, you
make p equals NULL. So, essentially what you have is you have a sequence of steps.
When you want to allocate two dimensional arrays, you first go and allocate a pointer to
a pointer to the basic data type and you iterate over that one at a time and allocate rows.
When you de allocate, you de allocate all the rows one at a time and then you de allocate
the array of pointers itself. So, this is the way you allocate and de alocate pointers. So,
first free all the rows, free the array of pointers, make p point to the NULL and you have
done.
So, this is p will keep pointing to NULL, until there is new allocation. So, with this we
are end up at this lecture about pointers and how to use dynamic memory allocation for
not just 1D arrays, it can also be 2D arrays. The basic element that you stored in an array
can be an integer, can be a float, it can be a character, it can also be a structures. You can
have a 2D matrix of structures, where each element could be an integer containing x, an
integer containing y may be you have a 2D array of pointers that is possible.
Thank you.
516
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science
Indian Institute of Technology, Madras
Module 12A
Lecture - 40
Linked lists
Welcome to this module. We will look at what link list are, and we will see how
structures can be used to represent link list. So, we have seen how structures are defined.
There is one important class used for structures and we will see how to do what is called
self-referential storage.
This is a very useful thing to do and this is also something that you are going to learn in a
lot of detail when you do what are called data structures. So, I want to do this in C little
bit and also, use the example here to motivate the need for c plus plus. So, the reason
why we are using c plus plus in the following lectures on data structures is that,the
syntax becomes much simpler and cleaner. Even though course is based on c
programming, there are few lectures which are done using c plus plus, and it is good for
you to know the syntax of c plus plus so, that you can appreciate what is happening in
those lectures.
517
(Refer Slide Time: 01:15)
So, let us start with the list. List is nothing, but a collection of items. So, maybe I have a
lot of books at home and I want to maintain a list of books on my shelf, or I teach a class
and I want list of the students in my class or even for a particular student, I want to track
all the list of marks in that particular semester and so, on. So, clearly you can implement
this using array. One small issue in arrays is that every time there is change in the
number of items that you need, you either have to shrink the array or expand the array.
So, C it does not give you a nice feature, to either expand of shrink array. So, if you say
array int a of 100, you have 100 integers and nothing more, right. So, this is a small
problem. Maybe I have only 90 students in my class, but I allocated space for 100 or
worse, I have 110 students in my class and I have space only for 100 students. It would
be nice to be able to expand or shrink based on the necessity and one basic way of doing
that is using what is called a link list.
518
(Refer Slide Time: 02:26)
So, let us see how link list can be implemented. So, usually what you have is, you have
the notion of what is called the data. The data is what you want to really maintain, but
you define a node, a structure called node which has the data that you want. It also has
something called next. So, such a structure would look like this. You have struct node
and you have let us say double data node star next. So, what we are going to do is, we are
going to use this field called data to store the data that we want and we are going to have
this field called next which is going to point to the next node. So, you can see that there
is struct node here, and node star here. This is what makes it self-referential. So, you are
having one element of the type whatever we want in this case it is double and we are
going to point to the next element which is of the same type and so, on.
So, you can think of this as playing treasure hunt. So, you start with one location where
there is a clue, and this gives you a pointer to somewhere else, where there is a new clue
and you keep chasing the clues till you go to the end where you have a prize. So, this is
how this game called treasure hunt works. You can think of link list in a very similar
way. You have a data instead of clues and you also have clue which tells you where to go
next and then, you go there and you have another clue and so, on. You have a chain of
these things, at some point, this list ends. So, I already mentioned self-preferentiality
comes from the fact that you are referring to node of the same type and here if you notice
we have struct node here this node is immediately visible. Here the data type called node,
it is immediately visible here. That is not a problem. C allows you to do that and c has
that feature specifically because it wants to support the self-referential structures.
519
(Refer Slide Time: 04:45)
So, now, let us say I have three nodes, namely a, b and c and let us say that node a is
supposed to have a value 12, node b is supposed to have value 17 and node c is supposed
to have value 14, and we have data, and what are called the pointers. What we are going
to do is we are going to chain them together. From a we link its next to b, from b we link
its next to c, and c is the last node in the list, and we do not what it to point anything. So,
we need to have some null at the end. So, now, we can see that this is the link list. So, it
is a list, but to reach one element, you have to start from the first element, follow the
links that point to the next element and so, on and keep going until the end. So, if you
want to reach 17, you cannot access 17 directly. So, in an array I would have accessed a
of 1 that would have taken me to the second element in the array, a of 2 would have
taken me to the third element in the array and so, on. However, in link list you access the
first element; you chase the pointer here, and get the next element, then chase its pointer
to the next element and get to the element after that and so, on.
So, we assume that a, b, c are all pointers here. Therefore, if you do star a, it gives this
structure and you can do a dot next equals b. So, remember b is pointer. So, a next field
is also a pointer so, these are compatible. Finally, for null I already mentioned that there
is keyword called null in c. So, star c dot next is null which means c has nothing
following at. So, if you are using some operation, if you are going through the list one
after the other, once you see null, you are at the end of list, you cannot go any further.
There is another way to do the same thing. Instead of star a dot next, you can also do a
arrow next equals b and so, on. We already saw this as pointers to structures and we saw
520
the arrow operator earlier. So, now, if I ask you what is a’s next, next? So, what is next
to next a? Then, you start with a, you take next, you go here, one more next that goes
here. So, a next to next is actually the pointed to this node called c here. So, whatever c is
pointing to that is the point you will get.
Let us see how this… So, all these looks abstract, right. I have a list and so, on. Let see
how this looks in memory. So, we want this abstract list, we want node 12 linking to
node 17 and node containing 17 should link to node containing 14, and node containing
14 should not point to anything more, this is what we want and these nodes could be in
different locations in memory. For instance, may be node containing 12 is at location
600, nodes containing 17 is at location 800 and node containing 14 is at location 700 and
so, on. If you establish the link list finally, this is how it must look like. So, you will have
12 at location 600, and the next field contains the address of the node containing 17.
What is that address? It is 800, so, that is what we have here. And if you go and look at
node containing 17 that is at location 800, what is its next value. We want it to point to
the node containing 14. So, that is at location 7, 12 and that is what we have here in the
address and so, on. So, in the memory this would look like this, and as an abstract way, it
is what you see here. So, 12 is next to 800 which means if you go to location 800, you
get the value next pointer. If you go to location 7, 12, you get the value and next pointer,
and null is nothing other than 0. So, null is defined to be 0 in c, and this is fairly simple
and straight forward way of using a structure to make a link list.
521
(Refer Slide Time: 09:15)
So, there are several operations that happen on link list. You may insert a node, you may
delete a node, you can find a node, or display the nodes and so, on. This is a very basic
set of operations that we do. Let us say I have 100 students to start with and another 50
students signed up for the course. I go on and insert 50 more nodes and have them in
order. May be some student drops out of my class, I want to delete that node or I want to
find the student who has the particular role number. I may have to search through the list
and find it and so, on. So, the notion of link list allows me to do all these things one after
the other.
522
Let see how insertion of node might be implemented. Let us say I already have a list
where I have nodes containing 12 and nodes containing 14. I have 2 nodes and maybe
there is the list that is following that, I want to insert this node containing 17 between 12
and 14. Let us say I want to do that. You have to do a series of pointer manipulations.
What you really want to do is, you have a new node which contains the value 17 and
since, you wanted between 12 and 14, you want this nodes next pointer to be 14, right.
So, how do you do that? If this node is b, b is next should be a’s next that is what we
have here. B’s next is a’s next. Now, this does not form a link list. So, it is not a chain of
nodes that we have now. Instead we have 2 nodes pointing to 14. There is no way in
which you can reach this node containing 17 from the node containing 12. We need to
change that and the way to do that is, if you make a’s next point to b, then a’s next feel
pointed to b. Now, if I start from a, so, a’s value 12 and a’s next will point me to b. B is
value 17 and b is next will point to be 14 and so, on. So, now, you have a link list, and
you have one element linking to another linking to another and so, on.
If I want to insert at the beginning of the list, let say I have struct nodes to my list that is
pointed to the whole link list, and I want insert node at the beginning of the list, then the
sequence of operations would look like this. So, I created a new node. I will take that and
point to the first node that is already in the list, and I would change my list to point to the
new node. So, in this case I am not showing what the contents of the new node are, but
that is not the point. I want to insert in node and if it add some valid value, let say d and
523
if I go through the list if I traverse the list from the beginning to the end, I will see d, a, b
and c.
There is also another way in which you can insert elements to the list, and this is the very
typical way of doing it. You will see this in the adt called list later. So, instead of having
a list of items and just the list, it is useful to have what is called a head node which has
no valid data. So, it is going to be a dummy node, and this dummy node is going to point
to the list. So, we will call this the head node. It will not have any valid data and the
actual list is only going to start from head dot next. What I want to do is, I want to insert
a new node here in the beginning of the list, right. So, clearly this node cannot be pointed
from here. My list should not point to this because, here we are assuming that there is
always a header node which is dummy and from the dummy node, you should be able to
get a list of all the elements. So, let us say I have this new node called temp. What I
really want is the dummy node should start pointing to temp and temp should start
pointing to a.
So, now, if I wants to traverse the list, I start with the dummy node. I will not look at the
value in it because I know that is dummy. So, I will go to its next. There is the valid
value there followed by here, here and here that takes me to the end of the list. So, this is
the typical implementation of a link list, where there is always a dummy node and having
the dummy node in place helps you a lot when you do inserts and deletes and so, on. You
will see this in more detail in a later lecture anyway.
524
(Refer Slide Time: 14:03)
So, let us say look at another method for finding a node. Let us say I have a link list and I
am going to look for student by a certain name. Then, I have to start from the beginning
of the list, and keep going, keep going along till I do not find the student in the list or the
moment I find the student I want, print details of the student. So, I am going to show you
a method for finding a node in a list. So, remember my list is pointing to a dummy node
called the header node, and the header node is actually pointing to the list. So, clearly I
should not start searching from the dummy node itself. I should start from the first valid
node which is this. So, we will have a pointer p which points to node which is past the
header node and we are going to look at one element at a time.
If I already reached the node that I wanted or that I was searching for, I can stop and do
whatever I want to do with it, otherwise I have to keep going along, right. Let us see how
to write the program for this. So, you start with p equals my list dot next or my list arrow
next. So, what this will do is, it will start with p, pass the first pointer like this. So, you
start with p, it starts my list dot, my list arrow next is this field and that is pointing here.
P will also start pointing there. So, it starts from there and the very first data itself could
be g and if it is g, I want to do something about it. If not, I have to go and chase the
pointer. I have to move from the first node to the second node and so, on. So, to do that
we have p equals p dot p arrow next. So, we start from here and if it does not satisfy the
condition that I am searching for, then I move here and so, on. So, you have else p equals
p dot next.
525
So, this takes you one step away and if you put this whole thing, if then else condition if
inside a while loop, it will move one element at a time till you either reach the null which
is the end of list or when you reach the condition. So, you need a while loop around this
structure, but this is the basic traversal step. If and else is the basic traversal step.
Now, let us look at how to delete a node. So, I have this list somewhere a, b, c is in the
middle of my link list, and header node is here. There are may be other nodes here and
other nodes here, and I want to delete the node containing b let us say. So, one way to do
that is a follows. We know that we have to find out where these pointed to from. So, b’s
predecessor is a and what I want to do is change the contents of a to point to c as the next
element, right. So, b’s predecessor is a, right. So, a successor should now be c and not b.
So, what we want us is a’s next field which is now pointing to b should change to c. This
is what we want, right and we should also delete b in the process because right now this
node is still in memory and if you do not delete it, is going to be a problem, right. So, we
need to be able to delete this also and this will be reclaimed by the operating system. So,
let us see how to do that.
So, you start from the beginning. You keep looking at elements till you reach the node,
right. In this case you are reaching b, but you cannot go to b and then delete b. So, you
keep looking for elements whose next pointer satisfies the criteria. So, if I start from the
beginning, I will keep going along till I hit a, because a satisfies the condition that b is
the next node, and this is what I want to delete. So, I stop at a, and make a’s next pointer
526
as a’s next to next, right. So, a’s next is b, its next is c and I take that and I put the value
in a.
So, let us see how to delete very first node in the list, and I am going to show you
program segment now. Let us assume for now that the list is non-empty and I have four
nodes in the list shown in the figure. May be there are more elements and I want to delete
the very first node in the list. If I want to do that, I start with variable called temp which
points to my list dot next. So, it is this field here. My list next is this field here and I want
my list next to point to my list next to next, right. So, my list next is now pointing to a, I
want that point to b, right. So, that means, you actually have this new link that is created,
you want that and temp is still pointing to a. A is not deleted from memory, A is also
pointing to b, but a is not in your list any more. So, at this point if I go and do a search on
a, it would be a failure because it starts with dummy node and I will go past the dummy
node, start printing from b, c and d and so, on. A is not in the list, right. However, a is
still in memory. You want to remove it.
You free temp which means a is gone, right. So, I talked about this in the dynamic
memory allocation class, so, a is gone. However, temp is still pointing to something
which is invalid anymore. So, it is not a valid memory location. So, you have to make
temp equals null. So, any further access to temp will be identified by during run time. So,
if you access temp next now after making it null, the run time, when the program is
running, this will be an error and you will have a condition where you are chasing a
527
pointer which is not valid anymore, this will be indicated during run time. So, this is the
simple piece of code to delete the very first node in the list.
How do we print elements in the link list? Printing the elements is just traversing the list
from past the head node till you reach null. So, I assume that print list is a function which
actually takes a pointer to the head node itself, and we want to print out all the nodes
except the head node and I also want to print the number of elements in the list. So,
display list is this function or print list. This should have been print list. So, we start with
zero elements, to begin with, we do know how many elements are there in the list. We
assume that there are zero elements and we take current node, this curr node to point to
node pass the head node. So, we start with head nodes next. If the list is empty, this ptr to
head, it is next will be null. So, current node will also be null, but even if there is just one
element, this ptr to head next pointer will be a valid value and current node will start
with a valid value. So, while current node is not equal to null, you print the contents of
the current node, move the current node to the next element and increment the number of
elements. You keep doing this in a loop till you hit current node being null.
Finally, this one prints the number of elements in the list. So, you can clearly see how
this notion of a loop takes you from one node to the next node to the next node and so,
on. And this current node is a pointer which keeps moving along and current nodes data
will be the data one after the other. So, there are several operations that happen on a link
list.
528
(Refer Slide Time: 22:36)
So, you have to create list, you may want to check if a list is empty or not, you want to
append to a list. You want to find if node is there or not. You want to delete node, may
be print the list insert at a particular position there may be several things like that you
may wanted to do with the list. So, you will see a subset of these things in the class on
list later. So, there are several things and you may want to implement these things and
when you do this, you have to be really, really careful. You have to watch out for the lot
of details about how this is implemented and so, on right. So, there are lots of things that
you have to remember and it is not good to always leave it to a programmer to go and do
this every time. So, instead it may be nice to have the whole thing packaged. So, just like
what we had for the basic data types like integer and floating point and so, on.
Sometimes it is good to actually package the data that go with the operations that are
allowed. So, for instance the moment I say integer, I know that only certain operations
are allowed and certain operations are not allowed and so, on.
529
(Refer Slide Time: 23:47)
So, what we are going to do is, I am going to motivate this in the next lecture and I will
also introduce c plus plus as a vehicle with which we will do this. And once you have
this next lecture on c plus plus as a very quick introduction if you notice, the course will
now use c plus plus as a medium of instruction. So, this is for all the lectures on data
structures you will see c plus plus being used. So, one reason why we decided to do that
with c plus plus is that the language lets you express the program much more clearly and
concisely, and c does not let you do that. So, you will see that the lectures on data
structures is going to use c plus plus, and it becomes much more cleaner and easily
understandable when you use c plus plus. So, this brings me to the end of this module.
Thank you.
530
Programming Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science
Indian Institute of Technology, Madras
Module 12B
Lecture - 41
Brief introduction to C plus plus
Hello, welcome to this module on C plus plus. I promised earlier that we will see a quick
introduction to C plus plus and this material is done in such a way that, it will give you
just enough material to understand what is happening in the rest of the course. So, I will
start with a disclaimer.
This module is not a complete treatment on C plus plus. C plus plus is a vast ocean and
this next 15 or 20 minutes is not going to give enough justice to C plus plus. So, it is not
complete exhaustive treatment of C plus plus. Also, do not expect a lot of object oriented
programming skills, to be learnt in this 15 minute module, right. There is going to be just
enough material to appreciate the syntax of C plus plus and by no means, it is complete.
So, the basic material is based on Stroustrup's slides. So, he teaches this course on C plus
plus and he is actually the inventor of C plus plus. So, in some sense this is from the
horse’s mouth.
So, let us look at the notion of classes and objects. So, if you are in slightly senior year in
your class, in the second year or third year may be have done some C plus plus
531
programming. So, the notion of classes and objects are probably familiar to you, but I am
not going to assume anything like that. So, I am going to assume that you do not know
what classes and objects are. Let us start with this basic declaration called int x.
We can think of int as a class. So, it is a basic data type and the moment you say
something is int or an integer, you know that, there are certain operations that you can do
on it, and there are certain properties of integers. So, for example, integers can have only
values like 1, 2, 3, 0, -1, -2, -3 and so, on. You cannot have a valid value 1.6, right. So,
we know some of these properties about int. So, any time when you have something
called a class, you know that there are certain types. So, there are certain properties of a
class and there are only certain operations that you can do on the data type. So, for the
basic data types, the language itself tells you that these are the basic set of values that
you can take, and these are the valid operations that you can do, right. So, that is the
notion of our class..
Here if you look at x, x is actually an object, right. So, what we mean by an object is, it is
not something abstract when I say integer, right. When I say int, I am describing only the
properties that int integers have and the operations. Only when I say int x, we get a
location allocated to it and on this location, we can do various operations. So, the
operations of whatever on x, whatever operations are allowed, only those operations that
are allowed on integers. So, x is an object of the type integer. So, the class name is int
and the object name is x. I can have more objects of the same type. So, for example,
when I say int y, y is another object of the same type integer. So, at some level you can
532
think of types as classes and actual variables are objects. So, here y is another object of
the data type integer. So, this is all for basic data types. So, for basic data types, you
really do not need classes and objects this distinction is not really necessary because the
language itself gives you all of that. So, the notion of operations that you do on integers
and so, on also is already given by the language, whereas, if you have a user defined data
type, then it becomes useful and necessary to define, what is allowed and what is not
allowed, and to define what operations can be done.
So, let us look at the basic idea of classes. A class is actually a user defined data type that
specifies how objects of its type can be created and used, how do you create an object of
a certain class, and how to use it is described in the class. So, it directly represents some
kind of a concept in a program.
So, whenever you think of it. So, this notion of it as an entity, it is plausible that it is a
class or it is an object. So, let see things like this. So, I have the notion of a vector, a
matrix, a string and so, on. So, these are probably classes, whereas, I have a matrix
which contains all my student’s names and let say records their marks and so, on. Then,
that is a specific object. I may have a matrix which contains all these integers and I want
to do multiplication of integers and so, on. That is also a matrix, but it is a matrix of
integers. So, the object offers particular class. So, it is a physical entity or it is an abstract
notion, and C plus plus gives you this basic notion of defining classes. You can define
what the basic data structure is or the data type is, and you can also have instances of that
particular data type and classes form basic building block for building alley large scale
533
program. So, let us take a small example here.
We have class x followed by left and right flower braces and a semicolon, and we are
going to put in data members and function members inside this. So, the data members are
supposed to store the information and the function members are supposed to tell you how
to manipulate the data members, right. So, this resembles how you did it for structures.
So, there also we had something called struct type x and followed by braces, we had
members inside. Only difference is that we not only have data members here, we also
have function members. So, let us see a small example here, Class x has int m which is a
data member and int mf of int v which is a function or a method. So, we have a basic
data type inside it and we have a method inside it. So, that is a class. So, x is a class and
it has a variable m or the member m and it has a function mf. So, this function mf is
supposed to take an integer v, and it takes the value of v, puts it in m, but the old value
that was there before the function is called is supposed to be returned. So, that is the
functions member. So, this is something called get and set.
So, you are getting the previous value which is stored, but you are also setting it to the
new value that you supply. So, v is the new value that you supply, and the old value of m
is supposed to be retrieved. So, let say that is the class description and we will get to this
notion of public in a little while. So, let see how this class can be used. We have x var.
So, remember x is a data type. So, instead of class x var which we did for structures,
right wherever we had structures, we said struct x and so, on. For classes you do not have
to do that in C plus plus. X var. So, var is an object of the type x or the class x. Now, if
534
you want to access the member m in it, we can say var dot m. So, this is just like the
structure, right. We saw this before, right. Var dot m is 7 will change the data member to
7. Interestingly you can do something like this. We can do var dot mf of 9, right. What
this will do is, we will take the value 9 and supply that to this class. So, if the previous
value that contains 7, you will get this small case x to be the value 7, but you set it to 9.
So, here if you go and print var dot m after this line, this would have the value 9. So,
basically the nice thing about this class is that we have the member’s access with dot m;
the methods are also accessed with dot. So, both the methods and the data members are
accessed using dot operator.
So, now let us go and look at what is this notion of public versus private. So, again if you
are exposed to C plus plus, you probably know this already, but in C plus plus you can
have members and functions that are of two types, public or private. So, if it is public,
these members can be accessed by anyone outside whereas, if it is private, then both. If a
member is private, only the class’s methods can access it. If a function is private, right,
only this function cannot be called from outside. So, private members are supposed to
have implementation details that are hidden from the outside world, and public methods
and members are directly accessible from the outside world. We will see specific
examples in a little while.
535
(Refer Slide Time: 10:13)
So, what are the difference between struct and a class? So, let see class x. Let us say I put
int mf. By default all members are private in a class. So, if I did something like this class
x int mf, it means that mf is a method that you cannot call from outside. So, let say I did
class x, object x and y is x dot mf. This would be invalid because mf is a private method.
It cannot be called from the external world, whereas, if this had been a structure, right
struct x int m, by default that means, it is actually equivalent to our class x with m being
a public variable and structs are primarily used for data structures, where the members
can take any value whereas, you use classes whenever you want something hidden from
the programmer. So, the programmer should get clean interfaces like insert, delete and
so, on, and whatever manipulation is happening internally need not be exposed to the
program.
536
(Refer Slide Time: 11:30)
So, let see a small example which explains this in a little more detail. Let us say I have a
struct call date which has year, month and day and I have date my birthday. So, let say it
is tracking a birthday. If you do this, there is nothing which will stop you from writing
something like this. My birthday dot y is 12, my birthday dot m is 30, my birthday dot d
is 1950. So, if you look at the right side, they are all integers and the left side data types
are all integers. So, you are setting y to 12, m to 30 and d to 1950. So, as three it is
separate integers used, assign three separate values which is all, but there is a problem. If
we go and deal with this as a date, there is a small problem. You are looking at year 12,
maybe it is 12 AD, but the month is 30 and the day is 1950. This does not make sense..
So, maybe it was a mistake. It should have been year 1950 and day must have been 30
and probably the month must have been 12, where we are talking about 30th
December1950, right. So, maybe it was a mistake, but if you have a structure from the
outside, by mistake if you do this, there is nothing which will stop you from doing it
whereas, if it is a class, then I can put some check and protect invalid values from not
being taken. So, let us look at some other additional things, right. Let us say I want to
initialize the day. I have Y, M and D which are passed as three integers and I want to set
up this DD which is pointed to the date data type. So, I want DD's, Y, M and D to be the
values Y, M and D that are passed on. So, maybe I want a function like this, initialize the
members of this structure to certain values or I may want to add a certain number of days
to the type.
So, let say the current date is 30th December 1950, and I want to add 2 days to it and
537
move to January 1, 1951. Let’s say I want to do that. I should be able to do it. Add a few
days and how a mechanism by which I not only recognize that 2 days from 30th
December is not 32nd December. In fact, it is not even in December, it is in January and
it is not even in January of this year, it is actually moving to the next year. So, if I add 2
days to 30th December, 1950, internally I want this to automatically move to January 1st,
1951. In fact, I may want to handle cases like leap year and so, on also appropriately. All
these details I do not want to expose it to the programmer. So, this becomes very
cumbersome. All I want to give to the programmer is add day as an interface. With
structures it is not easily doable because we can always come and manipulate these
values without going through these methods. You may go and improve increment d
directly without realizing that the triple D, M, Y is not valid. You may do that, but if you
are forced to go through add day as function always, then add day can be implemented in
a much cleaner way and all the errors can be checked and the function can be
implemented. So, that it is always correct. So, let us look at this, a small example. I have
int Y, M, D. I always want some valid value for the dates. So, I want what is called a
constructor. So, any time this object call date is going to be created, it has to be supplied
valid values, otherwise it would be incorrect and I want to be supporting add day. So,
even though it is given with struct date, this is not valid syntax. So, we would want class
date here, and class date will have three members Y, M and D and it will have two
methods date which will take the three initial values and add days which add then certain
number of days to the current date.
So, the moment you do this, if you do date my birthday, this would be incorrect because
it is not initialized. If you do date my birthday of 12, 30, 1950, the year is 12, the month
is 30 and day is 1950. Add day can be written in such a way that this can be recognized
as an error and this can be indicated. However, if you do date my day of 1950, 12, 30,
this would be recognized as a valid day and later if I do my birthday dot add day of 2, the
birthday moves from 30th December 1950 to 1st January 1951. So, this is clean.
However, if I do my birthday dot m is 14, this would be invalid because you are
accessing the member m directly and this is not correct.
538
(Refer Slide Time: 17:01)
So, if you do classes, the way to do that is you have Y, M and D as private and you have
all the methods as public. So, date is public. Add day will actually change the contents of
Y, M and D and we have three methods called month, day and year and these three
methods actually return the current month, the current day and current year. So, these
three methods give you only read access to the data. Date is supposed to give right
access. You can change the contents of Y, M and D and add day can also change the
contents of Y, M and D. So, date my birthday of 1950, 12, 30 will change Y, M and D to
be these three values and I can do printf percentage d my birthday dot month and my
birthday dot month is a method. If I go and look at that method, it returns m and what is
the value of m, it is supposed to be 12. So, it will print 12 here. However, if I do my
birthday dot m is 14; the compiler will catch it and dot it. Even when you compile it, the
compiler will catch it and say that m is a private attribute. It cannot be manipulated
directly. So, if you want to manipulate m, you can either, go and create a new variable
and access through it, or you can only add days to it. You cannot manipulate it directly.
539
(Refer Slide Time: 18:31)
So, the notion of a valid date is a very important special case, and you want to do this
and you want to try and design our data type. So, that there is always some guarantee that
the underlined data is valid. So, for example, may be I want to design a class that is
supposed to represent a point in the first quadrant, right. First quadrant is anything
including 0, 0 or origin, right. So, the first quadrant is anything including 0, 0 and if I
move to the right or I move up, that would be the first quadrant in a plane. I want to be
able to ensure that at no point of time, this point gets out of the first quadrant. I do not
want to do manipulations or I do not want to allow manipulations to go out of the first
quadrant. If I want to do things like that, then I will do operations and always check
whether the data is still in the first quadrant or not, and report an error if it gets out of the
first quadrant for things like this. So, to check validity, it is always useful to do this. To
allow classes to be defined and the classes can also go and check for the validity, right..
So, let see how this whole thing would look like. So, I was talking about this function
called date. This date has a special function which carries the same name as the class’s
name. So, we can see that blue and red one, they have the same name, right. The
constructors always take the same name as the class. In this case, the constructor is the
method which takes three integers yy, mm and dd and what it does is, it assigns your
local variable y to yy and the local variable m is assigned to the value mm, and the local
variable d is assigned the value dd. So, if you create an object of the type date and if you
put date of 12, 30, 1950, internally you can write code within the constructor which will
go and check that and say that it is invalid, right. However, if it is valid, it initializes it
540
and this check that you do inside the core may just leave it as it is.
So, if I end up define, let say I design a function call int date::season. This is supposed to
be written 0, 1, 2 or 3 depending on whether it is winter, spring, summer and autumn. Let
say that is what we want to do if you do int date::season; it means you are looking at a
function called season which is a method inside this class called date. However, the class
called date does not have a method called season. The compiler will catch it and tell you
that it is an error.
So, this notion of public and private is a useful distinction to make. We do not make
541
everything public by default because by keeping things private, we can provide a very
clean interface and we can also maintain things which are supposed to be invariance.
What is the validity of the data inside, we can maintain the invariance. This can also help
in debugging programs because if you manipulated something and if something went
wrong, it could have happened only through functions that can manipulate the variables.
It could not have happened through something else which is outside. So, you can go and
round up the usual suspects essentially and say that if manipulations happened, it
happened only through these methods and there is something in these methods which is
incorrect. You will also see that it allows you to change the internal representation. The
notion of a class allows you to change the internal representation. This is something that
you will see in a lot of detail in a later class when we talk about the notion of adt and
data structures, right.
So, as an adt for a list, I could use a link list internally or it could use an array internally.
The notion of a list is just a sequence of elements. I could have used an array internally
or I could have used a link list internally. This kind of implementation detail can be
hidden from somebody who just wants to use a list and this is possible if you use classes.
So, many times we go and look at what makes a good interface. So, we define classes in
such a way that an interface is minimal. It should be as small as possible and at the same
time, it has to be complete. So, it has to be small, it has to be small enough to do all the
basic things, but not any smaller than that. It has to be the smallest and elegant set of
things that you want to expose to the internal world, to the external world I mean and it
542
has to be safe. So, you do not want to have the arguments passed in a different manner
and ensure that it is type safe.
So, let see how to define the link list in a C plus plus way. This is what I was promising
earlier, right. So, to define link list in a C plus plus way, you would do something like
this. We define a class called node. This is very similar to a structure, right. So, we had
struct node data and next, here we have class node where the private members are data
and next. We also provide public methods. Let us look at the public methods. You have
set data and set next which will let data and next to be changed, and we have data and
next which will do the get. So, these two methods give you put access to node, right. So,
you are allowed to change with these two and you are allowed to read using these two.
There is another method called node which is the constructor for class node. This is not
doing anything. So, it is not changing anything at all. So, it is not setting of anything. So,
this is the basic class and we are going to use this class inside the link list.
543
(Refer Slide Time: 24:43)
So, what is the link list has? The link list has a node called the first node, right. So, the
link list has a first node and it supports various operations like print, append, delete and
so, on and it has a constructor called list. If I want an initially empty list, all it supposed
to do is ensure that there is no valid node that is pointed to from it. So, first equals null
and that is it. So, now, you can see that the notion of a link list becomes much cleaner,
right. So, if we want to manipulate anything in the list, you have to go through these
functions here; append, delete and print. You cannot go and access first directly because
by default first is private. You cannot access first directly. You can only go through the
interface functions namely print, append and delete.
544
(Refer Slide Time: 25:38)
Let see how a print method would look like. So, the implementation of print would look
like this. So, node star temp equals first. If temp itself is null which means it is an empty
list, you may want to print empty on the screen. If temp, if there is only one node in the
list, then the first nodes next will be null. We actually do this not using the next field, but
we call the method called next temps next. If that returns null, then print the character
which is contained in temp data and then, followed by arrow null. Otherwise we run a do
while loop which prints one character at a time and the way do we do that is, we do not
access the data element or the next pointer of the nodes directly. We call the methods
temp data and temp next to that and we keep doing this till we hit the end of the list.
So, this basic loop takes care of reading one element at a time from the nodes and
printing them based on whether there are only zero elements or one element or more than
one element. So, this is clean because any one who calls print does not have to worry
whether it is an empty list or does it contain one element or more. So, the print itself
takes care of the implementation. The detail is hidden from the user. So, the user can just
call print and be done with it.
545
(Refer Slide Time: 27:19)
Let see append to the list. So, for appending to the list, we are trying to add an element to
the end of the list. So, we create a new node, we set it to the data that is passed. So,
append is supposed to take a data and it sets the data to the last and sets the next pointed
to null. So, you create a new object of the type node and you start with a temporary
variable here which points to first. If the list is empty, then you just make first equals
new node because there is nothing else to do. You created a new node and the first
pointer will point to that. However, if it is not an empty list, then we keep moving the
pointer till we hit the end of the list at that point we insert it. So, this kind of a set up
where we traverse the list and. So, on is completely hidden from the outside world, right.
So, let me explain what I mean by hidden from the outside world by showing how the
program will look like.
546
(Refer Slide Time: 28:21)
Let me write a small program which shows how clean this notion of classes makes the
whole program. So, I want to create a new link list and I want to add elements a, b, c and
d and may be at a later point of time, I want to even delete the element b in the list. So,
here I declare a variable call list or an object list which is of the data type list and this
appends a, b, c and d is going to add these characters a, b and c and d to the list. Let see
how the syntax is. So, list is an object. When I say object dot append, this object is of the
data type list. So, it is going to find out if there is a method by the name append; and
what does it take. Append takes that data type character. So, we are passing a character
and list dot append will add a to the list. At this point of time, this list is empty. We do
not have to worry about that.
We just said list dot append of a, and the first time append is called, inside here you
create a node. This would be not true. You would just change the first point out in new
node and maybe we can print it. Even as soon as you create list with one node, you can
print it and then, we do list dot append of b. At this point the list already contains a, and
now the method will look at this is not the first node. So, it will go through the list, find
out that the null is pointed from a. At that point, it will be added. So, a will now point to
b. If you do this, b will point to c and when you print, you start from the first pointer,
print a, print b, print c and. So, on and go to the end. Now, you can append d. So, the list
will have a, b, c, d and finally, if you call list dot delete of b, if there is a function
implemented by the name delete, we assume that programmer has already taken care of
that. The classes designer is taking care of print append and delete as a user of the class.
547
So, when I said user and programmer and. So, on till now I am talking about user of the
class, right. So, the user of the class, as a user I want the link list. I do not want to worry
about how the list is implemented, how append is implemented; delete is implemented
and. So, on. I leave it to the designer of the class and this delete of b if there is a function
defined by the name delete, I will assume that gets done and list dot print I will expect it
to print a, c and d.
548
(Refer Slide Time: 31:22)
So, what did we get? The details of the implementation of append, delete and creation of
the list and. So, on are not exposed to the user of the class. The main program became
much cleaner. See this is much cleaner than making function calls to append and
checking whether this is the first and second and. So, on. We did not do any of that. This
becomes much cleaner here, and we trust the implementation of the class list to be
correct and complete. This is something that we get from the implementation of a class.
This is the reason why we use C plus plus in the data structure lectures that are
following.
549
So, in the lectures on data structures, you will see syntax of C plus plus. So, do not be
bogged on by the syntax of C plus plus. Just remember that you have individual
members that are going to get operated on or manipulated. You will either change the
values of the members, or you are going to read the values of the members. You will see
that there are public methods that are used to manipulate the individual members, and
you may also see private methods which are only called by public methods, and you will
see that the methods are actually invoked using the dot operator. For example, the link
list append would do my list dot append of x or you have my birthday dot month and so,
on. So, do not be surprised by functions being called with the dot operator. So, all you
need is the basic syntax of C plus plus which shows that there are classes which has
internal members and internal methods. They may have public members and public
methods, and the members and methods are going to be used just like you do it for
structures using the dot operator..
So, beyond this you probably do not need much of C plus plus and as I mentioned
earlier, we are not going to do anything more than that in this lecture. So, I said this is
not a completely justified introduction to C plus plus. I have done just enough. So, that
you can appreciate the slides that are following or the lectures that are following on data
structures. So, if you need C plus plus, this is actually a completely new course that you
have to do, where you start with the syntax and semantics of C plus plus and also,
understand how to do object oriented design. Since, this course is objective, the basic
programming and data structures and algorithms; we will not touch up on C plus plus in
any further detail.
So, thank you and this brings me to the end of this lecture.
550
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 04
Lecture – 42
Data Structures Abstract Data Type (ADT)
Basic ADTs
Advantages of Using ADTs
So, the next lecture is on data structures, but data structures the reason (Refer Time:
00:17) some time on giving a algorithm analysis and things like that. Data structure is
encapsulates the what is called a data type? And given a data type it also defines a set of
operations. So, basically what we will do is when we talk about data structures here, we
going to talk about abstract data types. Let me explain to what is an abstract data type,
and very similar to a procedure. What you do the procedure? Now basically if you what
we you already seen we saw some functions and procedures, you already seen in your
programming module, and we also seen just in the last class how to analysis we found
the sum of set of element, we found the search for element in an array, we shorted given
in set of elements so and so forth. So, what it is that, that we are saying? We will say
short thing and array. What you mean by there? Just I what is short thing? Short thing is
an operation, that is being performed on the array.
551
Similarly when I am looking at searching, searching is an operation that is being
performed on an array. So, the way we would like to look at is an abstract data type is
kind of similar to procedure. So, what is a procedure do, a procedure kind of generalizes
the notation of an operator. What you mean by that? Your familiar with a standard
arithmetic operators plus minus multiplication and division, addition, subtraction
multiplication, division. What we can do in a procedure is that, we generalizing is
particular notation and we are able to define our one operation. Shorting is an operation
that is defined on the array. Similarly searching is an operation that we define on the
array.
So, what we do in and in a data structure is similarly what we do is, we also generalize
the idea of a idea of user define data types, just I like we have operations which are
generalizable in procedures. In when you talking about abstract data types, we also have
a facility by which begin generalize data types. We can define user define data types.
Just I queue have what are data types now integer, float, let us say pointers, so and so
forth. Now you can do generalization of the whole things and say I am going to give
string for example, is another is a data type, character is a data type, float is a data type,
double is a data type, int is a data type. In see what we ((Refer Time: 02:51)) generalize
is the whole notation and say I am only give new data type. I call this list, I call this
stack, I call this a queue. Then what happens? Just is you had addition, subtraction,
multiplication, and division defined on let us say the integer data type, when I define a
552
new data type, I have to define operation on them. So now, that is what is abstract data
type? And abstract data type is one just like your procedures wish encamps encompass
encapsulate functionality, the abstract data types can be use to encapsulate data types and
we also define operations on this. So, let me go back this.
So, what is it that we have two kind of formalize this, you have a mathematical model,
and in informal algorithm, let me give you an example. Suppose I have sets then I have a
mathematical model has to how you represent the sets and then you perform a certain set
of operation on sets; your union, your intersection, your complement, and so on so forth
or set of operations that operator on set so begin. Now from the mathematical model we
goes to the abstract data type, and we define a pseudo language program, and this
becomes and data structure and higher level language program which operates on this.
So, this is how the data models, this is how you go from data models to programs, and
this is what is x this figure is illustrating. Let me give you one in other realization of this,
what is this mathematical model. Just I like this saw the ((Refer Time: 04:29)) something
that is closer for example, I can define an abstract data type called matrix with operation
like matrix addition, subtraction, multiplication and inverse defined on it. So, the
operation is not performing on the matrix or let us say on this, but I am need some kind
of representation in the matrix, and that kind your representation is hidden or
encapsulated in the abstract data type.
553
(Refer Slide Time: 04:58)
So, what will be do is you will start with some very basic abstract data type, which are
commonly use in computer science. These data types are the ADT list, the ADT stack,
the ADT queue very very ((Refer Time: 05:10)) data types, and what is nice why do we
want data types. Let me give you an example again, when you look at when you perform
the operations on integer, when I am saying that I want find the sum of 4 plus 6, and you
write on the left hand side, I write a variable like this.
554
I say x is equal to let us say 4 plus 5 was something like that. Or y is equal to x star z
when you write statement like this in the higher level language program, clearly you are
not aware of how x how the operation of multiplication is implemented nor are your
aware of how ((Refer Time: 05:53)) store. What we do in programming language, I
simply say int x, you know that is in integer data type, and x comma z comma y, and say
y is equal to ((Refer Time: 06:06)). Then after your second right something like this.
So, what is happening is even a programming language today actually heights the way in
which the integers are represented, and how the operation of addition, subtraction,
multiplication, and division heart performer. So, now what is what are the abstract data
type are, they give you tool by which you can define your own data types, and define
operation on them.
Then give this abstract data types to user hooking use it without knowing how the
implementation of the data types as being done, already do is I will say for example, let
me give your example, I would say x and y are of type matrix, this is what I have define.
Add I can write let say z is equal to x star y. The place it will for perform matrix
multiplication, the star is the operation of multiplication time into perform. So, it
automatically does in. Clearly these are not available in current day programming
languages. So, what we do is the whole idea of the using ADT is that the operations are
555
define on the private data, the representations completely hidden, and the data cannot be
access directly, there are operation that can be perform on the ADT to access the data.
And what is very nice is, suppose today you know I representing for shorting I
represented the date, I store the data in an array; maybe there will be a more efficient
algorithm which were I can change the internal representation from array to some list or
link list are something like that, and it my become more efficient. Or even in 5, I am
representing in an array, first let us say I did simply linear search. You ask for an
element, I return the element whether present in the array or not. Then after words I am
become a little cleverer and I said let me do, let me short the elements and perform
operation and so on. What is the operation and performing a performing search, then
when what happens, if I am let us go backs to example of an array.
So, initially I had in unsorted list. On the unsorted list I was looking for the element 12
which was found at the end of the array if remember it, we had some 7 3 10 15 25 20 32
and something different. So, what we it was we set search throw the array find the
element. So, clearly is very inefficient, because 12 is at the end of the array. Then next
((Refer Time: 08:42)) what is you know let us short this remember the binary search
algorithm that we did, we set 3 7 10 15 20 25, sorry we should be a 12 15 20 25 and 32,
you what we did? Then what we set was now clearly, I can do little bit better. What can I
do? When I looking at the middle, I can I can just search until I find the final elements
556
which is larger than the key. So, if I am looking a key equal to let us say 14 then I
compare with this, compare with this, compare with this, compare with this, compare
with this. Now what happens? Key is become smaller than 14, I can say elements is not
found in and come out, the one way of doing it efficiently.
Now next what it I do? As a know I can even make it even better, I can divide the array
into to once is short cut and do binary shorts. This algorithm even a 5 do linear search
key equal to 14, if I will ask the key equal to 35, I would had to search the entire array.
Then what we did was, we said now we do not, we are not expecting the property that is
actually shorted and I can do certain things, then we define the binary search algorithm,
and we said… Now I can divide the array into two and search only in that part where the
((Refer Time: 10:03)). So, one we have looking ADTs, if I could provide an ADT which
operates an arrays, can do searching or shorting for that matter and are different. And
then what ((Refer Time: 10:15)) every time you have been first time I gave, because I
initially wrote in inefficient algorithm for searching, which will you give me the location
of ((Refer Time: 10:22)).
And then next I given other little more in efficient algorithm, I can using linear search,
and finally I may do binary search, but as far as the user is concern your giving and
algorithm called search. Is like the addition being implemented maybe you know first
when you use this operation of here, x equal to 4 plus 5 may plus was inefficiently is
implemented. Later on it became more efficient, but that is kind of hidden from the user,
that is the big advantage of ADTs. And what is nieces the implementation of the ADT
can be changed continuously without changing the interface function. I want you give
you… I want you leave you with slightly kind of abstract notation of this.
If you look at human beings, how do we you know when you when you see somebody
after 10 years, suddenly you are recollecting something about the person and your able to
identify, he was ((Refer Time: 11:19)) 600 classmate. How you doing this, clearly the
way we have represented the data are information the brain is not note anybody, and
what is a operation that we are performing, you see this person and immediately you
want to recollect, you perform in operation on the data, the result come soon. So,
basically the representation of the data is completely hidden and so on abstract data type
is kind of nationalizes this particular property.
557
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 05
Lecture - 43
Lists
Operation on lists: Crate, Insert, Delete
Implementation of lists using linked list
Now, what we will do is, we will grocery items that I have. So, this is the simplest thing
that we can thing off. So, what you do when you on the list of grocery items, you know
name, all of them and put them together and say these are grocery items and sent it to
your work shop keeper to or call your shop keeper and say please deliver these items and
so on. So, when you look ((Refer Time: 00:31)), this is an unordered sequence of 0 or
more elements, I have a particular given element type. What is my element type here, in
this particular example of grocery items it is element type has grocery items.
So, what do I have? I have elements a1, a2, a3, an. The n term is generally greater than or
equal to 0 and ai is some particular type, n is a length of my list. In this particular
example, those will illustrate let us say n greater than or equal to 1, first element is a1
558
and last element is an.
Now, what are the things that you would like to do in an ADT list. So, when you want to
define an ADT list, what we will list, for example I can have a list of students who are
interested in music, list of students who are interested in sports, so on and so far. It can
be any kind of list, for that matter. Why where things it, we would like to do? I would
like to perhaps that I have this particular list, I would like to insert elements into the list,
I would like to find the end of the list.
Because I would like to know how many people are there, I would have to find suddenly
you know, I signed up for let us say know, game and I have registered for playing Tennis
or something like that. Let us humbled, suddenly I do not want to play Tennis, I want to
cancel my registration. Then I want to locate a particular element in the list and all I find
out, who is there at position x. Because I would like to let us say you know, this is an
unordered list and I think saying that all these people would have registered for the
hostel, you are going to put two pupils together. Then maybe I would like to find out, at
my position who is going to be my neighbor and I like to find out, whom neighbor is.
559
(Refer Slide Time: 02:14)
And I already said I deregistered let us say, from game or whatever it may be. Now, I
delete the element from the list, then I find out who is next, who is previous and so on, so
forth. So, all these operations if I want to perform on the list, we will not yet talked
about, how the list is going to be implemented, we will do that a little later.
560
So, what we are first defining is we define an ADT list and this tells me, what operations
going to the ADT list. We first creating an empty list, then insert into the list at a
particular position. Then you delete elements in the particular position, you can locate
whether particular element is in the list, you retrieve the element it the position p, find
the next element of position p, previous element of position p, print the entire list. Find
out what is there at the first of the list, what is there, get the position of the first element
in the list, get the position of the last element in the list and so on.
So, now how do I implement the list? Now, what we do is it could be implemented using
either an array or a linked list. These are the basic two data structures that are available,
we already talked about it in the last class. An array is basically enables random access,
whereas we already said a linked list on the other hand is a recursive data structure and
you have to start from the head of the list. So, you can do the implementation of the list
using either the array or the linked list. But the operations on the list are the same. And as
far as the users concern, the user is quit oblivious to how the implementation of the list is
being done? So, this is essentially the idea and as finally the user concern, he or she is
only going to do insertions, deletions, so on and so forth. Let us see, how this can be
done order.
561
(Refer Slide Time: 04:12)
So, what we will do is this is implementation of the list which I have showing over here.
So, what do I do, I have a program called LinkList dot cxx, I have implemented this in C
plus plus and noticed that I have a recursive implementation here. You can also use an
array, array is easier. Notice that I have basically, what is that I am making available to
the user. I am giving doing this typedef position, which is the p in the implementation
that we have over here. P corresponds to the position in the list over here, p is typedef to
something called it is position. And essentially, the user also get me an element at
position p, that is the way you will look at it.
562
(Refer Slide Time: 04:56)
So, what is he or she do? So, we have here, private notice that I have done an
implementation using link list and this is hidden from the user and because, it is defined
as a private data type. Then I have a facility to create a new list, then I have a facility to
insert element x at position p, so element types or something that is specified by the user.
Now, I am writing a generic list data structure, which is internally represented as a link
list.
Element type is provided by the user, because the user knows what element he wants to,
what set of element he wants to put in the list. For example, is it a list of box, is it a list
of plates, is it a list of grocery items, is it a list of students, it may be anything for that
matter. That information comes from this element type defined by the user. Then what he
or she will do is essentially we have an implementation here. I have created for my
convenience, I create one empty node in the list.
563
(Refer Slide Time: 06:02)
So, this is what I do, my empty list make talent for example, creates a list with one single
element. Only one element is there and it points to the next, I make a dummy header for
that factor. Then when I am inserting I always insert at the end of the list. Whatever I am
doing here, notice that I want to insert it at position p, I am inserting at p pointer next,
this is again not known to the user. So, when the user asked me for an element at p, I will
return the element at p point to next, that is the point of all of this.
564
(Refer Slide Time: 06:44)
Similarly, when I am doing deletion. The reason why I have done this dummy node is
primarily because, deletion becomes very easy, p pointer next becomes p pointer next
next and whatever it may be and return will give you the… Notice that please observe
this, obey here, we make position list, position list end. So, as far as the users concern, he
or she does not know, whether the position is pointer or to a recursive list or it is a index
into an array, because we have done the typedef in the definition itself.
Going back to this part, how I have done over here. We done a typedef here and all that
user uses a user position, in all hers programs. So, it does not matter if as long as I keep,
I can change the internal representation of position, here it is pointing to the pointer. It
could be 0. For example, corresponding to the index of the 0th element in first element in
the array. It could be done that way too, but I have just tells the user, link list operation
could be used.
So, this source the deletion operation and this source the insertion operation. So,
basically what is that we are doing here, we are essentially creating a new element over
here. Inserting it and then this store this pointer in a temporary variable and then, put it
back. So, if you notice how to find and what we do over here, if you notice here again,
let us look at the end over here, it is an interesting function. And for example, returns the
565
element noticed that we are continuously while p point to next, not equal to NULL.
So, it returns the actually the pointing to null at the end of the list. So, the primary reason
for this is that when I want to insert, I can very easily insert a key at the end of the list. P
pointer next is NULL, in that particular list. So, this is the list and I want to leave you
with a set of operations, so let me get back to a certain last the picture over here. Now,
one of the applications of list that you can do, let me I give you an example. I put it have
it here.
We can do things like, you can create a list with as half, let us say I have sets for which I
want to perform, s1 union s2, s1 intersection s2, s1 minus s2 all these can be
implemented using s. Let me give you one example that I would, so what would I do? I
would create a list of elements, let us say, s1 is made up of the following elements, let us
say 4, 3, 2, 7 and 6 is the something I have that and s2 is made up of 3, 2, 8 and 10. So,
what could I do? I would create, I would say that if you remember here, I would say that
going back to this.
566
(Refer Slide Time: 10:33)
I would say that my s1 comma s2 are of type list first. Then what I would do is I am
reading, let us say this input from somewhere and so I would have, while i less than or
equal to n1, i equal to 1, let us say. I would say first all, I will make a empty list. I will do
s1 dot make null and I will do s2 dot make null, which will create two empty list. Then
what will I do? And let us say n1 is the number of elements and I got all these elements
let us say in some array.
Then let us say that I have an array a of elements 1 to n1 and I have an array b of
elements 1 to n2 which need to be inserted into the list. So, what I would do is I would
say now s1 dot insert, I would say here, s1 dot end, because end of the list comma a of i,
i equal to i plus 1, end while. So, it is much as the same, I would do for s2. So, while
again I will say i equal 1, while i less than or equal to n2, s1 dot insert s1 dot end comma.
I am inserting at a position b of i, i equal to i plus 1.
So, now what have we done now, we have created two lists, let me call this s2 here, s1
and s2 into which we have inserted elements into the list. Now, next what we want to do?
We want to find the union, union means what now, I have to look at the union of these
two sets are what, a set is always made up of a unique number of elements.
567
(Refer Slide Time: 13:09)
So, now I look at s1 union s2, clearly what do we required now, s1 union s2 should be
made up of the elements, what do we have to do now? It should consist of 4, 3, 2, 7, 8, 6,
10, something like this I am keeping it unordered here. So, what is it mean, it only should
consist of unique elements, that is the definition of a set. So, what could we do is, we
could start off with set s1, when we know that the elements in set s2 are and till the end
of the list.
Take all of them, this insert it at the end of s1 with you do that. So, what would I do now,
while i less than or equal to I have already done this over here. So, I can start with i equal
to 1 and while i less than or equal to n2. What I am going to do? I am going to say s2 dot
retrieved, what I will do first, p is a position of the first element s2 dot first, use need the
first element. And what to be do, we put the outside over here, p is equal to s2 dot first.
Then I will retrieve what is there it p and we will insert into s1, s1 dot insert s1 dot end
comma s2 dot retrieve at position p. What is the meaning of this? I am inserting the
element at position p from s2 to the end of the list s1. So, I would do this. So, now what
will I do, I will say update p, p is equal to s2 dot next of p. Again, get the next position
and so on and so forth, I would do this.
568
So, this completes, so what is it do now, basically this will give me a new list, such that I
have 4, 3, 2, 7, 6, 3, 2, 8 and 10. So, clearly it does not satisfy the definition of a set, here
all the elements have to be unique. Next, what I can do is I can set this new list that I
have. I can try to another function which take as a argument a list, I can say list s purge
the duplicates innovation. I can write a separate function for that.
So, what is that mean, I take this list here, find out I go through and traverse the entire
list, see if the element is found. If that element is found, I delete that element, if there are
duplicates in this list, I simply keep on deleting it. Whereas I have stopped with this
position, start with the first element, then traverse to the entire list over here, I can go to
the end of the list, see that element is do we repeated, if it repeated, repeat the duplicate.
Then go to the next element and so on.
So, you can do this, so list is what is that, what is that interesting is I want to see this
particular segment of code here which I have written. I have not simply, I do not even
know, what the implementation of the list is. It may be an array implementation, it may
be a link list implementation, a and b are arrays which are provided, which the users
provided with data in them of type element type i and all around we use, insert at the
end.
So, this is the most important criteria about abstract data types, where we use only the
operations. We know that just like we have integer, what have you done here, s1 s2
corresponds to a abstract data type called list and what is that we are doing now, into the
list we have inserting elements. Where are the elements is coming from, from a user
defined array, he is doing it, he or she used doing it himself. So, what is it doing insert?
Insert it at the end of the list, we not bother because, initially what is going to happen,
either create an empty list, it creates an empty list. There it creates the empty list, where
is the end point to. End points to the first dummy element that I have in my
implementation and I am say, end of the list just keep on appending these items, that is
what it does. Similarly, that is done another list is created and you have done this.
Then to form union whatever we have doing, we first putting all the elements of s2 at the
569
end s1. So, it is forms the union, but a problems is it may not have unique. Then what I
do? I write a separate function called purge which was simply delete the duplicate. How
do I perform purge? I start with the first element, check what is the end of first element
and see, if you can locate the particular element in the rest of the list.
When I forget to locate the element in the rest of the list, if it is already there, then I
delete that element at a particular position. The important point I simply do not need to
know, what is the implementation of the abstract data type list is. So, I would like you to
go back and try the other operations. In particular, I am not given you this
implementation of purge. I want you to think about this, I talked about union, I like you
to think about complement and intersection on sets, whereas set is represented using a
list.
And also I like you to think of how can you use only the list operations to order the
elements in the list. So, when you ordering the elements in the list, what do I need, I need
something to compare. Because an order means putting an ascending order to putting in
descending order, in which integer or real I can do it. Suppose it is names of people, how
do you want to order them. So, the comparison operation is provided outside the list
implementation again. And just like here, to locate the element we are doing a
comparison, the comparison is provided by the user. So, let us let me stop with this list
ADT, and next we will look at stacks in queues.
570
Assignment on Data Structures
Prof. Hema A Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 44
Just to recap what is an abstract data type it is similar to a procedure. And just as a
procedure generalizes the notion of an operator, instead of using operators like plus,
minus, star and division, you define user defined operators. And also the data on which
the procedures operators can operates, can also the user defined data types. So, just as in
a procedure for example, you can encapsulate the functionality ADTs are used to
encapsulate data types also.
So, basically what I mean is if I using for example, the cin function or the cout function
in C plus plus, you do not have to know how cin is implemented in the class std, you just
use it, as long as you know how to use it, that is basically the idea.
571
(Refer Slide Time: 01:27)
And it has operations like addition, subtraction, multiplication and inverse defined on it.
572
(Refer Slide Time: 01:39)
So, generally when we are talking about ADTs, we are talking about a mathematical
model. For example, when we talk about matrices, you can give a mathematical model,
you know it is a two dimensional array. And there are informal algorithms to show what
is the multiplication of a matrix, what is the addition of a matrix, what subtraction, how
do you compute inverse and so on. Then, a more Computer Science way of looking at it
is, an abstract data type and a pseudo language program.
Now finally, when it goes in to the actual programming language, we have to use a
concrete data structure to represent this mathematical model of a matrix which was. Kind
of more closer to the programming language in an abstract data type and when the
corresponding operations that are performed, we had an informal algorithm here, here
you have a pseudo language program, because it is not a language specific. Finally, you
have a corresponding concrete language over here.
So, from here to here the difference is essentially that you defined it in a language
independent way over here and here it becomes specific to the particular language, this is
what we saw in the lectures. So, basically I have a data model and from the data model
you go through some intermediate step which is like a abstract data type and a pseudo
language program and then you do the implementation of the abstract data type in a
concrete language and the corresponding algorithms become higher level language
programs.
573
So, now what we in this part of the course, we would decided to use C plus plus
primarily because, C plus plus lend themselves, lends itself naturally for the
implementation of abstract data types. So, we looked at if you remembering the course
we looked at the ADT list, stack and queue.
And I am always straight away go to the list which we talked about and what it we say is,
it is a simplest kind of abstract data type that you can think of. Basically, it is a list of n
elements and the elements are of some element type, n is the length of the list and first
element is a 1 and last element is a n.
574
(Refer Slide Time: 03:48)
And what is interesting about the list is that you can, if you want to define it is an ADT
that is quick closer to the programming language, you will say a is a list of element type.
Notice that we are in the ADT here, we are not talked about what the implementation is
and then, what are the various operations that I want to perform, I want to insert an
element into the list at some particular point. Going back to this example over here
((Refer Time: 04:11)), I have a list of elements a 1 to a n. Suppose, I want to insert an
element somewhere in the middle, how would I go back to indeed, talk about that.
And this I want to go to the end of the list, that is after a n, I want to go there, because I
may want to do some operation there. Locate, I want to find an element in the given list
and retrieve I want to retrieve an element, let us say I have a position let us say 3 over
here. At position 3, what is the element that is the element list, delete and I want to delete
an element at the position 3. Next for example, find the position of the next element in
the list.
So, previous get the position we want to do why do I want this? Then, I look at next and
previous for that matter. Primarily, because we do not know what the underlined
implementation of the list is. The list could be implemented using arrays or link list and
we just want to know why, where is the next element located.
575
(Refer Slide Time: 05:08)
Or I might use something called a load space, for I have multiple list that are located.
And basically you need index is to point to where these elements are located. So, this is
figuratively showing what a list looks like.
So, these two different kinds of data structures that we have for implementing list is an
array, add a link list. Remember, the course we did using the link list of array, if I
remember. So, what did we do over there was we said, we did the implementation using
the main class. So, basically when you look at list for example using an array, one
576
corresponds to the first element in the array. And let us say this is the max length of the
array and as you keep in certain elements, you also have a pointer which points to the
last element in this list.
If you using a link list for example, you have pointer to the header, that is necessary and
last if you want you can have a pointer or you need not have a pointer and I keep
inserting the elements as I am required. So, what did we do in the course now, let we I
am going to take you through the code that we have here. Let us look at this I have two
implementations over here. I am going to look at the… So, basically we said I can in the
course for example, we did the pointer based implementation.
Now, ((Refer Time: 06:09)) just enlarging it a bit, so that you can see it better, so this is
the just 1 minute. So, we have this list over here. Notice that, this is the pointer based
implementation, notice that in this particular case I am having a list of integers and here
is something which is called an element type. An element type is defined by the user of
the list, whoever is writing an application using this and here typedef struct cell star
pointer is position.
Position for example, becomes the address of the next element as we saw. If it is an array
implementation, address becomes an index, it becomes an integer and the various
operations are given over here as indicated, because I have written this in C plus plus I
577
have used dell item instead of delete primarily, because delete is used for specifically
deleting an element in C plus plus.
Then, the something which makes an empty list, it create a dummy node as we saw in the
class and insertion for example, inserts an element at a position p in the given list.
And print prints the entire list and list end points to the end of the list. Let me give that
these kind of explain what we mean by this. Delete for example notice we have done this
over here, that is it is a single operation. Normally for example, when you look at link
578
list representation, we have to go to a particular position by to locate the position p and
to remove the element at a particular position p, you have to note that pointed in the
previous node.
What we do in this implementation, you are little clever and what we do is, we create a
dummy node and when you say insert a position at p, we insert at the position next to
that p, what we mean by that is the following.
What we doing is, so there are two implementations as we have already saw, we saw the
array and we also have list. So, what we do is we create a dummy node first and we
create a make null, then when we say insert at let me say this is a position p at which I
want to insert. What we do is the user notes what he or she is doing, then what we will
do programmatically is, we insert at this point and point this back.
So, obviously when the user wants an element to be retrieved, let us say this is 5, 6, 8,
10. So, initially it contained 5, 6 and 10 and you said please insert an element at position
p, you know that it corresponds to the next element, it is not. When you wanted this
element for example, it will can be retrieved or accessed by a pointer to this, you do not
have to worry about it.
So, we take care of it internally that whenever we insert an element, it is always inserted
at the next element. Primarily, because remember when we did the data structures and
579
algorithms, we said time complexity is the very important for that event. So, in this
algorithm by creating one simple dummy node, we make the delete operation in an order
one operation.
So, it basically the idea gives you and there are various operations that have been
performed. Now, what I am going to do is this is the linked list implementation, now I
am going to work you through the array implementation of list. Now, let us we see this
one.
580
Let us look at this now and what will you see now, what is the typedef int element type
prevents the same, because this is a list of integer elements. Notice that, position
becomes integer now, it was a pointer to CellType before and then notice the operations,
the operations look identical, it do not looked different at all. So, the user of the list the
person who is going to use your list to write programs can use either this list, either the
link list implementation or the array implementation.
Then, what are we doing here now, we are creating… Make null for example, it creates
an array of 100 elements and rest of the operations are very similar.
And when you asked for the… If you are inserting for example, in the array and this is
the position of the last node ((Refer Time: 11:19)). What do we do now, if you want to
add an element to the list, if I let us say add a particular point p, I want to put an element
here, then this last node must be moved here and put the element here and all these
should be moved downwards, that is exactly what is achieved. When we talk about the
end, this is the end of the list.
Similarly, when we talk about the end, this is the end of the list that is what we mean in
this as long as we are consistent, end means end of the list.
581
(Refer Slide Time: 12:07)
So, we do this and now what are I am going to do is we will look at, I will open it in the
programming windows, so that you can look at it carefully and understand it. I want to
open these two programs together and show you something.
So, I am opening both these programs, we have the list which is represented using an
array and the list which is represented using the… So, basically it is a same
implementation that we have, I want you to look at this part where we have the same
582
main program notice this over here. If we observe this over here we create an empty list
and we are inserting 10 elements into the list at the end of it.
So, initially where is the end now, end is pointing to the 0th element, the end in C is
starting from one in the array, although in C plus plus it will allow you to start with 0.
What am I doing, I am inserting I into the end of the list, same program over here. Then,
what are we doing, assign print the list, delete item, we delete the first item in the list,
print the list, delete item, the next of the first and I can print the list.
So, let us run these two programs and they should give us identical results, let us see how
this can be done. So, what do I mean here, so what did we do, we inserted 10 elements
into the list. So, this is what was done 0, 1, 2, 3, 7, 8, 9 then what did we do, we said
delete the first element, the 0th element is gone. Then, we said delete the next to the first
element which is the next to the first element to therefore, it is called deleted.
Now, let us look at list ptr, list ptr also does the same thing, remember the main program
does the same. In one case, we have use the array implementation of the ADT, in other
case we have use the link list implementation of the ADT, it simply does not matter.
What is important is the main program is an application that uses an ADT list, and as
long as you are consistently using the same ADT, it does not matter.
583
Now, in the assignment what you would see is that somebody would have given you a
particular list implementation and ask you to use that implementation in the program.
Now, what are the things that the two of the problems that are using list in your first
week assignments are big int and the other one is the hash tables. Now, what I am going
to do is I am going to show you one more example and then I will go to the assignment
problems.
Now, so for what we have seen, we saw the array implementation and pointer
implementation and we saw that the main program is the same irrespective of array or
list. Primarily, because we do not operate on the data which is internal to the ADT list,
that is a fundamental point. Now, what we will see is how can I use an ADT list that is
being defined to do something else, can I use the list to make something else. By now,
many of you might have seen the stack video.
What is the stack now? A stack is essentially as we saw in the course what did we see
about the stack, thus a stack is essentially an ADT where you insert at one end and delete
at a other end and we also looked at the conversion of infix to postfix in the stack. The
next video actually explains how this is done using the ADT stack. Now, in a sense the
stack is also a list, why a list ADT allows you to insert elements anywhere, delete
elements anywhere.
584
A stack on the other hand allows you to insert at only one end and delete only from the
same end. Therefore, we call the stack a last in first out data structure.
So, if I insert elements into the stack for example, suppose this is the stack, stack is
empty and again you can have a link list or an array implementation and so on. Let us
say I have inserted 5 elements into the stack. So, this was the, the stack was empty, I put
this first element and push this second, third, fourth and fifth.
585
Now, if I pop the elements from the stack what are we going to see is going to pop 5 out,
then it is going to pop 3, 2, 1 and 0 in reverse order. So, basically that is what we mean
by last in first out and as you all know, a stack is something that is used which you might
have either read about or heard about. In fact, a stack is something that we are always
using even in practical live.
Programs for example, when you have functions or recursive functions in particular,
what is done it puts everything on the stack, the recursive function calls itself again, you
have already seen the C part of the program where you did recursion. And finally, what
happens the last recursive call completes and then all the others complete in sequence in
the reverse order, this is what we have seen in stacks. So, now let us see the stack is also
a list, so can we implement a stack using a list ADT and that is what I am going to talk
about.
So, let us look at this example, let us say I look at stack from list is what I am calling this
program.
586
(Refer Slide Time: 18:05)
So, what I am doing over here is I am assuming that I have a list which is already
implemented and what we are doing is we are trying to make a stack out of it. So, what
happen doing here, I am reusing the list. So, if I look at it over here, there is a class list
which we have already defined that is a set of operations which are defined on it.
And now I want you to see what I have done in terms of implementing the class stack.
What am I doing when I am implementing the class stack, I am representing a part of it is
private data a list l, that is I am including a data type which is of type list l. Remember,
587
this is an ADT that I am defining over here. Then, what are the operations I want to
implement? Pop, top, push, empty, make null, we know all this.
So, now what we do is why deriving top then, top should corresponds to retrieve the
element, I am using I am always inserting at the first element and popping from the first
element. So, top means for me corresponds to l dot first in this implementation that I
have. So, I say retrieve the first element from the stack. What is pop now, it not only
retrieves the first element, it should also delete the element at the first position.
So, basically I am using list operations to perform the stack operations. Now, what is the
insertion now, notice that when I am pushing, I am pushing at the first element. So, what
is happening now, as we keep if I go back to this list over here, so what are we doing
when we are making this as a stack, we are simply doing the following.
So, this is what we have as the list and purposely we used a link list implementation here,
it is simply does not matter. So, when we say insert it at first, it is going to keep, so let us
say initially I had nothing, then I inserted 1, I pushed 1 into the stack. Now, next if I want
to push 2 on to the stack what I am going to do is, I am going to put it here and then this
will be moved forward. This is the meaning of the stack list being used as a stack.
So, next if I want to push the element 3 on to the stack and what am I doing here, I am
using the list like a stack, so I am pushing the element 3 here which will point to the
588
element 2 notice that 2 and 1 are being moved ahead in the list. So, now when I want to
pop, print all the elements in the or pop all the elements in the stack, what is going to
happen, it is going to put it in reverse order and that is what we will see in this particular
example.
So, let us look at this program over here, so what I have done now I am not done
anything, I am just simply used implemented the stack using the list which we already
know about. So, let us look at this program over here, it is have a stack a. The stack a has
been implemented as we have already saw and using the list ADT, then what am I doing
I am creating an empty stack and then I am pushing elements 0 through 9 in to the stack
and then what am I doing, I am popping out all the elements that are there in the stack.
So, what do we expect when I am pushing them, I am pushing them 0 through 9, so when
I pop them it will come out in the reverse order, then it behaves like a stack.
So, let us see this particular example, list this is what is doing. So, notice that we insert it
from 0 through 9. So, first that is an initialization at the stack and then we inserted it to
be remember and finally, when I pop to elements from the stack which is what I printed,
it is coming in reverse order.
589
(Refer Slide Time: 22:08)
So, I hope you understand what we have done over here, so what did we do, we said
pushing the elements 0 through 9 and then what are we doing, we are printing the
elements that are popped out the stack and this is what we saw. So, this kind of should
give you an idea about how to use an ADT in a given list.
So, now let us get on to the two problems that have been given to you to solve using list,
one was the big int problem and other is the hash table problem. So, let us look at each
one of them in detail, what are the big int problem, they said a particular definition of list
590
is given in your assignment, try, the reason for giving that it is you should be able to use
any user defined ADT. The definition of list can be different, I can it is a list of elements
and how do you use that list of elements to perform what is called big integer arithmetic
is what we said.
So, what is being done over there in that we essentially; that means, this begin int is
actually operating on large numbers. Let us say I have a 10 digit number 1, 2, 3. Let us
say I have a 10 digit number and something like this and I want to find the product of
two such large numbers. It may be I would not take 2s exactly the same size numbers 7,
8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 let us say I have 5, 3, 2, 1, let us have something
like this.
So, the problem is many of them will so; obviously, if you running with 32 bit machine
or even a 64 bit machine, computations of this kind can cannot be done, because the
numbers are too large. So, how do we use a list to do this?
We can create a list, it does not matter whether it is an array or user list ADT and we can
create a list of all these numbers. So, what we can do is you can use when you are
writing a program to compute let us say, you are asked to find out sum of these two
numbers. So, what do we have to do now, so we have to add these two numbers, then if
there is a carry, then this should be carried over here and so on and repeat this process
591
and maybe the number becomes larger than this 11 digit over here, you have to make that
happen.
So, what we do the way to do this using list that I am just going to give you an idea here,
what we want to do is you create a list with the elements 9, 7, 6, 5 and so on. It may be
actually convenient to put them in reverse order. I am not going to give you the details
for you to do that. And then what do we do I have 5, this number is written like this 2,
that is every single digit number is put into a, made an element in the given list and so on
have that. It will be convenient to put it in the reverse order, because then the first
element becomes the first element in both the cases; otherwise, you have to go the end of
the list, anyways it is a little bit more of cookie that is all.
So, what do I do, I perform the sum between these two elements. So, your objective is to
get define a new ADT called big int which uses a definition of list that is given.
Remember that there are append, prepend and so on and so forth, various kinds of
operations where you can prefix or you can suffix and so on and so forth.
Append is put it to the end of the list and prepend is put it at the beginning of the list. We
compute these operations over here, then what do we have to do now, this will generate.
For example, suppose this was 9 and what will happens I basically, if I am using this
version of the list, I have to pick both the last elements in the list. Then, I bring them out.
Because, the element type I know what they are, because I as a user I have just to built
the big int, I gave a string of two integers.
So, the big int problem that is being given, it is primarily a particular definition of list has
been given to which you have been ask to add some code write some code and the
operation that it is expected to perform is the sum of two large integers. So, what will
you do now, once you got the result, again the result possibly cannot be represented in an
592
integer variable in the computers. So, you after again converted back to string and give
the result.
So, that is the important point about the big int ADT that you are asked to implement.
And let us look at the hash table, what is the hash table tell you, it says that you are given
hash table is defined for you. Now, notice that big int is using the list to store large
numbers. So, what did we do we took these two strings, took the numbers of the strings,
took all the characters one by one, each one of the characters is it is digits and put it in
the list, that is what we did and then we use list to perform operations on the list, get
elements out of it. Then, perform arithmetic put it back to the list, so what we did. In the
hash table, it is slightly different.
What it is says that the hashing is defined, if we given a set of numbers. Let us say 99,
25, 33, 65, 75 and so on. You are asked to, these are called let us call these the keys and
you will given a hash function, let us say x percent 10. So, it means when the key is x let
us say and you taking the remainder when it is divided by 10, it goes into an array. And
this array has how many elements can it have maximum, clearly because this is percent
10 and we are doing in base 10 over here.
So, the numbers that remainders that you can have are 7, 8 and 9, so what will you
expected to do in this now. So, there is a large number of numbers that are given and for
these numbers you find out what it hashes to, 99 it hashes to. So, when I take percent 10
593
over here, this will give me 9. So, goes into these are called buckets 0, 1, 2, 9 are the
buckets are put it in this particular bucket, put 99 over here, then 25 percent 10 gives me
5.
Therefore, I put it in this bucket over here, then percent 10 gives me 3 and again I have
missed 2, 3 goes into 3 over here, this is 33 and 65. Now, there is a problem. When I do a
percent 10 of this what is happening, it gives me again 5 was the remainder therefore, it
goes into the 5th bucket. That means, now what done, it is already an element in this
bucket, therefore there is a collision.
So, what do we do now, we put 65 here and what you are expected to do as part of the
program is that the assignment that is given says as soon as you find a collision, if the
position in the input where the collision is occur. Therefore, 1, 2, 3, 4 it is the fourth
element and the number of elements in that particular bucket do not remember exactly
the order, let us say 2 here and index of the bucket. So, it is a 3 tuple answer, so this is
what is expected. Now, when you come to 75 percent 10 will give me 5 again. So, goes
over here therefore, I have the number 75 over here and what do we do now, it is the 5th
element in the list 1, 2, 3, 4, 5.
There are three elements now in this list and it is again the 5th bucket. Just check this
order, I am not show whether this is the order which we expected to get either the in.
But, make sure that you presenting it in exactly the same way as expected in the
question. So, this is the hash table question. Now, where is the need for list here?
So, basically you going to implement the hash table has an array of list as suggested in
the question, use arrays in list is what we says, so you use arrays in list. Now, what is this
now this list is the ADT list that has been defined as part of the problem, in that ADT list
it is very similar to what we did in class, except that it has also get count gives you the
total number of elements in the given list.
So, I hope now you are clear how you would solve the big int and the hash table
problems using the ADT list. In one case what we are doing, in the big int problem we
using the list like we did for the stack from the list, the list is used as a private variable
and operations on the list are performed such that, it limits the operations of a stack. Here
in the other hand what we are doing, we define a new ADT and we defining again as part
of the private part of the hash table an array of list.
594
Each element of the array is called a bucket and each element of the bucket pass points to
a list which consists of the list of elements that fall into the particular bucket given in the
particular hash function.
595
Programming, Data Structures and Algorithms
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Lecture – 45
Assignment on Data Structures
So, now, what you going to see is to see the solution to the hash table question.
So, let us go over this question once again, what is it say? A hash table is a data structure
that maps a set of keys to a given set of values. It uses a hash function to compute an
index into an array of buckets or slots, from which the correct value can be found or to
which a correct value can be inserted. A hash function will assign each key to a unique
bucket or slot. In practice; however, there are many more than 1 key that will hash to the
same bucket such an assignment is set to result in a collision. In this assignment you
have to implement a hash table using arrays and lists.
596
(Refer Slide time: 00:58)
The algorithm should accommodate the features described. You should take as input a set
of 10 non negative integers and insert them into a hash table using this hash function
where h of x equal to x for set 10. Then, input values that returns the same value for h of
x have to be properly accommodated such that if there is a collision the index from the
array to the appropriate bucket should point to a list that can take in all the values that
hash to the same bucket. So, basically what you need is you need an array of list such
that each entry of the array is a pointer to a bucket. For example, when h of x returns 4
then input value should go in to the list starting from a 4 if a is your hash table.
Your program should report when there is a collision and must specify the position
597
starting at one (Refer Time: 01.53) the list element in the input list of that key value
where the collision occurred. It should also report the bucket number and the number of
entries in the bucket. And if there are no collisions, the program should report no
collision. The hash table class has been provided with the print collision function which
you cannot re-write. What you have to do is the string that you want to print has to be
made available in the private data variable collision using list collision function in the
class hash table.
So, let us see how this can be implemented so, basically the constraints on the keys are it
can be a value between 0 and 1000.
598
And if you take a sample input like this what does it that we are say () 77 23 231 785 900
345 287 16 5 and 24. So, when you look at this 77 for example, when you take percent
10 it goes into the seventh bucket is the first element there is no collision. Let the go to
the board and give this example again for you are ready reflects.
So, what you have here? We have 77 and let me retrieve the numbers appears 77 23 231
785 900 and then I just go this first example 345. So, this is the key. So, what happens?
When you take this 77 percent 10 this gives you a value 7. This 23 percent 10 will give
you a value of 3, 231 percent 10 gives you a value of 1 and 785 percent 10 gives you a
value 5, 900 percent 10 gives you a value of 0, 345 percent 10 gives you a value 5. So,
now I am stopping with this, because now what is happen? So, what did you see now? 77
hashes to an index of 7, 23 will hash to an index of 3, 231 will hash to an index of 1, 785
index hashes to an index of 5, 900 to 0, 345 to 5.
So, what did you have now? We have an array of first we have to define array. Now, in
this array what are the how many elements to be have? We have 10 elements in the array
2 3 4 5 6 7 8 and we call this is a bucket, each one of the indices of this array is called a
bucket, because it is actually holds more than one value. So, now, what are we going to
do? We said due to store these case in this hash table so, this is the hash table. Now, the
problem is because, the, we have only 10 entries in the hash table. Clearly, because you
are talking about 0 through 9 9 9 as a number set are possible you have you have more
than 10 values and therefore, main numbers will hash to the same bucket..
599
So, that is the meaning of a collision has been already solved. So, now let see where we
will put 77 we will put 77 over here. So, we create a list at this point and put 77 over here
23 will go in to the third bucket we put it over here. Then 231 will go in to the first
bucket. So, far so, good there are no collisions. 785 will go in to the fifth bucket, this is
785 () here then 900 will go in to the zeroth bucket where remainder is zero and we have
still not seen any collision. Now, 345 comes alone and we have a collision so, what is the
collision now?
So, now, at 340 what is that? It remainder is 5 therefore, would goes into the fifth bucket
we already have a 1 element. So, we append this to the dust. So, what is this now here?
Notice that each element of the bucket is a list this, implementation of a list can be done
either using an array or a link list the list a d t is what you need to use over there. Now,
what do you supposed to report? You are suppose to report the index in the element in
the input list. So, the input list again 77 23 231 785 900 and 345. So, if you look at the
numbers over here. So, this is the sixth element in the list where the collision is occurred
and what is the bucket index? The index is 5 at how many elements are there in that
particular bucket? There are 2 elements.
So, now, and you are supposed to report the result like this 6 5 comma 2 and that is what
is given in the sample output and have more examples there are given in this in the
question that is being given ().
600
(Refer Slide Time: 07:55)
So, let us look at a solution to this particular program. So, this is kind of similar to the
ADT that was done in the lectures on the list. So, basically you have a link list
representation here.
And position point something point should list 8. In addition to that a new variable has
been defined in the, for count in the list.
601
(Refer Slide Time: 08:25)
So, it is gives you the number of elements in the list. Suppose count was not there what
would you do? You would write a function to count the number of elements in the list by
going traversing in the list from the beginning to the end. Then make an empty list
creation null node and insert elements in to the list find the end of the list all of them are
the same get count returns the number of elements in a given list.
So, let us look at a class hash table now, what are the class hash table contain now?
Notice this variable here list bucket of 10; that means, it is an array of list then it gives
you the bucket index, number of element bucket and position in the where you want to
insert in the particular bucket. And a strain a collision which has suppose to fill
602
corresponding to the collision which has suppose to the fill up and report called this is
required. Because they also say would print () whereas, be and no collision.
So, what are we doing now? Initially, because they the hash table is empty we said the
counter in the hash table in the count for example, to zero for every one of the list we
have an array of list how many do you have? We have 10 elements in the array and the
counter in every element in the list every element of the bucket hash table corresponding
to the bucket is said to 0..
Then what are we doing? To insert an element we find the bucket index that is as define
603
in the private variable. Once we get the bucket index and what we do? With this we
create an empty list of the count is zero;; that means, there is no list of there then we find
the position to insert and insert it at the end of the list. Then what are we doing? We get
the number of number elements in the bucket basically at what position it has to be
inserted in the given bucket and you return the number of elements on the given bucket.
That is what we insert function does slight variant of the insert that was done. And once
you have this they are all done then list collision what are you doing? You are giving the
position where the collision occurred.
604
The bucket index and the number of the number elements in the bucket that is all you
need to do. So, basically this is what you have to fill up this () no big deal over here this
is string to which your basically concatenating the position and the bucket index and
number of elements in the index.
And the print collision will take care of it then what is happening here? The print
collision function is given and what is a print collision do if it the, if for example, there
was the Boolean variable called report call. Basically there is no collision that occurs
then it will print on no over there.
605
So, this is the essentially what this ADT is all about. So, what are we doing here?
Basically are supposed to list the collisions that occur in the given data set as you have
seen in the example.
So, let us run this with the given example over here. Table and let us say I gave even
((refer team: 11:52)) 21 32 33 41 45 663 71 88 87 86. So, what is it telling me now, 21
for example, will hash to bucket 1, 32 will hash to bucket 2 and 33 will hash to bucket 3.
So, far no collision. But when 41 comes along it hashes to the bucket one again and what
does it do? It gives you so, 41 as the 1 2 3 fourth element therefore, the first indexes 4
here. And then what happens you have is the bucket number 1 and the number of
element. So, far in that bucket are 21 and 41 which is 2 then 45 again hashes to bucket 5
no collision. Then what happens? 663 hashes to bucket 3 and therefore, there is a
collision therefore, this is the sixth element third bucket and now there are 2 elements,
because 33 and 663 next what is happening? You have 71, now 71 will hash to one again.
So, now, this is the seventh element in the list hashes to the first bucket, but, now 21 41
71 there are 3 elements in the list. So, this completes the solution for the hash table
problem. So, next we will look at the solution for the big int problem let us look at the
big int.
606
(Refer slide Time: 13:42)
607
(Refer slide Time: 13:52)
So, what this is big int now, you are ask to find the sum of 2 large numbers here again
and other implementation of list is given a different implementation.
And you are suppose to use this implementation of list to perform big int arithmetic. So,
what is being done here let us see. So, what are we doing? To perform big int arithmetic
what us suggested is you have to implement at last call big int now, and with in this class
you using a private variable list just like we did this stack from the list.
608
(Refer Slide Time: 14:21)
And what are you doing? You are appending a given number prefixing the big int
number and you have to perform is add operations and print.
If you want you can have suppose there are leading is () 0 you can removed are
whatever. So, what are we doing now? The first function that is given to you it is
populate and it is suppose to write you code here.
609
(Refer Slide Time: 14:52)
So, what are we doing? As I am mentioned previously we read the big integers this is the
big int 1 and this is big int 2 we read them as strings and what is being done in this
particular program subbing clever is being done what is done is () what is the big int
using? It is using a list. So, what we doing is we are inserting every times so, I am
reading from left to right as well as you read this elements approved in the first position
next 9 is put in the first position. So, actually what is represented in the big int is I will
tell you why it is being done list 6 5 4 3 2 1 and 0 it written text.
And similarly, this other number here is written as 1 2 is inserted into the () list 1 2 3 4 5
6 4 1 2 and 3 and 5. The reason for this is both of these let me rewrite this again what we
are doing is in fact, this is the starting point in list one this let me call this list 1 are the
big int 1 and this is 8 2. So, what is being done is I store 1 2 3 4 5 6 4 1 2 3 and 5. The
reason for being this is that when we add 2 numbers remember we have to add from the
least significant number position. So, what happens even these 2 are the beginning you
can add these and propagate the carry..
Once you propagate the carry what you will do now? See if you add these 2 numbers this
gives you 1 there is no carry 2 plus 1 for put it back out here this is put create a new list
lecture 3 1 3 5 7 9. Now, this is an 11 here. So, now, this 1 should be added to this 2
elements 6 plus 4 10, 10 plus 1 11 and this one more here 9 here 9 plus 2 11 and this
becomes 12 and then finally, this 5 has to be added to the carry and should give you 6.
So, this is what the 2 numbers should be. So, what are you saying? 0 plus 1 is 1 2 plus 1
is 3 3 plus 2 is 5 4 plus 3 is 7 9 11 therefore, this becomes 6 plus 5 11 over here and 1 is
610
carry.
So, 6 plus 2 I wrote the number from over here 5 6 7 9 and 8. So, what would you do
here? 0 6 plus 5 11 well, we are here now, so, basically 0 and 1 1 and 2 2 and 3 5 7 4 and
5 9 6 plus 5 11. This is what is happening here then after the 6 and 5 have been add I get
a 11. Therefore, 1 is the carry then after I have carried the 1 here I have here 6 plus 1 7, 7
plus 4 11 again here then again there is one more carry 7 plus 1 8 gives you a 9. Because
the carry there and then 9 plus 2 11 and whether carry there and then we have 8 plus 3 11
plus 1 carry () said 12. And finally, this number which are longer than the second number
longer than the first number you add the carry to it.
So, essentially you should get this particular result, but, the result is in the reverse order.
So, basically finally, 5 plus 1 6 is what you are suppose to get. So, this should be the
result of the number, but, it is in the reverse order. So, what is done in this particular
program if you look at it is to make this convenient, because you can start from the
beginning of the list the population of the integer is done such that you print them put in
the beginning..
Then there is the basically there are two functions append and prepend which you give
here. Now, how you are suppose to write it when you add so, what you need now when
you adding now, and yet, because you are adding two elements at a time. They will be a
reminder and a carry which is represented by this and what are temp 1 and temp 2? Temp
1 temp 2 correspond to the 2 list which represent the 2 numbers.
611
(Refer Slide Time: 19:40)
So, the first part what is done is by both of them are the same length till that is what is
done is by temp 1 not equal to null and temp 2 not equal to null. What is being done is
you get that data from both this lists you find the remainder you find the carry. Then you
appended 2 other lists and you keep on to repeating this process until you come to the
end of at least one of the list.
Then what we you do is as I said in this particular case for example, list 1 ended first and
then list 2 ends later. So, it check which one ends first which one ends later and if there is
a carry you add the carry also same thing is done for if the other one ended first and the
carry is append..
612
(Refer Slide Time: 20:25)
Then if you want you can remove the leading zeros of they are there in, because you are
writing it in reverse order..
And basically what is done in this main program as was told you are not supposed to
write this function. You are suppose to populate the string a corresponding big int a big
int b and then perform the, add of the 2 of them and print them.
613
(Refer Slide Time: 20:48)
So, let us run this program what are what should expect now let me let say 1 1 1 1. So,
here I gave 2 reasonably big integers 1 1 1 1 2 2 2 2 and it gives me () let us run it again.
And that me gave 9 9 9 9 and give 1 let gives you notice that 9 9 9 9 is a 2 numbers this
is for the one in the one given a () is big int and other one has only 1, but, 9 plus 1 10.
So, what happens that is the carry and that is added to the next number and so, on this is
the repeated it. So, this is the big solution to the big int problem. So, what we have
looked at is the solution is to 2 of the problem that have been given as part of the data
structures big 5 assignments where remember right.
614
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 06
Lecture - 46
Stacks: Last in first out
Operations: push, pop
Example: convert expression to postfix
Implementation of stack
Good morning class, in the last class we did the ADT call list.
And to just recap what is the ADT list? List is the sequence of items unordered items for
that matter and then we defined a whole lot of operations on list. We also looked at the
implementation of list, what we talk about, we talked about two different
implementations although I showed you in detail link list implementation. Both the array
and the link list and I showed you a link list implementation of the list. I encourage you
to do the array implementation of the list. Now, I just have some problems over here, I
would like you to work on storing sets I gave you an example of storing sets using list.
615
(Refer Slide Time: 00:58)
And basically we want to perform operations on set union, intersection and set A minus
set B.
I also want you to look at another wide application of list is to or what we call infinite
precision arithmetic. That is if you want to do let us say you have a 64 bit machine and
you want higher precision in 64 bit, then you can use the list as a structure to perform
arithmetic on it.
616
(Refer Slide Time: 01:32)
So, basically what we do is, what we are talking about is in the list here you are a 1, a 2,
a n up to a n corresponds to different parts of the given number and then you define
operations to perform arithmetic operations on this number represented as a list of
element. What I mean by that is, the numbers represented in parts in terms of a 1, a 2, a n
and you want to perform arithmetic operations on them. So, I would like you to try these
problems.
I also have a few assignment problems we talked about this a little in the last class. But, I
617
would like you to write a function to remove duplicates in a given list using only list
operations. And basically a function which takes a list and returns the purged list and I
also want you to convert a given unordered list into an ordered list, again it should be
done in place. So, I would like you to do these two problems on this.
Now, next what we going to talk about is the different type of ADT called a stack. What
are the stack now? Stack is also a list, but it has a fundamental difference, it is the special
type of list ADT and the difference in the stack is that it operates on principles of what is
called last in, first out. What is the meaning of this, that is the last element which enters
into the list is removed first, this is called a stack.
Now, let me give you some example when you putting books on your table, then you put
the top most, the most recent book will be the top most book and then when you remove
for example, if you want to remove this particular book, you remove the top two books
before you can remove this book.
618
(Refer Slide Time: 03:35)
And other example let us talk about plates that are stacked in a cupboard, see put your
plates over here and like this and you remove the top most plate. The most recent plate is
what is first removed, if you want to remove the third plate from the top plate, you
remove the first two before you can remove the third plate. So, stack has lot of nice
applications and we will talk about a specific application of a stack.
The operations that are supported by the stack are you can push, you can pop and you
can find what is the top most element in the stack, can also check whether the given
619
stack is empty or not. So, these are the four operations that are normally supported by the
stack and now since stack is a special type of list, I want to show you one example here
where, since you know now the way we looked at it is I have already implemented the
list ADT and since I have already implemented the list ADT, I would like to use the list
itself to represent a stack.
So, how do I go about doing that the way I would do that is since the list ADT has been
you know you already debugged it, you have tested it and so on and so forth. So, what
we could do is we could use the list ADT itself as a stack. So, how would I do that then,
then what I will do is I will restrict my operations.
So, what I will says that the push operation, so with in my I can write an ADT stack here
and says that stack or a class stack and I will say that I have l which is of type list which
have I already defined it. This is my private data, then I will define a set of operations on
stack and what are the operations that I want, I want push, I want pop, I want top and
then I want empty. So, what will I do for push now also push x comma what will I do l
dot first, what does the l dot first return, it returns the position of the first element.
So, insert it at the first position, then I can pop, I can pop l dot first. So, this was the most
recent element that was inserted, top will also give me l dot first, pop will be l dot first
comma l dot delete l dot first and top is l dot first and what should be, empty is if the l
dot first equals l dot end, then I can say that the stack is empty. So, what I have done this
620
process here now, we have all these operations over here and it should be retrieve of top
should be l dot I would have return to be the retrieve of l dot first, this is what we have
how you do implement all these four operations of the stack using the list.
So, you can use the list ADT in it is present form which you implemented well as the
stack ADT and perform the four operations which are required for the stack, now what
we will do is somewhat I have this. So, this is one of the thing I am trying to encourage
you to do is that basically that if I also have a ADT which is implemented, I try to reuse
the abstract data type as much as possible. Because, why do I wanted to do that the
primary reason I wanted to do that is the list ADT which we implemented in the last
class, we let us say nicely debugged, no errors in it.
So, what I am doing is I will quickly define a stack using the list and then I will use a
stack to perform various operations. What I am going to do right now is I am going to
take give you one application of stacks and then we will look at the implementation of
the stack a little later.
621
(Refer Slide Time: 08:15)
Let us say why you want to do this, let us say I have an expression which is given like
this. Let us say I am given an expression like this a minus b star c plus d star f plus c by d
and suppose we know that star and slash had higher precedence than minus and plus and
the all operators are left associative. What do we mean by this is that a minus b plus c is
equal to a minus b plus c, rather than a minus of b plus c that is we assume that all the
operators associate to the left and this is a meaning of it.
I am given a expression like this I do not know whether I have to perform a minus b plus
c or should I perform a minus of b plus c that is where the associativity matters. What is
the interesting is that when I am given an expression like this and I do not know the
associativity of the operators, it is difficult to evaluate this expression.
So, what is done is given a particular expression, the expression is converted to a form
called the postfix expression. So, let us see what this postfix expressions is all about, let
me just take only a partial expression a minus b star c let us say, this will be written as a,
b, c star minus. What is the meaning of it, we take the top most operator. Then, you come
to the next operator and the next one is still an operator, so you push it somewhere, take
the next operator, again you use the stack for this. Then, you find on the top there are two
operators over here, two operands over here you compute this b star c.
The idea is that what happens is the operator associate with the some binary operator
associates with the nearest operands that are there. So, what is nice is the advantage of
622
this expression when you write it in this form, there is no issue about the evaluation.
Why is the no issue about the evaluation? Because, you look at the operator, you just for
example, if I keep moving along this as soon as a come across the first operator, all that I
will do is I take this operator, operated on these two operands get the result and put it
there and that we call it d, then I have a d and minus. Then, it means after find I am
assume a minus d.
So, that is absolutely no ambiguity when a given expression is given in postfix, the order
of the operations. Whereas, if I give the expression in this form which is called infix, I do
not know whether I am has to perform a minus b star c, I do not know the associativity, I
do not know the precedence of the operators. Everything is subsumed in the postfix
expression. Given a postfix expression all are I have to do is I traverse that postfix
expression from left to right, as soon as I come across an operator I take the nearest two
operands, perform the computation, then you can put it back on stack, this is the way of
evaluating again using the stack.
Then, once again the result I have a d and minus on the particular stack, then again when
I traverse this for example, next I see another operand over here and as soon as I see
another operator over here, then I take the most nearest two operands, perform this
operation and I get the result. So, the evaluation of expressions is normally done using
the postfix expression. You take an infix, infix expression is very convenient for us to
write.
But, what we will do is we take the infix expression, convert the infix expression to a
postfix expression and then perform the computation. So, now what I am going to show
you is the evaluation also can be done using stacks, but it will take one application. We
look at conversion of infix to postfix using the stack, let us see how you get this done.
So, what is it done here, when an operand is read that is I am getting this expression here
as soon as I find an operand, what am I going to do I am just going to simply place it on
the output.
623
(Refer Slide Time: 14:18)
Then, when an operator is read, what do I do I take this operator, so that means, let us go
back to this particular expression, take this expression now I see an operand I put it, this
is my output, this is my input, then what do I do I put it to the output. When an operators
read, what I do is I look at the element I have a stack here, right now my stack is empty.
Look at the element which is there one on the top of the stack, if the top of the stack has
higher precedence, then I keep popping all the elements from the top of the stack.
So, this part is not that, because it is empty, so what I am going to do, I am going to
624
simply stack the current operator, I put minus 1 on it, then I have b here then what do I
see, then I have a another I see another operator over here. So, now I look at the top of
the stack this is a minus sign here, minus sign has lower precedence. So, what I do I just
simply stack star also on to it, then again I see an operand move it to the output. Next
what do I see, I see a plus, when I see a plus what I do is the following, I look at the
content on the top of the stack.
So, star is the content on the top of the stack, so since star is a content on the top of the
stack I output and star has higher precedence. So, the precedence is like this star comma
slash and plus comma minus and of course, above this you have the brackets. So, what I
do is I pop the star. Then, once since I pop the star what do I have, I only have minus on
top of the stack. What the current operator that I am looking at plus, now clearly plus and
minus have same precedence.
So, what I do I also pop the minus, then what I do I put this plus over here onto the stack
and with precedence, this is how this whole operation course about. So, basically this is
what we do and now I have done, up till this part of the expression. Now, let us look at
the next one what do I have now, the plus is gone it is gone to the stack, I get d I push
this on to the stack. Then, what do I have, I have a left bracket I put the left bracket also
on to the stack.
Then, what do I have, I have a right, then I put the plus on to the stack, then I have c d,
then I have a slash over here and compare with what is there, put it on the top of the
stack, then what happens I come across the right bracket. So, as soon as we come across
the right bracket I pop everything of until I see the left bracket that mean I put the slash
here, I put the plus here and then what do I do, I come to the end of the expression star
and plus. So, this is how I complete the expression.
625
(Refer Slide Time: 17:56)
So, this is the conversion from infix to postfix. So, what we have done, we took the infix
expression, then we converted it to a postfix expression using a stack. Now, let us see if
it will give us the evaluation correctly, what did I say. I just keep this as it is, then as
soon as I see an operator I find the nearest two operands and compute the computation.
Let me call this b operand that is I computed these two and let us say this result is b
prime, then what do I have, I have a b minus.
Now, clearly there are two operators and there are two operands and there is an operator,
so I compute this let me call this a prime. So, what do I have now, I have already
computed this, now I have d f c d slash plus star plus, so what do I do now, I keep
moving from left, this is computed let me call it is c prime, then I have f c prime d a
prime. So, what do I do, I compute this let me call this f prime, so I have a prime d, f
prime plus and plus is already gone, d prime plus f plus c d.
So, let me call this c prime plus is already gone, I already computed this sum over here
and then I have a star over here and a minus. So, next what do I do, I compute a prime d f
prime let me call it d prime after the multiplication, because I see the multiplication here,
multiply these two put the result in d prime I have a minus and now finally I and get the
result which is essentially what the...
So, basically what it tells is if I follow the rules of arithmetic for a postfix gives
expression, what is a rule say I take the… whenever I see an operator, I take the nearest
626
two operands, perform the computation and store it. Then, what happens that becomes a
new operand now b prime, then a and b prime and this is an operator here minus. So, I
compute the difference of a and b prime, again I put it back on top. So, then I call it a
prime, then what happens I have d f c d, I just put them all together, then I see a slash.
When I see a slash what do I do, I take the nearest two operands, multi divide them, here
the operators slash, so I divide c by d I call it c prime. Then, I have a prime d f c prime,
then what happens I have f and c prime. So, this is this part, so I add them then what do I
had a prime d f prime let me call it, then d f prime star this star is here. So, I perform this
computation and because that is the first operator that I am seeing I take the nearest two
operands perform the computation, then finally I get let me call it I got a new operator,
operand called d prime and a prime d prime and I subtracts one from the other and the
postfix expression can be evaluated.
So, clearly what it tells me is using the algorithm that I have given here, the conversion
to infix to postfix is correct. So, basically I can use a stack to take this input convert it to
postfix. So, whatever you doing when you doing this, we are taking all the operators,
stacking the operators including brackets and when do we pop as soon as we come across
an operator which has lower precedence than what is there in the stack, I pop all the
operators from the stack which have higher precedence and the operators which have the
same precedence too and put this operator on to the stack that is exactly what we did. Let
me illustrate this with the pictorial example again.
627
(Refer Slide Time: 21:57)
So, let us say we have the same I have a same almost the same expression which I have
already gone through over here and never realize it. This is the same expression that is
there and here is a pictorial description of what is exactly happening stack the plus, this
is the top of the stack, then a is kept outside and then you get a, b, c then you put a, b, c
star, pop this and so on. So, this is how your output gets generated. As soon as you see a
bracket you just push everything out. So, this is how you convert and invert...
So, basically this is the first thing whether the stack is a stack of operators. So, what is
happening now plus and star has stack, then as soon as the next plus comes you pop both
plus and star, just I was already told to you and then you see the bracket, you put the
bracket on top of the stack and repeat the same process again. So, this is essentially how
stack can be used.
628
(Refer Slide Time: 22:54)
Now, let us look at the implementation of the stack and what are the operations, I am
looking at a separate implementation. Let us say that I am not going to use the list, I want
to implement my own stack. Before that what are time complexity of these operations
when look at l dot insert, l dot delete, l dot retrieve, what will be the time complexity of
all of these, if you can see if you look at the, it depends upon the kind of implementation.
If the implementation is a linked list it does not matter, doing this particular case I am
inserting at the first, all these operations can also be done in order one type, even when I
am using the list. But, we can do a cleaner implementation, we can implement a stack.
So, since that in this particular example I am using the operators I have created a stack
which is I am using an array implementation here, this is your static array.
So, basically the stacks size here, see you would also like to have a stack full perhaps.
So, I create or you resize the stack, if you want it to be transparent you can always
resizes the stack. Then, you create an empty stack which is make null, then you push the
character on to the stack, character pop returns the top most element on the stack, pop
deletes the element from the top of the stack, empty tells you that the stack is empty.
From here is how the various operations can be performed and basically what is
interesting is all the operands, operations here, if you look at this.
629
(Refer Slide Time: 24:37)
If you look at the analysis of this I want you to go through this over here, make null it is
simply creates the top of the stack to be stack size. Then, we just putting from pushing
from the stack, what are we doing we here in push for example, I making the stack size,
the top of stack initially match to the largest element possible in the array, largest index
possible in the array, then what we do, we decrement the top of the stack and then push
the element there and then when we... So, this is again an order one operation if you
notice, this is an order one operation, this is an order one operation.
630
Then, what we are doing if the top of stack is less than stack size, it is returning the top
of stack that mean, otherwise what is happening when you performing a top operation,
that is no element in the stack that is the meaning of this return 0 over here. Then, if top
of stack greater than stack size returns 0 again; otherwise, what are we doing you are
popping the particular element on the stack, that is when you are trying to pop more
elements than there are, otherwise if there is an elements on the stack, it pops this.
Then, if stack is empty, it is top of stack is greater than stack size, then you say return
greater than or equal to stack size, then you say that the stack is empty. For the time
complexity of all the operations, there are performance stacks using array is order one.
631
(Refer Slide Time: 26:12)
Now, what I want you to do is I want you to go back and implement the stack, I given
you an array implementation of stacks I want you to do a link list implementation of
stacks and all operations must cost the order one. So, I want you to do go back and
experiment this. Now, what I want to do is I will just close this with some applications.
Now, I want you to do this, right now we looked at operations, we only operators we
only looked at left associative operators. What happens if an operator associates to the
right, let me give you an example.
632
(Refer Slide Time: 27:04)
If I had an example like this x to the power of y to the power of z, then what is this, this
is essentially y to the power of z whole x to power of. So, I want you to think of this how
would you use is stack, how would you modify the infix to postfix converter, if you
worked with operators which are right associative. The other function that I would like
you to write is to determine whether the parentheses are balanced in a C program. I want
you to look at all these types of parenthesis. I also want you to write a function that uses
a stack you already talked about it to evaluate a postfix expression.
633
Programming, Data Structures and Algorithms
Prof: Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 07
Lecture - 47
Queues: First in first out
Operations: enqueque, dequeque, empty, front
Implementation of a queue using linked lists
Implementation of a queue using arrays
Implementation of a queue using circular arrays
modulo addition operations
In the last class, we just studied about the stack ADT. Today, I will talk about another
kind of list ADT, just called queue ADT. Queue is what, now queue is does not require
too much of an introduction. Stack for example, you already list you already know, you
always use this list all the time in your life. And stack for example, once I give you the
example a stacking plates on a cupboard or stacking books on a desk, you know that
immediately that the stack ADT is to be a last in first out kind of a data structure.
Now, a queue is something that you all know you, you stack in India we always stand in
queues for almost everything whether it is a railway station, the bus stand or we know
getting your registration done in your college, getting your id card, wherever you have
whole a lot of queues. And grocery store for that matter, if you go to big bazaar or
something that you are standing in the queue for a long time.
634
(Refer Slide Time: 01:15)
And that is what a queue ADT is all about, what is the property of the queue ADT is just
that the person who is at the beginning of the queue is served first and then the next
person and so on. So, this data structures somewhat different, unlike the original list
ADT where we said, you can insert anywhere and delete anywhere and the stack ADT
where we said, the last person gets priority, gets deleted first. This queue ADT is one
which is of the type what is called first in, first out.
635
You also knows as FIFO first in the queue and therefore, I must be served first. In India
of course, in some places when you we have a lot of population, it behaves like the
normal list. But, in a civilized society you will have that you follow the queue principles
first in first out.
So, what are the various operations that we perform, you want to perform enqueue,
dequeue, empty and front, you want to find who is there in the front of the queue, this are
the four operations which you would like to perform. Basically, let why would I want to
look at enqueue of course, a particular job as to be enqueued and dequeue when you
want to remove an element from the queue, empty queue we want to find whether the
queue is empty or not.
For example, if I want to close the counter and I decide that if I have multiple counters
let us say, waiting at the passport office or whatever a multiple counters, I would like to
check whether the queue is become empty, so that I can close a particular counter. And
front of the queue is to check who is there in the front of the queue. For example, if you
go to the passport office, the facilities tatkal verses ordinary or whatever.
So, I would like to move this person to different queue and so do not be in this queue, if
you are asking for some other priority base thing, because this is for non-priority people,
everybody has a same preference and if you come in first, you get served first. So, let us
636
see how we can operate on this and clearly since the queue is also a special type of a list,
we can again use the list ADT to perform the operations.
So, what will enqueue become now, if I have a list ADT it will say l dot insert at l dot
end. We have always inserting at the end of the queue and dequeue would be l dot delete
l dot front and empty will be l dot front, just like in the stack equal to l dot end would be
an empty queue. Front would be l dot retrieve of l dot first. So, what I have shown here, I
have shown you at the all the operations that you want on the list can be on the queue can
be implemented using the list.
So, what I could do is in my ADT I can say l is of type list and define operations on list
to perform this operations on the queue. So, this is the first thing that I always advice any
student to do. If you have one ADT which we implemented which is completely
debugged, then my advice is you just use the reuse that ADT again, that is what I am
doing again here, just like we did for the stack. I define the ADT queue in terms of the
list and do operations on list to mimic the operations of the queue that is all. Then, what
happens you very quickly have a queue ADT.
Let us see on the other hand I want to implement my own ADT queue, let that is what I
am going to talk about now. So, what is that we have, the operations which remove
elements are dequeue, front and empty which gets from the queue and things that put
elements on to the queue. First of course, you always have to create a empty queue, this
is make null operation and then enqueue which puts elements on to the queue. In my
implementation, this we were use in C plus plus what I do is to make deletion easy, I
have done a link list implementation here, we will talk about array implementation in a
little later.
637
(Refer Slide Time: 05:49)
So, in a link list implementation is shown here, you have pointers which points to the
front of the queue and another pointer will points to the end of the queue. In this example
here, that I have done, what I do is in addition to that these are the things by the way.
Because, you are hiding the implementation from the user of your ADT, you can do some
clever things in your implementation to make some of the operations easy.
What I have done in this implementation over here is if you notice here, my make null
operation create an empty queue or create a dummy cell, I did this for the list also if you
remember. I create a dummy cell then, my enqueue becomes insertion at the end of the
dummy cell. What is the advantage of this, I do not know I have to check whether the
queue is empty and then insert. I know that as soon as I have created an empty queue,
there is one dummy cell and therefore, insertion becomes simply inserting at the end of
this dummy cell.
So, then I let us say enqueued one more element. So, what do I do, I go to the end of the
queue and put another element over here, that is I have a front pointer and I have a rear
pointer. Please look at this slide, there are two pointers pointing to the queue, front points
to the same location and rear points to the same location, then it is empty. But, what is
nice is, because I created a dummy cell, what happens is when I created a dummy cell,
the advantage is that there is always one element there both at the front and rear end
pointing. It is not pointing to somewhere, it is nowhere.
638
So, whenever I do this when I am doing the enqueue I am inserting x over here. So, what
is the interesting is now front is pointing to the dummy cell, rear is pointing to the
element. Now, I am enqueue one more element then what happens, rear is updated and y
is enqueued after x and rear is updated earlier. So, now what I am going to do when I do
dequeue, I just when I dequeue I remove this element. So, I keep the dummy cell as it is
and dequeue the element next to the dummy cell, this is the fundamental advantage of
using a dummy cell and rear is pointing to y.
So, this is the advantage of implementing, when I am doing a link list expression study.
When I do a link list implementation of queues, this is the big advantage of, you know,
using a dummy cell to implement when you create an empty queue. This is the big
advantage of it.
639
(Refer Slide Time: 08:39)
And here is an implementation over here and I have just given you the C plus plus
implementation here. So, you have a recursive data structure just as before and notice
that again, I have what you called typedef position. So, for example I have said typedef
struct CellType position, let us see how you do this in the case of the array. In the case of
the array, when I do a typedef into the integer which will be typedef to this.
Basically, the user is simply going to use this typedef position, just like you use a type
integer, char, float, for that matter we create a new type called position. And how the
implementation of the position is, in this case it is a pointer to a recursive data structure,
in a case of array it is said to be an index to an index in the element in the array. And of
course, you need two positions front and rear, because we have two pointers for this. And
we have a creation of the empty queue, then you have the enqueue, then you have a
dequeue and then you have a sums an operation it tells you what is there in the front of
the queue and other which tells you whether the queue is empty or not.
640
(Refer Slide Time: 09:45)
And here is a simple implementation of how the rear is done, rear point and next it and
then basically wherever the rear is you create a new element at the end of, create a
CellType with new element and put this element over here. Dequeue what are we doing,
this is the fundamental advantage, dequeue is empty then you say queue is empty;
otherwise, we want to be do it just simply update a front to point to the next element.
What is the interesting, the time complexity of all these operations notice that there is no
loop in any one of them, there is no going from the beginning of the list to the end of the
641
list. Therefore, all these operations this is, if you let us take enqueue for that matter, this
is order one, this is order one, this is order one, this is order one. So, this is the form of p
1 plus p 2 plus p 3 plus whatever, if you take each one of these as a subprograms over
here.
Then, what are the time complexity of the segment it is basically the math’s of all these
therefore, the time complexity of enqueue operation is order 1. Similarly, we can argue
that the time complexity of the dequeue operation is also order 1 and creating an empty
queue, every one of these is order 1. So, this particular list implementation it is not
difficult, it only takes order 1 time.
Now, additionally sometimes for example, there is also another operation that you would
like to perform have which is called the queue length which will return a value which
tells you how long the queue is some ADTs also at the implementation of the queue also
have queue length. What is the advantage of this? For example, if I am again grocery
store like you know food world or something like that I may decide looking at the length
of the queue whether I want to increase the number of counters.
So, it is a big advantage to do something like this to. It can also have an extra function
called length. Now, if I am looking at the extra function called length, how expensive
will this operation be, if you keep… So, basically you need another variable called
length which maintains the length of the queue and then what will happen is you just
keep on incrementing it when it enqueues and decrementing it when it dequeues. So,
again that operation is not going to cost you more than order one in terms of time
complexity. So, we talked about a link list implementation of the queue and I like to talk
about an array implementation.
642
(Refer Slide Time: 12:23)
So, what we will do with an array implementation of queues, so let us say this is my
front this is the rear, let us say I have an array of some particular size, let me call it some
max size. I am going to leave you this I am not going to give you the complete
implementation of this. So, this is my front the rear, now I dequeue then what happens
front moves over here, this becomes front.
Again I dequeue this becomes front, then let us say enqueue, then may be rear moves
here, let us say I have e and then now I reach the end, again I rear. So, what happens is
the problem is that the queue is found somewhere between front and rear in the array, let
me say this is the... So; that means if I am looking at the pink color over here, the queues
found between front and rear, from looking at blue the queues is found between front and
rear and similarly the whites give me the queue between the front and rear.
But, the basic problem with this is this kind of an array implementation of queue what
will happen very quickly it will exhaust the size of the array. What will happen is,
suppose I keep on dequeuing an element, then the front comes over here and then I
cannot add any more elements, this not a good idea.
643
(Refer Slide Time: 14:05)
So, what we normally do is that we convert this array implementation to a circular queue,
let us say this is 1, 2, 3, 4, 5, 6 and so on. And this is the max size, it is still an array
implementation, but we do what you call a logical tying of the front and the rear of the
queue. So, what is the meaning now, if this is front here, this could be rear here, and it
could also happen that this is front and this is rear, how do you perform this kind of
arithmetic now. That means, our circular array and somewhere going clock wise, I will
find the front and the rear of the queue.
So, how do you perform these operations, you guessed it right, you have do a modulo
operations, modulo based on what max size, to find the location of the element. So, what
happens if all the time we will taking whenever we keep incrementing, the array for
example, initially let us say both front and rear are pointing to the first element or let us
say front is pointing to the first element and rear is pointing to max size over that to
show that it is symmetrically, I will tell you this is become a problem, but we will talk
about in a little later.
Then, what are we doing when I am going to enqueue, then what I am going to do now, I
am going to move, put the rear over here and I will put the element over here, this is
front let us say keep enqueue, this is front this is rear. Then, what happens I am always
doing modulo size arithmetic. So, when it exceeds it over here, then what will happen the
rear will start over flowing onto the beginning of the array is this problem with this
644
implementation I want to you think about it. I said, I start with front pointing here and
rear pointing here to indicate that it is an empty queue.
Now, how do we distinguish between an empty and a full queue, this becomes a
problem. So, we will need another variable or alternatively you can think of something
more clever, and I would like you to think about this particular problem. So, circular
queue the advantages is that the queue is found anywhere between two indices front and
rear. How are these indices computed, the indices are computed using modulo arithmetic
and a modulo is based on...
For example, I will say rear is instead of saying rear plus 1 we will do rear plus 1 mod
max size. And we have do a little more big book keeping as I already said when we… to
distinguish between a full and an empty queue, there are two ways of doing it, you can
introduce an extra variable. So, the point is if I am using an array implementation what is
happening, the linear array the array size gets exhausted very quickly. But, the circular
array whatever is away allowable in this, it the queue can be basically the queue moves
like this, that is the fundamental property of the circular force. nice data structure and I
want you to implement all of this. Interestingly, again all the operations on the queue can
be implemented in order 1 time. Let me leave you with this.
645
(Refer Slide Time: 18:07)
As I already said implementation is a circular queue in an array, there are issues you need
to find out how to fix this, difficult to distinguish between empty and a full queue. Then,
I wanted you to do this as an assignment, it is nice to simulation is very good way of
learning about ADTs. So, I would like to simulate a petrol pump, where I have a petrol
pump, it serves petrol for various people. Let us say you have two different petrol pumps,
pumps one for the 4 wheeler and one for the 2 wheeler and for each one of them you can
have arrivals that happen to the particular system, give you an example.
Let us say the two wheelers arrive very frequently may be I have every, I almost have a 2
wheeler every in every minute, whereas I have one 4 wheeler every 5 minutes or
something like that. Simulate this and put it in a queue and then enqueue and dequeue
and just give statistics of how long the queue is at different intervals of time. In other
very interesting implementation of queues is using the circularly link list.
646
(Refer Slide Time: 19:20)
That is, I am having a link list over here and I tie the back to the front and I am giving
you only one pointer p, no two pointers we have front and rear. I want you to implement
the queue, all operations on queues must be order 1, I want you to do this. I am giving
only one pointer, no front and rear this is only pointer p. So, now where should p point
to, should point to the front, should point to the rear. I want you to think about it, it is a
circularly linked list.
Now, this one more problem which is not listed there, how do you implement a queue we
saw in the beginning of the class, how to implement a queue using a list ADT. The
question that I want to ask you is how do you implement a queue using stacks? I want to
use a stack ADT. Remember we you reuse the list ADT to build a queue ADT, can I do
the same thing after all queues also a list, type of list says stack is also a type of list.
Now, can I use the stack ADT to implement the queue ADT? What would I require, I
want you to ponder about this particular problem.
647
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute Technology, Madras
Module – 08
Lecture – 48
Summary of list, stack, queue
Introduction to Trees
Motivation using family tree
Representation of a tree: array representation,
linked list and leftmost child right sibling representation
Binary trees
Operations on binary trees: create, findchild, findparent
And we talked about a generic list ADT, there we said insert any element anywhere.
648
(Refer Slide Time: 00:30)
And then we have talked about two other flavors of the list ADT, one is the stack LIFO
Last In First Out and the other is the FIFO which is the First In First Out or the queue
ADT. Now, the generic list ADT what it consists of, it consisted of a sequence of items.
So, it had a set of elements and if you want you could have completely an unordered set
of items as in the generic list or in the last in first out of the stack ADT, we had a
particular flavor where you said you can only insert at the top and delete from the top.
And in the first in first out this is the queue ADT, we inserted it at the end and we deleted
from the first. So, it is like you know when you look at generic ADT, it is like a vanilla
ice cream when you know, whereas the stack ADT is like chocolate and if you like
chocolate, then it is like chocolate ADT which is does something very special and on the
list ADT. And a first in first out is perhaps like butter scotch which just something else
special.
So, it depending upon what you like, I do know these are some of the most favoured
flavors in ice creams. So, it is something like that, so either a stack or a queue still
qualifies as a generic list now, there is one problem with the stack or a queue and the
problem is the following.
649
(Refer Slide Time: 01:49)
What we see is that whether directly or indirectly there is some kind of ordering in the
list. The items either or pushed on to the stack and popped from the stack in one end or
they are, you know pushed at one end, removed from the other end as in the queue ADT.
Now, today what I want to talk about is, let us say I want to represent the family tree.
Suppose I want to represent my family tree, let us say a is the parent has b e, a set of
children let me say that p are the parents it does not matter whether mother or father.
And let us see you have four children and these are your siblings, now this let us clearly
650
the old generation and this generation now for example, generally has generation 1 above
you guys has had perhaps only 2 children f and so on.
And suppose you want to find out, you know who is the grand parent of whom and so
on, then if you want to traverse you have to represented all of this using a list, then the
problem would have been, this would have been number 1 in your list 2, 3, 4, 5, 6, 7, 8,
9. Assuming that 6 came after 5, you put them in this particular list and to access an
element at 10, let us say or to access the element the 11th element for that matter, you do
have to go to the end of the list.
So, this would have, but at the same time the reason I am talking about this is when I am
using a list to represent something like this, then what kind of a list will I use, I will use a
generic list. Because, there is nothing like when you look at siblings for example, there is
nothing like a priority of one sibling over the other. But, just the representation by itself
it is, first you put them in and so on and so forth. But, you know deletion for example,
someone dies in the family, it can happen at any point in the list and all most all
operations will become very expensive, because you have to traverse mostly the entire
list. So, the idea is can we represent these kind of structures in a better way. So, one of
the most popular representations, what do we want from this, if I want to access the node
corresponding to j, then I would like some information.
What would like I to say I want to look at, then I would like j to say who is his or her
parent, who is this one’s parent, who is this is parent if I want to find a ((Refer Time:
05:20)) of j. So, how easy or how difficult is this depends upon the kind of representation
that we are going to use. So, a tree data structure kind of suites this kind of data very
well, so what we are going to talk about is we are going to talk about a general tree
something like this.
And then as I already said, the major objective is to be able to traverse this particular
tree, see if a particular element is present in the tree based on certain criteria that can be
satisfied and so on. We will come back after we do all these to generate a tree, but this is
in general a tree, now representation of the tree can be done in a number of ways.
651
(Refer Slide Time: 06:16)
You can use an array representation or you can use what is called any general tree is
represented by what is called the left most child, right sibling representation. What this
means is p, a, b, c, d is the representation for this and this is represented like this and you
have i which is represented like this, it has two children f and this one has the left most
child j, I will explain in a minute what this representation is all about.
So, what is done is notice that we take this particular tree over here and converted it into
to this format what would I do if I was representing an array, I would say here 0 this is p,
let us say this node corresponds to p. Now, I keeps some information 0 over here, then
the indices go from 1 to 2, I would say a comma 1, b comma 1, c comma 1, d comma 1
and I would say e comma 2, f comma 2, j comma 3, I will tell you in a minute what I am
representing over here, h comma 3 and i comma 6 and j comma 7. What I am storing
here, if I am this is an array implementation and this is a link list implementation.
What are the link list now, it is a recursive data structure, what is the problem with this
tree, we want to represent this tree, then using a link list then at every node I must be
able to say, if I was representing it by the number of pointers I must say that node has
some max number of pointers n. To avoid that you know, I could have a list of elements
on the other hand that is exactly what we are doing over here.
See, you looked at it over here after that we are doing, we are saying here p is a root
node and it has children a, b, c, d. Let us say a is the left most child or the oldest child,
652
then to make our representation more convenient, if this is the root node within that it has
a pointer to the left most child which whose right sibling point to all the other nodes.
And similarly it will also have a pointer to it is left most child, so the tree is like this, the
pointers that are pointing forward. On the other hand in the array implementation, why
are we doing is, because as I say one of the fundamental problems says we want parent
information or from the parent I would like to go to the child, one of these two
information is required. See if we look at this, the array implementation on the other
hand what do we do, we say that I store the parent in this particular, let me call it the
index of the node is 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 and 11 suppose I have storing it in an
array.
Then, what we do is we store a pointer to the nodes parent and now I have put the index
0, index 0 means what, this particular node is the root node. Next what I do is the
element a, now element a is stored here and who is it is parent, it is parent is p whose
indexes is 1. Similarly, the node b is stored in index 3 and it is parent a is a node 1 and I
keep that index. Now, for c what happens a, b, c, d is like that, when I come to e what do
I do, I keep the index of it is parent which is two, because a is the parent of e therefore, I
store e and 2 over here.
Similarly, for f what do I do, I store f and 2 over here, because for f the parent is again a.
So, you repeat like this and you can get use the array representation for any general trick.
In the link list representation, this representation is basically this is array representation
where we use pointers to the parent in every node. On the other hand, in the link list
representation what does every node have, every node has a pointer to it is left most child
and the right sibling.
So, notice that p is the parent, it is the root of this particular tree therefore, it has only
pointer to it is left most child, it is right sibling has pointer to null, because the root has
no siblings. And here what is that is happening here, a is the left most child and it is
pointing to the, it is right sibling this is right sibling, this is it is left most child, left most
child, left most child, right sibling pointer, left most child, right sibling, right sibling, left
most child, left most child, right sibling, left most child, right sibling, right sibling, left
most child, left most child, right sibling.
653
So, what I have done now in all the arrows now I have put the which corresponds to the
pointer, which corresponds to that of the left most child and right sibling, RS to the left
most child and right sibling. So, we make these pointers like this. Now, that means what
is nice about the structure, in every node we are having only two pointers the left most
child and a right sibling.
But, so basically you have two pointers in every node, but you are able to represent any
general trick. Now, this tree is useful if you want to look at any arbitrary tree. For the
time being we will kindly postpone this and we will talk about this is a little later. But,
remember that this is one such representation of a tree. What is the meaning of the left
most child, left most child is older sibling and a right oldest child, in the right sibling
corresponds to it is next sibling at that particular 11. We will come back to this history a
little later where we have either array representation of this.
What we are look at is we look at some special trees today. In particular we will look at
the binary tree. What is the binary tree now? Here is an example, where we have the root
and it also has two pointers one to the left child and the right side, you know hierarchy
here, that is the fundamental difference. It is similar to this where you have a node has
two pointers left most child and right sibling. The difference between the binary tree
node and the general tree node is that the left child and right child there is no hierarchy.
654
What you mean by that here a points to b, two had been nodded at the same level
whereas, the left most child points to a node at the one level lower. So, basically what we
are talking about here is one level higher, here in the other hand you are saying left more
child and right child, left child and right child are at the same level. Now, what is the use
of such trees, we will see in a minute.
Now, how do we define a binary tree, it defined like this, what is it. It is a finite set of
nodes that is either empty or consists of a root and two disjoint binary trees called the left
and right sub trees.
655
(Refer Slide Time: 16:02)
What is it telling me that, this is the binary tree, it can also be empty and it has left and
right sub trees which are again binary trees. Again going back to the example, if you
started from this node, this is the tree still and this is also a tree, this is also tree it has
only one node. But, basically what we are saying is at every level the same structure
follows again from every node, left most child right sibling, it is what we said.
Similarly, in the binary tree here also every node can have optionally two sub trees, it
can also have binary sub trees or it need not have any sub tree at all. If it does not have
the root, even an empty tree is a binary tree. So, this is a skewed binary tree, the picture
is given here in the slide, skewed means, it is skewed to the left and then I have drawn
other binary tree which is called a full binary tree, the nodes are full like this.
Then, you also have another variant of it called the complete binary tree, we will come
back to this. What we mean by this is when you fill this tree for example, notice that I
filled the nodes with the first level, nodes with the second level, nodes with the third
level when I am filling them, I am go from left to right. This is the complete binary tree.
656
(Refer Slide Time: 17:45)
While this one is not, this is not a complete binary tree, while it is a mirror image this is a
complete binary tree. Such binary trees have a lot of applications in building what are
called priority queues we will talk about that a little later. First let us see, why do we
want a binary tree in the first place and is there any use of this and what are the
operations that you will perform on a binary tree.
657
So, you need something, so what is this that we do, these are the... So, now what is the
binary tree now? The binary tree is also an abstract data type, let we saw the stacks,
queues and list or list, queues and stack and see you have up…
So, we have the binary tree ADT and abstract data type that we have a representation, the
Huffman ((Refer Time: 19:06)) representation says there is a root node and the left child
and a right child that is the representation binary tree node. So, essentially a recursive
data structure and then we have operations which can be performed and what are the
operations that you performed, because an ADT is like a mathematical module and it
perform various operations on it.
So, what you want to do, you want to create a binary tree, first create an empty binary
tree or create a binary tree with a particular node. Because, you have to start from
somewhere, then you want to find left child, right child or right sub tree, then you want
to find a parent of a given node and right child, left child or tree talked about.
658
(Refer Slide Time: 20:20)
And of course, we need to create empty tree always that should required, parent and not
always, sometimes they ask for the root of the tree. So, creation of empty tree and
sometimes get root, this is not good to have, but since some other books talked about it, I
kept this particular operation, because get root if you have it tells you what the
implementation of the binary trees. So, now what we will do is, we will take one
application, there is a lots of applications of binary trees, you can represent sets, you can
do Huffman coding, you can represents heaps and dictionaries.
659
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 49
Module – 09
Other applications: expression tree from
The post-order expression
Traversal of binary trees: preorder, postorder, inorder;
Illustration with expression tree and infix, postfix forms
There are many other applications like constructing the expression tree from the
postorder expression. I leave you with an idea as how to do it.
Suppose, I have a b c plus and star, what we do is, when we have symbols, first we have
again a forest of trees pointing to a, this pointing to b, this pointing to as use, you
traversae from the left to right of this expression. As you move from left to right of this
expression what you do, you just create pointers to trees in a forest. Then, what you do is
you come across plus, then you create a new node with plus and then, you take the top
most two, it is like a stack, it is actually like a stack.
Then, you it is actually a stack of tress. What we do is now I put these from left to right, I
put the trees a, b and c. And then, as soon as I come across the operator, what do I do, I
pop the most recent two trees, then I create a new tree with the plus and I make the first
tree the left child, the most recently popped out tree is the left child and the previously
popped out tree be the right child.
660
Now, again I have one more symbol left, I come across the star. So, I pop out both the
trees and I create put on to the stack, the star and the two pop trees become the, most
recently popped tree becomes the right child and the most recently popped tree becomes
the left child and the previously popped tree becomes the right child and as soon as, until
you get a single tree which exists on the stack. So, this is the other application of binary
trees and stacks. Both of them can be used quite efficiently to build what are called
expression trees. So, this is a very, very useful application of binary trees.
Now, another thing we always like to do when we build binary trees. Let us take this
expression tree itself, let us look at an expression. Let us say we have something like
this, then what we are doing the expression is evaluated,. So, it is evaluated from left. So,
let us say I have this here plus star b c actually go over this from the right and let me. Let
us say I form an expression tree, I am not known how many of you are familiar, we use
this kind of a expression parsing to do this expression form or expression and term is
form or a factor and let us say factor is the name as it is what it mean, let us say this is a,
b, c and. So, on here.
Suppose, I use this grammar to generate this expression, we have already done a course
on compilers. Let us see, this is completely an unambiguous grammar, we add up here is
plus or minus mult or star or slash, let us say. So, how do we parse this given expression?
So, we start from the root over here, expression is defined as expression add operator
terms, I start it over here.
661
(Refer Slide Time: 05:48)
Plus, then what do we have, then on the left hand side, then the other plus again add the
plus, then I will get a here, I will get star b c and star b plus e f, this is how you will get a
binary tree. What I have done is, all the exercise that I have done is to just show that the
expressions, how many expressions that I have written like this, it can be written as the
binary tree. Now, what we will do is we will talk about other things now. We will talk
about traversal of this tree. Let us see what it gives us. There are three types of traversal
preorder, postorder and inorder. Now, the definitions are given over here, how to traverse
these trees.
So, preorder traversal says visit the root, then what you do, you visit the left child in
662
preorder recursively, visit the right child in preorder recursively. It means what now, let
us take the…Then, the inorder traversal says visit the left child in inorder recursively,
visit the root this is the right child in inorder recursively. Then, postorder traversal says
visit the left child in postorder.
And visit the right child in postorder recursively, visit the root, got it. Let us see what this
gives,. So, everything has to be done recursively. What is the meaning of this?
Let us look at inorder traversal, visit the left child in inorder recursively. Let us say, I
want to do this traversal of the tree, traversal means simply walking around to it. Thus
663
the way the listed node is got changes. Now, what it says let us look at inorder traversal,
it says first we look at inorder, because it will become clear to you what we are doing.
Visit left child recursively, visit root and visit right child recursively is what here inorder
traversals.
So, let us look at the tree what could we do. So,; that means, I am starting from the root
here and I am going to recursively keep on going down to this until I reach the leaf node.
Then, what do I do, I display the leaf node. So, I keep going down, because I have to
visit the left most child. Then, what happens I go up, then I visit the root and get plus,
then again I go to the right child and again after recursively do the same thing; that
means, what preorder, inorder all these are recursive traversals.
So, I go to the right child, then again I will do left child recursively root, then then right
child and then go back. Then, what happens here,. So, I come down this plus go down
here, this becomes b star c and then what happens, this is node as we already been
visited,. So, I go back up to the root of the tree, b may plus, then again I come to the right
node star, again I have to do the recursively left child. So, I do d, I come to the leaf node
star, then I have to add e plus f, the bracket has to come, because it is one level lower.
So, inorder traversal essentially gives me the expression back. So, what is it tell me now,
may I look at the inorder traversal of the tree, I get the infix expression of the given tree.
This one extra information that we have used here, we use the factor star is done after
plus, because it is one level lower in the tree,. So, I get the infix expression. So, let us
look at what postorder expression, postorder traversal of the tree will give you.
What does postorder traversal say, ((Refer Time: 10:55)) postorder traversal says visit
left child postorder in postorder recursively, then ((Refer Time: 11:03)) visit right child in
postorder recursively, visit root and the same thing keeps on getting repeated again and
again. So, I take the left child recursively visit in postorder, let me just explain this over
here. Actually, what is mean to us, what does it gives you,. So, I am moved here, visit the
left child, I cannot came down, there are no more leaf nodes,. So, I display a.
Then, I have to go to the right child, go back left child, right child and then root is what it
says, go to the right child. Then, right child again I have to recursively go down, do
postorder, left child, right child, then root.
664
(Refer Slide Time: 11:45)
So, what will happen, I get a b c star, then, what do I get, now all of them have been
visited, I go back up,. So, left child, right child, then root get plus here and then, what
happens. So, the left child of this root tree is completed now,. So, I go to the right tree
over here. Then, what do I get now, what do I get here now, I have get d, then again this
is the left child, this is the leaf node, there is nothing else to see, therefore, I display the
leaf node.
Then, I go to its right child, again I have to look at postorder over there. Therefore, I get
e f plus plus e left child postorder, right child postorder, root then I will put the plus over
here that is how it comes here and then finally, this star and then finally, the plus, the last
plus sign, that we have. So, I am made a mistake here, this plus should not be here. See, a
b c star, then I have to do the right child, d e f plus star plus. So, what did we get now, we
got a b c star plus correct, d e f plus star plus. How many are there? 1, 2, 3 correct.
So, this is what we get as the expression. What is the expression now, this is nothing,, but
the postfix expression which all of us have already been done. When we talked about
stack, stacks we talked about generating the postfix expression from an infix expression.
So, what is interesting here is once it is represented as a binary tree, if I do inorder
traversal I get the infix expression, when I do postorder traversal I get the postfix
expression and obviously, if I do preorder traversal I will get the prefix expression.
So, this is also a very big application of binary trees. Now, once this expression is given
like this, I can use this stack of trees to evaluate this particular expression. So, I hope you
665
are with me here and you have understood what I have said. So, what is it we are doing
notice that neither we have looking at this various traversals to make it simpler is
preorder traversal can be thought of as, you display the node the first time you visit it.
In inorder traversal, you display the node the second time you will visit it. Remember, I
have drawn this path over here which goes around the tree and in postorder traversal you
display the node, the last time you visit it. So, these are simple traversals schemes. So,
now the next question that we would like to ask is suppose you are given only the
traversals, traversals are given, can we construct the tree exactly?
Let us look at only with respect to binary trees and see if I am given the, what is that we
have two traversals now, inorder traversal and we have the postorder traversal. We can
also do the preorder I encourage you to do the preorder traversal of the tree. Now, given
the inorder traversal can I construct the tree uniquely without any other information and
given the postorder traversal, can I construct the tree uniquely, let us see how you will do
this in the next lecture.
666
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 11
Lecture -- 50
Summary of list, stack, queue
Introduction to Trees
Motivation using family tree
Representation of a tree: array representation, linked list and leftmost child right
sibling representation
Binary trees
Operations on binary trees: create, findchild, findparent
Good morning, in the last class we looked at binary trees and we looked at two
applications; one is constructing of a Huffman code using binary trees, and we also
talked about the construction of an expression tree from a postfix expression using both
binary trees and stacks. Today and then I talked about the different types of traversals of
a binary tree, and we saw that when we take the expression tree and traverse it in order,
we got the infix expression. And when we traversed it post order, we got the postfix
expression and preorder we did not do, but you will get the prefix expression.
667
(Refer Slide Time: 01:00)
So, today what I am going to talk about is I am going to, and so just to recap, what is
preorder traversal. Preorder traversals visit the root and visit the left child in preorder,
look recursively.
And then visit the right child in preorder, recursively. Inorder is visit the left child in
668
order recursively, then visit the root, then visit the right child inorder recursively.
Now, and what is postorder? Visit left child in postorder recursively, visit right child in
postorder recursively, visit root.
669
And postorder is what gives your postfix expression. Now, what we will do is suppose
now we are given the traversal of the trees. Given the traversal of the trees, can we
construct a unit tree. So, here we have a binary tree which is given to you and let me go
through this example for you.
So, what we have? We have A here, B, C, D, E, F and G and H, some arbitrary binary
tree. Well, what is that I am assuming, I have given a binary tree like this. And given this
binary tree, we want to construct it, determinates it is preorder and postorder traversals.
670
(Refer Slide Time: 02:31)
Let us look at the preorder traversal. What are preorder traversals tell me? Visit root, then
visit left child and right child recursively. That means, what am I going to do? I am going
to the left child first, then find B, then this has to be done recursively. Remember, in any
recursion what do we have to do? We go down till the recursion terminates and only then
you can back turn. So, visit B, then I go down, this is the next node which I will display
and D, G, then we will come back, because this left sub trees completely over now.
Remember, we started with A, we came down to B, we came down to D, this has no
child. Therefore, D then look at the right sub tree. Then I can be go back up, then all the
sub trees of D are covered. Then we go to the node B, go back one level up, just as it
could happen in recursion, and then you look at the right sub tree and it has no other
children. Again, this has to be traversed in preorder. Therefore, you do not have since it
has no children, E is the last symbol that will be output.
And then what happens? all these nodes have been visited, you go back here and go to
the right sub tree and visit this in preorder. So, you will get C, F and H in that is what is
said over here. So, preorder traversal, give me this, so this is preorder, now let us look at
inorder traversal. What is inorder traversal? This is the left child recursively, then root
and then the right child recursively. So, what happens now, I go down A, I go down to B,
visit it is left child, left child, left child.
671
Because, the first node to be displayed will be the left child, then the root and then the
right sub tree. So, we keep going down, what do I do? I come up to D, D has no left
child, then I display the root. So, I display root over here, D makes right sub tree G. Then
I go back one level up, we have B where it has a right sub tree E. For example, if this one
had, then you would again done this in inorder. Everything has to be done inorder.
Then we go back, then now it display the root A. We come down here, again now you go
to the right sub tree which extends B again, traverse recursively inorder, recursively
inorder. So, it goes to it is left child, so now this will get displayed. Because, this one has
no left child, then it is root F, H and C. This is what will be displayed. F, it is something
wrong in that, instead of G there it should be H over there, F, H and C.
Now, this is the inorder traversal I encourage you to check this, you need at least inorder
and preorder or inorder and postorder, constructed tree uniquely. So, let us look at this
particular example and see how we can get this corrected. What we will you do is the
following. Now, clearly in the preorder traversal the root is the node which is displayed
first. So, now I look at this particular root node, this givens wrongly, so let we go with
this fully.
So, what I will do is we know that the root node is always displayed first. So, now this
must correspond to the root. Then what we do is we go to the inorder traversal, find out
where that root node exists, this is where the root node exists. So, what happens in
inorder traversal, the left sub tree nodes are before the root and the right sub tree nodes
are after the root. So, what do we have now, we have now that D, G, B, E correspond to
the left sub tree and F, H, C correspond to the right sub tree.
This is left sub tree and this is right sub tree, we know this information. So, I go back to
the preorder traversal. So, this corresponds to these four nodes. That means, now I have
B, D, G, E and D, G, B, E as the preorder traversal of left sub tree and this is the inorder
traversal of left sub tree. Now, again what we have the first node in the preorder traversal
must correspond to the root of the left sub tree, so I mark this. So, what is this mean now,
D, G here belongs to the left sub tree, E belongs to the right sub tree. So, once again I
mark this in the preorder traversal.
672
(Refer Slide Time: 07:55)
Now, what do have? We just doing the left sub tree now. So, it has D, G as the preorder
and we have D, G also as the inorder traversal. So, we know that D has to be root type
and it is in the same order, therefore this is the root. That means, in inorder traversal what
is it mean, now G is part of the right sub tree. Therefore, now we can claim what is that
we have done, we have A here, then we have B which was the root of the left sub tree.
Then we had D and G which were part of the left sub tree of A. So, what have we done,
now we were essentially constructed this part of the tree. Similarly, here what we have
now if you look at the right sub tree of the inorder traversals is only E and there only one
node. Therefore, it has to come over here. So, in much as we can go ahead and construct
the entire binary tree and now, you can convince yourself that given the preorder and
inorder traversals of a given tree, given binary tree.
The tree can be uniquely constructed, you can convenience yourself about this. What it, I
would like you to go back and see how you can construct a unique tree from, do this as
homework, from inorder and postorder. Now, those of few words if you want to be more
challenged, can you derive a proof to show that this proof can be done by construction
and using induction, that preorder and inorder will give you a unique tree.
673
(Refer Slide Time: 10:39)
So, let us next what we will look at is, so we have looked at the binary tree, where binary
tree is one where every node has two children and both the children are binary trees. It
may also optionally have only one child or it may optionally have no children. Even an
empty binary tree, there also exists empty binary trees. This is what we have already
seen. Now, today what I am going to talk about is I am going to talk about another type
of application of binary trees called the binary search tree. Now, what is the binary
search tree? A binary search tree is it is also a binary tree, it may be empty.
674
(Refer Slide Time: 11:29)
If it is not empty, then it is satisfies the following properties. Every node, every element
or node has a key and node to elements have the same key. What is the meaning of it that
means, the keys are all unique and there is another property. The keys in a non empty left
sub tree must be smaller than the key on the root. The key in a non empty right sub tree
must be larger than the key at the root. The left and right sub trees are also binary search
trees.
675
(Refer Slide Time: 12:06)
Now, let us take an example and see how this will look like.
Let us say take the same letters of the alphabet. Let us say I have notice that all the keys
are unique. Let us say each one of the letters of the alphabet is a key. Then this is a
binary search tree. Notice that all nodes on the left sub tree have a key which is smaller
676
than the root, I am assuming lexicographic ordering here and all the nodes on the right
sub tree have a key which is greater than the root and that is true of every node.
For example, if take the node B here, left sub tree has smaller key values than B, right
sub tree has larger key values than B, E here again smaller than G and right sub tree has
larger value than the given node. Interestingly, this is also a binary tree.
And by the same token, we also have that this is also a binary search tree. Now, what will
we do is we look at the sub tree. So, basically why this is also a binary search tree and
this is also a binary search tree? By definition what we have, a binary search tree has
keys that are unique, no doubt about it, all the keys are unique. And what did we say,
elements on the left sub tree, binary search tree can also be empty and elements on the
left sub tree should be smaller than the root, we just smaller than the elements on the
right sub tree.
And this is true for this tree and it is also true for this tree. These two trees are what we
called skewed binary search trees. What is the application of this binary search tree? For
example, you can put the roll numbers in a class in a binary search tree and search for a
particular, if you want to find a marks of a particular student, you can find the marks of
677
particular student using a binary search tree.
How would I search now? This corresponds to the root. Suppose, I want to find out what
was the marks that A obtained in this tree, then I give the key and say, key is A, go back.
Let us say this is additional information along with A, I only given the key here. Marks
and other things are stored over here, I compare A with D. It does not match, then clearly,
but A is less than D. So, go down to left sub tree, compare with B, A and B again is not
matched, but A is less than B.
Therefore, I go down. It is left sub tree and I find this particular node and I can retrieve
all the information corresponding to the given node. This is about searching, the
operation of search, I want to perform. Now, if I want to insert a node what happens now,
let us say I want to insert F, E, G, H is what I have. Suppose I want to insert the node F,
then I compare with the root where should it go now. Compare with the root, then clearly
it is not matched, but F is larger than D.
So, I go down to sub tree, then I compare with G, G and F are again matched, but clearly
G is larger than F. If I go down it is left sub tree, I compare with E, E and F are again not
matched, but F is clearly larger than E. Therefore, I go down here and I insert F over
678
here. Now, suppose I wanted to insert E, can this be done? I compare E with D again
starting from the root, clearly E is larger than D. Therefore, go down the right sub tree, I
compare with G, G is larger than E, therefore I go down it is left sub tree.
I found a match, but since already the binary search tree has a key E. Clearly, I cannot
insert the node E, because the keys in a binary search tree must be unique. This is the
very, very important property. Keys in a binary search tree must be unique, this has to be
satisfied. Now, suppose I want to delete the node G, so what are the operations you are
looking at search, we looking at in insertion, we are looking at deletion.
This is also common, A student leaves a program and you want to delete the particular
name from the list. So, if you want to delete the particular node G, then what should I
replace it. I should replace it with some node, such that the binary search tree property is
not satisfied. We can do one of two things, we can replace, suppose I want to delete G, I
can replace G with either the largest node on the left sub tree or the smallest node on the
right sub tree.
Why do we do that, because then we get guaranteed that the tree will still remain to be a
binary search tree. So, this is exactly what we do. So, I put F over here, the largest node
on the left sub tree, so I find out. So, I go to the right most node on the left sub tree, find
that and put that node over here, delete it, but what might happen is this node might have
some left children. But, since clearly in this particular case, F did not have any left
children. But suppose F had some left children and what do I do? I take this left children,
make it the right children of E, because that is, because all of them have to be larger than
this given node. So, this is your operation of deletion.
679
(Refer Slide Time: 19:47)
So, the operations on the binary search tree that we would like to perform are insertion,
search insertion and deletion. So, these are the three operations that we would like to
perform.
Here is one same example, I have done it with numbers in the slide over here to show the
680
operation of insertion. Notice, how the… Now, let us see how do you get these skewed
binary trees basically, if I start out with.
Let us look at the insertion over here, let us say I have been given 8, 5, 7, the keys 8, 5, 7,
4 and 6, 14, let us say 13, 18 and 16. Suppose, these keys are given and you want to
insert them into a binary search tree. This will give us an idea, how, why these trees
become, because we are always inserting that just let us go through the example. I have
given a partial example in the nodes. So, I have 8 here, so there is initially an empty tree.
So, into the empty tree I insert the node 8, the next come across the node 5, 5 is smaller
than 8, therefore I make it the left sub tree. Notice that again it is being inserted as the
leaf node. Then 7 for example becomes the, again you compare with 8, 7 smaller than 8
greater than 5, therefore this has to become the right sub tree of this 4 compare with 8
smaller, but smaller than 5, therefore 4 goes over here 6 compare with 8, compare with 5,
compare with 7 and then it comes over here and so early here, compare 14 and 13, 18
and 16. Always what we are seen in this process, all the nodes simply get inserted at the
leaf node at every stage.
681
(Refer Slide Time: 22:19)
Now, suppose is that giving the sequence I had given you the sequence 1, 2, 3, 4, 5, then
initially there is an empty tree, I insert 1, then I insert 2 it lies right larger than this, then
3 then 4 and then 5. So, this is how a binary search tree can become very skew. So, let us
do some analysis of the complexity of this binary search tree. What are the costs of
search now? How expensive can we search now? So, clearly in the worst case what is
that it going to be, I may have to traverse down the height of the tree.
Now, what is the height of the tree now? Basically, you compute the top from length of
the path from root to the leaf node 1, 2, 3. To the worst that you have to do, suppose I
want to compare with 6, if the key that I am searching for the 6, therefore, the
complexity becomes the height of these trees 1, 2, 3, but I have to make 1, 2, 3, 4 order
height plus 1. So, I am introduced a new concept today, the height of the tree.
So, the height of the tree is the number of comparisons plus 1 is set to the maximum
number of the comparisons that you may have to perform, if you want to search for a
given node and this is also true for deletion, because I want to delete or insert. For
example, I have to come down and then move back up and deletion, whereas in insertion
again, insertion happens at the leaf. Therefore, I have to go down to the height of the
tree.
682
What can be the height be in the worst case, height what can it be in the worst case? In
the worst case, what can happen is you can have a completely skewed tree and the height
can be n minus 1. If n is the number of nodes in the tree nodes, then the height of the
binary search tree can be asked all as n minus 1. So, when you are talking about order of
h plus 1 comparisons, in the worst case you have to do order in comparisons in the
binary search tree.
So, this kind of completes the and I am just, let me give you a small implementation of
insertion, here is a small segment of C code which is given here. So, let us say that you
have a pointer to tree as we had in the Huffman tree. So, what we are doing over here is
if tree is equal to null, then what are we doing, we are creating a new symbol. In this
particular case, I am assuming a binary search tree which only has numbers, otherwise
what I am doing, I am comparing if it is not in empty tree, I compare with tree symbol
and then what we do, we insert it.
If it is smaller, then you go to the left sub tree and then do the insertion again, insertion is
called recursively over here. Similarly, otherwise if letter is greater than B given node,
then what we are doing, we are inserting in the right sub tree. Notice that we are not
doing anything, if the symbol is already present in the tree. So, this is the very important
683
point in binary search trees. So, there is something that is a small segment of code over
here, recursive implementation of the insertion into a binary search tree. There should be
a correction here, this letter should read number, please correct this. It is else if number
less than tree symbol, number less greater than trees, so this is about binary search trees.
684
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 13
Lecture – 51
Priority queues – heaps
Max heap: Complete binary tree, which is also a max tree
Operation on heaps: create, insert new element, delete largest element
Implementation of heap using
We talk about different types of binary trees, we look at the construction of Huffman
code using the binary tree ADT. And then we also look at another special type of binary
tree call the binary search tree, and, then we also look at how to construct a binary tree
from we preorder and in order traverses given the preorder and in order traverses.
Now, I am going to give you one more application of binary trees, which is very
important called the heaps or priority queue. Now, in the first few classes when we did
list for example, we talked about queue as a special type of list, where we set insertion
happen at one end and deletions happen at the other end. And it was the first in first out
queue, you got it to the queue first, you got serve first that was the ideal. But, now
sometimes is possible that you want have queue is where you give some priority tree.
685
For example, if I am computer center in charge and there are large number of programs
that are running, I like to ensure that those programs or those process is which take very
less time should be executed first, rather than process is take a lot of time. So, I would
like gives some kind of a priority, are you know if you have they your internet service
provider, if you request is very small, you get higher priority compare to request where
your downloading huge videos.
So, now how do we implement priority queues, that is the idea, suppose we implemented
priority queue using an array like this or link list like this. And we also stored at every
node the priority, let us say this was 3, 8, 7, 6, 15 and something like this and same
numbers over here 6 of 15 remember in a queue, you always dedicated and this pointed
to the front and this pointed to the rear and this pointed to the front and this pointed to
the rear.
Now, if you want to serve the jobs based on priority then what do you have to do, it will
keep on moving down front, until you come to the rear end for example. Because, that
much the where the highest priorities and then serve that job. Similarly, in this link list
example you start from the front, keep moving down till you reach the priority with the
largest value and serve that. So, basically what happens is whether it is the array
implementation or the link list implementation, remember this is an abstract data type.
So, everything is hidden from the user you have to use only the front keep moving down,
until you come find out which is the largest priority. And you will know that only if you
have end reach the end of the queue. Therefore, the time complexity of implementing a
priority queue can be as largest order n, where n is number of elements in the queue
number of elements in the queue, the question is can we do better and that is what going
to answer using what a call priority queues, let see how you can use priority queue is to
do this.
686
(Refer Slide Time: 03:57)
The definition of a priority queue comes from the definition of the maximum tree. What
is the maximum tree? Maximum tree is a tree, in which the key value in each node is no
smaller than the key value in it is children if any. A max heap, look at the definition over
here is a complete binary tree that is also max tree, what it be remember sometimes back
we saw the definition of a complete binary tree and we said that complete binary tree is
look like this remember.
687
We said this is complete binary tree, while this is not complete binary tree. So, what we
are saying is we not at the have to have this max tree satisfied, it is no smaller than this
children. That means, this can be 8, 7, 6, 4 and this is a heap, this is what is the definition
of the max heap and it is a complete binary tree, not a full binary tree it is a complete
binary tree now the questions.
So, what our this complete binary tree is a binary the structure of the tree being a
complete binary tree is very important for the implementation of operations on heaps.
We already saw in the context of queues, we inserted in to the queue we deleted from the
queue.
So, in much the same way by look at a operations on heaps is a priority queue, what a
created empty priority queue, you want a insert it new element in to a priority queue and
you want to delete the largest element from the priority queue. But, at the same thing can
be done for minimum also, does not mater can use a min tree or max tree. The difference
between max tree and max heaps is that, the max heap is a max tree which is also
complete binary tree. So, now, let see how we can perform these operations of insertion
into a max heap.
688
(Refer Slide Time: 06:15)
So, what is cleaned is that when you implement priority queues using heaps, the time
complexity is order log n for both insertion and deletion. And we already talk about this
that be, if we used normal queue it would be very expensive. But, priority queue is after
be implemented has complete binary tree using arrays, that is what gives it this property
of log n let us take an example.
Here is an example, let us say I want to I will go through this example completely.
689
(Refer Slide Time: 07:01)
Let us say I want to insert the elements, let say 6, 7 and 15, 3, 2 and 5 into a priority
queue. Let us see how this is done, it is implemented is an array in this example here, the
root node corresponds to the index of the array 1 and the next level for example,
corresponds to the index of the first left child, corresponds to the index of the array 2. So,
for example, in this particular tree what we are saying is, 15 has index suppose if this an
array implementation a is the array that we are using, then 7 is at index 2 and 2 is at
index 3, this is a very, very important.
And so you would go essentially, if you look at it the array index is go through
something like a level order, this is first level, second level, third level and so on and 3 is
at a 5 and so on. Let us see now, how we insert in this elements into the heap, let say this
is what was give to this, see is start with initially there is an empty heap. So, you create a
node with the number 6, there is a store now this is store at a of 1.
How many elements they are in the heap? There is only one element in the heap. So, you
keep track of variable call last and say last is pointing to 1, last is being pointing to the
newest element data has been inserted into the heap, next say I want to insert call. So,
what do I do, I insert it at a of 2 initial then I find that, because is now what is happening
now, if I inserted here 7 and 6 when I compare what is happening, the heap property is
valid.
690
So, what I do is I exchange this numbers I bring here 6 and 7, next what do you have, we
have 15. So, now, I insert initially 15 at the position I compare 15 and 7 I find that 15 is
larger than 7. So, I exchange 15 and 7 next I want to insert 3, so where will I insert 3 is
initially, this is a 1, a 2, a 3 suppose is the array implementation and a at 4 I will first
insert the element, then what do I do I compare this node with it is parent and you find
this is correct order, so I leave it as it is.
Next I insert 2, now 2 is also in the correct place with respective it is parent. And next I
insert 5 which will be in the correct place. In the example let us given in the slide, again
the slightly different example, suppose I had inserted 2 earlier suppose it was their at this
position in the tree, then if I was inserting 5, then what will happen I will compare 5 it to
would pair it, I find that 2 is smaller than 5, therefore I will move them and get this new
tree.
691
(Refer Slide Time: 11:34)
Now, let us see how long we took to insert a new element into the heap, first when we
inserted element 6 there was only one element to be inserted. Therefore, constant time
we set, then when we came to the second element we have to go one level down and so
on. How many elements where there are in the heap at that type? Their only two
elements in the heap and we are to go height of one.
Now, when I came to inserting the let us say I want to insert 3 for example, then what
happen, now clearly what do I do, I am initially putting it over here and comparing it.
Suppose, let say instant of 3 I have to insert 25 or something like that, let say this where
not. So, I put 5 in the original position which is at the position of last, so this is last, this
was last, this was last, this was last and finally now last is equal to 4.
Then what I do is compare it with it is pair it, then what do I do, I move it to it is position
depending up on I keep comparing with this. For example, this is clearly larger than this
therefore, this should be about this then I compare with it spared it keep going doing this,
until I reach the root. In this particular case what will happen, will find that 6 will move
here, 15 will move here and 25 will move here.
So, this is the idea of insertion do it, how long it will take now there are 4 nodes and we
have to go only 2 levels down or we have to basically do log in operations 2 insert and
element into a heap. It really makes lot of difference when you have huge heaps,
especially jobs in a priority queue.
692
(Refer Slide Time: 12:38)
Here is an implementation of the heap have defined a class heap and I have given
insertion into the heap, notice that I defined a priority heap size basically an array of
heap size. The heap size can be fix, let us say reallocated if you want, that is a insertion
of element x as usual element type is defined by the user and delete to you always only
in a priority queue, you only have to delete the root.
So, now, let us see what will you do if you have to delete this root, because highest
priority element has to be serve first, then what will happen it will root let us start. So,
693
what we do is be take this put it over here, so 3 will come here once 15 has been deleted.
And then what we do, it compare with it is children, clearly 3 is greater than 7 and it is
also greater than 6. So, 3 will come here, 6 will come here and 7 will go here and this
will be the new structure of the heap. So, when you delete a particular element, again
how many element should we do, it took the last element moved it here move to steps
down. So, again the complexity of delete is also only log n.
So, here is a C plus plus implementation and insertion what have doing, we incrementing
the position last by one and what have we doing here, they checking whether the priority
of the given node is greater than priority of i by 2. So, greater than i by 2 where ever it is
for example, it putting in that particular position and moving all the other elements
appropriate.
694
(Refer Slide Time: 14:21)
Our it finding the in this is, if this is i clearly if this is the notice that, the index of the
parent is 2. So, index of the root is 1, index of this child is either 2 or 3 and index of 2
children is what now 4 or 5 and so on. So, given the index of the parent I can find the
index of the child children and given the index of the children, we can finding index of
the parent, this is possible only because I am implementing the priority queue as an
array, otherwise it will not be possible.
So, what is this piece of code is simply doing is, while i dot equal to 1 while looking at
we are starting from the last element and we are contemptuously checking trying to
move up the tree depending up on where that particular node has to be insert, notice that
we are starting from last. So, we are assigning to i priority i by 2; that means, the
eliminated i by 2 a is replaced.
So, basically if you here if this one is smaller than what is being recently inserted, you
may me move it one down, the example I already talk about in the context of insertion
and this is I need repeat that until you have reach the root node.
695
(Refer Slide Time: 15:36)
Now, this is the deletion again what are we doing, we are looking at basically first what
we do is, here we once again deletion means what the number of elements is going to
reduce by one. First of course, what do we do is, we start with the parent as the root,
child as the two the left child of the root and we take the last element from the node and
in percolated down, that is what I say go back to the root and the percolated down. So,
what I doing over here, we first check which of the two children has higher priority and
switch them, because you want to higher priority node to move up to the root.
696
Let us what we are saying is, if I have 15 here and I have deleted 15 and this was 17, this
was 14, let us say this was 12 and this was 14, then what I do is, I want to make sure that
14 goes on tops. So, that the first thing that I do this is being deleted, so the next element
that was become root is 14 and that is what this step is doing over here it is comparing
which of the two children have higher priority.
And then after words what is do, it keeps on finding out the child by taking parent times
2. Because, if the parent is i by 2 which we saw in the insertion case, in the deletion case
parent is that child is act parents star to. So, what we do is be essentially, what are we
doing over here, we have essentially repeating the same process over here. And we do
the deletion of the given node, again percolated let us take the last element from the heap
put it to the root and percolated down. So, that is all that is true heaps and what is the
interesting is the complexity the of the heap, whether insertion or deletion is only a log n
operation.
697
(Refer Slide Time: 17:50)
Now, I what you to think of some nice problems, let us see I am giving you a heap and
let us look at some problems on heaps which I would like you to do thing about this let
given a heap that is already, let us call it a max heap. Now, what I want you to do is, I
given the index of a node problem 1, how do I change the priority of the node. I want to
give you one more problem, let say I am given k sorted list, I want to create one sorted
list, and I want use a priority queue to do this, let see how you can do these two
problems.
698
Programming, Data Structures and Algorithms
Prof. Hema Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 15
Lecture -- 52
Hierarchical versus non-bierarchical data structures
Graph:nodes(vertices),edges (arcs)
Directed versus undirected graphs
Paths on graphs
Labeled versus unlabeled graphs
Example of a graph problem:traffc light on a five-point intersection
Representation of a graph:adjacency matrix adjacency list
The last few lectures we learnt, you know about different types of data structures. We
talked about list, then we talked about trees, then we will specifically binary trees, binary
search trees and priority queues, this what we are studied. Now, if we look at all these
kinds of data structures that we have studied, whether it is a list, it is a tree, binary tree,
binary search tree or a priority queue. We always had some kind of a node which had
kind of a higher priority and this is a header node, in the case of list which then pointed
to other nodes, does not matter whatever the implementation array are we put in
hierarchy there.
699
For a tree for example, we said there is a root and then there are children and so on. So, if
are a binary tree we had what is called a left child and right child, and we had a node that
was designated as the root. Now, let us think about other problem, today with the all of
you are on face book I am sure and when you look at face book what do you doing, you
have you have a set of friends. And so set of friends watch you, and then you watch
somebody else and so on, you are connected to somebody else who is also having a set
of friends, you also watch them and so on.
Now, in certain example and this one has perhaps and other set of friends saw the may be
disjoined with this respective, let us say this is you, this is your friend 1 friend, 2 and so
on. So, what is the interesting is this is called a social network and interestingly this
social network has a lot of branches. And in the social network there is nothing like you
know in your friends circle. For example, you have some x friends and among is x friend
one of your x friends has another set of friends, who disjoined with you is possible.
And it nothing like a hierarchy, in the earlier data structures that we saw, we saw the
header known, then we saw the root node and we said access to any other node is why
are the root node here, why are the header node in the list it is in all that, here in the other
hand there is no hierarchy amongs the nodes. So, you want to represent something like
this, this is a very, very common natural phenomena in nature.
But, generally things can be represented such that there are different nodes each one has
it is functionality and your connected to each other by some property. Then to represent
something like this for example, I may want to find out who are all the friends that are
connected to you or I may want to find all the friends are connected to f 1 or f 2 or f 3 for
that matter. Then if I was using a tree then I would say if you are the root, then I would
say at to access any of your friends after go through only you, then what happens people
who are not friends of you I cannot get access to them.
700
(Refer Slide Time: 03:38)
So, another nice type of a data structure is what is called a graph, a graph is essentially
consists of a set of vertices. The vertices here are your, you and your friends in the social
network and these vertices are connected by arcs, if there is some connection over here.
So, I have a set of vertices and if there is a connection between the two of them
something you are a friend or whatever it may be, then that is represented by an arc. The
vertices are also called nodes or points, there arcs are also called edges. And edges may
be for example, you may think for example, that f 1 is your best friend, but f 1 does not
think that you are he is or her best friend.
701
(Refer Slide Time: 04:20)
So, in that graph for example, when I am looking edit of over here and it have if I am
looking at only best friends you think F 1 is your best friend. But, F 1 thinks F 2 is his
best friend and. so on is possible something like this and F 3 perhaps thinks, why is you
are his best friend, but you do not feel the same way. Such graphs are what we called
directed graphs, can have directed graphs or you can have undirected graph. For
example, if I am looking at you know network communication across how do you
connect to various websites.
702
(Refer Slide Time: 04:54)
Then you are sitting at your home with your computer over here, then you are connected
to let say if you using BSNL, connected to a BSNL router then this BSNL router goes
through number of various links, through the internet and perhaps your server is
somewhere come over here. And normally these links are what we called bidirectional
links, and then we say that this is a undirected graph, both the graphs are important in
practice we like to analyze both of these graphs.
So, basically what is that defines whether a graph is directed or undirected, the edges
may be directed or undirected, when the adjust are directed the graph is called a directed
graph, otherwise the graph is called an undirected graph.
703
(Refer Slide Time: 05:54)
How do you define an arc or an edge, we represented as an ordered pair of vertices. See
the example over here, if there are n vertices in the graph v 1 to v n, if there is an edge
between v 1 to v 3. Let us say, we represented by a symbol like this or if you writing it
you put it is an ordered pair u comma v, v 1 comma v 2 in this particular example there is
an edge between v 1 and v 3 you write it as an ordered pair like this. Now, then you also
have what are called.
So, if you want to defined a path, how do I defined a path if I want to find a path from
here to here, then let us say this is node v 1, v 2, v 3, then I would say my path is from v
1 to v 2 to and v 2 to v 3 is my path. So, this corresponds to path, so you can have a set
of sequence of vertices which gives you the path. So, at piece what am I saying here, I
can represented this is v 1, v 2, v 3 corresponds to a path; that means, there is an edge
from v 1 to v 2, v 2 to v 3 and that represents the path starting from here to reach this
particular node. Now, you can have both labeled and unlabeled graphs, but labeled
graphs are what we are going to look at.
704
(Refer Slide Time: 07:26)
So, when I look at a labeled graph I have something like this vertices are labeled here.
So, here let us say and if have weights on this 4, 6, 8, 9, so both edges and nodes can be
labeled the nodes and there are edges it can be labeled, what is this weight's mean to give
you an idea, if you want to let say travel from one place to another. And then this weight
could correspond to the amount of time, it takes speed to reach from point A to point B
for that matter. So, this is what a labeled graphs.
705
(Refer Slide Time: 08:19)
Now, I want to just leave you with one example, all the way we will not we discussing
this. Graph problems can be found in various applications here is a very, very interesting
example, let us see this there is you know here is a graph, where I want to this is a 5
point intersection, there are 5 lines which are intersecting over here. There one road is
called A and another road is called B, C, D and E, C and E are one way, A B D are
bidirectional.
Now, I want it let us say you know my job is to design a traffic signal for this, I want a
put a traffic signal such that no accidents happen, if you follow the signaling rules. So,
how do I go I defining it, first thing let us assume that this is a India and all left hands are
free for the timing, we will assume that therefore, B to C is permitted. Similarly, A to B
is permitted and E to A is permitted and D to D to is of course, is a right turn as not
permitted with out of signal.
So, we will assume what will we do is, I wanted in just see what I have done here. I have
converted this problem of designing at traffic signal for this. Such that, I have converted
it to a graph problem, what have I done over here, I have barely a numerated all the
possible parts that exist, there is I can go from D to B, I can go from A to C, I can go
from E to C and go from A to D, we can go from D to A, D to B and so on. So, whatever
706
I do at look at all the parts that all the roads that can be reached from A, from A I can go
to B from A I can go to C and A I can go to D, so at put make the most nodes.
Similarly, from B what are the parts that I can take, I can go from B to A, I can go from
B to C and I can also go from B to D. So, there are three parts again similarly I do it for
that s 2 also D to I have D to A, D to B and D to C and E to A from E to I have E to A, E
to B, E to C and E to D all the four parts are possible. So, basically what are the nodes
the nodes are this graph correspond to the roads, that the parts that are possible. If I am
traveling by on this road and if I am taking a vehicle which are all the legal parts that are
allowed.
Now, my job is to design a traffic signal, then what I do is I join a pair of nodes by an
edge, if both of them cannot happens simultaneously. So, let us look at this, let us look at
A to D and E D and let us say A D and let say that I am looking at A C A D and D B. So,
this A D and this is D B remember is a left and right left hand it traffic. So, this is going
this way and this is coming this way, so both of them cannot happen simultaneously.
So, what I do is I connect here this may be a little incomplete, but what I have done is, I
connected all the nodes in this graph, which cannot happen simultaneously by an edge.
Then what happens is this becomes what is called, now as I said I want to define a traffic
signal. So, what is a problem of traffic signal now, basically it is a question of colors,
what are the colors that you are familiar with you have green, yellow and red or green
and red for the timing let us say that.
So, what this means is that when I am doing defining colors for this two parts which
intersect cannot have green at the same time. So, what I essentially do is I try to find out
how many different colors are required to design this traffic signal. How do I find the
number of colors. So, what happens is it is very interesting that designing the traffic
signal for this intersection, simply becomes a problem of finding a edges in the graph.
And ensuring that two nodes which are adjusting to each other that is as two nodes which
are connected by an edge do not have the same color. So, first for example, so a in this
example if you see A B C E is simply does not matter at all, they you can do not have
707
draw any color for that. Because, you do not have any edges, they completely free left
hands AC if I color as green then B A and B D cannot have the same color.
So, you do this kind of then you basically if I given then you find out what are all the
colors that A C can go with, what are all the turns that A and C can happen together. For
example, if I can go from A to C, and I can of course, make a D C this not I can make a
free left turn from B to C. So, these two do not intersect, so it need not have to have the
same color. So, basically what you do is, you look at this particular graph start off with
coloring, this put a give a different color to this.
And similarly keep doing this and till you have satisfied all of them. So, if you find the
until all the nodes in the graph are colored. So, this is just an example coloring problem
is a difficult problem. In fact, finding the minimum number of colors is not a simple
solution, but this is just to be illustrate that any there are you start finding graph kind of
problems, even for something like this you want to think about it, if an intersection that
you want to design and for this intersection you can convert into graph problem and then
it simply biased on to question of not giving to adjacent nodes the same color.
Developing the algorithm for this problem is a little tough, it is a little beyond is scope of
708
this course, but you just give you an idea. Now, how do you represent graphs, now
graphs are can be represented by matrices here is a graph and what it tells me is,
whenever be this is what is called an adjacent c matrix. This is a two dimensional matrix,
where you have what is this on the y axis and also on the x axis and if between a pair of
nodes there is a edge you mark it as one otherwise 0.
Since, this is an undirected graph we also show that d has edges to a b and d, because
both the forward and the backward edges are present that is what we assume into b. So,
these are two different representations of graphs. So, now we have I think I have
motivated you sufficiently to look at graph problems, and what we will do is in the next 1
or 2 lectures, we will look at some different problems and graphs. And so we will stop
with this introduction on graphs now.
709
Assignment on Data Structures
Prof. Hema A Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 53
This is another supplementary video, where we try to explain to you how the conversion
from infix to postfix was done using a stack, you know what is stack already we already
discussed it. Therefore, you can push elements on to the stack and pop elements of the
stack, you can look at the top to stack, you can check whether the stack is empty. So,
basically these stack is what is called a last in last first out ADT. So, what it means is
that, when we are pushing elements on to the stack and this is the stack here.
710
(Refer Slide Time: 00:50)
Then, let us say I push these elements we already saw this example 4 and 5, then suppose
this is the top most position of the stack a pushes element here to here and so on. But, if I
now pop I can only pop from here therefore, the elements if I keep continuously popping
the elements will be popped in reverse order, as we saw in the example.
So, basically the operation of the stack is what is called a last in first out. And in the class
know, but I did this lecture I told you how we can use this to convert a infix expression
to postfix expression. What I am going to do now is, I am going to give you some C plus
711
plus code which does exactly that, what did we say look at the input when an operand is
read place it immediately in the output.
When an operator is read what did we do, if the top of the stack has higher precedence
than the operator, then you pop all the operators of the stack until top of stack has lower
precedence, then you stack the current operator. When you see a left bracket you place it
on the stack, when a right bracket is right you pop all the elements until the left bracket
pop the left bracket. Then you pop all the elements of the stack and we keep repeating
this until the end of the input this pop is saw.
712
(Refer Slide Time: 02:19)
So, now what I am going to do is, I am going to show this using a program. Let us look
at this is program is in fact even more complicated.
What we have done is, we have also permitted, what is called this program that we have
implemented is a little more complex and let us go through this over here. So, here is the
usual implementation of the stack, now I am using an array implementation of the top
stack does not matter, we can use array or link list as we already saw, we already saw
program which uses both and exactly runs the same way.
713
(Refer Slide Time: 03:01)
What I have done is, here is something which will give me the priority of a given, what
is priority now the precedence of a particular operator.
Remember in scientific calculators when you say 4 plus 5 star plus 3 then this 5 star c
will be computed first and this will be 4 plus 30 plus 3. And of course, you do the
evaluation of the expression from left to right. So, we have to ensure that why do are we
converting this in to postfix expression, because the postfix expression is a very
convenient way of taking care of precedence's.
714
(Refer Slide Time: 03:41)
For example, if I have something like this 4 plus 5 star 6 plus 3 when I convert into
postfix this will become 456 star plus and 3 plus. So, what is it mean now, if I want to
evaluate this expression I keep moving from left this is the assignment that has been
given to you. As soon as I come across a star, I can again use a stack for that I pop of the
top most two elements on the stack and perform the evaluation.
So, when you look at this expression for example, unless you know the precedence of
plus and star the relative precedence is between plus and star, you do not know which
has be evaluated first. If plus is having higher precedence, this has to be evaluated first,
this has to be evaluated first and then star, alternatively if here in the normal
understanding of powers plus is having lower precedence. So, this has to be implemented
like this.
715
(Refer Slide Time: 05:05)
For example, we know that when you look at x power y power z, it does not it is actually
x power y power z in goes like this, this how it works. So, this is called associating to the
right, so when we looking at exponentiation for example, now if I have a number 4
divided by 2 power 3 power 2 this is actually 4 divided by 2 power 3 power 2 and not 4
divided by 2 power 3 power 2. This is important thing that we need to understand, such
operators are what we call to the right associative, the associate to the right.
So, in this example and I am taking about there are and not do we that exponentiation
also has higher priority than multiplication it is done first.
716
(Refer Slide Time: 06:08)
So, there are two things that we are looking at, one is the precedence of the operators.
So, I write one simple function which we look at the operator, which is the some
particular element type and then it will tell us whether it is we know the precedence of
the particular operator for example. we also put bracket, because brackets also going on
to the stack as we saw in the example in the class.
717
(Refer Slide Time: 06:40)
Going back to this example in the class what it we do, if you remember right this is what
we did, let us take that example now and let us say this was the expression, remember we
also put the bracket on to the stack. So, this was the expression that was given a plus b
star c plus d plus bracket d plus e slash f what it we do, if there is an operand with
pushing it to the output an operator push it on to the stack, then we have star here then
what do you do, you also again operand move it to the output star push it on to the stack
operand push it to the output.
Then what happens, the next plus comes then what do you do, once a new operator
comes here we did not do anything, because the star operator has higher precedence then
that of plus. So, which has put it on top of it when a new operator comes I look at all the
operators that are there are in the stack. So, I look at a top most way, so clearly star has
higher precedence than plus therefore, we pop it off.
And then we look at next what is the on the top of the stack, the plus which is on top of
stack. And clearly plus and this have the same precedence and since the operators what
we call left associative, it push this plus star; otherwise we will not push it out has been
for the exponentiation operation, which is given in this piece of supplementary code.
718
(Refer Slide Time: 07:52)
And then when a bracket comes to put the bracket and then on to the stack and then you
do the rest to the operation to the usual way. And then what do you do, when is you
come across the right bracket into pop everything until the right bracket, we saw this in
the course. So, now, what we can to do is, so the here is a function which will return
back in the priority of a given operator, I have also put exponentiation.
Then, here is another function which I have added because, so for when we look at plus
and minus and multiplication and division, we always associate to the left. What I mean
719
by this is, if I say a minus b minus c it is a minus b minus c, rather than a minus b minus
c. This means association to the left and this means association to the right I said only
exponentiation is different, it associates to the right as we saw some time back. So,
basically what we are saying is when you doing this over here, it is 2 to the power of 3 to
the power of 2, that is the fundamental difference. So, we also included that operation
just to make it a little more complicated.
Then, what is it do here now, then we returning just for some book keeping for example,
if this what is it do, this one determines the associativity. So, in associativity it is
returning either 0 or 1 and then we looking at the kind of the operator, we if it is not an
operator for example, then we returning a 0 over here. And now finally, let us look at a
convert to infix to postfix.
720
(Refer Slide Time: 09:43)
So, this is what this convert infix to postfix is a regular function like, how you would
write in C or in C plus plus. What am I doing, within this convert infix to postfix we are
using a stack. So, what are we doing now, so we create a empty stack, after creating a
empty stack you have basically. So, what is the postfix the infix expression is given in
this strin, we already saw the benefit of if this is the infix expression, this is going to be
the postfix expression.
The advantage of the postfix expression is we do not have to worry about brackets, we
do not have to worry about precedence. As soon as you see an operator you move from
left here, as soon as you see an operator you take the most two reason guys and perform
the operation, depending upon whatever you are how that is all there is to it in terms of
the infix to postfix, you do not have to worry about associativity, you do not have to
worry about the precedence of the operators, that is the fundamental property of it.
721
(Refer Slide Time: 10:53)
So, what we will doing over here now, we check whether it is a operator, because the
string is an expression that is given.
And then what are we doing, if the priority of the top of the stack equals to the priority of
the input and if the associativity is also the same. Then, what are we doing we simply
popping all the elements from the stack; otherwise, what are we doing now that is we are
starting if the operator is this. Then, what we do is, if check if what are we going to do
722
now, we need to pop elements from the top to the stack, until the stack is the priority
which less than or the equal to the input or the stack is empty.
Then, we check of course, whether it is left associative or right associative that is not. So,
important for you, we ensure when you do right associativity if to ensure that the element
on the stack has a priority which is greater; otherwise, not equal to that is the
fundamental difference.
So, in the operators are left associative we keep on popping all the elements, this is
essentially the piece of code that we are looking at for infix to postfix conversion. So,
basically the idea is that what I want to impress upon you all those function looks rather
long is that we have an input string and then what are we doing, this input string is
processed and that is we are going through this input string from left to right.
So, this is the input string is given go through left to right, if it is then what do you do,
you look at this as soon as you c an element just return that, if it is not an operator then
the you just simply push it to the output. And on the other hand, if it is an operand for
example, if it is an operator then you check if it has the same priority as the input and is
left associative or is it right associative and appropriately you decide whether you will
pop the operators of the stack or not. And if on the other hand if it is an operand, you
simply push the operand directly to the output, that is the fundamental activity that we
are doing in this particular function.
723
(Refer Slide Time: 13:26)
But, what I want you to notice this here, this is where we are doing this, just it is an
operand. So, I do not know have to put it onto the stack, so I simply copy it to the output
string, what is interesting about this program is that. Notice that, this is again I repeat this
is a normal function looks like a normal function in C, but within that it is using a stack,
stack is a abstract data type. And we using the operations that are permitted on the stack,
to do whatever you want and then we finally, output the particular string.
724
So, let us simply run this particular program and see what it gives you as output. So,
what am I doing here now, I have this infix to postfix program and this going to run this
particular function. So, it is asking me for the input string let me give a plus b star c plus
d slash f whatever. So, what do we expect now, now basically there is a bracket there
therefore, it has to output the elements on the bracket and the plus within the bracket
should be perform first.
So, we do this and this gives us a b c d f notice; that means, the first operation is going to
be perform is d slash f and then it performs this plus and then the star and finally, the
plus. So, now, we one of the assignment problems is to perform postfix evaluation using
the stack. So, what is going to happen now, earlier you had be the element type on the
stack which consisted of characters which corresponded to your operators.
Now, the element type on the stack will be integer type. So, what are we doing now, you
given a string, so what you have to do is given a string you have to perform the assume
that you are given a postfix string and you are suppose to evaluate the postfix string I
thing we that is basically the idea. So, you can again use a stack here is as obvious
because d and f are operands here.
So; that means, I just keep on pushing all the elements on to the stack. So, what happens
still I come across an operator when I come across an operator what do I do. So, on my
elements of the stack I have a b c d e f I take of the top most two elements perform the
725
operation and put it back on the stack. When I see a plus then what do I do, I take of the
top most two operands pushed on to the stack, let us look at with an example over here.
So, what it that we are say if this is the stack at push a, b remember this is the post fixed
expression. That means, we are giving a, b, c, d, f slash plus star and plus that is what we
have as the postfix expression. So, what are we doing now on to the stack now, if this is
our stack we simply push all the elements. That means, I am reading this post fixed
expression from left to right, I push it on to the stack d, f now what happens I am seeing
an operator.
So, as soon as I see an operator I pop these two elements from this stack, it perform
because it is from left to right and performing d slash f. I perform d slash f then what do I
do, I push it back on to the stack d slash f. Now, next what do I see, I see another
operator plus when I see the other operator plus what do I do, I pop these two elements
and what do I do I have C plus, plus is the operator. So, p plus d slash is the f is perform.
726
(Refer Slide Time: 18:04)
Again you push it on to the stack, next you see a star when you see a star what it will be
doing now. Again we perform b star, because these are the c plus b slash f is there and b
star therefore, this is multiplied with this contained, again you push it back on to the
stack. So; that means, what are we doing now, we have b plus c plus b star c plus d slash
f push it on to the stack and then you have a, you have a minus or plus it is a plus over
there.
So, you have plus here and then you do a plus b star c plus d slash f and the stack
becomes empty, there are no more. Notice that, the input string is over and you just
simply output the stack is also empty, because what happens I remove these two
elements and then I am simply putting the result. Therefore, the result consist of the is
corresponds to the evaluation of the expression. So, this is exactly what you are expected
queue on the assignment.
What I have shown you here is infix to postfix conversion using the stack over here. So,
what it we do, we just giving the input string here and then given that this is the input
string, it converts is to the postfix string over here. So, this is primarily your task on the
second part of the assignment. So, I hope by now you have understood two important
things, what are the two important things that we saw, in this lecture we saw that you can
use a stack to perform infix to postfix conversion.
727
And if you remember right notice that over here, this function infix to postfix which is
there in the code over here, notice this over here. So, this convert infix to postfix is
something that we are not get c in before, convert infix to postfix is another function.
And what is I do, it uses a stack internally a stack has already been defined and the
element type it goes on to the stack is what we user specifies.
And then he simply using this stack like, you know that plus will if I give two floating
point numbers plus will add to floating point numbers, here is stack will push elements
on to the stack pop elements from the stack is a last in first out ADT. And that precisely
the property of the ADT, that the user uses here is the convert infix to postfix function
which is doing this particular operation.
And then notice I have another name program and top of it, where what do you do you
have define some particular expression link, that is the users idea. So, I gives the
particular expression line, inputs the string the string is read and then what is we do, he
again creates another. So, given the length of the string it creates a new output string and
then passes this two, passes string in and string out.
728
(Refer Slide Time: 21:15)
Notice that, this whole main program is completely harmless main program does not
know that convert infix to postfix is actually using a stack inside. So, this is something
that is very important to understand, so we have seeing primarily two different
characteristics over here, we have seen the stack ADT implemented with the particular
element type.
And we have seen the convert infix to postfix for example, is actually using the stack
ADT inside, the main program is not aware of it. So, this is the important take a way and
in your assignment for example, in post fixing evaluation what are you expect to do, is
not a stack of operators, it is a stack of operands that was given to you. So, given that
what we have done is, in that particular assignment for example, the stack has been
implemented, the implementation may not be exactly the way I have done it in the course
it can be a little bit different.
But, it still has the pop the top and empty operations, that is kind of irrelevant to you how
it is be implemented, using that stack how can you implement postfix evaluation that is
the question that is asked. And we see now what is it that we have to do, as you see an
operand every operand is pushed onto the stack and an assure as you see an operator, you
perform the evaluation. Notice the fundamental difference between convert infix to
postfix and postfix evaluation. In convert infix to postfix what are we doing, we pushing
729
the operators on to the stack, in postfix evaluation we are pushing the operands on to the
stack.
730
Programming, Data Structures and Algorithms
Prof. Hema A Murthy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 54
Assignment on Data Structures
What we going to do is, this is the solution to the queues problem, what where you
given, you have told that, that you should implement use the queue ADT that is given.
And basically what we are looking at is, you are expected to create three different
queues.
731
(Refer Slide Time: 00:27)
So, basically it is like doctor's clinic and there are severe cases and there are non-severe
cases and there are moderate cases, for what is that is three different queues are created.
Obviously, the people with severe problems have to be treated in greater numbers than
the moderate people and the persons with not any severity at all can be treated the least.
But, the same time, but you cannot do is even many severe people you cannot only treat
severe people and not treat the people with moderate illness and people with very less
illness.
So, otherwise people will get tired they will not go to the doctor at all. So, what was
suggested in this was, you create a fourth queue to the doctor. So, let us say there was a
receptionist at the desk where you go and register your name, the person listens to your
complaints and realize whether it is severe, moderate or what you called the basically
what it does is, it looks at your priority is your priority 1 is your priority 2.
Basically depending upon the severity and depending upon the priority it is supports to
put you in once the person identifies what kind of severe diseases that you have, deputy
if your priority is let us say very high, when you that is severe goes into the first queue
and if your priority is not so, severe goes into the second queue and that is exactly what
is done.
732
(Refer Slide Time: 02:08)
So, basically what we doing is we taking as input at token and priority, if priority is equal
to 1 it is put into the first queue, priority is equal to 2, it is put into the second queue
moderate and priority is the not so, severe, not at all severe, you go into that third queue.
And what is the question say it say that is doctors queue. And what we should ensure is
that, there are 10 patients in the queue doctors queue for example, at any point time here
all having 10 elements in the queue.
So, what you do is the way you allot them is that, first you put 3 patients from the high
priority queue, when you put 2 patients severe there from the moderate queue and then
put 1 patient from the least moderate queue and you keep repeating this order. So, that
for example, if there are let us say 6 people who have severe problem, then the first three
will get served, then the moderate person will get served, two moderate people will get
served and then one person will non-severity will be served and again the three people
will severity will be served, that is the application.
733
(Refer Slide Time: 03:19)
And what are you supposed to give, is supposed to output the elements in the queue. So,
let us look at this program as it runs.
So, very, very simple it is problem on queues which you already studied is not really
priority queue. What we are doing is you remember that queue that we studied in the
class for example. Most important point here is that, if you look at queue 1, queue 2 and
queue 3 people enter the queue what is happening is, first comes first serve we are, in the
sense for that particular list. That is will a particular severity if you come, you going into
the particular list, even the next person who comes into with the particular severity, when
you go into the same list,, but it end of in that queue that is exactly what we are having
734
over here.
So, let us run this, what are you given, you given a token and priority. So, let say 10 3, 11
1, 22 2, 23 1, 24 2, 25 1, 26 3, 28 3 and at say 30 3 and 43 3 because normally we will
have people with low priority. Now, what is it giving so, number 1 means token and
priority here. So, basically if you notice the 11, 23 and 25 3 patients who are having
severe problems.
So, doctor's queue has 11, 23and 25 the token number of this paper and then what
happens, there are two people with a lower severity 22 and 24. So, the next 2 guys go
over here and then what do we have, the rest of them. Because, there are ideally you are
supposed to allow only one person with low severity, but since there are no other patients
with higher severity, it goes into this particular list.
So, this is about using the queue ADT, because defined and using it to create three
different queues, remember this is not a priority queue ((Refer Time: 06:45)). It say that,
every queue is a first in first out queue,, but there are three different queues,, but it
creates what is called a fourth queue which corresponds to the queue of the doctor, in
735
which we put elements into that queue depending upon the priority. So, this is something
that you need to understand.
And this shows the execution of the program and what are we showing here, we are
giving the token numbers of the elements in the doctors queue. So, now we look at a
solution to another problem, which is the postfix evaluation problem.
We always saw the infix to postfix conversion. So, let us look at this postfix evaluation
problem, this uses a stack and here remember, when we did the infix to postfix
conversion, we were given an expression and we said the expression has to be converted
to postfix. So, when we did that what we did was, we defined or stack of characters if we
remember I of some particular element I.
And in that case what we did was, why we define a stack of characters. Because, in that
problem we put the operators onto the stack and as soon as you came across an operator,
which has lower priority then what was there on the stack lower or equal to priority, we
popped all the elements of the stack.
736
(Refer Slide Time: 08:17)
Now, this is slightly different problem, you are given opposed fix this expression and
what you suppose to do is let me take this example over here. So, what we mean by this
is the following in the postfix evaluation. Let us look at this problem here,. So, you want
to add some integers in the postfix evaluation problem.
So, let us say I have the numbers minus 4 50 plus and this is one token and one token and
let us it 4 plus 6 and star. So, this is a postfix expression,. So, now, , I am putting this
boxes around all of this, because I cannot treat them as simply as a string of stream of
characters for the simple reason that the integers. For example, where you look at that in
terms of characters, depending upon the number of digits that they have can have more
737
than one digit like this example over here.
So, what are we excepted to do in this, we notice that this minus 4 is a number is a unary
number minus over here. So, as given as a number here,. So, I want minus 4 plus,. So, as
soon as I,. So, what are we do is, I want to use a stack to do this problem. So, what do we
do here now, how do I do this now the way it works like this, as soon as you seen an
operand you put it onto the stack. Now, minus 4 is a operand and put it onto the stack 50
is the next operand we put it onto the stack.
But, as soon as you see the operator what you do is, this is operand 2 and this is operand
1 and you perform operand 1 plus operand 2. So, what we do is we first pop of both these
elements. So, let say I put opnd 2 first element has been popped is 50 and the second
element has been popped opnd 1 is minus 4, when you perform opnd 1 plus because we
have seen and the operator here opnd 2 and then minus 4 plus 50 is 46 let say this is the
result and this is equal to 46. So, since we have popped both the elements, the stack is
again empty I push 46 back of the stack.
Then what you do, we have now this is done come to the next token at push it onto the
stack, because it is an operant and again I see a plus. Therefore, again now opnd 2 it will
to 4 opnd 1 equal to 46 and the result is,. So, a pop both of them out. So, again my stack
is empty, when add these two numbers and in push it back onto the stack. So, push that
back onto the stack, then what do I do next stack here we have already finish the operator
here at push it on to the stack, then I see multiplication now what do I expect.
738
So, basically the opnd 2 6 opnd 1 is 50 and we do multiplied both of them result is 300
push it back onto the stack, now you come to the end of the expression. So, I pop the
element of the stack and that is the result. So, let us exactly what is being done in this
part of the problem. So, what is being done is here, if you notice there is in stack of
particular size, there is an implemented and we push the element onto the stack.
But, remember that what is this element now, the element is that are being push onto the
stack or a type integer, remember this integer stack. But, what are we doing when we
are,. So, basically I here is something a result which will compute, depending upon the
operator we will compute multiplication and what is that, as soon as we get the operands
which are popped of the stack it compute the operation and returns the result.
Then, here is a function which converts a character string to an integer, there is see why
this is required in a little byte. And basically you have to take your sign and. So, on and.
So, forth, then remember we are looking at tokens there. So, it is looks for a token which
is purely and an operator and that is what is being passed over here. Now, let us go to the
main program here, what are we doing we are defining a character array called postfix,
which consists of a string of integers. Why are we doing this, because remember that
when you look at a postfix expression, there is a integer here, there is an integer here,,
but there is an operator here. But, you going to give this string as it is,, but as we already
saw as you explained sometime back curly each element in the postfix string is not a
single digit number, it can have multiple digit. So, what we do is, we represents each one
of the tokens, we call this tokens in the postfix expression, this token 1 token 2, 3, 4, 5, 6
and 7 each one of them as a string.
So, to do that what is done is a character array of n, suppose first what we do is you get
the number of tokens in the string. So, what it is we are saying here, how many token are
there 1, 2, 3, 4, 5, 6 and 7,. So, 7 tokens in the string. So, what it does here, you here for
example, the input n is given and then it create a number of such character strings which
are of size n and then what it is do, we create assumes that the string is no more than 5
characters long and then what it does it.
So, there is an allocation that is done here the string is read and it is put into this postfix
array. So, what is the postfix array is actually a two dimensional array and what is it
having, if you look at a two dimensional array in the first element is 4, second element is
50, that is each element of the array is a string of length 5 characters maximum plus 6
739
and star in this particular example. So, this is the postfix array and then what is done, you
once you have done that it is basically going to call, then what is happening here we need
to perform this evaluation.
So, what is done is it look at what the,. So, what is doing here it gets each one of the
elements as I said that there are n tokens and for each one of the token it is check
whether is an operator or not. Otherwise, it pops the elements from the operator for
example, v 2 and v 1 this is operand 2, this is operand 1 and then performs the operation,
then what this do once it evaluate it is evaluation is the result here, we pushes it back
onto the stack.
So, where encountering and operand for example, because it is also string it converts the
character string to an integer and pushes it and then pushes that onto the stack. Because,
the stack is a stack of integers, because we do not need anything other than the stack of
integers, because we putting the operands onto the stack. So, let see how this program
runs.
So, postfix final I to give 7 because I am let say I looked at 7 operands here minus 450
plus 4 plus 6 and star. So, what it is doing it pushing minus 4 onto the stack, pushing 50
onto the stack, it encounter an operator next as indicated here. Then it gets the result 46 it
is pushes it onto the stack, there is a season operand again pushes the operand onto the
stack. Again it encounters an operator then what it is do, it computes the sum of pops
both 46 and 4 from the stack ask them gets the result 50 pushes it back onto the stack.
740
Then, it comes encounter an operand again 6 it pushes it back onto the stack then it
encounters an operator and what it is do, it pushes once again encounters an operator is
going to pop 50 and 60 6 first and then 50 and then perform the operation of
multiplication, the result is 300 and it pushes 300 back onto the stack.
But, clearly by now we are come off to the end of expression and has been exhausted the
7 therefore, it is give you the result. So, this is how the postfix evaluation program can be
implemented for those of few have perhaps not done it properly, I would encourage for
the go back and try it out again.
741
Programming, Data Structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 55
Greedy Algorithms-Job Scheduling
So, today's lecture is about greedy algorithms, and specifically we will be studying one
algorithm for a problem call job scheduling. The initial part of the presentation will
present you the motivation for greedy algorithms by a very simple algorithm, very
simple example. The example is the change making example. So, let us consider the
following problem where for an example let us consider we have 832 rupees, and using
as few notes and coins as possible, we should keep 832 rupees let us say in our purse. In
general we know the there are 9 denominations of rupees in the Indian currency. So, let
us call these denominations d 1 to d 9, indeed they take the values from 1000, 500, 100,
50, 20, 10 , 5, 2 and 1.
Naturally we wish to carry as few notes and coins as possible and the generic question is
given M rupees identify 9 variables; these are integer valued variables, positive integer
valued variables. Such that M rupees can be written as a sum of x 1 times, the first
742
denomination d 1, x 2 times the second denomination d 2 so on up to x 9 times the ninth
denomination d 9. For example, 832 can be written as one 500 rupees plus three 100
rupees one 20 rupees, one 10 rupee, and one 2 rupee coin. In general do you have a
natural approach here to solve this problem. The natural approach seems to be that
iteratively we select the large largest denomination, which is available which is smaller
than the current amount. Let us observe this by us the example itself 832 is the current
amount in the largest denomination which is available to us is a 500 rupee note. So, we
pick up 500 rupees and the balance that is to be expressed is 332. Now 332 the smallest
the largest denominations smaller than 332 is a 100 rupee note, naturally we pick three
times 100 th, we pick 100 once then the balance is 232, the smallest denomination large,
the largest denominations smaller than this amount is another 100, and so on. So, we pick
three times 300 rupees. Then we pick at 20 rupee note, because a balance that is to be
express is 32 rupees and so on. So, the generic idea seems to be that we select the largest
denomination that is available, such that it is smaller than the current amount that we
need to express.
Does this always work independent of the denominations, this is an interesting question.
Observe that we did our exercise with the Indian currency with a fix set of
denominations, let us imagine whether this approach will work for any arbitrary set of
743
denominations in some currency. Let us assume that there is a country with currency
right whose denominations are 1, 3 and 4 respectively, and there is no other
denomination. Would our natural generic approach yield the optimum solution, let us see.
Let us consider the simplest case of 6. So, if you apply our generic approach we will pick
the largest denominations smaller than the amount which is 6, therefore we will pick 4
after which we are left with 2, which can be expressed as 1 plus 1.
So, our solution would be 4 plus 1 plus 1, on the other hand it is clear in this very simple
example that the best denomination possible, the best representation of 6 possible in this
currency is 3 plus 3. Therefore, it is clear that this generic algorithm which seems to be a
very natural approach, it does not work for all currencies, it depends only the seems to
depend on the denominations. At definite exercise which we will not address in this class
is does this work for the Indian currency in if so why does it? We will not addresses, but
this is a left as an exercise for the interested student.
This takes as to this whole idea of greedy algorithms. So, observe that our algorithmic
approach is a greedy approach, we choose the locally best possible choice. So, given a
certain amount, we choose the largest possible denomination without exceeding the
current amount. So, this is in some sense a greedy choice hopping that this will
744
eventually lead to a optimal solution. As we have seen it does not work for all currencies.
So, therefore, greedy algorithms should have an optimal substructure. What is an optimal
substructure? We should be able to guarantee the existence of an optimal solution to the
problem, such that this optimal solution also contains optimal solutions to the sub
problems. This is a very important concept, you have a problem and you also have sub
problems of the problem. This is best illustrated by the shortest path problem in any
network. Let us assume that the network is undirected, and let us sink of the shortest path
problem between a source and destination.
Observe that if you pick any shortest path between the source and destination, and if you
pick any pair of vertices on the shortest path not necessarily, so source and destination, it
is clear that the shortest path contains a shortest path between the intermediate points.
So, very natural property of shortest path. So, this is the optimal substructure. Observe
that no algorithmic choices here, it is just a property of an optimum solution. The greedy
choice property is an algorithmic choice, it is says that you can obtain a globally
optimum solution by making locally optimal choices. We have seen this example of the
change making problem, and we wonder whether it has a greedy choice property.
745
respectively, if you take an optimum solution. Let us take the optimum solution for 6 in
this example, which is 3 plus 3, note that every sub set of notes is the optimum change
for it some. So, in this case every sub set is just the note 3 itself, which is optimum
change for itself. So, this is easy. The greedy choice on the other hand is not satisfied as
we have already seen we will select 4, then we selected one followed by a one in this
turned out to be suboptimal. So, we repeat this exercise would the greedy choice work
for the Indian currency. Indeed in this slide we have observed that the optimal
substructures seems to work for any currency.
However, like I said earlier we will not address it in this presentation, because the
problem was not as easy as it seems, it is very easy to state it, but it is very closely
related to problems, which after many years of research have not yielded to give efficient
yielded to efficient algorithms for example, there is a problem called the knapsack
problem. It does not have efficient algorithms to date it is believed not to have efficient
algorithms, there is also the optimum denomination problem which is actually a problem
face by currency designers. Where the question is what is the best denomination to
ensure that for every number you get the smallest possible change by our approach.
So, therefore, let us ask as slightly different question, other problems for which greedy
746
algorithms result in optimum solutions, this is the focus. The change making example
was a natural example to visualize the greedy algorithm scenario.
Let us look at the problem, which is very well in call, very well known as a scheduling
problem. There is a single machine, and there are n jobs; each of these jobs can run only
on this single machine, and should be run on the single machine, and the jobs are already
known to have running times t 1, t 2, to t n respectively. In other words the job j i takes t i
units of time on the machine M. They aim is the following, the aim is identify a schedule
of jobs, that is the order in which the jobs will execute in the machine. So, that we
minimize the sum of the completion times.
Now minimizing the sum of completion times can also be thought of as minimizing the
average of the all the completion times, it is very important for the student note that this
average duration, this is not the average duration which is a fixed number, it is the
average time that a machine spends waiting for the average time a jobs spends waiting
for a machine, and then the time that it spends on the machine itself, one once to
minimize this quantity. Therefore, the goal of this exercise is to find an ordering of jobs
to execute on a machine. So, that we minimizes the sum of completion times.
747
(Refer Slide Time: 10:03)
Let us do the small example; there are 4 jobs; job one takes 15 units of time, job 2 takes
8 units of time, job 3 takes 3 units of time, job 4 takes 10 units of time. The total duration
that the machine will spend executing these jobs, clearly is 15 plus 8 23 units plus 3 26
plus 10 36 unit of time. Therefore, the average duration on the machine seems to be 9
units of time is 9 units of time.
748
However, the completion time is a completely different entity as shown by this graphic.
Consider the first schedule in this graphic, where job 1, job 2, job 3, and job 4 are
scheduled; job 1 finishes after 15 units of time on the machine, job 2 then finishes after 8
units of time, therefore the completion time of job 2 is 23, job 3 which actually spends a
least amount of time on the machine, which is 3 units of time completes at time instant
26 and job 4 completes a 36 as a time ((Refer Time: 11:10)). Therefore, now if you
average these completion times, we see that the average is 25, that is 100 units of time is
spent, there is sum of the completion times is 100. On the other hand, let us consider the
second schedule, where interestingly the shortest job is schedule first, then the second
shortest job, then the third shortest job, then the fourth shortest job which is the order,
job 3, job 2, job 4, and job 1. Now observe that the average completion time is 17.75
units of time, which is smaller significantly than the schedule where the jobs where
scheduled according to the order in which they were presented in the input. So, now do
you have an algorithm here to minimize a sum of completion times. To do this let us
write down the formula for the sum of the completion times.
Before that let us look at the schedule. Let us look at properties of the schedule before
we do the calculation for the sum of the completion times, let us look at a property of a
schedule. One observation that we can make is that if we taken arbitrary schedule and
749
exchange the position of a shortest job in particular let us say the first shortest job in the
schedule with the first job in the schedule. So, let us look at our example in the first job,
if we exchange the jobs j 3 with a first job observe that we will get a better schedule,
then the one that was given first. So, if we assume that there is always an optimal
schedule where the first job is a shortest job, then it is clear that there is a very interesting
optimal substructure that if you remove the shortest job, the remaining schedule for the
remaining jobs is indeed optimal. In other words, if you take a optimal schedule in which
the shortest job is schedule first by removing that shortest job. The remaining schedule is
indeed an optimum schedule for the remaining N minus 1. Of course, we do not know if
there is an optimal schedule which contains the shortest job first, that is what we are
going to study now by writing down a formula for this sum of the completion times.
So, do this let us assume that there is a schedule which we refer to using the Greek letter
sigma, and let us assume that sigma 1 denote the first job and sigma N denote the Nth job
in the sequence. Now, let us look at the sum of the completion times, the sum of the
completion times is return as a first formula where there are N jobs, and let us observe
that the time taken by the first job, that is the job sigma of 1 that will be counted it will
delay every sub sequent job, apart from using t sigma of 1 units of time on the machine.
In the first job that we schedule which is sigma of 1 takes t sigma of one units of time on
750
the machine, not only there it also delays remaining n minus 1 jobs by t sigma of 1 units
of time, same for t sigma of 2 it is a second job - it is completion time is the time there it
spent waiting for the machine which is t sigma of 1 plus a time that it spends processing
on the machine which is t sigma of 2. In general if you look at the whole expression for
the sum of completion times, the formula is given there, it is N terms in the summation
and there are n minus k plus 1 copies of t sigma of k.
And that is explained in the equality there which says that the completion time is t sigma
of 1 plus the completion time of the second job which is t sigma of 1 plus t sigma of 2
plus a completion of time of the third job which is t sigma of 1 plus t sigma of 2 plus t
sigma of 3 and so on up to the completion time of the nth job which is t sigma of 1 plus t
sigma of 2 up to t sigma of N. If i rewrite the summation by adding and subtracting a few
terms, we get the third term in the whole equality sequence which is viewed as 2
summations. Observe that the first summation which is N plus 1 multiplied by
summation of the processing times of the N jobs, that is the first term minus summation
k times t sigma of k that is the kth job the processing time of the kth job. Observe that the
first term is independent of the schedule and the second term really be is very dependent
of the schedule. Observe that this sum of completion times is indeed valid for every
schedule sigma, this is a very important thing.
So, what we have done in this slide is to write down the close form expression for the
sum of completion times, for a arbitrary schedule which we have call sigma. And then
we have observe that this sum of completion times can be viewed as 2 summations; one
that is independent of the schedule itself and the second one which is dependent of the
schedule. And there is a subtraction term there, therefore as a second term increases the
total cost becomes smaller. So, let us see what makes a second term to decrease? Let us
see what makes the second term to actually increase, and therefore reduces sum of
completion terms completion times.
751
(Refer Slide Time: 17:21)
So, let us see a property of an optimal schedule. So, let us imagine a schedule sigma, and
let us assume that there is an index x which is more than an index y, but the processing
time for the job which is scheduled as sigma of x is smaller than the processing time of
the job schedules sigma of y. In other words y is scheduled sigma of y is later than sigma
of x, but the processing time of sigma of y is larger than the processing time of sigma of
x. So, let us try the most natural thing, let us exchange the positions of the 2 jobs. So, in
other words, what do we do we exchange the position of the job in position sigma of y
with the position of the job sigma of x. And let us see how would changes the duration.
So, let us see what happens, we should just go back to the formula and observe that the
terms just change by modifying the multipliers appropriately. In other words, we have
written down in inequality which says that x times t of sigma of x plus y times t of sigma
of y, this is the contribution to the second term by these 2 jobs, and then we may
exchange the position of these 2 jobs. The contribution just becomes y times t of sigma
of x plus x times t of sigma of y.
We now show that x times t of sigma of x plus y times t of sigma of y is smaller than the
summation after swapping it, that is y times t of sigma of x plus x times t of sigma of y.
This is very easy to see by the following sequence of expressions by just rewriting x
times t x times t subscript sigma of x plus y times t subscript sigma of y, this sequence of
752
expressions actually shows that the exchange actually increases the value of the second
term.
As a consequence of this, it is clear that if we start off with an arbitrary ordering and if
we identify a pair of jobs at sigma of x and sigma of y with the property that sigma of y
is later than sigma of x, and the time taken by the job schedule that sigma of y is more
than the time taken by the job scheduled at sigma of x. If we exchange these 2 jobs. The
contribution to the second term in the sum of completion times reduces, and therefore we
have a new schedule whose sum of completion times is strictly smaller.
Consequently the greedy algorithm is a very simple algorithm it says that from the given
set of jobs schedule the shortest duration job first. That it is indeed an optimal algorithm
follows from our analysis of the completion time that this schedule has a smaller
completion time than any other schedule.
753
Algorithms Dynamic Programming
Prof. N.S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institution Technology, Madras
Lecture - 56
After looking at the basic algorithms and techniques like the greedy techniques, today we
come to advance techniques called dynamic programming.
754
(Refer Slide Time: 01:03)
Let us look at the shortest path problem as an example, what you see is the layered graph
it has 4 vertices S, A, B and T and you also see the edge cos. And if one uses the greedy
technique to find the length of the shortest path from S to T by being greedy. In the sense
that from S we choose the edge of these to 8, then from A we chosen edge of the least
weight and form and B we choose an edge of the least weight. Then, we can conclude
that the greedy algorithm will output path of the length 8; however, it is quite clear that
the length of the shortest path is indeed 9.
755
In this example, the algorithm does output the shortest path, but let us look at the shortest
path in a slickly more complicated looking graph. If we are greedy then the greedy
algorithm will choose the path, which goes from S to the vertex A. Because, the edge
weight 1 is shortest among all the edge are go out of S and from A it is choose us the
cheapest edge width, which is 4 and a takes it to the vertex B and D we choose the
shortest edge, whose cause is 18 and we conclude that the greedy algorithm outputs a
path whose length is of 23 units. As opposed the shortest path which is from S to C, C to
F and F to T and the cos of this path is indeed 9. Therefore, it is clear that to solve the
shortest problem a greedy strategy does not work.
Let us just look at the dynamic programming approach and let us also makes some
observations about the shortest path. The length of the shortest path from S to T is obtain
by calculating the shortest paths viva the vertices A B and C respectively and then
picking the shortest path among the 3 in this example, it is quite clear S and S has 3
neighbors only, which are called A, B and C respectively.
Now, if we know the shortest path from A to T and the shortest path from B to T and this
shortest path from C to T respectively, then adding of the appropriate edge cos from S
and then choosing the minimum will very clearly give the shortest path from S to T.
Therefore, what we have now observed is that, it is solve the shortest path problem, we
need to solve recursive versions of the shortest path problem from neighboring vertices.
756
Let us this look at the formulation of the distance from S to T R. In other words shortest
path from S to T, clearly as we just discuss distance from S to T is minimum over 1 plus
the distance from A to T, the one comes because the edge weight from S to A is 1, 2 plus
distance from B to T. Because, edge weight from S to B is of cost 2 and 5 plus distance
from C to T the edge weight from S to C being 5. Indeed if we know the shortest path
from A to T, B to T and C to T respectively, this minimum gives us shortest path from S
to T.
Now, the distance from A to T the relevant part of the graph shown here, the distance
from A to T as you can see is the minimum of four 4 distance 4 plus the distance from D
to T plus. The distance from A to T is given by the minimum of 4 plus the distance from
D to T and 11 plus the distance from E to T, where D and T are only neighbors of A.
757
(Refer Slide Time: 05:09)
We can similarly now formulate, the distance from B to T the distance from C to T and
distance from S to T can then be used to calculate. So, the distance from B to T is again 9
plus the distance from D to T. The distance from B to T is given by the minimum of the 3
distance is viva D viva E and viva F respectively. Similarly, the distance from C to T that
is just single path that goes through F and this is given us 2 plus the distance from F to T.
One can over observe that the distance from S to T will indeed choose the path that goes
viva C which is of cost 9, because that is where the minimum is obtain. What we have
done in the slide show for is so observe that a recursive formulation of the distance
function in terms of distances from other vertices to the designation is helpful in
computing the shortest path.
758
(Refer Slide Time: 06:26)
It is also possible to observe that one can calculate the distances from the recursive
formulations which we have just observed. One can observe with the distance from S to
D and intermediate vertex, not essentially T is the minimum of the distance from S to A
plus a distance from A to T. And similarly distance from S to B plus the distance from B
to D, in other words we are taking the minimum of the distances viva intermediate points
A and B respectively. Similarly, the distance from S to E and distance S to F are also
presented.
759
Now, that we can seen in this example, let us just formally state dynamic programming
and it is purposes. It is use for solving optimization problems which are minimization
problems or maximization problems. And in all these problems set of choices must be
made to get an optimum solution, in the examples that we look at the appropriate edges
must be chosen and find the length of shortest path from S to T.
The choices are the edges that must be taken by a shortest path from S to T, there may be
many search paths. For example, an under reacted graph I am every path with a least
number of edges from S to T is indeed shortest path and our goal is to find an optimal
solution, then other words to find the optimal set of choices.
And then compute the value of the optimum solution in a bottom of fashion, the last
computation is indeed done viva program. But, we first to are analytical exercises and
the computation indeed implements the recursion.
760
(Refer Slide Time: 09:04)
On the first line the processing stations are called S1,1 to S1, n and in the second line the
processing stations are call S to 1 to S to n respectively there are n stations. Now,
corresponding to the stations, each of the stations have a certain processing time, in
particular for the station S1, j that is the j station on the line 1 the processing time is a1, j
units of time. And similarly at the station S2, j the processing time is a to j units of time.
The entry times into the station are e1 and e2 respectively, the entry times into the lines
that is the entry into the first station s1, 1 and the entry into the first station S2, 1 are e
one need to respectively and the exit times from the station are x1, 1 x 2 respectively.
761
(Refer Slide Time: 10:58)
One can assume that in this automobile factory are car chase enters one of these to
assembly lines and then goes through from one station to another and exists with a
completed vehicle alone the way. The car station, the car alone the way are car can stay
on the same line by going to the next station in the line and it does not pay any cost in
terms of time duration or it go transfer to the other line with a cost which is t i j units of
time.
In other words if a car get process at station a S1, 1 then it can either go instantaneous to
station s1, 2 are in t1, 1 units of time it can go to the station a2, 2.
762
(Refer Slide Time: 12:11)
So, what is the problem in this whole exercise? The problem with a whole exercise is
that a chase must be rooted through this network, it must visit every station it does not
matter in which line it get's processed. But, it must get process at every station and exist
in the minimum amount of time, this is for a single chase.
Now, one of the ways of solving the problem is to actually try all the possible ways by
which a chase go through the assembly lines. And it is very clear that there are 2 power n
of them and computationally spending 2 power n units of time to actually find out which
763
is the most optimal sequence is not an efficient approach.
So, the implementation of this idea is essentially to have a big binary vector, which
essentially says if a certain bit. For example, in this case at step 3 if a bit set to 0 then the
vehicle goes through station 2 in step 3 and if it is want the vehicle goes through station
1 in step 3. The implementation of this Brute force algorithm is to keep a big vector,
where 0 indicates the chase will go through line 2 and 1 indicates chase will go through
line 1.
And once these are fixed the time that the chase spends in the factory can be easily
calculated, it is just a length of the path that goes to these stations. The some of the time
durations on edge will tell us, the total time that the vehicle will tell us the total time
chase spend in the factory, before existing. And then we can choose the big vector which
gives the least solution and indeed we would have solve the desire problem; however, we
would as spend 2 power n units of time.
764
(Refer Slide Time: 14:45)
To avoid this inefficient see in terms of amount of time that has been spend by the
algorithm, in finding the optimal path. We try an understand the structure of an optimal
solution, which is the first step in the design of the dynamic programming algorithm. In
this graphics we indeed have highlighted, the optimum path in dark color, we will see
how the algorithm will indeed find this particular path.
To understand the optimal solution, we invent a parameter of interest. Let us look at the
station j on the first line and let us ask, if a shortest path goes through S1, j then do we
765
have some information has to shortest paths to other stations that presided it that are
computed in that shortest path. To understand these structure of the optimal solution, we
ask the following question, if you look at the set of all possible ways from the start to
exit viva S1, j there are two possibilities for are path that goes viva S1, j.
The previous vertex before the station S1, j could have been the station S1, j minus 1 and
direct transfer to S1, j are the previous station could have been S2, j minus 1 that is the
station on that second line and the transfer over to S1, j.
We known make the observation that, the fastest way through S1, j is through S1, j minus
1. In other words, if we consider all the paths that go from the start to exist, which go
through S1, j and among these, if we consider the path which is the fastest. And let us say
that, such as shortest path or such as fastest path indeed goes through S1, j minus 1.
Then, it is also clear that such a path must have taken the fastest way from start through
S1, j minus 1.
The reason is very straight forward, indeed if there is a faster way through S1, j minus 1
then we could have used it and found a faster path through S1, j. This is really the
optimal structure are it is a structure of an optimal solution.
766
(Refer Slide Time: 18:08)
The structure of an optimal solution is, the fastest ways through S1, j contains with it an
optimal solution to the fastest way through S1 j minus 1 or S2, j minus 1 whichever is
there in the fastest way through S1, j. This is refer to as we optimal sub structure
property, it is this property that is used to right down the recurrence for the length of the
shortest path from S to T or the fastest path from S to T.
So, what we are going to do now, which is a second step it the process of dynamic
programming is to right down the optimal solution in terms of the optimal solutions to
767
sub problems.
Let us introduce some necessary notation, let us assume that f star is the fastest time to
go through the whole factory. And let f i j denote the fastest time to go from the starting
point, through the station S i j. Therefore, f star is very clear, f star is a minimum of the
two terms which is f 1 of n plus x 1 that is the time taken to exit from line 1 comma f 2
of n plus x 2 which is the time taken to exist from line 2.
768
(Refer Slide Time: 19:53)
Let us start of with a base case, let us consider f 1 of n, let us consider f 1 of 1 that is
through station 1, what is the fastest path that goes through station 1 on line 1, it is a time
taken into enter line 1 and get process set of the first station, in this case as you can see it
is 9 units of time. But, the generic formula that we can right down is that, f 1 of n is e 1
plus a1, 1 this the time taken to enter into line 1 and the time taken to be process at the
first station on line 1.
Similarly, f 2 of 1 is a time taken to enter into line 2 plus the time taken to be process at
station 1 on line 2, the formulae are return there f 1 of 1 is e 1 plus a1, 1 and f 2 of 1 plus
e 2 plus a2, 1.
769
(Refer Slide Time: 21:01)
In general we can write a down recursive specification of f1, j or f2, j they are symmetric
let us just look at the case for f1, j. So, the fastest way through s i j has only two
possibilities. The two possibilities are use the fastest way to come to station j minus 1 on
line 1, use the fastest way to cross station j minus 1 on line 1 and then the process at
station j on line 1 or use a the fastest way to arrive at station j minus 1 on line 2, then
transfer to line 1 and then get process at station j on line 1.
770
(Refer Slide Time: 22:38)
And this basically tells us the recursive solution of interest for every station on each of
the two lines. Having formulated this recursive specification of the time taken to cross
station j on line 1 on the time taken to cross station j on line 2 and this we done for every
j between 1 and n, we know wonder how what it is to calculate these values from these
formulae.
And this is the exercise of the computing the optimum solution. And one of the ways of
solving the optimum solution is this solve the unit top down fashion and clearly if on
771
tries to compute f 1 of n and f 2 of n in a top down fashion at every step, we are taking
the minimum of the two quantities. And it is clear that the running time of this algorithm
which is to evaluate the recurrence, in the top down fashion will take an exponential
time.
In other words, it will take 2 power n units of time, which does not seem to be a
significant improvement, which is not an improvement at all over the simple algorithm
that we started out with the brute force algorithm.
On the other hand, if one uses the bottom up approach, one can indeed observe that the
values of interest f 1 of n and f 2 of n can be very easily calculated. The main observation
that we make z for j greater than or equal to 2, the value of f i of j depends only on f 1 of
j minus 1 and f 2 of j minus 1. In other words, the fastest way of crossing station j on
either line depends only on the fastest way of crossing station j minus 1 on line 1 and the
fastest way of crossing station j minus 1 1 line 2. And we compute the values of f i j has
describe here, in increasing order of j.
772
(Refer Slide Time: 25:07)
Let us look at the time taken to cross station 1 which we know, in this example we can
see that the fastest way of crossing station 1 on line 1 is 9 units of time, on station 2 it is
12 units of on line 2 the fastest way of crossing station 1 takes 12 units of time. After this
let us look at station 2, the fastest way of crossing station 2 on line 1 is 18 units of time
and the number in parenthesis says, which station on which line was a previous station.
In this case, the fastest way of crossing station 2 on line 1 is viva station 1 on line 1
itself, which is an instantaneously transfer to station 2 and then 9 units of processing
time. Also the fastest way of crossing station 2 on line 2 is to actually through station 1 in
line 1 and then transfer to station 2 on line 2 and the process at station 2 on line 2.
Observe that, this is 16 units of time has suppose to 12 units of time plus 2 unit of time to
transfer has suppose to 12 units of time at station 1 on line 2.
And then instantaneously be transfer 2 station 2 on line 2 and then use for 5 units of time
which makes it is 23 units of time. Similarly, at station 3 on line 1 observe that the fastest
way of crossing station 3 on line 1 goes through station 2 on line 2 and uses 20 units of
time. As suppose to crossing station 3 on line 2 which takes 22 units of time, similarly
crossing station for on line 4 takes 24 units of time viva station 3 on line 1 itself.
And on line 2 it takes 25 units of time viva station 3 on line 1 and crossing station 5 takes
32 units of time viva station 4 on line 1 and 30 units of time viva station 4 on line 2 and
then the exit takes 3 and 5 units of time respectively. Therefore, the quickest way of
773
getting from entry to exit is 35 units of time viva station 5 on line 1.
Having gone through these hole exercise of recursive of identifying the optimal sub
structure and recursively specifying the distance function. And observing there it can be
computed in at a bottom of fashion, computing the optimal root that is the sequence of
stations through which the shortest path pass through is left as an exercise. Then, next
example of dynamic programming which we will study is the problem of optimal matrix
chain multiplication which will be done then will be meet next.
Thank you.
774
Programming, Data Structures and Algorithms
Prof. N.S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institution Technology, Madras
Lecture – 57
Dynamic Programming
Today we are going to study a second problem which is solved using the method of
dynamic programming; this problem is called the matrix chain multiplication. The
problem has that we are given a sequence of matrices that can be multiplied. So, A 1, A 2
up to A n are n matrices, which are given. And our aim is to compute the product of these
n matrices. Let us assume that the product can be perform, in other words the orders of
the matrices are appropriately given. There are many parenthesizations to compute the
product. What is the pareanthesization? The paranthasization is an allocation of
parenthesizes around the expression that has been given. So, that the expression can be
evaluated has specified by the paranthasization.
775
(Refer Slide Time: 01:11)
Let us now consider this example, where there are 4 matrices - A 1, A 2, A 3, and A 4;
and aim is to compute the product of these 4 matrices. There are 5 possible
paranthesization, let us inspect these each of paranthesization, this is the first one. We
know that in any expression which is parenthesize, the innermost parenthesize is
evaluated first. Therefore, in this paranthasization the first one. A 3 and A 4 are
multiplied first, the result is then multiplied with A 2 and the result is then multiplied
with A 1. In the next parenthesization the inner most parenthesis is the one that encloses
the product A 2, A 3 which is computed first. The result is then computed is the result is
then multiplied with A 4, the result of this matrix multiplication is then multiplied with A
1.
The other parenthesis this is more interesting then the first two, because it is different
observe that there are two inner most parenthesis; one parenthesis contains the matrix
product A 1 A 2, and the second inner most parenthesis contains the matrix product A 3 A
4, and again using expression evaluation rules, the leftmost innermost parenthesis is
evaluated first. Therefore, the expression involving the product A 1 A 2 is evaluated first,
then the expression involving the product A 3 A 4 is evaluated, then the two results are
multiplied and this is given us a result of the matrix product of the four matrices. The
following two are symmetric to the second and first respectively, and the explanation is
776
similar. What distinguishes these 5 different parenthesisation? Let us just see that.
What distinguishes them is the total number of multiplications. Our aim is to now count
the total number of scalar multiplications which are necessary. To do this, let us
understand the number of multiplications required to multiply two matrices, in this case
let us assume that the matrix dimensions are given, the two matrices are A which is a p
by q matrix, and B which is a q by r matrix. We knows that the result is a p by r matrix,
and let us call this result matrix, the value the matrix C. It is clear that the total number of
matrix multiplication that need to be performed is p q r. It is clear the total number of
matrix multiplications that need to be perform is p multiplied by q multiplied by r. How
to this affect? The behavior of chain matrix multiplication.
777
(Refer Slide Time: 04:22)
For this let us consider the following example, where we consider three matrices; A B
and C. A is a order 10 by 100, B is a order 100 by 5, and C is a order 5 by 50. Clearly the
three matrices can be multiplied that is the product A B C can be computed. There are
two ways of parenthesizing this, this is the first way where the product AB is computed
first the result is multiplied with C. Let us assume that the product AB is called D, we
know that it is a 10 by 5 matrix, and C is a 5 by 50 matrix the multiplication AB takes
5000 scalar multiplications.
The product DC takes 2500 scalar multiplications, therefore total number of scalar
multiplications is 7500. Let us consider the second parenthesizations, where B and C are
multiplied first followed by A. So, let the outcome of multiplying B and C be the matrix
E which is the 100 by 50 matrix. So, the product B multiplied by C which performs first
takes 25000 scalar multiplications. The sub sequence product of A and E takes 50000
scalar multiplications, and therefore we can already see that the first paranthasization
uses only 7500 scalar multiplications, but the second parenthesization uses 10 times
more number of scalar multiplications, that is it uses 75000 scalar multiplications.
Clearly from a efficient C point of you, the first parethesization is a more preferred
parenthesization, then the second parenthesization.
778
(Refer Slide Time: 06:27)
This gives rise to a very interesting minimization problem. This minimization problem is
called the matrix chain multiplications problem. The input to the matrix chain
multiplications problem is a chain of n matrices; A 1, A 2 to A n. For every i the i'th
matrix has dimension p i minus 1 cross p i; that is the matrix A i has p i minus 1 rows and
p i columns. The first matrix A 1 has p 0 rows and p 1 columns. The goal is the
parenthesize this matrix, the goal is the parenthesize this chain A 1, A 2 to An. So, that
the total number of scalar multiplications is minimize. Recall from the previous example
that the recall from the previous example that different parenthesization give raise to
different number of scalar multiplications, and our aim is to choose the optimal scalar
multiple optimal parenthesization to minimize the total number of scalar multiplications.
One natural approach is the brute force method, where we try all possible
parenthesizations, I leave this an exercise to the student to calculate how many
parenthesization are there for a chain of M matrices. It is indeed an exponential and n the
exact function is left as an exercise to the student.
779
(Refer Slide Time: 08:02)
So, now let us using a dynamic programming approach to come up with an algorithm to
find the minimum parenthesization. Let us use the dynamic programming approach to
come out with an algorithm which will come out with the parenthesization, that uses
from the minimum number of scalar multiplications. To do this let us understand the
structure of an optimal solution which in this case is a parenthesization. For this we need
some simple notation, we use the notation A sub scripted by i upto j to denote the matrix
which is a result of the product A i A i plus 1 and so on upto A j. Let us now observe that
in an optimal parenthesization which we do not know which is whatever the algorithm is
trying to compute. In an optimal parenthesization, let k be the index where the product A
1, A 2 to A n is split, therefore the approach for the computing the product would first be
to compute the matrices A 1 k and A k plus 1 n, and them compute the product of these
two matrices to get the final matrices A 1 n.
780
(Refer Slide Time: 09:32)
The key observation that we make about these whole exercise is that if we consider an
optimal parenthesization of the change A 1, A 2 to A n, then the parenthesiztion of the
sub change A 1, A 2 to A k and A k plus 1 A k plus 2 to A n will also we optimal. This is
the optimal sub structure, recall that from the previous lecture this is one of the
properties of recall from the previous lecture for dynamic programing to be use the
problem must have the optimal sub structure. In other words in this case the optimal
solution to the parenthesization contains within it the optimal solution to sub problems.
781
(Refer Slide Time: 10:33)
So, we will verify the client that this problem has optimal sub structure while coming
that with a recursive formulation of the optimum values. In this case we again introduce
a few variables which are necessary for us to compute the minimum number of scalar
multiplications. So, we use the two-dimensional array m i comma j to denote the
minimum number of scalar multiplications necessary to compute A i j. We let m i comma
j denote, let m i comma j by b, let m i comma j be the minimum number of scalar
multiplications necessary to compute A i j. Now we can see with the minimum cost of
compute the chain product A 1 to A n, recall this is A sub scripted by the range 1 to n is
the value m of 1 comma n. Suppose the optimal parenthesization of A i j splits the
product between A k and A k plus 1 where k is a number in the range i to j. Then we
write down a recursive formulation of m of i comma j.
782
(Refer Slide Time: 12:08)
So, recursive formulation uses this parenthesization. The matrix A i j is obtain by the
parethesization, the matrix A i j is obtain by multiplying the matrix change A i to A k
with the result of the matrix chain A k plus1 to A j. In other word, this is the product of
the two matrices; A i k multiplied by A k plus 1 j. Therefore, the total cost of computing
A i j is the cost of computing A i k plus the cost of computing A k plus 1 j plus the cost of
multiplying the two matrices A i k and A k plus 1 j. Note here that the cost is the total
number of scalar multiplications. So, we know that the third term, the cost of multiply A i
k and A k plus 1 j is p i minus 1 multiplied by p k multiplied by p j, this is the because
the order of the two matrices are p i minus 1 cross p k and p k cross p j.
So, we specify the recursive now completely, which says that the minimum number of
scalar multiplications for the chain, the minimum number of scalar multiplications for
multiplying the change a i to a j is equal to m of i comma k plus m of k plus 1 comma j
plus p i minus 1multiplied by p k multiplied by p j for k between i and j. And indeed the
number of multiplications to compute an empty product is 0; that is m of i comma i is the
cost of multiplying A i where they are no multiplications operations involve, therefore
this take it be the value 0.
783
(Refer Slide Time: 14:36)
784
And this specifies completely the recursive formulation of m of i comma j. If i and j are
the same, it is 0 because we do not have perform any multiplication. If i not equal to j
and i is strictly smaller than j then m of i comma j we know stores the minimum number
of scalar multiplications to multiply the chain product A i to A j. So, this is obtain by
finding the best value of k by computing m of i comma k plus m of k plus 1 comma j
plus p i minus 1 multiplied by p k multiplied by p j and choosing the best possible k that
gives the minimum value of m of i comma j.
This completes the recursive formulation of the minimum that we are interested in. Now
we need to convert this recursive formulation into an algorithm, and we have to specify
the algorithm and efficient algorithm to compute the minimum. To do this we introduce a
second two dimensional array which we call S; S stands for split and we refer to this
two-dimensional array as the split table. The split table tells us where to split a chain. In
other word S of i comma j is that value of k at which the chain A i to A j is split for an
optimal parenthesization. The steps in this algorithm are to compute the minimum
number of scalar multiplications for chains of length 1 from there we compute the
minimum number of parenthesization for chains of length 2, and 3, and so on. This is the
bottom of calculation method of the optimum value of m of 1comma n.
785
(Refer Slide Time: 17:17)
This is the algorithm description. There is an initialization face where the min cos table
m is initialize with 0 for all the diagonal entries, because they do not involve any
multiplication. This is followed by three nested iteration to essentially implement the
recurrence, and the outer loop iterates over… So, let us consider this algorithmic
description to compute the optimal cost. Using this data we will then compute the
optimal parenthesization also. The input to these algorithm is an array which an n plus
one element array which contains the dimensions of the matrices. For example, the feels
p of 0 and p of 1 give us the information about the dimensions of matrix A 1, that is p 0
cross p 1, the array entries p 1 and p 2 tell us the dimension of the matrix A 2 and so on.
The result of this algorithm is we get a min cos table and is split table; these are two
arrays that we get. The min cos table stores the value of the minimum cost
parenthesization of the chain multiplication involving the chains involving the chain of
matrices A i A i plus 1 upto A j. Similarly the split table the entry S of i j stores the value
of the index k at which the chain A i to A j is to be split.
The algorithmic as follows it has 4 for loops. The first for loop is an initialization face
where the diagonal entries are all initialize to 0, this is natural because the diagonal
entries store the value 0 to denote the fact that there is no matrix multiplication involving
the single matrix. The remaining 3 for loops are nested and the intent of these for loop is
786
to fill the remaining entries in the upper half of the matrix is to fill the entries in the
upper half with the matrix, and this is done by filling each diagonal. Observe that there
are m minus 1diagonals apart from the principle diagonal of the matrices. The outer loop
iterates over the diagonals of the matrix, the outer for loop which is index by the variable
l, iterates over the diagonals.
The next for loop is setup to instantiate each element in the appropriate diagonal and the
innermost for loop is the one that evaluates the value of the min cos parenthesization. So,
in the second for loop, the initialization of the variable j to i plus l minus 1 is the choice
of the appropriate element in the l'th diagonal. So, m of i comma j is initialize to the
value empty which is the standard think for the minimization problem which takes a
positive values m of i comma j is initialized to the value infinity which is standard
practice for minimization problems which takes positive values. The inner most for loop
is the loop that implements the recurrence that we have return to formulate the value of
m of i comma j. The way this is done is to iterate over all the possible values of k starting
from i to j minus 1, and the value q is computed has m of i comma k plus m of k plus 1
comma j plus p of i minus 1 multiplied by p of k multiplied by p of j.
The if statement updates the value of m of i comma j, if the value of q is smaller and it
also updates the value of the split entry, if the value of q is smaller than the current value
of m of i comma j. At the end of this whole iteration the matrices m and s are computed,
and this store the optimum parenthesization for every i comma j, this store the optimum
number of scalar multiplications for the chain multiplication involving A i to A j and the
parenthesization information is stored by keeping track of a split value in the matrix S.
787
(Refer Slide Time: 22:55)
The split table is use to compute an optimum solution, the algorithm computes first the
min cos table and the split table S as we saw in the previous slide. And the optimum
solution can be calculated from the split table using the value k, which is stored in S of i
comma j. This enables has to compute S of 1 comma n recursively.
Thank you.
788
Programming, Data Structures and Algorithms
Prof. N. S. Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Module – 11
Lecture – 58
Problem: single source shortest path
Assumptions on graph, weights
Data structure: Priority queue and operations
Distance array
Algorithm and example
Correctness argument
In today’s algorithms lecture, we are going to study the Dykestra’s algorithm. Let us start
off with a pronunciation of the inventor of this algorithm, his name is Dyke Stra and you
can, his name is Dyke Stra, think of it as two parts Dyke and Stra that is how the name is
pronounced.
Dykestra’s Algorithm is used to solve the single source shortest path problem in a given
graph G, the vertices of G are V and the edges are E. The goal is to find the shortest path
from a single designated source vertex to every other vertex in G. This is the definition
of the problem. The input to this problem is a weighted graph G where every edge has a
real valued weight and a special source vertex denoted by s. The output of the algorithm
should consist of shortest path from the given source vertex s to every other vertex v in
789
the graph. Dykestra’s algorithm is an algorithm that uses the greedy strategy and we will
see how it uses the greedy strategy.
We make some assumptions about the graph and we also make some assumptions about
the data structure which is used by the algorithm. First of all, the graph may be either a
directed graph or an undirected graph. We also assume that all the edge weights are non-
negative. We will see in the analysis of algorithm that this is very crucial to guarantee the
correctness of the algorithm. We will also assume that the algorithm has access to a data
structure which is a priority queue, we will use the letter Q to denote the priority queue.
A priority queue of n elements has the following features which we will assume exists,
we will not worry about the implementation of the priority queue, when we design and
study the Dykestra’s algorithm. We will assume that the priority queue has a DeleteMin
function which will return the least valued element from the queue in order of login time.
Not only will it return, it will also remove that element from the queue. The second
operation that we assume comes with a priority queue is the operation of decreasing the
priority of a specific element in the queue.
We assume that this can also be done in login time. The initial priority queue of the n
elements can be constructed using an insert function in order of n time, recall that n is the
total number of elements in the queue. As an additional point, the interested student may
refer to the data structure called heap or a min heap to understand the implementation of
790
a priority queue. However, that is not an important for us to analyse the Dykestra’s
algorithm.
Here, at the data structures that are maintained by the algorithm, we assumed that the
algorithm has access to an array called dist, short form for distance and for every vertex
v, the element to the array dist index by v that is dist of v contains an upper bound on the
length of the shortest path from s to v. This is an invariant that the algorithm maintains
repeatedly. The distance of s from itself is equal to zero and initially the distance of
every vertex from the source vertex s is taken to be infinity in the description of the
algorithm.
For each vertex v, we also maintain the predecessor on the shortest path from s. This is
maintained in an array called pred which is a short form for predecessor and initially the
predecessor over every vertex is chosen to be nil. Except for the vertex s which will be
its own predecessor.
791
(Refer Slide Time: 05:26)
Here, is the description of the algorithm and all the statements which you see in red color
are commands and these are not executed. All the statements that you see in red are
commands and these are not executed. These are to help the reader, understand the
operations performed in the algorithm. On this slide, the algorithm is described in two
parts, the left part of the side is a starting point of the algorithm and what is on the right
part of the side is a continuation of the left hand side part of the code.
In other words, this slide is essentially the whole description of the algorithm. In other
words, this slide contains the description of the whole algorithm. It is logically split in to
the two parts, the first part can be imagine that is the initialization phase for the
algorithm to run and the second part of the slide, that is the right hand side part of the
slide consists of a computation of the shortest path from the source vertex s.
So, let us focus on the left hand side part. The first step is to initialize the priority queue.
It is initialized to empty and then the elements and then for every vertex v in the graph,
the array distance is organized as a priority queue. For every vertex v, the distance is set
to be infinity from s and the predecessor of v is initialized to the value NIL and now we
initialize the priority queue. And to do this, we use the distance value as the value that is
present in the queue.
The distance of s to itself is then set to 0 and the predecessor of s is taken to be the vertex
S itself. After doing this, we decrease the priority of the vertex s to 0 using the decrease
792
function. Let us just look at this function call. It is to decrease the priority of the vertex s
to be the value, distance of s in the queue Q. After the initialization phase, we move to
the right hand side part of this slide and now we compute iteratively the distance of the
each vertex from s.
Initially, V t is chosen to be empty that is we assert that for no vertex the distance for s
has been computed and here is a while loop, till the queue becomes empty. As long as
the queue is non empty, the while loop will be executed. What are the steps that are
performed inside this loop? The first step is to pick a vertex which has the least value in
the queue. This is achieved using the DeleteMin operation from the queue and let us say
u is the vertex which is returned.
In other words, u is the vertex for which the distance value is the least among all the
elements in the queue. V t is now augmented to contain u, the invariant is that V t always
contains all the vertices for which the shortest path from s has been computed. After
having added u to the set V t, we update the distance bounds from the vertex s in the
fourth coming loop. The way this is done is to consider the every vertex v which is
outside the set V t that is in V minus V t.
With the property that v has an edge to u. Recall that u is the vertex which I just been
added into the set V t, that is u v must be an edge and v must be outside the set V t. Now,
we update the distances. If we observe that the distance from s to u plus the weight of the
edge u comma v is smaller than the current estimate of distance of v, then distance of v is
updated. The predecessor of v is also updated to be u. If this condition fails, no action is
taken and nothing is changed.
Indeed, if this condition succeeds, the priority of v is updated in the queue using the
decrease function. This completes the description of the algorithm. Observe that the
algorithm will definitely terminate, this is very important because, at every step one
element is removed from the Q, therefore eventually the Q will become empty.
793
(Refer Slide Time: 11:18)
We will see that the Dykestra’s algorithm that we have just see in the previous slide,
eventually computes the distance of every vertex from the source vertex A, when the
algorithm terminates. Initially, the distances of A to itself is set to 0 which is indeed
correct, which is the distance of A to itself.
794
(Refer Slide Time: 12:20)
In the next step, upper bounds of the distances 2, vertices which are adjacent to S are
obtained. In particular, the vertex B is adjacent to the vertex A, where an edge whose
weight is 10. Therefore, the distance value of B is updated to 10 which is a current upper
bound on the length of the shortest path from A to B. Similarly, further the yellow colour
on the arrow indicates that the current predecessor of B is A. The orange coloured
vertexes are also those vertexes for which the distance from S has already been
calculated.
795
Similarly, the upper bound in the distance from S to C in this iteration is the distance 3
and the predecessor of C is selected to be A.
In the next iteration, we will now select the closest vertex from the Q, in other words the
vertex which has a least value. This is coloured red and observe that in this particular Q,
C is the vertex with the least value which is selected. As you can see, C is the vertex
which is closest to S, based on the current distant estimates.
796
Now, C enters the set of vertexes for which the distance from S has already been
calculated. In other words, the length of the shortest path from S has already been
calculated. Now, C is a current vertex, we go into the iteration where we update the
distances of all the other vertices which are outside the set V t which is, we will now
update the distances for the vertices B, D and E.
Let us start this with the vertex B. Observe that the distance estimates from S to B is now
changed to the value 7. It was recall, earlier the value 10. Now, the distance from S to B
is found to be shorter via C and this is updated to the value 7 from the previous estimate
which was 10. The predecessor of B is also now updated to be the vertex C.
797
(Refer Slide Time: 15:29)
This is also done next for the vertex D, the distance estimate from S to D was infinity
earlier. Now, the distances is found to be not larger than 11, because the distance from A
to D via C is found to be 11, this is updated.
The same is done for the vertex E. Observe that the predecessor of D and E ((Refer
Time: 15:58)) are both the vertex C. The next step was the algorithm we will now choose
the vertex which is the closest to S. In this case, it is going to be E.
798
(Refer Slide Time: 16:16)
Therefore, we extract the closest vertex E from the priority queue and now E is added
into the set of vertexes for which distance from S has been calculated, E is now coloured
orange. And now, we go ahead and estimate the distances of, now we go ahead and
update the distances of the vertexes B and D.
Now, observe that the distance from S to D going via E is 14 which is more than 11,
therefore the distance value of D does not change.
799
(Refer Slide time: 17:14)
Similarly, the distance value of B also does not change. The next step, we extract the
closest vertex which is B from the queue which is now coloured orange and the
predecessor of B is taken to be C. And now we update the distance of D via B which is
only 9 which is smaller than 11 and now the whole procedure has come to an end.
Because, D is now removed from the priority queue, because that was the only element
in the queue, the queue has became empty the algorithm comes to an end.
800
(Refer Slide Time: 17:42)
Now, let us analyse the running time of this algorithm. Let us look at the initialization
phase of the algorithm and let us look at the work that is done. For every vertex v, the
distance value is initially set to infinity. There are n vertexes or if we take v to be the
cardinality of the set v itself, then there are V vertexes, capital V number of vertices and
they are all initialized to once. The predecessor of each vertexes also set once in an
initialization phase, the insertion into the Q for every vertexes also done once.
Therefore, the initialization phase takes order of V time, where V is a total number of
vertexes in the graph G. In this iteration, observe that this iteration, the outer iteration
runs v number of times, in every iteration 1 vertex is removed, however the dominating
factor in the running time is in this while loop.
However, the dominating factor of the running time is in this for loop, where every edges
inspected once and the primary operation which is done here is, further the most
expensive operation, among the three operations here is to decrease the priority in the
queue which takes log v time and there are E edges, E number of edges therefore, the
total running time is E times log v. Therefore, the running time of the algorithm is order
of v plus order of log v. Recall that the log v guarantee comes from the property of the
priority queue data structure.
801
(Refer Slide Time: 20:07)
We know have to conclude that the algorithm is indeed correct and we have a sequence
of correctness arguments. The proof of correctness of the algorithm is guaranteed by the
claim which is, for every vertex v when it is removed from the priority queue, the value
dist of v is the distance from s to v in the graph. This is the claim that we need to proof.
Let us start off with the base case, the claim indeed is true for s which is the first vertex
to be removed from the priority queue, because the distance from s to itself is 0.
We know prove our claim by induction on the index of the vertex to be removed from
queue. Let us assume that the claim is true for all vertices which are removed from the
queue before the i th step, where i is at least 1. Let us call the set of vertices that have
been removed, the set F and F stands for the word FOUND for which the shortest
distances have been found already. Let v be the i th vertex to be removed from the Q.
Now, let u be the predecessor of the vertex v, when v is removed, in other words, let pred
of v be equal to the vertex u. We know that from the description of the algorithm, we
know that the distance of v, dist of v is equal to dist of u plus the weight of the edge u
comma v, this is from the description of the algorithm.
802
(Refer Slide Time: 22:22)
We now show that any other path from s to v has the property that the length of that path
given by w of P is at least as large as the value dist of v. To understand this analysis, let x
comma y be the first edge on the path P, where with the property that x is in F and y is
not in F. It is important to note that F is the current set in the analysis, in this iteration in
the algorithm that is F is the set before v is added to F. We now argue that the length of P
is at least as large as distance of v.
We observe that the weight of P is the distance from s to x, by induction we know that
because x is already in F, the distance from s to x is indeed the length of the shortest path
from s to x plus the weight of the edge w of x comma y plus the weight of the path P
from x to v. Now, we will also show that this is at least as large as distance of y plus
weight of P x v.
803
(Refer Slide Time: 25:40)
We now to show that the weight of the path is at least dist of v, we now show you that
the weight of the path P is at least dist of v. To do this, we proof the following sequence
of inequalities. We first show that weight of P is equal to distance of x plus w of x
comma y plus weight of the path from y to v, we then show that this is at least as large as
dist of y plus weight of the path from y to v and then with this is at least distance of v.
Now, let us focus on the first equality, this follows from the definition of w of p and the
application of the induction hypothesis. How do we apply the induction hypothesis? We
observe that x is already in F therefore, by induction dist of x contains the distance from
by induction dist of x contains the value of the length of shortest path from s to x.
Therefore, the length of P is distance from s to x plus the weight of the edge x y plus the
length of the path y v, this is the length of the path P.
Now, the second inequality follows, because by definition distance of y is at most the
distance of x plus the weight of x comma y, this is the definition of the distance in the
algorithm. The third inequality follows because of two reasons. One is that we have
chosen distance of v to be the one with the least value from the queue. Therefore,
distance of v is smaller than or equal to distance of y. Observe that v and y are not in F
and the algorithm choose distance of v to be the one with a smallest value of distance.
Therefore, distance of v is smaller than or equal to distance of y.
804
It is very important now to observe that because, weight of P y v is non negative, this
inequality follows. Putting all the three together, it follows that the length of the path is
at least distance of v.
Consequently, it follows that distance of v, where v is ith vertex to be added is indeed the
shortest length or the shortest path, in other words the distance from s to v. Now, v is to
be added to F, the proof by induction is complete. In other words, for all the vertices in
F, the distance from s is already calculated. This shows that Dykestra’s algorithm is
correct and the running time of the algorithm is also something that we have analysed,
the running time...
805
Programming Data Structures and Algorithms
Prof. N.S.Narayanaswamy
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 10
String Matching - Boyer Moore Algorithm
Hello every one, welcome to this lecture number 10 on algorithms for the string
matching problem. String matching problems are almost everywhere ubiquitous every
computing system provides us with facilities to match strings, browsers, editors, shells
all of them provide us with abilities to, with utilities to match strings. And one of the
algorithms that is of interest and which is a very efficient algorithm is called Boyer
Moore algorithm.
This algorithm was invented in 1977 and specifically it has an implementation which
runs in linear time in the worst case and one can actually do an analysis and argue that it
takes sub linear time to find a pattern match in most practical cases. So, let us get to the
formal details of the string matching problem. In this problem, we have sigma an
alphabet. Example of an alphabet is a two letter alphabet consisting of the letters 0 and 1.
This is the alphabet over which the binary strings are defined.
One can also think of the alphabet which consists of the letters a to z, the 26 letters of the
English alphabet and. So, on and. So, forth. The string matching question is the
following. You are given a pattern of m letters and this we refer to as the pattern P and
806
the characters in that pattern or the letters in the pattern are index using indexes from the
range 1 to n. And we are given a target text which is T which has m characters in it or m
letters in it and both P and T are over the same alphabet.
Now, the question is to design an algorithm to find whether P occurs in T that is one
question. The other question is to find all occurrences of P and T. So, the Boyer Moore
algorithm is a very interesting algorithm which uses, quite a lot of concepts from data
structures to design an efficient algorithm for this problem. As we go along the
description of the Boyer Moore algorithm, we will see what is straight forward and a
simple algorithm will do, as a special case of the description of the Boyer Moore
algorithm.
So, the outline of the algorithm is the following. We look at the pattern P and look at the
given text T, now we align P with a left end of T and we check for a match from the right
end of P towards the left end of P. We will discuss this more detail in the next slide, but I
hope this picture is clear. If P is found to match the sub string of T with which it is align,
then we report that P has been found, otherwise we identify how much to shift P to the
right or rather to align P with another sub string of T which occurs to the right hand side
of the text string T and this maximal shift that we used is given by one of two rules
which are called the bad character rule and the good suffix rule.
And this lecture is to basically describe to you the details of the bad character rule and
the good suffix rule and also show you an implementation of it. The running time
807
analysis is not done in this lecture, on the other hand the formal implementation is
discussed. So, here we get an idea saying that if P does not match with the current
alignment with T, then the natural thing that we would do is, to shift P one cell to the
right and align it with that part of T and then iterate this procedure, clearly we get an
algorithm which will check if P is present in T or not.
It is a very simple exercise to analyze the running time of this algorithm. You can see that
it is the order of m times n, because P has n letters in it. It is matched against the sub
string of T which also has length n. Therefore, an order n time we can check if P matches
with a sub string of T with which it is aligned and P has to be shifted an order of m times
to the right in the worst case. Therefore, this is an order m n algorithm. We will see that
by observing a lot of structure with respect to P and T, we should be able to do better in
the worst case.
So, let us just discuss this shifting. Of the two rules that we just spoke about, the first rule
tells us how many positions the pattern P should be moved to the right for the next
pattern matching exercise or for the next search. Now, this depends upon the character
with which our match was failed. The second rule also tells us, how many positions to
the right the pattern P has to be moved and this is dependent upon the number of
characters which were matched successfully before a failed match was obtained. We will
see examples in the coming slides.
808
(Refer Slide Time: 06:04)
So, let us just understand one of the central features of this Boyer Moore algorithm
which is the right to left comparison. So, the right to left comparison is very simple,
imagine that the pattern P is matched against the text string T at some place. So, what
you do is, you check if the last character of P matches with the corresponding location in
T. If there is a match we go to the second last character and then perform a comparison.
This comparison process goes from right to left, we start with the last, then the second to
last and third to last and. So, on and. So, forth, till we come to the starting string of the
pattern P, staring character of the pattern P. Whenever we find the mismatch, our
algorithm, these two rules will tell us how much to shift the pattern P and work with a
new alignment and repeat this right to left comparison procedure. So, this is essentially
the approach of one of the, this is one of the essential things in the Boyer Moore
algorithm.
809
(Refer Slide Time: 07:16)
So, let us just go to the bad character rule, recall that this is the first of the two rules that
we spoke about. In the bad character rule, the motivation is to come up with an approach.
So, that the pattern P is shifted by more than one character whenever possible. To
achieve this, an array R is maintained and the number of letters or the number of
locations in this array is the size of the alphabet sigma. So, let us assume that x is a
character in sigma and R of x takes the value zero, if x does not occur in the pattern P.
And if x does occur in the pattern P, R of x contains the index of the right most position
in which x occurs in the pattern P. There is an example here, let us just take a look at this
example. The pattern is the string A C C T T and T. Here, we show the array R of P, R is
the name of the array and P is the pattern. So, the first one says that, if there is any other
letter other than A C C T T T in the alphabet then R of P for that letter will take the value
zero. This is the shortcut in the presentation.
On the other hand, A occurs exactly once and we keep the right most location where A
occurs which is at the first location. Therefore, it takes the value 1. Observe that C occurs
at the third location and it does not occur anywhere to the right of the third location,
therefore, R of C is the value 3. T occurs at location 6 which is the length of the pattern
string and therefore, R of T is the value 6.
This data structure as you can see can be populated in linear time, in one pass of the
pattern array by looking at every character exactly once we can populate this array with
the appropriate values. This is left as a small programming exercise which I am sure, all
810
of you must be able to do.
Continuing with a bad character rule, let us see how to use this array R to come up with
the appropriate shift of the pattern P whenever a mismatch is obtained. Let us present the
rule first and then go to an example. So, let us consider a situation where P is aligned
against T and for some i, the right most n minus i characters of P match the
corresponding characters in T. And the character P of i that is the ith symbol in the
pattern P does not match with a corresponding character in the text string T.
We look at this character T of k and identify the right most location where it occurs in the
pattern. This recall is present in the array R let us refer to this particular value as j. Let
me repeat this, T of k does not match with P of i and P of i and T of k are aligned with
each other, R of T of k is the right most location in the pattern P where the character T of
k occurs. Let us give a shortcut to this particular value and let us call it j. So, let us just
go to this example and let us look at this alignment, this is the text string and P is aligned
with it.
So, let us just do a comparison, as we do a right to left comparison of P with the match
portions T or the aligned portions T, let us look at these two characters this is the match,
then we come to the second to last character, this is the mismatch. Now, let us look at the
cause for the mismatch. So, the cause for the mismatch is that in the text there is a C here
and in that pattern, there is the T here, this is what we refer to as T of k in the general rule
that we just note down and let us look at R of C.
811
Observe that R of C is the value 3 that is C occurs in the pattern, at the third location it
does not occur anywhere else further to the right. Now, further let us also observe that the
value j which is 3 in this case is strictly to the left of the place where the mismatches.
The mismatch has happened at the index 5, 1, 2, 3, 4 and 5 the mismatches at the index
5, R of C is at location 3 and therefore, we shift the pattern P. So, that this right most
occurrence of this mismatch symbol, when it occurs to the left is now aligned, this is the
shifted symbol.
Observe that now, C and C are matched, this is the rule and the whole pattern P is shifted
to the right and in this case as you can see, it is shifted by i minus R of T of k. Observe
that i is 5 in this example and R of T of k that is R of C is the value 3 and therefore, it is
shifted by two units, the pattern P is shifted by two units to the right. So, this is the bad
character rule. One can observe that this is the correct option to perform, if indeed there
is a match for P in T and if this match, observe that this is the correct operation, if P has
to be matched to some sub string of T.
So, continuing further with a bad character rule, if j is more than i, in other words the
right most occurrence of the mismatch character occurs to the point of mismatch, then
shift P to the right by 1. There is also a third case here, if the mismatch character does not
occur in P at all, then; obviously, the pattern P must be moved completely to the right of
the current alignment string T that is align P of 1, 2 to n with T of k plus 1 to k plus n.
Recall that T of k was the place where there was a mismatch. It is easy to check back that
812
the algorithm is correct, by checking that these three cases will ensure that P will
definitely match with a first occurrence of T from the left, in other words it will not skip
over the first occurrence of P in T the first occurrence is checked from the left hand side.
So, therefore, the bad character rule is indeed a correct algorithm and therefore, what we
have is a string matching algorithm, already. Let us now discuss how efficient is this
algorithm.
Let us now check that this algorithm is extremely inefficient, let us consider the text
pattern which is all 1’s followed by the pattern which is a 0 followed by three 1’s.
Observe that for every alignment we will find a mismatch, because the pattern 0 1 1 is
not present in the text string T that is very clear. However, if we applied the bad character
rule, we will see that in this example the worst case time taken would be an order of n
comma m, before we discover that this pattern is not present in the target at a given text
string T.
Therefore, the bad character rule is not strong enough for providing the linear time
algorithm, something else is necessary. So, therefore, let us just see what we need to
handle. We have to handle the case when the mismatch, the right most occurrence of the
mismatch character occurs to the right of the point at which the mismatch has been
discovered. So, we need to identify the way of handling this particular case.
813
(Refer Slide Time: 16:30)
To do this let us just think of an idea and the idea would be to how to use that, how to use
the fact that we have matched some characters in the patterns suffix with the current
align portion of the text string T. How does one use this knowledge? So, let us just ask
the question if we have matched a suffix of characters in T, now let us ask what is the
smallest shift of P which will align a sub string of P with same suffix of characters, let us
we visualize this pictorially in the coming slide.
And this rule is called a Good Suffix rule and you will see why it is called a suffix rule.
So, here is a target string T and the pattern which has matched is referred to as small t
814
and here is the mismatch that is x and y are not the same. As we come from the right, we
compare letter by letter and. So, many matches have been discovered between T and P
and now x and y are different. Now, you need to ask how much should we shift, the
pattern by.
So, let us see what would be a natural thing to do. If t occurs again has t prime to the left
of t and let us assume that this is the first occurrence of t prime as we go from the right to
the left. Then, the natural shift would be to shift P. So, that t prime matches with this t as
it is displayed in this particular pictures. So, the shift for P would be shifted. So, that t
prime aligns with t and this is a very important thing to do that if there is going to be a
successful match that involves this t.
Then, such a successful match could only involve t prime and no other sub string to the
right of t prime, because this is the first occurrence of t to the left of, t prime is the first
copy of t that occurs to the left of this pattern t. So, therefore, this is the natural shift to
perform, we will formalize this shortly, but before that let us take a look at an example.
So, let us just look at the pattern and T aligned in this fashion, these two match A and A
match, now there is a mismatch and A is this character. So, now if we ask how much
should we shift P by, let us just check that there is an A T here, there is an A T here. So,
of course, this A T could be aligned with this A T. So, that could be a shift of a certain
amount. But, observe that the preceding symbol of this sub string is G and it is the same
G which is the mismatch.
815
Therefore, we do not shift it. So, that this A T is aligned with A T, this A T. Let us look at
this A T now and let us look at the preceding string A and in this case of course, there is a
match and therefore, we shifted all the way, shift p all the ways. So, that this A T aligns
with this A T. The specific property is that we look for a letter here which is different
from the character, which cause the mismatch with A. In this case of course, it turns out
that A and A are one and the same and we are actually, if you see this shift will indeed,
check that there is a match of P in T. So, let us just formally state the first case of the
good suffix rule.
Before we do that let us also check what happens when such a t prime does not exist. So,
let us just go back to the slide and take a look at ((Refer Time: 20:52)) what we mean by
this. If such a t prime does not exist at all, then how much to be shift P y. This is given by
the suffix rule in the second case, using the second case. This is what we title the slide as,
we call it if there is no such t prime then we are in the case two.
The idea now is to shift P the pattern by the least possible amount such that the suffix of t
matches the prefix of P. One can check that this is indeed a correct step to perform and
we will not miss any pattern, any occurrence of pattern P in t by this approach, indeed
this is the minimal shift that must be done. Now, if such a suffix does not exist, then we
recommended, you shift P n units to the right. So, these are the three steps in the good
suffix rule algorithm.
816
(Refer Slide Time: 21:57)
Let us just take a look at an example for the case two. Recall that we have already seen
an example for case one. There are two cases here, two examples here, one two examples
if I… The first case of case two and the second one to illustrate the second possibility in
case two. So, this is the target string and this is the text string, like before A T is a match
and there is a mismatch here. Now, observe that there is no occurrence of A T to the left
here, with the property that the preceding symbol is different from this particular T.
So, let us just take a look at this. This is A T it did match, this is also A T it did match, the
preceding symbol of this is T same as the preceding symbol of the matched portion.
Therefore, it is a bad idea to or it is an unnecessary effort to shift P to just ensure that this
match happens and then perform a comparison again. Because, we already know that
such a comparison will fail. Therefore, the idea now is to shift P. So, that a prefix of P
matches with the suffix of T and we perform the minimum such shift and let us just see
this.
The minimum such shift such that some prefix of P. So, let us look at the prefixes of P,
that is T, T A, T A T and. So, on. What is the minimum shift of P such that, the prefix of P
matches with a suffix of this particular part which we have refer to as T. In this case, we
can see that this is the minimum shift that is possible. So, this is the first possibility in
case two, the second possibility is that even such a shift does not occur. Then, we would
have to shift the whole pattern n units to the right. In this case, it is five units to the right.
Let us just see such an example, the pattern in this case is the string C A A A T and the
817
target is the same thing. Observe that this A T and this A T match, there is a mismatch
between C and A here and now observe that the pattern A T here does not occur
anywhere to the right and therefore, we would attempt to apply the first possibility of
case two. But, observe that in the first possibility of case two, no prefix matches with a
suffix of the matched portion already which is T.Because, all of them start with a C and
C does not occur in this part at all.
Therefore, the algorithm suggest that simply shift P n units to the right which is shifted
all the way to six units to the right and now align it with the target string T. So, this
example simplifies the good suffix rule or this illustrates the good suffix rule, completely.
So, as we have done. So, for in this course, it is very important to argue the correctness
of every step that we performed.
Here, it is left as an exercise, you can check that the good suffix rule is correct and you
can do a proof by induction on the length of P. In other words, you should verify that if P
occurs as a sub string of T, then the good suffix rule will definitely identify the first
occurrence of P in T and the first occurrences from the left will definitely be discovered
by this rule.
And the way to do this proof is by induction on the length of P, you can start off with a
base case which is the case, when P is of length 1 and you can verify that the algorithm
works and then make the induction hypothesis and verify it and you can actually use the
weak induction hypothesis alone to complete the proof of this claim. This is left as an
818
exercise to the interested student. Now, let us just step back and take a look at the two
rules, the bad character rule focuses on the character mismatch, whereas, the strong
character rule uses the sub strings and the matches which have happened.
Now, to implement the good character rule, to implement the good suffix rule how does I
am get t prime for a given t. This is the implementation exercise and we are going to look
a data structures to answer this particular question.
So, one of the data structures is that we maintain an array which we call the l prime of i
and let us just look at what l prime of i is. So, the l prime of i is a largest position smaller
than the length of the pattern n which satisfies the following property. The property that
is satisfied is that for the i, P i to n that is if you look at the suffix of the pattern P is
starting at the index i, it must match a suffix of the pattern considered from the first
character up to l prime of i, this is the value of l prime of i.
Further, the additional condition that must be satisfied is that the character preceding the
suffix is not equal to P of i minus 1. So, P of i minus 1 is a character preceding P of i and
that value P of i minus 1 must not be equal to the character preceding the suffix match in
1 to l prime of i and l prime of i is a largest value which satisfies this particular property.
If no such position is found, then l prime of i is set to be 0 and we will refer to P of i to n,
the suffix starting at i suffix of the pattern p starting at i with the letter T. Let us just
visualize this array and it is role.
819
(Refer Slide Time: 28:41)
So, let us just recall this is the alignment of P against t, this is a right end of P, small t is
the matched portion and x and y is where there is a mismatch, we look at the largest
value l prime of i such that the suffix and recall that i is the point of mismatch. So, i is
the point to the right of the mismatch, in this case i as it is marked here. So, one looks at
the suffix of the pattern string form i to here, which in this case matches with the target
string t and l prime of i is a largest value at which this whole pattern t occurs as a suffix
of P1 to l prime of i.
In other words, this is the position where t occurs just to the left. Of course, this could
not be well defined, in which case we have the case two of the good suffix rule. So,
observe that if you know l prime of i and if it is non zero, then that basically tells us how
much we must shift be by, this is a very clear thing. So, one has to shift it. So, that t
prime aligns with t and this l prime of i being the largest such value ensures that t prime
was the copy of t and this is the first copy of t, when seen from the right hand side. So,
therefore, l prime of i is a useful piece of information to compute.
820
(Refer Slide Time: 30:32)
So, let us look at the example case for l prime of i for a pattern p. So, let us look at the
pattern p. So, let us look at the l prime of 1, l prime of 1 essentially looks at the pattern
starting from the first character up to the last character and ask for a preceding part of P
that contains this whole thing as a suffix and obviously, that is not well defined. So, that
is 0. Similarly, l prime of 2 is also 0, up to l prime of 6 which is definitely 0 and in all
these cases that is because the string is too long to be contained as a suffix of a preceding
portion.
So, therefore, it is a natural that all these values are 0. Let us look at P prime of, let us
look at l prime of 7. In this case, the part of P that we are interested in a string starting at
7 and ending at 11 that is a string A T G A T. Let us see what is the prefix of P for which
this is a suffix and for that match, G is not the previous element, G does not occur
immediately to the left of this particular match. So, let us just see this. In particular,
indeed we want let us look at the possible values for l prime of 7.
So, in particular let us see that l prime of 7 can definitely take the value 8, because the
pattern is A T G A T and the value that occurs just before this A T G A T is A, on the
other which is different from G. And therefore, in this case we can say that l prime of 7
takes the value 8 and you can also check that further to the right, we will not find a
match of A T G A T in the strength P. Similarly, one can check that the largest place
where A T occurs, such that G is not a preceding part is definitely the value 5, A T occurs
here.
821
So, therefore, the l prime of 10, the candidate value is 5 and indeed it is 5, because just
before A T it is A and not G. This is the additional condition that must be satisfied.
Observe that A T also occurs here, but the symbol that occurs just before this A T is G
which is same as the occurrence of G just before A T, therefore, l prime of 10 is the value
5 and this is 0. So, this did, this example is used to illustrate what the array l prime
contains.
Of course, how does one compute l prime. Indeed, it is possible to compute with a lot of
effort. So, an order of m n time, one can very easily compute l prime by doing a lot of
pattern matches, but we will see that a clever way of maintaining additional data will
help us compute l prime. So, we maintain now two arrays which we refer to the N array
associate with a pattern P and indexed by j and also we look at another array that we
maintain which is called the Z array which is indexed by i for the pattern P.
So, let us just look at the jth element of the N array. So, that jth element of the N array is
the longest sub string that ends at the jth location which is also a suffix of P. Another way
of visualizing this definition is to look at suffixes of P and see how long the suffix of P
occurs or ends at j starting from the first location. So, that is N j is a length of the longest
sub string that ends at j that is also a suffix of P, this is a formal definition. The
visualization as I just said now is to look at suffixes of P and look at the longest suffix
which ends at location j in the pattern P itself.
Kind of the mirror image of this particular array is Z array which is the length of the
822
longer sub string of P that starts at i and it is a prefix of P. That is what we do is we look
at the prefixes of P starting from the first location and look at the longest prefix which
starts at i and the length of such prefix as it is maintained in the Z array and the length of
the suffixes, the longer suffixes are maintained in the n array.
So, again let us do an example, let us look at the pattern string which is A T A A T G A T
G A T. Now, let us just look at the value of the N array. Now, let us look at the longest
suffix of the pattern which ends at the first location. So, let us look at the last symbol, it
is T, whereas, the first symbol is A and therefore, the length of the longest sub string that
ends at the first location which is also a suffix of P that length is 0.
Let us look at the longest suffix, let us look at the longest string which is also a suffix of
P that ends at location 2. Let us look at it, this is A T, this is a string and let us look at the
last part, this is A T. Now, clearly this is a suffix of P and this definitely occurs in the
locations P of 1 and P of 2. In other words, A T ends at location 2 in pattern P and
therefore, the length of the longest string which ends at location 2 which is also a suffix
is 2.
Therefore, using this definition you can now verify that these values are the appropriate
lengths. We will also use this Z array along with a reverse of the pattern for a specific
purpose and we will come to that in a short while, before that let us look at the reverse
pattern P of the given pattern P, the reversal of the pattern P. In this case, the pattern is
written backwards that is T A G T A G T A A T A. You can see that, that is a reverse of
the pattern P.
And one can now see that the longest sub string of P that starts at i and matches a prefix
of P is actually the reversal of the N array which is what you will see here. This is an
easy exercise for the student to verify. So, therefore, we are. So, for maintained three
arrays. So, one is a capital l prime, the second one is the N array and the Z array. And the
goal is to compute the values of the entries in the capital L prime array and for that we
are going to use the N array and the Z array.
And recall that I have just mention that the Z array will always be relevant to the reversal
of P, in other words it will turn out that the reversal of N of P is exactly the Z of the
reversal of P which is what we have seen in the previous example.
823
(Refer Slide Time: 39:22)
So, now we present the rules for obtaining the value of L prime, again we present these
rules without proofs, but the examples support what we actually do. So, to obtain L
prime of n, L prime of i that is for a particular location i, the value of L prime as defined
a few slides back is taken to be the largest j satisfying the property that N j of P is n
minus i plus 1. So, here observe that for L prime of i, we use the array N and we define L
prime of i to be the largest value of j, such that N j that is the jth location in N array has
the value which is n minus i plus 1.
Now, once we have the N array, it is clear that L prime can be computed in linear time by
performing a single pass over the N array. We need one more piece of information which
would be used, when the first case of case two or the first possibility of case two does not
apply.
824
(Refer Slide Time: 40:56)
For this, we introduce a fourth array which we referred to as l prime. So, let us just look
at l prime, again the l prime is associated with the ith location in the bottom. L prime of i
is a length of the largest suffix of the pattern starting at the location i and ending at the
location n and this was also be a prefix of P, if it exists. So, let us just look at this picture.
So, this is the use case, this is a part which is matched and there is a mismatch here.
This is what we have been discussing. So, for. Now, l prime of i is a suffix of this
particular pattern which is P of i to n and l prime of i is the length of this portion which
also satisfies the property that it is the prefix of the string. Remember that this could not
be, it could be the case of, this is not well defined. But, if it is well defined, l prime of i
takes this particular value, otherwise it takes the value 0, because it is a length. Because,
if this property is not satisfied, l prime of i will take the value 0.
825
(Refer Slide Time: 42:06)
How does I compute l prime of i, the value l prime of i can be computed again from N j
and in this case, we will use the reversal of N j and this can be checked. So, we save the l
prime of i is the largest j which is smaller than t, such that N j takes the value equal to j.
Let us just recall that t is a shortcut for this suffix P of i to n that is where i comes into
this picture.
Therefore, we interested in l prime of i, you are looking at the suffix which consists of
the letters in the pattern, starting at location i upto location n, I refer to this as t, this has a
certain link we look at the largest value j which is smaller than or equal to t, such that the
value N j in the N array is exactly equal to j. Again, this is an illustration of this and this
can be worked by an interested student by hand.
826
(Refer Slide Time: 43:11)
This brings us to the Boyer Moore algorithm. So, now what we will do is to illustrate the
steps of the algorithm using the arrays that we have set up. So, one can just concentrate
on just a steps in the algorithm and here this is the Boyer Moore algorithm using the
good suffix rule. So, when a mismatch occurs at position i minus 1 of P, let us just see
this t is the matching pattern, P is aligned with t, this is the right to left comparison. The
mismatch occurs at location i minus 1 of P and it occurs with, let us say this index is k.
The first step is to check if this pattern t occurs to the left as a pattern t prime. This is
obtained by looking at the l prime array, if this value is greater than 0, then it says that t
prime does exist. What we do is we shift n, we shift the pattern P by n minus l prime of i
positions to the right. So, let us just see this, this is l prime of i. So, what we do is we
push the whole pattern starting from here, n minus l prime of i positions to the right, in
which case the alignment is that this t prime gets aligned with this t. So, this is the shift.
827
(Refer Slide Time: 44:35)
Now, we have to worry about the case where l prime of i is equal to 0. In this case, it
means that the pattern t prime does not exists, in which case this means the t does not
occur to the left of it is current occurrence. Then, it says that we can shift P pass the left
end. So, that, that is the pattern P is shifted. So, that a prefix of P matches the suffix of t
and the way this is done is to compute the value l prime of i and shift the pattern P to the
right by n minus l prime of I and this is the shift that is recommended.
By the shift, the pattern t goes for that to the right and the portion of P which matches
with a suffix of t is what will match after the shift, this is the good suffix rule.
828
Now, let us just complete the whole algorithm, what we do is we select a shift value for P
which is a largest one given by either of the two rules. That is we take the value, shift
recommended by the bad character rule and we look at the shift given by the good suffix
rule. Recall that some matches have happened which is used by the good suffix rule,
there is a mismatch that is happened, that is taken by the bad character rule and each of
these two rules specify a shift value and the Boyer Moore algorithm takes the larger of
the two shifts which are recommended by these two rules and that is what is used to shift
the pattern P to the right and this is what the whole Boyer Moore algorithm specifies.
We have not given the proofs of correctness of these rules. What we have done is set up
the whole data structure and given examples in the slides and we have enable a potential
implementation by an interested student. So, let us just summaries, the input consists of a
text T, a pattern P. The output is the occurrences of pattern P or a response that pattern P
is not found in T. The algorithm is very simple, we have to look at the description of
these three arrays which is L prime, I prime and R.
Recall that R is used by the bad character rule, the capital L prime and the small l prime
are used by the good suffix rule. There is also an additional array which is called the…
There are also two additional arrays which are called the N array and the Z array which
are used for the values, for the computation of the values in the small l prime. So, this
completes the presentation of the Boyer Moore algorithm.
829
Programming, Data Structures and Algorithm
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture - 60
FILE Input / Output
Welcome to this last module for this course. We are going to do two things in this
week’s videos, one is about file input and output that will be the first part and let we also
learn something about structure programming. So, let me give you a little bit of
motivation for the lecture itself on file layout. So, it is not reasonable to expect the user
to always give inputs from the key board or always just print things on the screen.
There are several places and instances where you want something recorded and you
wanted to be reused later and this is a place where files come in to pictures. It is not the
notion of files it is probably not earlient to you. So, what we will do in this lecture is we
will try and learn what a file is and how to perform input and output operations on a file,
we will also see how to write programs that we will read from or write to files and we
learn one another topic about command line arguments. So, that will be the first video for
this week. So, let us look at the notion of a stream.
830
(Refer Slide Time: 01:19)
When we look at files, things like mp3 files, video files, document files or even html
pages that you download are all files, they have all been created somewhere, they enable
you to transmit information from one place to the other, save it, retrieve it and so on and
without files, we cannot do any of those. So, if you expect a user to always give inputs
over a key board that is not always good, sometimes input is not even coming from the
keyboard, it could be coming from some other device.
So, the notion of a file let you both store and retrieve information and in this context, we
will use this notion called a stream. A stream is essentially a flow of input or output data,
this could be anything. This could be characters, bytes, numbers anything that you send
from one program to another program or from one device to another device and so on.
So, let us look at this basic example.
831
(Refer Slide Time: 02:22)
So, let us say I write a program and what we have been doing is we are always been
taking inputs from the key board, whatever I do demonstration of, I take inputs from the
key board and the output was always on the screen. And when you did your
programming exercises online, this input itself was not from a keyboard, you had saved
something and it got saved in to a file, but the output was still on your screen.
There are other places for example, let us say my program is actually an mp3 player or a
CD player, the input is going to come from a compact disk and the output is probably a
speaker or the input stream is coming from a compact disk and let us say, you take a
wave file and you make a mp3 out of it. So, the input is from your CD, you are ripping a
CD and you are writing to a disk which is the hard disk.
So, we need to be able to not just interact with keyboard and monitor alone, we need be
able to interact with the other devices also, at the same time we do not want to be bogged
down by all the details of these devices and so on and that is what the notion of 5 layers
come in.
832
(Refer Slide Time: 03:26)
So, C basically has no direct support for input and output and so instead it is supplied
through a library and we have seen this so for, the header file that we have been
including called stdio dot h is essentially that. So, it has several functions that will enable
you to do input and output. You do not have to really remember all of them, but it is
good to know that all the functions that you need for input and output are actually in this
file, are all in this header called stdio dot h.
833
So, let us now concentrate on what C gives us. So, every C program is automatically
associated with three standard streams, so that is why you will see the prefix called stdin,
std for it, so there are three standard streams stdin, stdout and stderr. So, stdin is for input
stream, stdout is for output stream or anything that gets return from the program and
stderr is for printing any errors and it is usually associated with the screen itself. So, stdin
is usually associated with keyboard, stdout is usually associated with the screen and
stderr is also usually associated with the screen.
And whenever we did things like scanf and getchar and so on, we were actually reading
from the key boards, so that say standard stream, you do not have to do anything
specially and output was always return to the screen also. So, with printf we did not
bother what to do with it, because we know that the default stream is anyway stdout
which is connected to the monitor.
834
(Refer Slide Time: 05:04)
But, when you have files that you want to deal with on your own, it is not the case
anymore. So, even though stdin, out and err are automatically connected, if you want to
do anything more with other files, if you want stream from other places you need to do
something more. So, reading and writing a file in C requires three basic steps, you need
to be able to open a file, you need to be able to do all the reading or writing that goes
with it and finally you should close the file.
And instead of dealing with file names explicitly in our programs, you will deal with
what are called file pointers. So, file pointer is essentially a pointer to a structure that
contains a lot of information about the file. Again, we do not have to know a lot of
details about what is in the structure, but just remember that we will deal with file
pointers and not file names directly.
835
(Refer Slide Time: 05:54)
So, let us start with the first step namely opening a file. So, if I want a programmatic
word to open a file, so it is not about clicking on a screen and opening a file. So, I want
my program to be able to open a file and do some operations on it, so to do that you
declare a file pointer and you open a file using this function called fopen. So, file pointer
is declared with as you see in the second bullet here, FILE star fp.
So, if we say r it is supposed to be in read only mode, we are trying to open the file data
dot text in read only mode which means you do not want to write anything from your
program, you want only read contents of the file data dot text. So, one can notice that
both the file name and the mode are actually put within double quotes. So, fopen expects
two strings, one string which is the name of the file and another string which is the mode
itself.
836
(Refer Slide Time: 07:26)
So, let us look at the other modes of opening a file, so r I already mentioned in the
previous slide, w is the other mode in which you open the file, but only for writing which
means from your program you can only write to it, you cannot go and read what is in the
file. So, that is called write mode and a, which is called the append mode. So, let us look
at the distinction between I mean among these three modes, r means it will try and open
an exciting file.
So, clearly what if I try to open a file that is not even existent, we need to be able to
capture this file. So, let us say I am going to open a file, but there is no file by that name,
if that happens we need a mechanism which says there is no file by that name. So, that is
an issue that we have to deal with for r. For w, you are trying to write to a file, you are
not going to read it from there at least in this program as of now.
So, if the file already exists and since you are only writing, the previous contents of the
files does not matter, we can as well remove all the contents and start with zero length.
So, since you are only writing from the current program, if you by mistake open an
existing file, the previous contents will already deleted. But, normally you would expect
your program to create a new file, so if it is a new file and since you are writing, it will
automatically create a new file with the name that you have suggested.
A stands for append, append means add at the end and this is as the name indicates, it is
useful for writing only at the end of the file. So, clearly this is similar to write, it has
837
some similarity with the write, if the file already exists, the initial contents are
unchanged. However, if the file is not there, append is as good as actually opening with
the w mode. Only that all the rights will always happen to the end of the file, so these are
the three modes.
Let us now look at how to open a file, so we have fptr1 which says fopen mydata comma
r, you are trying to open a file called mydata in read only mode and fptr1 should be file
star fptr1. In this example, fptr2 is fopen results comma w, we are trying to write
something into results file and so both of these once they are opened, you are essentially
it is like opening a gate through which a stream can flow. So, once you open a file you
can now start reading and writing, so you can either read from the stream or you can
write to the stream and these files will remain open until you explicitly close them. So, if
you do not explicitly close them, the files will remain open till the end of the program.
838
(Refer Slide Time: 10:15)
So, let us look at how do you test for successful open, I mention this as a problem. So, I
try to read a file and the file is not existent, what do I do now. So, let us see this example,
file star fptr1 and you try to open mydata. So, it will try and open it from the directory in
which the program is running from and if the file is does not exists, fptr1 will be supplied
with a value called NULL. So, if fopen fails it will return NULL, however if fopen
succeeds it will be a valid pointer.
So, you can use this information and go and check if fptr1 is null or not, if it is null you
know that the file open fail for whatever reason, may be the file was not there, that is the
most common reason in failed and you can say that file my mydata did not open and you
have to tell the user to input another file name or some such thing. So, unsuccessful open
for read is a problem, because you cannot proceed from there, if the file is not even there.
Whereas for write, fopen may not work, because you do not have enough space in your
disk and so on, so there could be other reasons why fopen may fail, if you open it in
write mode.
839
(Refer Slide Time: 11:29)
So, now let us get look at this second step, so we said we will open the file now, we
know how to check the error if the file cannot open, let us look at the second step reading
from the files. So, second step was either reading or writing, let us start with reading. So,
int a comma b these are two integers and we have a pointer fptr1 which is of type file
star, let us look at this line here. We are open the file, in this example there is no check
on whether fptr1 is null or not, but let us see how to read two integers from the file.
Let us assume that fptr1 has two integers or the file mydata has two integers saved in it,
you can, so this line here is trying to do a scan and you can see some similarity with
scanf that you have seen before. We have been using this all along, so let us look at this.
So, it is similar to scanf, except that there seems to be three difference sets of parameters,
so this is the format that you give to fscanf.
So, this is the format in which you are going to read, this is the set of pointers that we are
passing, so this is very similar to scanf, except that you also pass the file pointer fptr1.
So, fscanf takes three parameters, one which is the file pointer, two which is the format,
three which is the set of variables in which you have to read. So, in scanf all we have
done is we got rid of the fptr1 because scanf by default always reads from stdin. So,
fscanf once this is over, it would have read two integers from the file mydata.
840
(Refer Slide Time: 13:05)
So, when you read a file you could be writing a loop to read one line after the other, let
us say I have a file in which I have ten lines of two integers each. I keep reading, at some
point I have to find out that I have reached the end of the file, because once I reached the
end of the file, for the program it is probably no processing to do any more. So, it has
process the file and may be all the work that needs to be done is already done.
So, there could be some other file in which there are thirty lines, another file in which
there are hundred lines and so on, you need a mechanism by which you do not have to
know the number of lines in the file for your program to operate. And so you keep
processing one line after the other and at some point you realize that you have to find out
that you have reached the end of file and that is done by C using this function called feof.
So, if you do a scanf and let us say you are expecting something from the input stream
and if I already reached the end of the file, then if you check on this thing, feof of fptr1,
so this eof stands for end of file. So, in your file stream if you have reached the end of
file of the pointer fptr1, it means that whatever that file that you are reading using fptr1
you have reached the end of the file, there is no more processing to do. So, this is one
way to find out if you have reached the end of the file.
841
(Refer Slide Time: 14:32)
There is also another way in which you can check whether you have reached the end of
file. You keep reading one line at a time, at some point if you reached the end of the file.
Let us say you did this fscanf fptr1 percentage d ampersand var ((Refer Time: 14:45))
that is exactly what we had earlier, we do a scanf. Interestingly, scanf also returns a
return value, so we never bothered checking the return of scanf, but fscanf returns a
value which can be used to check whether you have reached the end of input stream or
not.
So, there is a special character called a special value called EOF, so it is capital E capital
O capital F. So, that is a special value, if fscanf returns this special value, then it means
there was some problem in scanning the… So, it actually indicates that you have reached
the end of the file. Otherwise, fscanf would return a value which is not EOF. So, if fscanf
returns EOF, you know that you have reached the end of the file.
So, either you call feof of fptr1 or you call, you copy the value return value from fscanf
and you check against EOF and it will tell you that the end of file has been encounter.
842
(Refer Slide Time: 15:42)
So, that is one of the things that you can do in the second step, the other thing that you
can do is you can actually go and write to files. So, for example if you look at this code
as you can expect, it is a variation of printf. So, as before we have two integers and we
have a file pointer called fptr2, we open the results file in write mode which means if the
file exists, you will over write it, if it does not exists it opens a new file with the name
results.
And now this fprintf is similar to printf, so you have the format specifier, you have the
parameters that are supposed to be printed, but you also pass the file pointer as the first
parameter. So, this line will actually write a comma b, the values a comma b into the file
pointed by fptr2 and you keep writing and you will keep writing one line after the other
till the end, till then you can, so you can stop writing.
So, for example if you have another fprintf fptr2 percentage d percentage d back slash n,
let us say 5 comma 10, so that we will write one line of 5 comma 20 and another line
would be 5 comma 10, you will see two lines in your file.
843
(Refer Slide Time: 16:50)
The third and final step in having file I/O done is closing the file, so fclose of fptr1 and
fclose of fptr2 will close the file associated with the stream fptr1 and fptr2. So, and
whatever extra work that it has been doing, it will release all of that back to the system.
So, it is important that you actually close the files, because if you actually open a reading
stream maybe it is not that become an issue, but if you have been writing to a file and if
you did not close, you can run in to problems. So, closing a file is important because,
usually output is buffered.
844
So, what we mean by that is let us say I want to printf to a screen, I wanted to do printf it
may not appear in the screen immediately. So, what can happen is whatever I supposed
to be return, it could get return to the buffer and eventually the buffer gets flushed to the
actual device. So, the flushing could be to the hard disk or the flushing could be to your
monitor and so on. So, the best example that I can give is if you use USB sticks, many
times it tell you to remove the USB sticks, especially if I have written something on a
USB memory drive, they ask you to eject it carefully.
So, you would go and click on something and you would say safely remove the device
and so on. If we have wondered what you have been doing, what it actually does is when
you remove, if you written anything to a device it is possible that the actual entries have
not been written, the actual bits and bytes have not been return, they are still in a buffer
sitting in the RAM and only when you ask the system to be flushed, then there will be a
commit to a device.
So, this happens for various reasons which we cannot get in to now, but if that buffering
happened and if you plugged our USB memory stick, your USB memory stick is not
guarantee to have all the data. However, if you go through your operating system and say
safely eject the device, it will flush all the buffers to your device and then you can
remove it.
So, fclose essentially forces all the buffers to be flushed and all the contents that you
wrote from the program is guaranteed to be written into the file. So, there are several
other modes that you can use.
845
(Refer Slide Time: 19:20)
So, there is a mode called r plus, there is a mode called w plus and another called a plus.
So, the plus indicate something, so this is the reason why we have been passing it as a
string. It is not just one character, at times may have to pass more than one character, so
in this case r plus w plus are a plus. So, r plus is it indicates that you want to be able to
read, but you want something more from at this mode. So, you open an existing file for
both reading and writing, because this is also something that you may need once in a
while.
So, what happens is because you are opening in r, it will not delete the file it will not
delete the contents. The initial contents of the file are unchanged and the initial position
at which you can start doing your operations will be at the beginning of the file. So, if we
want to over write only part of the file, you can use r plus, you opened with r and r plus
and you can start write, over writing from the top and you can stop at some point instead
of deleting all the contents.
W plus is also in some sense similar to what you are doing in r plus, but there is a key
difference. So, both of them open a file for both reading and writing, but w plus being
this notion that you destroy the file when you open, something in w mode it does exactly
that. If the file already exists, it is truncated to zero length otherwise a new file is created
and on this you will be able to do both reading and writing.
846
Finally, a plus is distinct from w plus, this also opens the file for both reading and
writing, but if the file exists, it is initial contents are unchanged, in that senses it is
similar to r plus. Otherwise, it will create a new file, in that sense it is similar to w plus.
So, these are three other modes in which you can open a file. So, what we will do now is
we will do a quick, I will show you a quick demo of file I/O, I have written some small
programs to show you file I/O.
So, there is this file called file1 dot c in which I have done something. So, I am going to
open a file called mydat1 dot text in read only mode and this file is expected to have a
bunch of integers and what I am going to do is I am going to sort the numbers which are
in the file and I am going to write it to this file called sorted dot text.
847
(Refer Slide Time: 21:28)
So, I am going to do three things, open a file called mydat1 dot text into an array, sort the
array in your program and write the sorted results to your another file called sorted dot
text that was this program achieves.
Let us see how this is done. So, there are two file pointers fp1 and fp2, fp1 opens mydat1
dot text in read only mode and I also assume that the number of entries in the file is not
more than 10. So, I assume that utmost ten integers was stored and nothing more. So,
848
what I have done is I declare an array called a of 10 in size which means it can store up
to ten integers and look at this line, line 21 through 24.
If you look at it, while fscanf for fp1 percentage d ampersand temp, what does fscanf do.
Given a file pointer, it will try and use the format and read from the input stream, in this
case from mydat1 into this variable called temp. So, when you read, it is possible that
you have ten integers, but at some point you will hit the end of the file. So, fscanf you go
and check, it will do this work fscanf fp1 percentage d ampersand temp.
So, this line actually reads, tries and reads from a file, but if it is unsuccessful and if it
returns eof, the while loop will terminate. Otherwise, it keeps reading the contents into a
and as long as there is 10 or less lines, a will not be over run, it will have ten entries. So,
up to ten integers from this file mydat1 is, then I am calling this functions sortnums a
comma cnt. I will not worry about sortnums now, so I have written a small program, a
small piece of code which does insertion sort, so I will not worry about that.
So, I will assume that a gets sorted and I am going to write things to a file and how do I
do it, I open fp2 which is opening this sorted dot text in w mode. If fp2 is null, it cannot
file the open for writing, otherwise you do fprintf fp2 percentage d a of i, it takes one a at
a time and it write it is fp2 in the format integer. And one thing you can notice is line
number 25 and line number 36 closes the two files.
849
So, this file you have done with reading at line number 24, you do not have anything
more to read, so you can close the first file mydat1 dot text at line number 25. And since
you have done with all the writing of all the variables that you, of all the values we can
close this file at line number 36. So, I will compile this and I will show how it is going to
run. So, I have compiled it. Let me show what the file has.
So, I am going to show you what mydat1 dot text has, so it has a bunch of integers, so
20, 80, 30, 50, 70, 60, 10, 40. There are 80 integers clearly less than 10, so I am good.
So, I have already compiled it earlier, so the executable is called file1 dot exe that is
what I call the file as file1 dot c. When I compiles and gets file1 dot exe and when I run
it by now what I should have done is it should have return something called sorted dot
text.
You can see that the numbers 10 to 80 where all here in an unsorted order and they are
all in sorted order now. So, just to show that this was actually created by the program, let
me delete the sorted dot text, because it is a file that you are supposed to write every time
the program is run. You can see that sorted dot text, there is no file by that name and if I
run the program again and see what is in sorted dot text, you can see that the file got new
entries now.
So, I deleted the file and when I ran the program it created the file called sorted dot text.
So, for a while let us assume that this mydat1 dot text did not exist, I will instead call it
850
mydat dot text, this was expected by the program mydat1, I renamed it. Now, if I run
file1, so I printed this error called file not present, this file which is not present is mydat1
dot text, it would not change the sorted dot text, because I exited from the program. So,
this is a small piece of code that I wanted to show. I have another piece of code which is
also interesting.
So, it is possible that I have two files which are both sorted entries, now I want to merge
these files. So, in the previous case one thing that happened was I actually knew that the
contents of the file had only ten entries, but I may not know that up front. I need a
mechanism by which I do not want to depend on any count. So, in this program what it
does is it takes two files, it assumes that both these files are sorted and it will try and
merge these two into one single sorted file. So, we are going to take two inputs sorted1
dot text and 2 dot text and we are going to write a file called sorted dot text. So, you can
see that sorted dot text gets opened in write only mode.
851
(Refer Slide Time: 27:01)
And if either fp1 or fp2 file failed, you know that you cannot read, so it is not present.
Otherwise, you scan one integer from each one of these streams, so you have one integer
in stream one and one integer in stream two, you read that and then I have written a piece
of program from line number 25 to 37 is a piece of program which runs on a while loop.
So, what does it do, so I have two files which are of different lengths, it is possible that I
will end up consuming everything from one stream before I even finish the other stream.
So, what I am going to check is have I not reached the end of stream one or stream two.
If I have not reached the end of stream one and not of stream two either. As long as I
have not reached the end of any of the streams, then I can always read two integers and I
will be able to compare them and do something with it. So, at this point I have two
integers that I have read, I have not reached the end of the stream which means I should
be able to compare and write the results back.
So, the first stream you read into temp1, the second stream is reading into temp2, so you
are comparing temp1 and temp2. If temp1 is less than temp2, then write temp1 into the
output file and read one more integer from file1. Otherwise, if temp1 is greater than
temp2 or equal to temp2, you write temp2 into the file and you read one more integer
from file2, so you can see what is happening here. So, either the entry in file1 or the
entry in file2 will be, one of them will be the larger than the other.
852
Here write that and move one step in that steam and what could happen is when you
exists this loop, you may not have exhausted both the streams. One steam could end
prematurely and other one has more numbers, but remember that the numbers are sorted.
So, I can do something like what you did not merge sort, that is what I have been trying
to do here. Line number 25 to 37 is merging two streams instead of merging two arrays,
but if the arrays are of different size, then you have to just concatenate whatever is left
out at the end of the resultant array and that is what this loop does.
So, line number 47 to 50, what it does is it, so this 39 to 46 finds out which stream got
over. So, if fp1 got over fp2 should be copied, if fp2 got over fp1 and to the end should
be copied and what line number 47 to 50 does is, it just copies whatever is left out in
whichever file it is and it keeps scanning and keeps coping till the, that file is also
exhausted. And once that is over, we can close all the streams fp1, 2 and 3 and you can
return this 0. So, let me again compile it and there are no compilation errors.
So, let us look at sorted dot text, because that is the output we are going to write. So, it
has 10 to 80 now, but let us look at sorted1 dot text which is one of the input files, it has
10 to 70 and I am sorted2 dot text I created with more values than sorted1 dot text, it has
5, 15, 25 all the way up to 1 naught 5. So, there are seven values here, but there are
eleven values here. So, your program will finished this stream before it finishes this
853
stream. So, we will have more work to do after this stream is processed, so I will run
file2 now.
So, I run file2 and it was printing various things on the screen.
Now, let us go and look at sorted dot text. So, it got over written first, sorted dot text was
earlier written by file1 dot c. Now, I have file2 dot c, this is the second program and it
over wrote the sorted dot text. Now, you can see that the entries of the resultant file are
in sorted order. So, we read two files, the key thing I want you to see is ((Refer Time:
854
31:07)) this feof is another way to look at end of file and in this case, I am
simultaneously forwarding both the file pointers by…
So, I am trying to see if either one of them reach the end, but one of the file pointers will
move inside the loop, either this or this will move depending on which one was lower.
Let us get back to the presentation, so, we looked at two examples in which we read and
wrote two files.
The other key thing that I want you to learn is the notion of what are called command
line arguments.
855
(Refer Slide Time: 31:43)
So, if you notice the things that I have been doing here, so let us say I want to copy file1
dot c to another dot c. So, I wrote this thing called, so should it is called copy, so copy is
a windows command to copy one file to another, but, copy itself is a program. Somehow,
I am able to pass values file1 dot c and another dot c here and I am able to get copy to do
some work. I did not say copy and then I typed file1 dot c in keyboard and another dot c
in the keyboard and so on.
When I launch the program itself I was able to give the parameters that it is needed and
the copying got done. So, this is called command line parameter. So, we are in a
command line, we are calling an executable and we have given two parameters file1 dot
c and another dot c, we want to be able to process command line parameters. That is
your, that is the next part of this video.
856
(Refer Slide Time: 32:41)
So, command line arguments work like this. You can pass arguments to C programs by
passing them right when the program is executed. So, you can sit in the command line
and type along with the parameters that you want. You do not have to type them in later
when the program is running. So, for example if you typed echo, hello comma world,
then you would see hello world printed on the screen. So, echo is actually taking
command line parameters hello comma and world and it is processing it and it actually is
printing on the screen.
857
So, this is done using a very sophisticated mechanism. So, you remember the main
program does not take arguments by default, so we never used... So, I even though I said
main is a function we never passed any arguments to it, but you can actually pass two
arguments int argc and characters star argv of array. So, argc is an integer whereas argv
if we look at it, it is a pointer to a pointer. So, it is a set of pointers to character pointers.
So, argv this square bracket means this is a set of pointers and what does it pointing to it
is character star. So, it is a pointer to pointer, so you can also think of it, it is an array of
strings. So, argv of 0 is usually the name of the program, argv of 1 is the first argument
that you have passed, argv of 2 is a second and so on. So, argc is an integer value that
takes the number of arguments including the file name itself. So, for example ((Refer
Time: 34:17)) if I did copy file1 dot c, another dot c this is, there are three arguments
copy file1 dot c and another dot c. So, argc would be 3, argv of 0 would be copy, argv of
1 would be file1 dot c and argv of 2 would be another dot c.
So, what I have, what really happens is you get argc equal to 3 and argv is a pointer to a
pointer and what do you have, you have three pointers which are pointing to echo, hello
and world, remember these are strings. So, you have a backslash 0 at the end and the
fourth pointer actually points to null, so you have in some sense a two dimensional array
of characters. Again as before, I have written a small program which does something
very simple ((Refer Time: 34:59)).
858
So, it takes argc and character star argv, all it does is it iterates over all the arguments
and prints them on the screen. So, it prints what is the argument number and what is the
value of the argument. So, i runs from 0 to less than argc, so it goes 0, 1, 2, 3 up to argc
minus 1 and it prints i comma argv of i. So, I have already compiled this program, let me
run it now.
So, I call this program echo, let us say I typed only echo dot exe, I did not pass any
parameters. It just gives me the 0th argv which is echo dot c, echo dot exe itself. Let us
say now I give echo a, a is one of the parameters. So, this is the 0th argv and this is the
1th argv, so you get those two, I can pass an arbitrary number of inputs, see this, so you
have 0, 1, 2. So, you have three arguments echo dot exe a and b, I can do this and so on
and it is not necessary that you pass only one variable type, I have passed an integer here,
then I can pass the real value here, you can do any of those.
859
(Refer Slide time: 36:10)
So, anything that you pass in the command line will be passed to the program as argv.
So, they are treated as strings inside and I am printing one string after the other. So, this
comes in handy, whenever you do not want the keyboard input at all from the program.
Even to read a file name, let us say I want to write a program like this, copy file1 dot c to
file2 dot c, so I do not want the user to type it later.
Instead, I want that to be given in a command line itself and automatically this file, so
you will treat that as a string, you have to open the file by that name, read it, open the
other file in write mode, write it and so on, all of that is there. But, you have passed the
file names right in the command line itself. So, these are two things that is very useful,
the notion of being able to do file I/O and the notion of being able to take command line
arguments.
860
(Refer Slide Time: 37:02)
So, that is a same program that is here and this brings us to the end of this module.
861
Introduction to Structured Programming
Prof. Shankar Balachandran
Department of Computer Science and Engineering
Indian Institute of Technology, Madras
Lecture – 61
Welcome to the very last video for this course for this Mooc. So, we have stuck with the
course for. So, long and. So, this is the place, when I do not want to teach something
completely new. So, this is something that you have done, you have already done the
programming, but when you write large piece of software, you may have to do a few
things to be just careful and. So, that it becomes usable even later.
So, one thing that happens with the software is that, lot of people start contributing to
code and your code that you wrote to be able to read by others and may be they want to
change it later or you may even go and changed your own code to do a few other things
later and so, on. So, you need to follow a few principles to do this. And one major thing
that you have to do is, take a problem and divide that into smaller sub problems. So, that
you can solve each of the sub problems separately and put them in different places.
So, it may be you have not seen this or you have not done it so, far, because you are
writing smaller program so, far. But, this can really become a problem if you write lots of
code. So, to motivate this whole thing, I am going to give a small programming
assignment to you, it is not a very hard assignment. So, I want you to go and write a
program to print these figures.
862
So, there is a hexagon looking thing at the left and then, there is something like a cup,
there is a stop sign and we will call this the hat. So, let us say this will call this the egg,
the cup, the stop sign and the hat. Let us say, you want to draw these things. So, I am
asking you to write a program which you will draw, you could do something very easy.
So, in the first line draw dashes, in the second line draw this forward slash, give some
space, draw a backward slash, you can do a lots of things.
So, let us look at the very first crack at this problem. The first version is an unstructured
program, we will create an empty program and the main function. We will just copy the
expected output, but surround each line with printf and run it to verify the output, we can
do that very well.
So, your program would looks something like this, int main void, printf you have serious
of printf, in each one you can see what you are trying to print. So, the only thing that is
slightly different then what we will see in the output is for every back slash, we have to
use this escape character called back slash itself. So, every back slash is actually
supported by another back slash. The other thing that is missing is the serious of new
lines that I have not explicitly added, but otherwise your program would looks something
like this.
So, this is what I will call an unstructured program. So, you have not put too much
thought into it, you are asked to print something like this, you just write a serious of
printf statements and this will print some. It will print what do you want, but it is not a
863
very nice thing to do.
So, the first thing that we do in any engineering step is take a problem and divided that
into sub problems. The same thing applies to writing software. Can I take this problem
and divided that into sub problems. So, the main thing that comes with this is what is
called decomposition. Can you take the problem and decompose that into smaller sub
problems and identify, if there is any structure to the problem itself. So, let us go and
look at this.
So, there is some a structure. So, the first figure will call that the egg, the second figure
864
will call that tea cup, the third figure is will call that the stop sign and the fourth figure
will call it a hat. So, now I can think of this problem as drawn an egg, draw tea cup, draw
a stop sign, draw a hat. That is slightly better than draw some line and draw some other
line and draw some other line and so, on. So, this is slightly better.
Let us see, how this might look like. In your main program, you will have four function
calls draw egg, draw tea cup, draw stop sign, draw hat and you would have to write four
functions for each one of them egg, tea cup, stop and hat. So, this is something that you
can do and it is something that you can write at this level anyway.
865
So, you have these four things. So, main depends on the function would depend on the
function egg, would depend on the function tea cup, stop sign and hat. So, that is another
way to write the program, but this is still not the best thing.
So, let us go and look at what is happening. Your main program becomes much simpler,
the task that you doing in main is I have to do these four tasks in this order, that is all you
care about. But, you do not care about how exactly egg is drawn, how tea cup is drawn
and so, on, because you have delegated this to a function. Remember, when we did
functions I talked about delegations and we did exactly that now. So, when you call this
function egg, this function egg has all the printf statements.
Of course, the new lines are missing I did intentionally added, because you can see the
structure of the output here clearly. So, series of printf statements, this is slightly better.
What you have is from the main program the intend is clear, you want to do draw egg,
draw tea cup, draw stop sign and draw hat, but the functions are themselves still ugly,
you have only the serious of printf statements. So, this is one level of decomposition.
From, something which is just a sequence of printf statements, you have four functions
calls, the main program is clear or the main method is clear, but the internal functions are
still a bit ugly. So, now let us see whether we can decompose the problem in little further.
866
(Refer Side Time: 05:50)
So, this is actually just finishes the program with all these four egg, tea cup, stop sign
and hat. Let us see, if there is something better that we can do about this whole thing, is
there some other structures to drawing this or should I be drawing or writing a series of
printf statement for each one of these functions. So, let us look at the egg to begin with.
So, the egg there is something in the top half and something in the bottom half. You
cannot really draw the top half with the bottom half, code or anything like that, there is
no reuse that you can do. So, the top half has to be drawn and the bottom half has to be
down separately. So, this will require a printf of a line followed by forward slash and the
back slash and forward by another line with the forward slash back slash and in this, it
867
would be a reverse.
So, you cannot reuse anything. So, let us assume that there is a function which can draw
the top of the egg, there is another function that can draw the bottom half of the egg. So,
we will call those functions egg top and egg bottom, that is the way we have identify
this. Now, let us go and look at this next one which is supposed to be for drawing the
cup. If you notice, cup has something which is similar to the bottom of the egg, but there
is something else also. The saucer here is just a line we need something to do there.
So, if you want that. So, the tea cup actually can reuse the egg bottom, drawing the egg
bottom. So, if you draw the egg bottom and if you draw a line with the pluses on both
ends that is actually. So, if you know how to draw bottom of an egg, you can actually
draw a tea cup. Only thing you have to do is, draw the bottom of the egg and draw a line
underneath. So, tea cup requires, you to know how to draw the bottom of the egg and
bottom of the line.
If you have already written that as the function, you can call the same function once
more from tea cup, there is no harm. Now, let us go and look at the stop sign, the stop
sign is also interesting.
868
(Refer Slide Time: 08:00)
Because, the top half of the stop sign looks like the top of the egg. The bottom top of the
stop sign looks like the bottom half the egg and also the top of the cup and that is a line
in between which has the stop. So, for drawing a stop sign if you draw an egg top and if
you print this line and if you draw egg bottom, you have a stop sign. So, we will call this
line, the stop line. So, this is called just a line, this is called a stop line and for drawing a
stop sign you need egg top, then you need to draw stop line and we need to do egg
bottom, you have that.
Then, let us look at the last one which I called the hat. The hat seems to be the top of the
egg and the bottom of the tea cup. So, I already wrote those two things, let us I have
functions to do those, I have top of the egg drawn and bottom of the tea cup. So, I have
these two. So, I do not have to rewrite code anymore.
869
(Refer Slide Time: 09:01)
So, we have these two and I already have the methods. So, now if you look at this whole
structure, main program requires you to know how to draw an egg, tea cup, stop sign and
hat. Because, those are the four functions calls we are doing. Egg in term will call egg
top and egg bottom in that order, tea cup will call egg bottom and line in that order, stop
sign will call egg top, stop line and egg bottom in that order and hat will call egg top and
line in that order.
So, for writing egg all you have to do is write egg top and egg bottom and make two
function calls, but somebody ask to still write egg top and egg bottom and line and stop
line and so, on. So, now there are two questions one can ask. What are the methods we
should be defining and what is a good order in which we have to implement and test this
methods? So, clearly main depends on these four and these four depend on these four, it
dictates that we have to know how to implement these four.
Without building this, there is no point in building these and without building these, you
cannot build your overall diagram which is on the left side, this egg followed by cup
followed by stop followed by hat, you cannot draw that unless you have drawn, you
know how to draw these four structures.
870
(Refer Slide Time: 10:21)
So, we will start each one of them. So, main requires of call to egg, tea cup, stop sign and
hat. This is just like what we had in the version 2 of the program. Only that we have a
version which draws top of the egg and we draw version which draws bottom of the egg.
If you have these two, I am ready to draw the egg itself. What does the egg need? You
need top of the egg, you need bottom of the egg and we need to draw the egg by calling
egg top and egg bottom.
So, forget this printf for a while now. It is actually a mistake it should have a back slash n
there. So, printf back slash n. It will draw a line after the egg is drawn. So, if you look at
the sequence, main calls egg, egg calls egg top which will draw this, then it calls egg
bottom which will draw this and it is supposed to be printing a new line. Again in these
cases, the new lines are intentionally left out. So, that it does not clatter the program
write now.
871
(Refer Slide Time: 11:17)
Then, let us looked at how to draw a line. So, you do a plus followed by dashes followed
by a plus, this is for our saucer. To draw a tea cup, we need the egg bottom on the line
and. So, there will give as the cup and saucer. To draw a stop sign, we need the egg top
and egg bottom, but we need a stop line in between. So, this top line is needed only for
the stop sign, it is not needed anywhere else. So, instead of calling a function, I could just
put the printf statement here itself, because it is not used by more than one function.
So, stop sign is required, this top line is required only for the stop sign, nothing else. Hat,
you do not need do anything new at all, egg top you have already drawn, you know how
to draw a line. So, you do these. So, now you all look at the structure, the program is
much cleaner. You have reused the code that we have written for egg top and egg bottom
in several places. So, egg top is used in drawing an egg and hat, egg bottom is useful in
drawing the egg and the tea cup and egg top and egg bottom are useful in stop sign.
So, now this is the much nicely structure and nicely decomposed program. So, for instant
if you make one small mistake in the space in the egg top, if you cut and pasted these to
this structure multiple times, you have to go and change this multiple times in your
program. But, if there is a small mistake in egg top, all you have to do is fix it here,
automatically all the functions which call it will have the fixed versions.
872
(Refer Slide Time: 12:49)
So, this is the setup. So, even though I said this is what you should do. There is some
more things that you have to do. So, first thing is taking a problem and dividing that in to
sub problems and writing functions for each one of them, we have already done that. We
need to do something more, the thing is we have the. So, called basic shapes. So, I am
going to call egg top, egg bottom and line, I am going to call these things as basic shapes.
Using basic shapes, you build the shapes and using the shapes, you draw a complete
picture, this is the complete picture. So, these are basic shapes, these are shapes and this
is the complete picture and how to we do that. So, I want to be able to separate the
concern of each one of them. Drawing the basic shapes and being able to do something
there is difference from being able to draw their shapes and being able to draw shapes is
different from being able to draw the picture.
So, I am going to do this in a bottom of fashion. So, you go and look at this, we will go
and develop this in a bottom fashion. We will try and write these things first, methods for
these things and then write methods for these things and then this, but we are also going
to do something more which we have not seen. So, far.
873
(Refer Slide Time: 14:08)
So, the first thing we are going to do is. So, for we have been using the. So, called header
file, we are not really developed our own header files. So, we use stdio dot h and string
dot h and so, on. So, you need to separate the concerns, this is the terms people used in
Software Engineering separate concerns. So, one you are mixing the program, what the
program is supposed to be doing with what the interface of the functions are.
So, the implementation and the declaration are all in the same file. So, far, but we can
separate these concerns. So, use the code dot h or give a file name dot h to do all the
prototypes and so, on and write file name dot c to write the program. So, the
implementation usually goes into dot c files and the declaration is going to dot h files.
874
Let us say, we adopt that and we are going to do something with the header files. So, for
every problem there is some solving the problem itself and all the functions or other
things that you need for it. So, we will see how to do that.
So, the header files usually have this format. So, there is hash if not def of code name,
then define code name, put all your structures and prototypes and so, on, end if. So, let us
not very about this if not def defined in end if, but all the declarations go with in these
three structures, hash if not def, hash define, hash end if. So, what it tells you is, if
something called code name is not defined, define it now, define all these things and end
it.
So, let see how this look likes for basic shapes. I am creating a file called basic shapes
dot h, this takes care of only the declarations for all the functions which have for basic
shapes, egg top, egg bottom and line are three functions. So, add the declarations for
these three functions in a file called basic shapes and. So, whenever I use a file name, I
will use the same thing for the if not def. If not def basic shapes h define basic shape h.
If this has been not defined. So, for, define this variable now, define all these function
prototypes, end it. So, this is the declaration for the basic shapes.
875
(Refer Slide Time: 16:24)
The description. So, will come later. So, if you want to use the dot h file, you do hash
include basic shape dot. This is like hash include stdio dot h. So, put your header file in
the same folder as your C files and use hash include, for example, in stdilb. So, I will say
you how to write basic shapes dot c.
So, basic shapes dot c will be a C program, this has the implementation. This include
stdio and also include basic shape and it has the function descriptions for egg top, egg
bottom and line. So, basic shapes dot h and basic shapes dot c, you have something that
can draw basic shapes and nothing else.
876
(Refer Slide Time: 17:07)
Now, you want to be able to draw shapes with it. So, let us move up.
I am going to write a file called shapes dot h, which has all the things required for
drawing the shapes. So, the declarations for drawing the shapes and this is the
description of those functions.
877
(Refer Slide Time: 17:23)
So, shapes dot c has shapes dot h, because it needs those descriptions and how is the egg
implemented. The implementation is in this file, egg top, egg bottom and printf, tea cup
egg bottom, line and printf and so, on.
This is what you need for drawing the shapes. So, now we have four files basic shapes
dot h, basic shapes dot c, shapes dot h and shapes dot c. So, shapes dot h has only the
declarations, it also includes basic shape dot h and so, on. So, now we have four files,
you cannot compile just one file now, you have to create what is called a project. I will
show you, how the project is done in the demo.
878
(Refer Slide Time: 18:10)
Finally, the top most thing I am going to call the draw figures dot c, draw figures needs
shapes dot h and. So, now, egg, tea cup, stop sign, hat are all provided by shapes dot c.
Shapes dot c in turn depends on basics shapes dot c. So, this is the overall structure, we
have five files, draw figures, basic shapes dot h in c and shapes h in c. So, I will already
return this here. So, you can see basic shapes dot h, it has a declarations, basic shapes dot
c which has the description, shapes dot h which has the declarations, shapes dot c which
has the description and draw shapes dot c which has the description for the main
program.
I have included all of these into our project called draw shapes. So, you can do that by
saving file new project. So, I did file new project I created a file by name, draw shapes. I
included these five files into the project. Now, I can compile the project and what it does
is it takes all these files and compile them together and when I run it, it actually runs the
main program. So, if you noticed the individual once do not have main routines.
We do not have main routines in any of these. The main routine is only in this one. So,
for drawing shapes dot c and basic shapes dot c and shapes dot c, they are also compiled
and they are compiled along with draw shapes dot c, you can see the result of the print
out here. So, you can see the egg, the tea cup, the stop sign and this that came from
actually having taken the problem and dividing that into sub problems, identifying the
structure which is common and being able to reuse and so, on.
So, for whatever reason if I two cups tomorrow, I can actually call the cups twice. So, I
879
can call tea cup and a tea cup, it will draw two tea cups one below the other. So, this idea
of being able to taking a problem, dividing that into sub problems is a necessary skill.
You probably did that with functions itself, but separating the concerns of
implementation from the. So, called interface or just the declaration is the another thing.
So, once you have something if you divide that into multiple files, you can quickly go
and see which file has the basic issue and which file has the problem and so, on. So, for
example, tomorrow I may not want to draw egg and stop sign and so, on, but I want to
draw two cups and three cups, I can write a different program which will take the cups
and draw them explicitly. So, this notion of dividing the problem is decomposition and
organizing them into multiple header files in C files is a very necessary skill, when you
build large and large pieces of software.
So, I want to end this course with one small piece of advice. So, there are lots of things
that we did in the course, but we never wrote or explicitly use libraries for many of them.
C comes with a lot of libraries. These are standard libraries that come with any standard
C package. So, your compiler package these things for you, thinks to operate on the
strings, things to operate on characters. For example, you take a lower character and
convert that to upper case, you can check whether a character is lower case or upper
case, things like that.
There are math libraries included from math dot h, trigonometric functions, floor,
absolute value, power and things like that are all available from math libraries. It is not
880
just that these are the functions that are there. Even some algorithmic things are
available. For example, from stdlib you learn binary search, you do not have to write
library search yourself, you can call the binary search function available from stdlib.
So, you learnt how to do various sorting algorithms. So, qsort for instance is an
algorithm which has something called quick sort. So, you can call qsort or an array of
integers for instants. So, you have to learn how to do it though, I am simplifying a few
things here, but for there are sorting, searching functions, mathematical libraries and the
other things that are already available out there. If it is available as a part of your
package, learn how to use it.
So, at some point all we have to do is make appropriate function calls, include
appropriate header files, make appropriate function calls and compile with appropriate
flux. So, I am simplifying a lot of things, but learn how to use the basic libraries and not
write things from scratch, every time you write a program. So, this bring to us to the end
of this module and. In fact, to the end of this course and I hope you enjoyed the course as
much as we enjoyed creating it and this is been probably a rough and tuff, 9 or 10 weeks
that you have been through with lots of programming assignments and assessments and
so, on.
But, we hope that this course helped you, in some way of the other to learn either from
scratch or at least be able to if we have already done C programming, at least cleared a
lot of concepts for you and taught a few new things like data structures and algorithms
and so, on. So, for doing this course we took help from quite of you people. So, of
course, we three where the faces that you saw, but there is a big team that was behind it.
We have 10 TAS who are all behind this. So, they were actually responding to you on the
forums. There is a team from Google which created the platform and there is also this
local team, the NPTEL team in charge of video recording scheduling and. So, many other
things. So, there are probably 30 or 40 people who got involved in creating the contents
for this course and delivering it you. So, I personally want to place on record that we
three thank each and every one of them.
So, we cannot possibly list all of them by name, but I want to thank all of them to make
this course, the success it is now. And I am hoping that you guys will tune in later for
other courses that NPTEL has to offer. So, all the best for your exams and I hope to see
you in some other course at a later point of time.
881
Bye bye.
882
THIS BOOK
IS NOT FOR
SALE
NOR COMMERCIAL USE