Pds Practice Questions (2)
Pds Practice Questions (2)
Partha Bhowmick
Professor
CSE Department, IIT Kharagpur
http://cse.iitkgp.ac.in/~pb
October 5, 2024
2
©Partha Bhowmick
Contents
1 Introduction 9
1.1 Computer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Components of a computer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Algorithm and flowchart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4 Computer program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3 Conditionals 27
3.1 Solved problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4 Loops 35
4.1 Syntax of while loop and do-while loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2 Syntax of for loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3 break and continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.4 Nested loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.5 Solved problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.6 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3
4 Contents
5 One-dimensional arrays 45
5.1 What is array? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.2 Why array? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3 Declaring arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.4 Initializing arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.5 Accessing and working with arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.6 Solved problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.7 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6 Functions 55
6.1 What is function? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.2 Defining a function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.3 Execution of a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.4 Prototype versus Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.5 The return statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.6 Local and global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.7 Scope of a variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.8 Parameter passing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.8.1 Passing by value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.8.2 Passing by reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.9 Recursive function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.9.1 Activation record and recursion stack . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.9.2 Tower of Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.9.3 Direct and indirect recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.9.4 Mutual recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.10 Passing an array to a function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.11 Macros (#define) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.12 #define with arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.13 Extra topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.13.1 Generating random input using rand . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.13.2 main() with arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.14 Solved problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.15 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7 Strings 86
7.1 Characters and strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
7.2 Declaring a string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.3 Initializing a string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.4 Reading strings with %s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
©Partha Bhowmick
Contents 5
8 Pointers 97
8.1 What is a pointer? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.2 Types of pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.3 Use of pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
8.4 Operations with pointers and dereferenced pointers . . . . . . . . . . . . . . . . . . . . . . . . 101
8.4.1 Dereferencing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
8.5 Pointer to 1D array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
8.5.1 Usefulness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
8.5.2 Indexing with pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
8.6 Pointer arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
8.7 Scale factor: sizeof() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
8.8 Passing pointers to a function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
8.9 Dynamic memory allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
8.9.1 Memory Allocation Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
8.9.2 Dynamic memory allocation and error handling . . . . . . . . . . . . . . . . . . . . . . 108
8.10 Solved problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
8.11 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
©Partha Bhowmick
6 Contents
11 Sorting 143
11.1 Bubble Sort: A basic sorting algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
11.2 Selection Sort (Version 1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
11.3 Selection Sort (Version 2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
11.4 Insertion Sort: Another basic sorting algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . 149
11.5 Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
11.6 Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
11.7 Classification of sorting algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
11.8 Recursive vs Iterative Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
11.9 Exercise problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
©Partha Bhowmick
Contents 7
General note
1. Questions at the advanced level are marked with ♣ (harder) and ♠ (hardest).
©Partha Bhowmick
1 | Introduction
1.1 Computer
Computers have changed our world, making it easier to work, learn, and play. At the heart of every
computer is programming, which tells the computer what to do. Programming turns ideas into instructions
that computers can follow, enabling amazing things in science, medicine, and everyday life. As we dive into
C programming, we will learn the basics that allow these incredible machines to solve problems, run apps,
and power the digital world. Understanding programming is like unlocking a superpower, giving you the
skills to create and innovate with technology.
A computer is a rapidly evolving machine engineered to process data and extract information. The
processing is done according to a given sequence of instructions called a program or code. These programs
enable computers to perform a wide range of computational tasks. More importantly, many of these tasks
are performed at lightning speed—much faster than even the most brilliant minds—such as adding a million
numbers in less than a millisecond!
(i) Central Processing Unit (CPU) with a microprocessor that carries out all arithmetic and logical
operations, and with a control unit to arrange the order of operations as the need be.
Monitor Computer
drwxrwxr-x 5 root root 4096 2008-07-23 02:32 kernel
drwxrwxr-x 5 root root 4096 2008-07-23 02:15 lib
drwxrwxr-x 2 root root 4096 2008-07-23 02:15 mm
drwxrwxr-x 41 root root 4096 2008-07-23 02:33 net
drwxrwxr-x 9 root root 4096 2008-07-23 02:12 scripts
drwxrwxr-x 4 root root 4096 2008-07-23 02:15 security
drwxrwxr-x 18 root root 4096 2008-07-23 02:15 sound
drwxrwxr-x 2 root root 56 2008-07-23 02:12 usr
-rw-r--r-- 1 root root 466 2008-07-23 02:33 ..tmp_kallsyms1.o.cmd
-rw-r--r-- 1 root root 466 2008-07-23 02:33 ..tmp_kallsyms2.o.cmd
-rw-r--r-- 1 root root 634 2008-07-23 02:33 ..tmp_vmlinux1.cmd
-rw-r--r-- 1 root root 650 2008-07-23 02:33 ..tmp_vmlinux2.cmd
-rw-r--r-- 1 root root 41410 2008-07-23 02:32 .config
-rw-r--r-- 1 root root 40956 2008-07-23 02:12 .config.old
-rw-rw-r-- 1 root root 572 2008-02-25 17:59 .gitignore
-rw-rw-r-- 1 root root 3657 2008-02-25 17:59 .mailmap
-rw-r--r-- 1 root root 70 2008-07-23 02:32 .missing-syscalls.d
-rw-r--r-- 1 root root 851087 2008-07-23 02:33 .tmp_System.map
-rw-r--r-- 1 root root 1244271 2008-07-23 02:33 .tmp_kallsyms1.S
-rw-r--r-- 1 root root 309464 2008-07-23 02:33 .tmp_kallsyms1.o
-rw-r--r-- 1 root root 1244271 2008-07-23 02:33 .tmp_kallsyms2.S
-rw-r--r-- 1 root root 309464 2008-07-23 02:33 .tmp_kallsyms2.o
-rwxr-xr-x 1 root root 4932528 2008-07-23 02:33 .tmp_vmlinux1
-rwxr-xr-x 1 root root 5129136 2008-07-23 02:33 .tmp_vmlinux2
-rw-r--r-- 1 root root 2 2008-07-23 02:33 .version
-rw-r--r-- 1 root root 638 2008-07-23 02:33 .vmlinux.cmd
-rw-rw-r-- 1 root root 18693 2008-02-25 17:59 COPYING
-rw-rw-r-- 1 root root 91435 2008-02-25 17:59 CREDITS
-rw-rw-r-- 1 root root 1530 2008-02-25 17:59 Kbuild
-rw-rw-r-- 1 root root 89876 2008-02-25 17:59 MAINTAINERS
-rw-rw-r-- 1 root root 50404 2008-02-25 17:59 Makefile
-rw-r--r-- 1 root root 182981 2008-07-23 02:33 Module.symvers
-rw-rw-r-- 1 root root 16930 2008-02-25 17:59 README
-rw-rw-r-- 1 root root 3119 2008-02-25 17:59 REPORTING-BUGS
Printer
-rw-r--r-- 1 root root 851087 2008-07-23 02:33 System.map
-rwxr-xr-x 1 root root 5129136 2008-07-23 02:33 vmlinux
root@rainbird:/usr/src/linux-2.6.22.19# _
Mouse
Keyboard
8
Chapter 1. Introduction 9
A computer program is a sequence of instructions for a computer to execute. A computer program in its
human-readable form is called source code. A source code is written in a programming language (also called
high-level language or HLL), such as C, by a programmer. For example, a program a01-1.c written by you
in C language is the source code. In C language, the source code needs a compiler such as gcc to run on it,
in order to get the binary executable code (also called executable code, or simply executable, for brevity).
For example, the command gcc a01-1.c gives you the executable a.out corresponding to the source code
a01-1.c. Any executable code is basically a machine code, as it consists of machine-language instructions.
The compiler gcc that you run on any C code is just a machine code. There are several important stages
during the compilation process; the important ones are shown in Figure 1.3.
The source code a01-1.c and its executable a.out are all stored in the hard disk of your computer.
When the executable a.out is requested for execution, the operating system loads a.out from the hard disk
to the RAM and starts a process. The CPU switches to this process as soon as possible so that it can fetch,
decode, and then execute the machine-language instructions of a.out, one by one; we then say that the
process is running in the computer.
Library It contains many files written by experts. We call them header files or library files. One such
header file is stdio.h, which must be included in any C code in its first line, using the instruction
#include <stdio.h>. Any other header file, such as math.h or stdlib.h, should be included this way
in the beginning. Each header file contains detailed instructions for complicated tasks. For example, the
functions scanf and printf are contained in stdio.h, and the square-root function sqrt is contained in
©Partha Bhowmick
10 Chapter 1. Introduction
Start
Read a and b
1. Read a and b scanf("%d%d", &a, &b);
Yes No 2. If (a > b) then if (a > b)
a ą b? 3. print a printf("%d", a);
4. Else else
Print a Print b 5. print b printf("%d", b);
End
Start
Read a, b, c
Yes No
a ą b?
Yes No Yes No
a ą c? b ą c?
End
Figure 1.2: Top: The flowchart, the related algorithm, and a part of the C program needed to find and print the
larger between two numbers, a and b.
Bottom: The flowchart to find and print the largest among three numbers.
math.h. You have to include these library files in your C program to use their functions. For example, you
have to include stdio.h to use scanf or/and printf, by writing #include <stdio.h> in the beginning of
your code. Similarly, you you have to include math.h to use its sqrt function, by writing #include <math.h>
just after #include <stdio.h>.
1. rLargest among three numberss Draw a flowchart to read three numbers and determine the largest.
You may use an extra variable but must not exceed two comparisons in total.
©Partha Bhowmick
Chapter 1. Introduction 11
e
e
d
d
de
co
co
co
e
Compiler Linker
bl
t
ce
ec
ta
ur
u
bj
ec
So
O
gcc
Ex
a01-1.c Library a.out
stdio.h, math.h, stdlib.h, etc.
Figure 1.3: The steps of compilation in C using the command gcc a01-1.c.
2. rSum of 10 numberss Draw a flowchart to read 10 numbers one by one and compute their sum. You
may use two extra variables and a maximum of 9 additions in total.
3. rLargest among 10 numberss Draw a flowchart to read 10 numbers one by one and determine the
largest. You may use two extra variables and a maximum of 9 comparisons in total.
4. rLargest and smallest among 10 numberss Draw a flowchart to read 10 numbers one by one and
determine both the largest and the smallest. You may use three extra variables and should aim to
minimize the total number of comparisons.
©Partha Bhowmick
2 | Variables and expressions
2.1 Variables
The data used by any computer program are stored in variables. A variable is identified by its four attributes:
name, type, value, address. These are explained below.
The name of a variable is written by the programmer. It can be any word or string composed of letters,
digits, and the underscore character. It must begin with either a letter or an underscore. Uppercase and
lowercase letters are treated as distinct in the C language. Here are some examples of variable names:
x, dx, y12, sum_1, _MYvar, realNum, complexNum, point, area, tax_rate, list, Set, Vector
The type of a variable is determined based on the type of data it stores. Accordingly, we have different
types of variables, which are also termed as data types. Further details are given in §2.2.
Depending on the type, a variable has a specific value within a specific domain. This value is either
assigned or computed when the program runs. To store this value in the main memory (i.e., RAM), a variable
needs some space during execution of the program. The amount of space, referred to as size, depends on
the data type of the variable. Hence, before using any variable, its type must be declared in the program.
This is called variable declaration. For example, by the declaration int a, the variable name is a and its
type is declared as an integer; hence, a space of 4 bytes will be allocated in the memory during execution of
the program. As another example, by the declaration char c, the variable c is declared as a character, and
hence a space of 1 byte will be allocated in the memory.
The address of a variable specifies its storage location in the main memory, which is settled during the
execution of the code. The address is denoted by the ampersand character, i.e., &; for example, for the
declaration int a, the address of a is denoted by &a. While reading the value of a variable for taking input
using scanf, its address is needed to store the value; that’s why in scanf the address is passed as an argument.
For example, to take as input the value of the integer variable a, we have to write scanf("%d", &a) to tell
the compiler that an integer has to be scanned in the decimal number system ("%d" means that) and stored
at the address &a.
Apart from the above-mentioned attributes, every variable has a scope and an extent in its corresponding
code. Its scope describes the part or block of code where it can be used. The extent describes its lifetime in
the execution of the code, i.e., the duration for which it has a meaningful value. The scope of a variable is
actually a property of the name of the variable, and the extent is a property of the storage location of the
variable. Scope is an important part of the name resolution of a variable. Because two variables (in the same
code) may have the same name but with different scopes. For example, a variable with the name x can be
declared and used in one for loop, and another variable with the same name x may be declared in another
for loop. They will be assigned different memory locations and will work therefore without any conflict.
There are certain reserved words, called keywords, that have predefined meanings in C. These keywords
cannot be used as variable names. They are listed in Table 2.1. Note that the keywords are all written
in lowercase only. Since uppercase and lowercase characters are not equivalent in programming language,
it is not illegal to write a variable name as an uppercase keyword. However, this is considered a poor
programming practice.
12
Chapter 2. Variables and expressions 13
A handful of basic data types are defined in C language (Table 2.2). Clearly, all the above data types
basically store numbers. It should be understood that the data type char also stores just a number, which
actually represents a single character.1 Since the number of characters is limited, one byte is enough to
represent all of them in a unique manner. Unless specified as unsigned, each of the above data types is
signed. For example, the declaration unsigned int a specifies that the variable a is unsigned, i.e., all its 32
bits are used to represent its absolute value. If it is not unsigned, then its leftmost bit is used to represent
the sign and hence termed as sign bit. If the sign bit is 0, then it is a positive number, else it is negative.
In case of char, there are 8 bits; by default, it is considered unsigned, and hence its value ranges from
0000 0000 “ 0 to 1111 1111 “ 255. If it is declared as unsigned, then this range is same. However, if it
is declared as signed, e.g., signed char c, then the leftmost bit is used to fix its sign, and so its value
ranges from 1000 0000 “ ´128 to 0111 1111 “ 127. Know that the magnitude of 1000 0000 is given by
its 2’s complement as follows: 1’s complement of 1000 0000 “ 0111 1111 (by reversing each bit); now, the
2’s complement is given by adding 1 to the 1’s complement, thus giving 0111 1111 + 1 “ 1000 0000 “ 128;
taking the negative sign into account, we get ´128 in the decimal number system.
For every other basic data type, the default specifier is signed. Explicit declaration has to be done to
make it unsigned. For example, int a means a can be a positive or negative integer, and since it uses 32 bits,
its range is from ´231 to 231 ´ 1, which is ´2,147,483,648 (1 followed by 31 0’s in binary) to 2,147,483,647
(0 followed by 31 1’s in binary). If during execution of the code, the value of a goes out of this range, there
could be faulty output. If the declaration is unsigned int a, then the variable a can take only non-negative
integer values from 0 (32 0’s in binary) to 232 ´ 1 “ 4,294,967,295 (32 1’s in binary).
Apart from the above basic data types, there is also a special type specifier named void, which indicates
that no value is available. It is used to specify the data type returned by a function, which we shall see later.
1 In this context, it should also be well-understood that all data in the computer are essentially binary numbers or binary
strings; the data may be simply a text or an image or an audio or a video or any other entity.
©Partha Bhowmick
14 Chapter 2. Variables and expressions
We also have another useful data type called pointer that can store the memory-address of any variable.
This will also be discussed later.
Using the basic data types, a programmer can obtain new data types called derived data types. These
include string, array, structure, and union, which will come up in later chapters.
2.3 Constants
Constants are broadly of two types: numeric and character. A numeric character is either integer or real.
Character constant means either a single character or a string of characters. A single character is specified
within single quotes, e.g., ’0’, ’y’, ’+’. A string is specified within double quotes. Some typical examples
are as follows.
Integer constants can also be written in hexadecimal number system. A hexadecimal integer must begin
with 0x or 0X and then contain one or more hex digits. The hex digits are 0 through 9 and a through f
(or A through F). The six letters a through f (or A through F) represent the decimal numbers 10 through 15,
respectively. Following are some hexadecimal numbers: 0x, 0xA, 0X2D, 0xf5a; check that their respective
values in decimal number system are 0, 10, 45, 3930.
There are also multiple ways of representing a real number. For example, 5 ˆ 104 can be represented by
any of the following floating-point constants: 50000, 5e4, 5e+4, 5E4, 5.0e+4, .5e5, 50E3, 50.E+3, 500e2.
2.4 Statements
In C language, a statement is a complete instruction that tells the computer to perform a specific task.
Statements are the building blocks of a program and typically end with a semicolon (;). Statements can
include operations like assigning values to variables, controlling the flow of execution, or calling functions.
Following are some examples.
Note that the sign of equality (=) is used to assign values to variables. Assignment is an operation, and
hence = is called the assignment operator. The left of = is termed as l -value. In x = a + b/2, the l -value
refers to the value of x. An l -value has a definite address in memory during execution. On the contrary,
whatever occurs at the right side of = is referred to as the r -value. In the last example, it corresponds to
the expression a + b/2. It has no address of its own, although the variables a and b have definite addresses
in the memory during the execution.
©Partha Bhowmick
Chapter 2. Variables and expressions 15
Types of l -value and r -value should preferably be the same; e.g., either both are integers or both are
real. If not, the type of the r -value will be converted to the type of the l -value, and then assigned to it.
Consider the following example.
double a;
a = 2*3;
In the assignment statement, the type of r -value is int because 2 and 3 are both integers, and so its value
is 6. However, since the type of l -value is double, the value stored for a is real, i.e., 6.0.
Now, consider another example:
int a;
a = 2*3.4;
Here, the type of r -value is real and the value is 6.8. But, since the type of l -value is int, so its integer part,
i.e., 6, is stored in a.
©Partha Bhowmick
16 Chapter 2. Variables and expressions
For operators of the same priority, evaluation is from left to right as they appear. For example, a*-b+d%e-f
means a*(-b)+(d%e)-f. Parenthesis may be used to change the precedence of operator evaluation.
While working with expressions, it is the responsibility of the programmer to write an expression in the
correct form. For example, 1.0 / 3.0 * 3.0 will produce the value 0.999999, while 1.0 * 3.0 / 3.0 will
produce the value 1.000000.
The value of c will be the integer obtained by dividing 10 by 4. In the integer domain, this value is the
quotient of dividing 10 by 4, and so c receives the value 2. The value of x will be the integer obtained by
dividing 10 by 4, which is 2 again, and but mapping it to the real domain gives 2.0; hence, x gets the value
2.0. By type casting, we can store the value 2.5 to x. It can be done by:
x = (float)a / b;
Since the right side is a mix of real ((float)a) and integer (b), the operation is done in the real domain.
As another example, the following code won’t produce correct output when a+b is odd.
int a, b;
scanf("%d%d", &a, &b);
avg = (a + b)/2;
printf("%f\n", avg);
©Partha Bhowmick
Chapter 2. Variables and expressions 17
1. Arithmetic expression: b+c is an arithmetic expression in which the addition operator + acts on the
operands b and c. On assigning the values 2 and 3 to b and c respectively, the value of the expression
b+c becomes 5.
Although it may sound strange to one who is new to computer programming, a=b+c is also an
arithmetic expression with two operators, namely addition (+) and assignment (=). Here, the value of
the expression b+c is computed first, and that value is assigned to a, which eventually becomes the value
of the whole expression a=b+c. For example, on assigning the values 2 and 3 to b and c respectively,
the variable a gets the value 5, and hence the value of the expression a=b+c becomes 5. Note that the
value of a=b+c is same as the value of a, because in C language, due to the assignment operator, such
an expression always gets the value of the variable (i.e., operand) to the left side of the assignment
operator. It is the l -value and refers to the value of a, as explained in §2.4.
The value of an expression is important because that expression may be used as an operand in
another expression. For example, d=(a=b+c)+1 is an expression composed with the previous expression,
a=b+c. On assigning the respective values 2 and 3 to b and c, the value of a becomes 5, which means
the expression a=b+c receives the value 5, which gives 6 as the value of d, and this finally sets the value
of the whole expression d=(a=b+c)+1 to 6.
2. Relational expression: Consider the relational operator < used in the relational expression a<b. The
value of a<b will be 1 if a is less than b, and 0 otherwise.
A relational operator is used to compare an operand with another. The operand may be a variable
or a non-variable expression. For example, in the expression a != b the operand != is used between
two variables; it yields 1 if a and b are unequal, and yields 0 if not. On the contrary, in the expression
a != b*b+1, the same operand works between a variable and an expression.
3. Logical expression: Here we need to be careful — any nonzero integer means True, and only the
integer 0 means False. For example, a&&b evaluates to 0 if and only if at least one of a and b is 0. On
the contrary, a||b evaluates to 0 if and only if both a and b are 0. Equivalently, a&&b evaluates to 1 if
and only if both a and b are nonzero, whereas a||b evaluates to 1 if and only if at least one of a and b
is nonzero. The notation && denotes the logical and operator, whereas || denotes the logical or.
The notation ! denotes the logical not; it is a unary operator and hence acts on a single operand.
For example, the expression !a evaluates to 0 if and only if a is nonzero. That is, if a is any number
other than 0, then !a evaluates to 0, and it evaluates to 1 only if a is 0.
Clearly, since a logical expression basically works with the truth value of the associated operands,
the operands can be any real number.
Consider a practical expression: ((!weekday && hobby) || (weekday && study)). It has three
variables working as operands, namely weekday, hobby, and study; and it has four logical operators
in total. With weekday “ 0, hobby “ 1, study “ 0, it evaluates to (1 || 0) “ 1. With weekday “
1, 2, . . . , 6, it evaluates to 0 if study “ 0, no matter the value of hobby — can you check and argue?
4. Bitwise expression: An expression where bitwise operators are used for computation at the bit level.
It makes the computation fast. Bitwise operators work with integer -valued operands and cannot be
applied to non-integer real numbers such as float or double. The bitwise binary operators are & (and),
| (or), ^ (xor), << (left shift), and >> (right shift); the bitwise unary operator is ~ (1’s complement).
©Partha Bhowmick
18 Chapter 2. Variables and expressions
For example, a<<k means a left shift of the bits of a by k places. Say a has the value 5; so, its
8-bit representation is 00000101; then a<<3 means a left shift of the bits of a by 3 places, appending
three 0’s at the right side so that it again has 8 bits. This gives 00101000, thereby changing the value
of a to 5 ˆ 23 “ 40. On the contrary, a>>3 does a right shift by 3 bits, changing the value of a to
00000000 “ 0. Similarly, a>>1 applies a right shift by 1 bit, changing the value to 00000010 “ 2; and
a>>2 applies a right shift by 2 bits, changing the value to 00000001 “ 1. That is, bitwise left shift by k
bits is equivalent to multiplying by 2k , and bitwise right shift by k bits is equivalent to dividing by 2k .
The following code shows uses of the bitwise operators. Note that if the leftmost bit is 1, then it is
a negative number. For example, the 1’s complement of a (i.e., ~a) is a negative number. To print its
unsigned value, the format is %u. To print its signed value, the format is %d; and that value is given by
the 2’s complement of ~a, which, in turn, is given by the 1’s complement of ~a (i.e., a) plus 1.
1 #include <stdio.h>
2
3 int main() {
4 int a = 20; // 00...0 0001 0100 = 20
5 int b = 13; // 00...0 0000 1101 = 13
6
17 return 0;
18 }
1. rOne-variable integer expressionss Given an integer a, compute and print the values of the following
expressions: ´a, 2a ´ 3, 2a2 ´ 3a ´ 4.
1 #include <stdio.h>
2
3 int main(){
4 int a;
5
13 return 0;
14 }
©Partha Bhowmick
Chapter 2. Variables and expressions 19
2. rTwo-variable integer expressionss Given two integers a and b as input, compute and print the val-
ues of the following expressions: a ` b, ´a ´ 2b ` 3, ´2ab, 1 ´ 2apb ´ 3q.
1 #include <stdio.h>
2
3 int main(){
4 int a,b;
5
14 return 0;
15 }
3. rLeft shifts Given two integers a and b as input, compute and print the value of 2a ` 4b without using
any multiplication.
1 #include <stdio.h>
2
3 int main(){
4 int a,b;
5
9 a <<= 1;
10 b <<= 2;
11
14 return 0;
15 }
4. rReal-domain expressionss Compute and print the values of the xy and x1 ` y1 in floating point, where
x, y are nonzero real numbers given as input. The value of the 1st expression should be printed up to
the 6th decimal place, and that of the 2nd expression up to the 3rd decimal place. For example, if x “ 2
and y “ 3, then the printed values should be 0.666667 and 0.833, respectively.
1 #include <stdio.h>
2
3 int main(){
4 float x, y, z;
5
©Partha Bhowmick
20 Chapter 2. Variables and expressions
10
11 return 0;
12 }
5. rInteger-to-real maps Compute and print the values of the following expressions in floating point
(rounded off to 3rd decimal place), where a, b are positive integers given as input.
˜c 1
¸ a`b
a 1 1
a ` b, , ` .
b a b
For example, if a “ 2 and b “ 3, then the respective printed values will be 5.000, 0.667, 0.982.
As the values should be real, the computations should be in the real domain. You should use the math
library (math.h) and compile your code as follows: gcc <input file> -lm
1 #include <stdio.h>
2 #include <math.h>
3
4 int main(){
5 int a, b;
6
14 return 0;
15 }
6. rPre-increment and post-increments Write the new values obtained after the following statements
are executed one after the other.
int a, b, x; ÝÑ a, b, x all have ‘garbage values’
a = 10, b = 20, x; ÝÑ a gets 10, b gets 20
x = 50 + ++a; ÝÑ a = 11, x = 61 (a is first incremented and then added)
x = 50 + a++; ÝÑ x = 61, a = 12 (a is first added and then incremented)
x = a++ + --b; ÝÑ b = 19, x = 31, a = 13 (b is first decremented and then added)
x = a++ - ++a; ÝÑ x = -2, a = 15 (a++ is 13 as operand, ++a is 15 as operand)
In the last statement, there is a side effect: while calculating some values, something else get changed.
It is always better to avoid such complicated statements.
7. rOne-variable integer expressionss Given an integer a, compute and print the values of the following
expressions: ´a, 2a ´ 3, 2a2 ´ 3a ´ 4.
1 #include <stdio.h>
2
3 int main(){
4 int a;
5
©Partha Bhowmick
Chapter 2. Variables and expressions 21
13 return 0;
14 }
8. rTwo-variable integer expressionss Given two integers a and b as input, compute and print the val-
ues of the following expressions: a ` b, ´a ´ 2b ` 3, ´2ab, 1 ´ 2apb ´ 3q.
1 #include <stdio.h>
2
3 int main(){
4 int a,b;
5
14 return 0;
15 }
9. rMinimum multiplicationss Given two integers a and b as input, compute and print the values of
a2 b, a2 b2 , and a2 b4 , using at most 6 multiplications for all three of them in total, without using any
extra variable, and without using the math library.
1 #include <stdio.h>
2
3 int main(){
4 int a,b;
5
9 a = a*a;
10 printf("Answers = %d, ", a*b);
11 b = b*b;
12 printf("%d, ", a*b);
13 b = b*b;
14 printf("%d.\n", a*b);
15
16 return 0;
17 }
10. rLeft shifts Given two integers a and b as input, compute and print the value of 2a ` 4b without using
any multiplication.
1 #include <stdio.h>
2
3 int main(){
4 int a,b;
©Partha Bhowmick
22 Chapter 2. Variables and expressions
9 a <<= 1;
10 b <<= 2;
11
14 return 0;
15 }
11. rReal-domain expressionss Compute and print the values of the xy and x1 ` y1 in floating point, where
x, y are nonzero real numbers given as input. The value of the 1st expression should be printed up to
the 6th decimal place, and that of the 2nd expression up to the 3rd decimal place. For example, if x “ 2
and y “ 3, then the printed values should be 0.666667 and 0.833, respectively.
1 #include <stdio.h>
2
3 int main(){
4 float x, y, z;
5
11 return 0;
12 }
12. rAverages Read in three integers and print their average as a real number.
1 #include <stdio.h>
2
3 int main(){
4 int a, b, c;
5 printf("Enter three integers: ");
6 scanf("%d%d%d", &a, &b, &c);
7 printf("Average = %f\n", ((float)(a+b+c))/3);
8 return 0;
9 }
13. rArea of a triangles Read in the coordinates (as double-precision real numbers) of three points on
xy-plane, and print the area of the
atriangle formed by them.
You can use the Heron’s formula sps ´ aqps ´ bqps ´ cq, where s “ 21 pa ` b ` cq, and a, b, c denote the
lengths of three sides.
You can use sqrt function of math.h library to compute the square root, but should not use any other
function.
1 #include <stdio.h>
2 #include <math.h>
3
4 int main(){
©Partha Bhowmick
Chapter 2. Variables and expressions 23
14 a = sqrt((x2-x3)*(x2-x3) + (y2-y3)*(y2-y3));
15 b = sqrt((x1-x3)*(x1-x3) + (y1-y3)*(y1-y3));
16 c = sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
17
18 s = (a+b+c)/2;
19 area = sqrt(s*(s-a)*(s-b)*(s-c));
20 printf("Area = %f\n", area);
21
22 return 0;
23 }
14. rCompound interests Read in the principal amount P, the interest rate R in percentage, the number
of years N, and print the compound interest C earned after N years. Read P and R as floating-point
numbers, and N as an integer. The compound interest C should be printed as the nearest whole number.
For example, if P = 100, R = 10, N = 7, then the value of C is Rs. 94.871712, which should be printed as
Rs. 95.
You can use pow function of math.h.
1 #include <stdio.h>
2 #include <math.h>
3
4 int main(){
5 float P, R, C;
6 int N;
7 printf("Enter P: ");
8 scanf("%f", &P);
9 printf("Enter R: ");
10 scanf("%f", &R);
11 printf("Enter N: ");
12 scanf("%d", &N);
13
14 C = P*pow((double)(1+R/100.0), (double)N) - P;
15 printf("Compound interest = Rs. %0.0f\n", C);
16
17 return 0;
18 }
15. rOne-variable logical expressionss Compute and print the logical value of !a, where a is any integer
given as input. You should check (and know) that !a “ 1 if and only if a “ 0. That is, !a “ 1 when
a “ 0, and !a “ 0 when a “ 1, ´1, 2, ´2, . . ..
1 #include <stdio.h>
2
3 int main(){
©Partha Bhowmick
24 Chapter 2. Variables and expressions
4 int a;
5
11 return 0;
12 }
16. rMulti-variable logical expressionss Compute and print the logical values of the following expres-
sions, where a, b, c are the integers given as input.
(!a) && (!b) && c
(a && b) || (!c)
1 #include <stdio.h>
2
3 int main(){
4 int a, b, c;
5
9 printf("Logical value of (!a) && (!b) && c = %d\n", (!a) && (!b) && c);
10 printf("Logical value of (a && b) || (!c) = %d\n", (a && b) || (!c));
11
12 return 0;
13 }
n
17. rSum of seriess Without using any loop, compute and print the value of i, where n is a positive
ř
i“1
integer given as input.
1 #include <stdio.h>
2
3 int main(){
4 int n;
5
10 return 0;
11 }
18. rNumber of digitss Read in a positive integer n. Assume that it has at most 4 digits. Without using
any conditional such as if-else or switch-case, print its number of digits. Also, print the expression
you have used to get the answer.
1 #include <stdio.h>
2
3 int main(){
©Partha Bhowmick
Chapter 2. Variables and expressions 25
4 int n;
5 printf("Enter the value of n: ");
6 scanf("%d", &n);
7 printf("Number of digits in n = %d\n", 1 + (n>=10) + (n>=100) + (n>=1000));
8 printf("Expression used: 1 + (n>=10) + (n>=100) + (n>=1000)\n");
9 return 0;
10 }
a && (b || c)
a && ((b || c)==0)
!(a && (b || c))
(a && (b || c)) == 0
(a && (b || c)) == 1
(!a) && ((!b) || c)
((a == 1) || ((b == 0) && (c == 1))) || ((a == 0) || ((b == 1) && (c == 0)))
((0 <= a) && (a >= 10) && (11 <= b) && (b <= 20) && (15 <= a+b) && (c >= 5))
2. rRight shifts Given two integers a and b as input, compute and print the value of a2 ` 4b without
X \ X \
using division. (Observe that 2 and 4 are the respective quotients obtained when a and b are divided
Xa\ Xb\
by 2 and 4 respectively.)
3. rInteger-to-real maps Compute and print the values of the following expressions in floating point
(rounded off to 3rd decimal place), where a, b, c are integers given as input. You can use the math
library.
ˆ ˙ ?1
a b c 2 3 2 3
? ? 2 5 5
a ` 2b ` 3c, ` ` , p1.25a ` 2.75a ` 5.625a qpb {p1.25 ` c qq, 2a ` 3b ` c3 .
2 2 3 4
As the values should be real, the computations should be in the real domain. For example, if a “ 3, b “
2, c “ 1, then the value of a2 ` 2b ` 3c would be 1.500 ` 1.000 ` 0.333 “ 2.833.
4. rLargest fractions Given as input six positive integers a, b, c, d, e, f , find a/the largest among ad , eb , fc ,
using only integer computations.
5. rSum of seriess Without using any loop, compute and print the values of the following sums, where
n is a positive integer given as input.
n
ÿ n
ÿ n
ÿ
i2 , i3 , pi ` 1qpi ` 2qpi ` 3q.
i“1 i“1 i“1
©Partha Bhowmick
3 | Conditionals
Conditionals or conditional expressions are basically expressions used in codes for taking required decisions.
They have two possible constructs: (i) if or if-else and (ii) switch-case. The if construct means if
some condition is true, then do something. The if-else construct means if some condition is true, then do
something; otherwise do whatever is mentioned after else. The if-else construct can be extended to build
a logical chain of if-else, but should be carefully coded. The two curly braces, i.e., { and }, are used to
fix the logic.
Complications in the coding logic are often handled using switch-case or a combination of if-else
and switch-case constructs. In the switch-case construct, out of multiple cases, every particular case is
handled based on the value of a single expression in the argument of switch. The value of that expression
is computed only once and it is compared with the value of each case one by one. If there is a match, then
the block of code associated with only that case is executed, rest are not. The break statement breaks out
of the entire switch block. The default statement is optional, and its code is executed only if there is no
match with any case in that switch block.
Examples
1. Suppose weekday is an integer in r0, 6s, where 0 represents Sunday, 1 represents Monday, and so on.
Suppose that on Sunday, everyone wants to enjoy a movie. Its construct will be:
if (!weekday)
printf("Go for a movie!\n");
Now, suppose that movies are restricted other than on Sunday. Then its construct will be:
if (weekday)
printf("Try to avoid movies!\n");
Now, suppose that movies are prescribed on Sunday but music is prescribed for everyday. Then its
construct will be:
if (!weekday)
printf("Go for a movie or listen to music!\n");
else
printf("Avoid movie and listen to music!\n");
2. To complicate the above example, suppose that you need to write a code that will suggest for seeing a
movie on Sunday only, playing some sports on Monday, Wednesday, and Friday, going to a restaurant
on Tuesday, and visiting the library on Thursday and Saturday. Since there are several cases, they are a
little difficult to code using the if-else construct, but quite easier using switch-case, as shown below.
switch (weekday){
case 0:{
printf("It’s Sunday! Go for a movie!\n");
break;
26
Chapter 3. Conditionals 27
};
case 1: case 3: case 5:{
printf("Go for sports!\n");
break;
};
case 2: {
printf("Enjoy your favorite food in some restaurant!\n");
break;
};
default:{ // all other cases
printf("Visit the library!\n");
break;
};
} // end switch
1. rLarger fractions Given as input four positive integers a, b, c, d, find a/the larger between a
c and d,
b
1 #include <stdio.h>
2
3 int main(){
4 int a, b, c, d;
5
14 return 0;
15 }
16
17 /* Examples:
18
2. rPrime digitss User supplies a positive integer having value less than 100. Find and print its prime
digits if any. For example, in 47, the prime digit is 7.
1 #include <stdio.h>
2
3 int main(){
4 int n, a, b, k = 0; // k = no. of primes
©Partha Bhowmick
28 Chapter 3. Conditionals
9 a = n/10;
10 b = n - 10*a;
11
12 printf("Prime digits:");
13
24 if(k>0)
25 printf(".\n");
26 else
27 printf("None.\n");
28
29 return 0;
30 }
3. rLine intersections There are two straight lines ax`by `c “ 0 and px`qy `r “ 0, where a, b, c, p, q, r
are all integers and given as input. (a) Check whether they are parallel, and if not, (b) find their point
of intersection. Its coordinates should be printed up to the 3rd decimal place (using %0.3f instead of
%f in printf). 2024S
1 #include<stdio.h>
2
3 int main(){
4 int a, b, c, p, q, r;
5 float x, y;
6
7 printf("Enter a, b, c: ");
8 scanf("%d%d%d", &a, &b, &c);
9 printf("Enter p, q, r: ");
10 scanf("%d%d%d", &p, &q, &r);
11
12 if(q*a == b*p)
13 printf("Lines are parallel.\n");
14 else{
15 x = (float)(-q*c+b*r)/(q*a-b*p);
16 y = (float)(-p*c+a*r)/(p*b-a*q);
17 printf("Point of intersection = (%0.3f, %0.3f)\n", x, y);
18 }
19
20 return 0;
21 }
©Partha Bhowmick
Chapter 3. Conditionals 29
4. rOne is sum of twos Read in three integers and print a message if any one of them is equal to the
sum of the other two.
1 #include <stdio.h>
2
3 int main() {
4 int a, b, c;
5
9 if (a == b + c) {
10 printf("a is equal to the sum of b and c.\n");
11 } else if (b == a + c) {
12 printf("b is equal to the sum of a and c.\n");
13 } else if (c == a + b) {
14 printf("c is equal to the sum of a and b.\n");
15 } else {
16 printf("None of the integers is equal to the sum of the other two.\n");
17 }
18
19 return 0;
20 }
5. rRoots of quadratic equations Read in the coefficients a, b, c of the quadratic equation ax2 `bx`c “
0, and print its roots nicely. For complex roots, print in x ` iy form.
1 #include <stdio.h>
2 #include <math.h>
3
4 int main() {
5 float a, b, c;
6 float discriminant, realPart, imaginaryPart, root1, root2;
7
8 // Read coefficients a, b, c
9 printf("Enter coefficients a, b, and c: ");
10 scanf("%f %f %f", &a, &b, &c);
11
©Partha Bhowmick
30 Chapter 3. Conditionals
35 return 0;
36 }
6. rOperation selections Create a simple calculator program that takes two numbers and an operator
(+, -, *, /, %) as input and performs the corresponding operation using a switch-case statement.
1 #include <stdio.h>
2
3 int main() {
4 char operator;
5 float num1, num2, result;
6
©Partha Bhowmick
Chapter 3. Conditionals 31
41 printf("Invalid operator!\n");
42 return 1;
43 }
44
7. rVowel or consonants Develop a program that takes a single character as input and determines
whether it is a vowel or a consonant using a switch-case statement.
1 #include <stdio.h>
2
3 int main() {
4 char ch;
5
9 switch (ch) {
10 case ’a’:
11 case ’e’:
12 case ’i’:
13 case ’o’:
14 case ’u’:
15 case ’A’:
16 case ’E’:
17 case ’I’:
18 case ’O’:
19 case ’U’:
20 printf("%c is a vowel.\n", ch);
21 break;
22 default:
23 printf("%c is a consonant.\n", ch);
24 }
25
26 return 0;
27 }
8. rDay of the weeks Write a program that takes the day of the week as an input (1 for Monday, 2 for
Tuesday, etc.). If the day is Saturday or Sunday, print Weekend. For other days, print the name of
the day. Additionally, check if the day is a special day (e.g., Wednesday) using if-else, and print an
appropriate message.
1 #include <stdio.h>
2
3 int main() {
4 int day;
5
9 switch (day) {
©Partha Bhowmick
32 Chapter 3. Conditionals
10 case 1:
11 printf("Monday\n");
12 break;
13 case 2:
14 printf("Tuesday\n");
15 break;
16 case 3:
17 printf("Wednesday\n");
18 break;
19 case 4:
20 printf("Thursday\n");
21 break;
22 case 5:
23 printf("Friday\n");
24 break;
25 case 6:
26 case 7:
27 printf("Weekend\n");
28 return 0;
29 default:
30 printf("Invalid day\n");
31 return 0;
32 }
33
41 return 0;
42 }
9. rMarks to grades Suppose that you have to print the grade of a student, with 90–100 marks getting
EX, 80–89 getting A, 70–79 getting B, 60–69 getting C, 50–59 getting D, 40–49 getting P, and less than
40 getting F. Read in the marks of a student and print his/her grade. You should use switch-case.
1 #include <stdio.h>
2
3 int main() {
4 int marks;
5 char grade;
6
©Partha Bhowmick
Chapter 3. Conditionals 33
17 case 8:
18 grade = ’A’;
19 break;
20 case 7:
21 grade = ’B’;
22 break;
23 case 6:
24 grade = ’C’;
25 break;
26 case 5:
27 grade = ’D’;
28 break;
29 case 4:
30 grade = ’P’;
31 break;
32 default:
33 grade = ’F’;
34 }
35
43 return 0;
44 }
1. rLargest fractions Given as input six positive integers a, b, c, d, e, f , find a/the largest among ad , eb , fc ,
using only integer computations.
2. rLargest elements Given as input five numbers a, b, c, d, e, find a/the largest among them using at
most four comparisons. You can use an extra variable.
3. rTemperature alert systems Create a program that takes the current temperature as input and
provides a message based on the following conditions:
• Above 100˝ F: "Heat Alert!"
• 85˝ F to 100˝ F: "Warm Weather"
• 60˝ F to 84˝ F: "Pleasant Weather"
• 32˝ F to 59˝ F: "Cool Weather"
• Below 32˝ F: "Cold Alert!"
Use if-else to evaluate the temperature and display the corresponding message.
4. rTraffic light simulations Create a program that simulates a traffic light. The program should take a
character input (’r’ or ’R’ for Red, ’y’ or ’Y’ for Yellow, ’g’ or ’G’ for Green) and display a message
indicating whether to stop, slow down, or go. Use a switch-case to handle the different light colors.
©Partha Bhowmick
4 | Loops
?
Loops are needed
? ? to perform repeated tasks of same nature. For example, to compute the value of 1 ` 2 `
3 ` ¨ ¨ ¨ ` 99, we have to compute the square root of a number, 98 times, and so a loop will be useful. A
loop essentially executes a block of code as long as a specified expression is true. The specified expression is
treated as a Boolean condition.
Loops are of three types: while loop, do-while loop, and for loop. They are all equivalent in the sense
that one can be used in place of another, but sometimes one is a little more convenient than the others while
writing the code.
The general syntax of the while loop and the do-while loop are as follows. As mentioned above, the
expression written inside while(...) is treated as a Boolean condition. A loop iterates till the condition
is true.
? ? ?
The above two loops for the specific problem of computing the value of 1 ` 2 ` 3 ` ¨ ¨ ¨ ` 99 are
written below. Notice that the condition for either of these two loops is i < 100, which is just an expression
(as explained earlier in Section 2.5). The value of this expression is nonzero (i.e., true) if and only if i is
less than 100. In every iteration, the value of i is increased by unity. So, here the variable i acts as the
loop variable and determines how many times the loop will iterate. Check that in this example, the loop
iterates for 98 times; and when the value of i becomes 100, it terminates. This happens for both the while
loop and the do-while loop.
int i = 1; int i = 1;
double sum = 1.0; double sum = 1.0;
while (i < 100){ do{
i++; i++;
sum += sqrt((double)i); sum += sqrt((double)i);
} } while (i < 100);
34
Chapter 4. Loops 35
©Partha Bhowmick
36 Chapter 4. Loops
Examples: The following code finds the smallest number n such that n! exceeds 1000. See how it uses a
break statement.
int fact = 1, n = 1;
while(1){
fact = fact * n;
if (fact > 1000){
printf ("Factorial of %d exceeds 1000", n)
break; // breaks the while loop
}
i++ ;
}
The following code goes on adding positive integers until a 0 is encountered, ignoring all negative
numbers that appear in between. See how it uses break and continue statements.
#include <stdio.h>
int main(){
int i, j;
for (i = 10; i <= 20; i++){ // outer loop
for (j = 10; j <= 20; j++){ // inner loop
printf("%3d\t", i * j);
}
printf("\n");
}
return 0;
In the outer loop, the variable i iterates from 10 to 20, representing the rows of the multiplication table.
In the inner loop, for each iteration of the outer loop, the variable j iterates from 10 to 20, representing
©Partha Bhowmick
Chapter 4. Loops 37
the columns. After the multiplication, the product of i and j is calculated and printed, with each value
separated by a tab (\t) to format the table neatly. After each row is printed, the program moves to the next
line using \n. The output looks as follows.
100 110 120 130 140 150 160 170 180 190 200
110 121 132 143 154 165 176 187 198 209 220
120 132 144 156 168 180 192 204 216 228 240
130 143 156 169 182 195 208 221 234 247 260
140 154 168 182 196 210 224 238 252 266 280
150 165 180 195 210 225 240 255 270 285 300
160 176 192 208 224 240 256 272 288 304 320
170 187 204 221 238 255 272 289 306 323 340
180 198 216 234 252 270 288 306 324 342 360
190 209 228 247 266 285 304 323 342 361 380
200 220 240 260 280 300 320 340 360 380 400
©Partha Bhowmick
38 Chapter 4. Loops
1 #include <stdio.h>
2
3 int main(){
4
13 return 0;
14 }
See that in the expression ’a’+i, ’a’ is a character, whereas i is an integer. The operator + is used to
add the ASCII value (which is always an integer) of ’a’ with the value of i. Consequently, the value of
’a’+i is also an integer. The same holds for the expression ’A’+i as well. The overall output will be
as follows:
a: 97 b: 98 c: 99 d: 100 e: 101 f: 102 g: 103 h: 104 i: 105
j: 106 k: 107 l: 108 m: 109 n: 110 o: 111 p: 112 q: 113 r: 114
s: 115 t: 116 u: 117 v: 118 w: 119 x: 120 y: 121 z: 122
A: 65 B: 66 C: 67 D: 68 E: 69 F: 70 G: 71 H: 72 I: 73
J: 74 K: 75 L: 76 M: 77 N: 78 O: 79 P: 80 Q: 81 R: 82
S: 83 T: 84 U: 85 V: 86 W: 87 X: 88 Y: 89 Z: 90
2. rSums Given n real numbers from the keyboard, find their sum. User input is n and the numbers.
1 #include <stdio.h>
2
3 int main(){
4 int i, n;
5 float x, sum;
6
7 printf("Enter n: ");
8 scanf("%d", &n);
9
18 return 0;
19 }
©Partha Bhowmick
Chapter 4. Loops 39
3. rReading characterss Read in characters until the n character is typed. Count and print the number
of lowercase letters, the number of uppercase letters, and the number of digits entered.
1 #include <stdio.h>
2
3 int main() {
4 char ch;
5 int lower_count = 0, upper_count = 0, digit_count = 0;
6
21 return 0;
22 }
n
4. r5th-power sums Given a positive integer n, compute the value of k 5 , without using math.h.
ř
k“1
1 #include <stdio.h>
2
3 int main(){
4 int k, n;
5 long long int sum;
6
7 printf("Enter n: ");
8 scanf("%d", &n);
9
16 return 0;
17 }
5. rBit-lengths Given a positive integer n, determine the integer ` such that 2`´1 ď n ă 2` . Here, ` is
called the bit-length of n.
1 #include <stdio.h>
2
3 int main(){
4 int n, i=1, len=0;
5
©Partha Bhowmick
40 Chapter 4. Loops
9 while(i<=n){
10 i <<= 1; len++;
11 }
12 printf("Bit-length = %d\n", len);
13
14 return 0;
15 }
6. rDecimal to binary, 8 bitss Given an integer n P r0, 255s, print its 8-bit binary representation. You
can use at most 3 integer variables and no character variables.
1 #include <stdio.h>
2
3 int main(){
4 int n, i, j;
5
19 return 0;
20 }
7. rGCDs Given two positive integers a, b, compute their GCD. Use the fact that if a ď b, then
if a “ 0
"
b
gcdpa, bq “ (4.1)
gcdpb mod a, aq otherwise.
Since no function other than main() is allowed in this section, you have to do it iteratively using a loop.
1 #include <stdio.h>
2
3 int main(){
4 int a, b, c;
5 printf("\nEnter two positive integers: ");
6 scanf("%d%d", &a, &b);
7 while(a!=0){
8 c = a;
9 a = b%a;
10 b = c;
11 printf("a b c = %7d %7d %7d\n", a, b, c);
12 }
13 printf("GCD = %d\n\n", b);
14 return 0;
15 }
©Partha Bhowmick
Chapter 4. Loops 41
x2 3
8. rex as a finite sums We know that in the infinite sum ex “ 1 ` x ` ` x3! ` ¨ ¨ ¨ , the trailing terms
2!
have negligible values. Given a positive value of x, compute the sum up to its nth term and return that
value as the approximate value of ex as soon as the nth term is found to be less than ε. Also print the
value of n. Needless to say, you should not use the math library.
3 #include <stdio.h>
4
5 int main(){
6 double x, epsilon, ex = 1.0, current = 1.0, previous = 0.0;
7 int n = 1;
8
9 printf("Enter x: ");
10 scanf("%lf", &x);
11 printf("Enter epsilon: ");
12 scanf("%lf", &epsilon);
13
3 #include <stdio.h>
4
5 int main(){
6 int a, b, p, q, r, s, t;
7 printf("Enter b: ");
8 scanf("%d", &b);
9
10 p = b, q = 2*b-1;
11 printf("%d/%d ", b, 2*b-1);
12
©Partha Bhowmick
42 Chapter 4. Loops
1 #include <stdio.h>
2
3 int main(){
4 const int SIZE = 5;
5 int row, col;
6 for (row = 0; row < SIZE; ++row){
7 for (col = 0; col < SIZE; ++col){
8 printf("* ");
9 }
10 printf("\n");
11 }
12 return 0;
13 }
Note that const indicates that the value of SIZE cannot be modified after it is initialized. It is a
constant, so trying to change its value later in the code will result in a compilation error.
The output is shown in Figure 4.1(a).
11. rPrint lower triangles Print the lower triangle of a 5 ˆ 5 square filled up with ’*’. The output is
shown in Figure 4.1(b).
1 #include <stdio.h>
2
3 int main(){
4 const int SIZE = 5;
5 int row, col;
6 for (row = 0; row < SIZE; ++row){
7 for (col = 0; col <= row; ++col){
8 printf("* ");
9 }
10 printf("\n");
11 }
12 return 0;
13 }
12. rPrint upper triangles Print the upper triangle of a 5 ˆ 5 square filled up with ’*’. The output is
shown in Figure 4.1(c).
1 #include <stdio.h>
2
3 int main(){
4 const int SIZE = 5;
©Partha Bhowmick
Chapter 4. Loops 43
* * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * *
(a) square (b) lower triangle (c) upper triangle
Figure 4.1: Output of the codes for printing a square, its lower triangle, and its upper triangle.
Use #define DIFF 0.00001 to terminate the summation when the sum up to nth term differs by less
than DIFF from the sum up to pn ´ 1qst term. Needless to say, you should not use the math library.
©Partha Bhowmick
5 | One-dimensional arrays
Suppose that you have lots of storybooks, poetry books, comic books, etc.,
and also have many other books on different subjects such as Physics, Chem-
istry, Biology, Mathematics, etc. While keeping them in a bookshelf, you will
naturally try to keep them in an organized way so that you can get any book
down from the shelf right away, whenever needed. The same idea is used
when you have to keep many elements in the computer memory. You have
to arrange them in an organized way in the memory so that any of them can
immediately be accessed when the code is running. For this arrangement, the
concept of data structure comes into use. One such data structure is array—it
is just analogous to your bookshelf!
44
Chapter 5. One-dimensional arrays 45
indexes 0 1 2 3 4
main memory (RAM) 7 ´2 6 3 6 main memory (RAM)
8D2C
8D30
8D34
8D38
8D3C
8D2D
8D2E
8D2F
8D31
8D32
8D33
8D35
8D36
8D37
8D39
8D3A
8D3B
8D3D
8D3E
8D3F
8D40
8D41
8D42
8D43
byte addresses
(in hexadecimal)
Figure 5.1: An array with 5 elements having values 7, ´2, 6, 3, 6. Each element is a 4-byte integer, so the total space
allocated in the memory for this array is 4 ˆ 5 “ 20 bytes. Its first and last byte have the addresses 8D2C
and 8D3F, respectively. The address of an element is basically the address of its first byte (expressed as
a hexadecimal number, a typical convention). So, the respective addresses of the five elements here are
8D2C, 8D30, 8D34, 8D38, and 8D3C.
Second, the elements of an array occupy contiguous locations in the memory when the executable file
is executed. As a result, any element of the array can be accessed directly, just by specifying its index.
In particular, a[i] gives the element with the index i in a[]. The direct accessing is possible because the
address of the element with index i easily follows from the address of the element with index 0. The simple
reason is that all elements have the same data type and hence they all take equal amount of space (measured
in bytes) in the memory. Further, every byte in the memory has a unique address, and two contiguous bytes
have consecutive addresses. To see this, suppose that the amount of space for each element is k bytes. As
&a[0] is the address of the element with index 0, the indexes of the subsequent elements are &a[0]+k for
index 1, &a[0]+2*k for index 2, &a[0]+3*k for index 3, and so on. That is, the address of a[i] will be
As an example, see Figure 5.1. Here a[] is an integer array and its 1st element a[0] has the address 8D2C; so,
its 2nd element a[1] has the address 8D2C + 4 “ 8D30, 3rd element a[2] will have the address 8D2C + 2*4
“ 8D34, and so on. If b[] is an array of characters, then &b[i] “ &b[0] + i. If the 1st character of b[]
(i.e., b[0]) has the address AC8F, then its 2nd character b[1] will have the address AC8F + 1 “ AC90, its
3rd character b[2] will have the address AC8F + 2 “ AC91, and so on.
Third, the index need not be just a single variable but can also be an expression whose value is an integer
in the interval r0, n-1s, where n is the number of elements in the array. For example, in an array a[] with
100 elements, a[i+2*j] is a valid element with i and j as two integers, as long as the value of the expression
i+2*j lies in r0, 99s.
Another example could be a[b[i]], where the element b[i] of an array b[] acts as the index of an
element in the array a[]. Such kinds of indexing are sometimes required while writing codes for tricky
problems. For example, see the following code. It takes as input some integers in r0, 9s, stores them in an
array a[], and then prints them in increasing order, avoiding repetitions. It uses an additional array b[],
initialized with all zeros, to count the frequency of each distinct element stored in a[]. Since there will be
at most 10 distinct elements in a[], the size of b[] is set to 10. The trick is that b[a[i]] means how many
times an element a[i] appears in a[].
1 #include <stdio.h>
2
3 int main() {
4 int n, i, j;
5 printf("Enter the number of elements: ");
6 scanf("%d", &n);
7 int a[n], b[10];
8 for (i = 0; i < 10; i++)
9 b[i] = 0;
10
©Partha Bhowmick
46 Chapter 5. One-dimensional arrays
25 return 0;
26 }
27 /*
28 Enter the number of elements: 13
29 Enter the elements (0 to 9): 5 4 7 8 4 0 4 4 0 5 7 8 7
30 Sorted array: 0 4 5 7 8
31 */
type array_name[size];
where type specifies the type of elements (char, int, float, etc.) that will be stored in the array, array_name
denotes the name of the array, and size is the maximum number of elements that can be stored in the array.
Here are some examples:
In the above examples, all three arrays are declared to contain 5 elements each, and their values are initialized.
We can also declare them to contain more elements, say 10 each, but only the first five are initialized. Here
is how:
©Partha Bhowmick
Chapter 5. One-dimensional arrays 47
When you partially initialize an array in C, the uninitialized elements are automatically set to 0 for numeric
arrays and ‘\0’ (null character) for character arrays. The non-initialized as well as the initialized values
can be changed later if needed. Here’s what happens in the above cases:
int a[5], i;
for (i=0; i<5; i++){ // writing to a[]
scanf("%d", &a[i]);
}
int p;
for (i=0; i<5; i++){ // reading from a[]
p = a[i];
}
We cannot use the operator = to assign one array to another; i.e., even if a[] and b[] are two arrays of
same type and same size, then a = b; or a[] = b[]; cannot copy the elements of b[] to a[] (it will give
compilation error). For copying the elements of b[] to a[], we have to use a loop as follows:
int i;
for (i=0; i<n; i++){ // n = number of elements in a[] and in b[]
a[i] = b[i];
}
As a specific example, suppose that there are at most 100 students in a class. Now, consider the problem
of getting their marks and finding how many of them have marks above the average. Clearly, following are
there main tasks:
1. Read the marks as input and write them in an array.
2. Compute the total marks.
3. Count how many students meet the given criterion.
Clearly, Task 1 and Task 2 can be done in a single loop. But Task 3 needs an additional loop after that
because it can be done only after Task 2 is over. That is, it is impossible to write a code for this problem
using a single loop only. (Understand the importance of this observation!) The following code is written
based on this observation. Notice that for Task 1, we are writing into the array using the indexes, while for
Task 2 and Task 3, we are reading from the array using the indexes again. In either case, the integer i is
used as the index variable.
©Partha Bhowmick
48 Chapter 5. One-dimensional arrays
1 #include <stdio.h>
2 int main(){
3 int n, i, k;
4 float marks[100], total, avg;
5 printf("Enter the number of students: ");
6 scanf("%d", &n);
7 printf("Enter their marks: ");
8 for (i=0, total=0; i<n; i++){ // Task 1 and Task 2
9 scanf("%f", &marks[i]);
10 total = total + marks[i];
11 }
12 avg = total/n;
13 for (i=0, k=0; i<n; i++){ // Task 3
14 k += (marks[i] > avg) ? 1 : 0;
15 }
16 printf("Number of students above the average = %d\n", k);
17 }
3 #include <stdio.h>
4 #define SIZE 1000
5
6 int main(){
7 int a[SIZE], n, i, max;
8
9 printf("Enter n: ");
10 scanf("%d", &n);
11 printf("Enter the elements: ");
12
16 max = a[0];
17
©Partha Bhowmick
Chapter 5. One-dimensional arrays 49
19 if (max<a[i])
20 max = a[i];
21
3 #include <stdio.h>
4 #define SIZE 1000
5
6 int main(){
7 int a[SIZE], n, i, j, flag = 1;
8
9 printf("Enter n: ");
10 scanf("%d", &n);
11 printf("Enter the elements: ");
12
25 if(flag)
26 printf("Distinct.\n");
27
28 return 0;
29 }
3. rFibonacci sequences The Fibonacci sequence tf piq : i “ 0, 1, 2, . . .u is defined as
1 // Fibonacci sequence
2
3 #include <stdio.h>
4 #define SIZE 1000
5
6 int main(){
7 int a[SIZE], n, i;
8
©Partha Bhowmick
50 Chapter 5. One-dimensional arrays
9 printf("Enter n: ");
10 scanf("%d", &n);
11 a[0] = 0, a[1] = 1;
12
21 return 0;
22 }
4. rSelection Sort: positive elements, extra arrays Given a positive integer n as the first input, take
in as the next input n positive integers from the user and store them in an array A. Take another
array B of the same size and put the elements of A into B in non-decreasing order. For example, if
A “ r4, 2, 3, 2, 6, 4s, then B “ r2, 2, 3, 4, 4, 6s.
3 #include <stdio.h>
4 #define SIZE 1000
5
6 int main(){
7 int a[SIZE], b[SIZE], n, i, j, max;
8
9 printf("Enter n: ");
10 scanf("%d", &n);
11 printf("Enter the elements (all positive): ");
12
32 return 0;
33 }
5. rArray unions Given two arrays A and B containing m and n integer elements respectively, find the
©Partha Bhowmick
Chapter 5. One-dimensional arrays 51
elements in A Y B, store them in another array C, and print C. Assume that all elements of A are
distinct, and so also for B, although an element of A may be present in B.
1 // Array union
2
3 #include <stdio.h>
4 #define SIZE 1000
5
6 int main(){
7 int a[SIZE], b[SIZE], c[2*SIZE], m, n, i, j, k, flag;
8
9 printf("Enter m: ");
10 scanf("%d", &m);
11 printf("Enter the elements of a[]: ");
12 for(i=0; i<m; i++)
13 scanf("%d", &a[i]);
14
15 printf("Enter n: ");
16 scanf("%d", &n);
17 printf("Enter the elements of b[]: ");
18 for(i=0; i<n; i++)
19 scanf("%d", &b[i]);
20
38 printf("c[] = ");
39 for(i=0; i<k; i++)
40 printf("%d ", c[i]);
41 printf("\n");
42
43 return 0;
44 }
6. rClosest pair in 2Ds Given an integer n ě 2, followed by a sequence of n distinct points with real
coordinates, determine a pair of closest points. You can store the x-coordinates and the y-coordinates
in two arrays.
1 #include<stdio.h>
2 #include <float.h>
©Partha Bhowmick
52 Chapter 5. One-dimensional arrays
4 int main(){
5 float x[100], y[100]; // assuming that there are at most 100 points
6 int n, i, j, p, q;
7 float dcur, dmin; // we consider the squares of distances; need not use math library
8 dmin = FLT_MAX; // the maximum value of a float
9
©Partha Bhowmick
Chapter 5. One-dimensional arrays 53
compute the values of for k “ 0, 1, . . . , n, using the value of n as input. You should do it with a
` n˘
k
single array.
5. (Selection Sort: positive and distinct elements, extra array) Given a positive integer n as the
first input, take in as the next input n positive integers from the user and store them in an array A.
Take another array B of the same size and put the elements of A into B so that they are in increasing
order and all the elements of B are distinct. For example, if A “ r4, 2, 3, 2, 6, 4s, then B “ r2, 3, 4, 6s.
Since the size of B is same as that of A, you have to keep track of the number of elements in B.
6. (Selection Sort) Given a positive integer n as the first input, take in as the next input n integers
from the user and store them in an array A. Without using any extra array, rearrange the elements
in non-decreasing order. For example, if input is A “ r4, ´2, 3, 0, ´2, 6, 4s, then the output will be
A “ r´2, ´2, 0, 3, 4, 4, 6s.
7. (Array intersection) Given two arrays A and B containing m and n integer elements respectively,
find the elements in A X B, store them in another array C, and print C. Assume that all elements of A
are distinct, and so also for B.
8. (Maximum collinearity) Given an integer n ě 2, followed by a sequence of n distinct points with
integer coordinates, determine the maximum number of collinear points. You can store the x-coordinates
and the y-coordinates in two arrays.
©Partha Bhowmick
6 | Functions
3 int fact(int n) {
4 int result = 1;
5 for (int i = 2; i <= n; i++)
6 result *= i;
7 return result;
8 }
9
10 int main(){
11 int n;
12 printf("Enter n: ");
13 scanf("%d", &n);
14 printf("fact(%d) = %d\n", n, fact(n));
15 }
54
Chapter 6. Functions 55
argument types
function name argument names
return
data type float dis(int m, int n){ header = protoype or delclaration
function name
no argument
return
int main(){ header = protoype or delclaration
data type
int a, b;
printf("Enter a, b: ");
scanf("%d%d", &a, &b); body = definition
Figure 6.1: Top: Components of a user-defined function named dis. Notice that dis calls another function, sqrt,
which is defined in the math library.
Bottom: Calling the function dis from main(). Here, dis is the called function, and main() the caller.
4. A function written by the programmer is called user-defined function. It needs to be written when it is
not predefined in any standard library. One such example is to compute the number of permutations of
r elements chosen from n distinct elements, given by the formula P pn, rq “ pn´rq!
n!
. We will discuss in
§6.4 about its two possible implementations, given in Code 6.2 and Code 6.3.
5. A function may take some input arguments or parameters and may return some value. It may be called
multiple times, with same or different arguments. For example, in Figure 6.1, the function dis(...)
is called twice from the function main(). Thus, functions are particularly useful when they need to be
called repeatedly. It makes a smart programming—write once, call again and again. They come handy
while developing a software in a modular fashion. Large codes become easy to read and easy to debug
or revise.
When no value needs to be returned, the return data type is void. When no input argument is required, there
is nothing between the opening and the closing brackets; one such example is the function int main(), shown
in Figure 6.1. Observe that the main() in this case calls the user-defined function float dis(int m, int n)
with two integer variables as arguments.
The header of a function in a code is analogous to the domain of definition of a mathematical function;
©Partha Bhowmick
56 Chapter 6. Functions
e.g., for n!, the domain is the set of positive integers. The body, on the other hand, contains the definition
of the function and its returned value, if any; e.g., for n!, the definition of the function fact in Code 6.1 is
1 ¨ 2 ¨ 3 ¨ ¨ ¨ n and its returned value is an integer. We shall study this again in §6.4.
Let’s now see what happens when a function is called. Consider the previous example (Figure 6.1). In the
first call of the function dis from main(), the arguments are a and b, while in the second, they are a+1
and b.
Let’s see what happens inside the computer when a.out is executed. After compiling the C program to
get the binary executable a.out and running it, the operating system initiates the execution of a.out. This
running instance of a.out, involving both the CPU and the RAM, is referred to as a process.
In many cases, a process involves both RAM and the hard disk. While
running, its code and data may not always be in RAM but might be stored
on the hard disk. Additional elements, such as library files and data files,
may also reside on the hard disk. These are loaded into RAM from the
hard disk as needed to run the process.
(iv) The heap is reserved for dynamic memory allocation (not utilized in this example).
3. Initializing execution:
(i) The entry point of the program, i.e., the first instruction of main(), is determined.
(ii) The CPU begins executing the code from main().
4. Executing main():
©Partha Bhowmick
Chapter 6. Functions 57
(ii) It then waits for user input, which is read by scanf() and stored in a and b.
(iii) The values of a and b are passed to the dis() function, transferring them to the memory
locations of m and n.
5. Calling dis():
(i) The function dis() receives the values of its arguments m and n.
?
(ii) It calculates the distance using the formula m2 ` n2 .
(iii) The result is sent back to main().
(i) main() prints the result of dis(a, b) formatted to three decimal places.
(ii) main() then calls dis() again with a+1 and b.
(iii) The result is printed in the same format.
7. Program termination:
(i) After main() has completed all instructions, the program returns 1 (indicating the termination
status), and the process concludes.
(ii) The operating system reclaims the memory allocated to the process and may allocate it to other
processes.
We should carefully distinguish between the terms ‘prototype’ and ‘definition’, as together they constitute
a complete function. This distinction is illustrated in Figure 6.1 and elaborated upon in the corresponding
discussion. Here, we focus on writing a prototype, which can follow two distinct approaches depending on
the interdependencies of the user-defined functions within the code.
Typically, each function is declared and defined before any of its calling functions. In other words, both
its header (i.e., prototype) and body (i.e., definition) are written together before any function that calls
it. This approach is illustrated earlier in Figure 6.1. See also Code 6.3 for an example of this approach.
Consequently, the main() function usually appears at the end of the program. This method allows the
compiler to identify all function definitions in a single pass through the program file.
However, in cases where functions call each other in an arbitrary manner, it may not be feasible to write
all function definitions before main(). For instance, consider two functions, f() and g(), where main()
calls f(), f() calls g(), and g() in turn calls f(). This scenario creates a paradox: g() must be defined
before f(), and f() must be defined before g(). In such scenarios, functions cannot be written in full
before main(). So, it is effective to write just the prototypes of all functions before main(), regardless of
the order of calls, and then define the functions after main(). This strategy ensures that the compiler is
aware of all functions in advance and can efficiently organize the executable file a.out. This is referred to
as mutual recursion or cyclic recursion, which is related to indirect recursion and discussed in more detail
in §6.9.4.
As a specific example, consider the problem of computing the number of permutations of r elements
chosen from n distinct elements. It is given by the formula P pn, rq “ n!{pn ´ rq!. Our task is to compute the
values of P pn, rq for r P r1, ns, where the value of n is provided by the user. We discuss two versions of the
code to perform this task. In the first version (Code 6.2), the function prototypes are declared separately
before main(), with their definitions appearing later. In the second version (Code 6.3), the prototypes are
not written separately, requiring the functions to be written in full before main(). Further, the fact function
is written before the npr function because npr calls fact. If fact were written after npr, a compilation error
would occur, as a function must be defined before it is called if prototypes aren’t separately provided.
©Partha Bhowmick
58 Chapter 6. Functions
Code 6.2: Computing P pn, rq, with separate declaration of prototypes. n_permute_r_ver1.c
1 #include <stdio.h>
2
6 int main(){
7 int i, n;
8 printf("Enter n: ");
9 scanf ("%d", &n);
10
Code 6.3: Computing P pn, rq, without separate declaration of prototypes. n_permute_r_ver2.c
1 #include <stdio.h>
2
15 int main(){
16 int i, n;
17 printf("Enter n: ");
18 scanf ("%d", &n);
19 for (i=1; i<=n; i+=1){
20 printf ("%d choose %d = %d \n", i, n, npr(n, i));
21 }
22 }
©Partha Bhowmick
Chapter 6. Functions 59
The return statement plays a critical role in function execution by managing control flow within a program.
To understand this, let f be a function that calls a function g. Then, f is said to be the caller function, and
the function g, being invoked by f , is the called function.
In case f is recursive, g may be f also — even then, the discussion here
applies. In that case, the first instance of f refers to “the caller”, while the
second refers to “the called”.
When the function g is called from f , the execution transfers from f to g. Suppose g has one or
more return statements. Upon reaching a return statement in g, the process terminates g and passes the
control back to f , the caller. The program then resumes execution from the point in f where the function
g was originally invoked. Additionally, the return statement in g can return a value to f , which is used in
subsequent operations in f .
The return statement also frees up memory allocated for the local variables and the stack frame of g
(discussed in §6.9.1). Thus, the return statement not only marks the end of g’s execution but also facilitates
control transfer, value return, and memory cleanup, ensuring orderly and efficient program flow.
To summarize, here are the key points:
1. A function with a return type other than void always returns a single value that matches the return
type.
2. If the return type is void, the return statement is optional. In Code 6.4, the function printLine
includes a return statement.
3. It is also logically correct to omit the return statement in a void function, as written in Code 6.5.
4. The body of a function may contain multiple return statements, which can terminate the function’s
execution before it reaches the end of its body. For example, Code 6.6 uses return three times to
determine the largest of three integers.
5. In a value-returning function, return performs two distinct actions:
Code 6.4: Example of a function with return type void. Note that the function includes a return
statement at the end, although it is not required for functions with a void return type.
returnStatement_void.c
1 #include <stdio.h>
2
9 int main() {
10 char c;
11 int n;
12 printf("Enter the symbol and the length of the line: ");
13 scanf("%c%d", &c, &n);
14 printLine(c, n);
15 return 1;
16 }
©Partha Bhowmick
60 Chapter 6. Functions
Code 6.5: Example of the same printLine function used in Code 6.4. The only change here
is exclusion of the return statement, which is also logically and syntactically correct.
returnStatement_void2.c
1 void printLine(char c, int n){
2 for(int i=0; i<n; i++)
3 printf("%c", c);
4 }
©Partha Bhowmick
Chapter 6. Functions 61
1 #include <stdio.h>
2 int A; // This A is a global variable
3 void main(){
4 A = 1;
5 myProc();
6 printf("A = %d\n", A);
7 }
8
9 void myProc(){
10 A = 2;
11 printf("A = %d\n", A);
12 }
In the following code, note that the declaration int A = 2; in myProc() creates an integer variable
with the same name, A. This A is local to myProc(), and when it is printed, the output is 2. This does not
affect the value of the global variable A. Consequently, when A is printed from main(), the output is 1, which
is the value of the global variable A.
1 #include <stdio.h>
2 int A; // This A is a global variable
3 void main(){
4 A = 1;
5 myProc();
6 printf("A = %d\n", A);
7 }
8
9 void myProc(){
10 A = 2;
11 printf("A = %d\n", A);
12 }
This practice of declaring local and global variables with the same name should be avoided when writing
code.
Parameter or argument passing is required while invoking functions, i.e., when a function calls another
function or calls itself, the former being referred to as the calling or caller function, whereas the latter as
called function. The parameter passing is done to the called function from the caller function. For example,
in Figure 6.1, the values of the parameters a and b are passed to dis from main().
It is also referred to as call by value. The called function gets a ‘copy’ of the value of each parameter, as it
is passed to it by the caller function. In simpler words, the copy is treated as the value of a new variable
associated with the called function only. Thus, execution of the called function has no effect on the values of
the parameters of the caller. The copy does not exist when the called function ends and the program control
returns to the caller function. A parameter passed from the caller may also be an expression, e.g., n-r is the
argument passed from main() when it invokes fact(n-r).
©Partha Bhowmick
62 Chapter 6. Functions
©Partha Bhowmick
Chapter 6. Functions 63
if n “ 1
"
1
n! “ (6.1)
n ¨ pn ´ 1q! otherwise.
Its recursive implementation is straightforward and shown in Code 6.7. Note that the base case is specified
before the recursive call, making the recursive call appear at the end or ‘tail’ of the function. This is known
as tail recursion. An equivalent implementation using non-tail recursion, where the recursive call precedes
the base condition, is provided in Code 6.8.
Although Code 6.8 is correct, it is not preferred due to its use of non-tail recursion. Non-tail recursion
generally consumes more memory than tail recursion because each recursive call generates a new stack frame.
The concept of stack frames is further discussed in §6.9.1. Therefore, whenever possible, recursive functions
should be written using tail recursion. However, some computational problems, such as the Tower of Hanoi,
cannot be solved with tail recursion, which we will examine in §??.
Tail recursion means that all base conditions and other non-recursive operations appear before all
recursive calls, and the function returns immediately after the last recursive call. If not, we call it a
non-tail recursion. Tail recursion can often be optimized to use a constant amount of memory space. See,
for example, the iterative function to compute n!, shown in Code 6.9. It is obtained by removing the tail
recursion in Code 6.7.
Code 6.9: Computing n! by an iterative method, designed by removing the tail recursion in Code 6.7.
factorialIterative.c
1 int fact(int n) {
2 int result = 1;
3 for (int i = 2; i <= n; i++)
4 result *= i;
5 return result;
6 }
©Partha Bhowmick
64 Chapter 6. Functions
fact(4) 4*6 = 24
1st recursion
if(4==1) return 1;
else return 4*fact(3); 3*2 = 6
2nd recursion
if(3==1) return 1;
else return 3*fact(2); 2*1 = 2
3rd recursion
if(2==1) return 1;
else return 2*fact(1); 1
Base case
if(1==1) return 1;
else return 1*fact(0);
Figure 6.2: Effect of calling the function fact(4) from main(). Top: Unfolding and backtracking of recursive calls.
Bottom: Expansion and contraction of the recursion stack during the unfolding and folding phases of
recursion.
An important property of recursion is that when the recursive function is called next time, it acts on
a smaller input, and so eventually the recursion reaches a/the base case. For example, consider the code
of Fibonacci sequence written after Eq. 6.1. As demonstrated in Figure 6.2, the base case is for 1, and
from that point the recursion wraps up, to get the values of fact(2), fact(3), and fact(4), in that order.
This ‘wrapping up’ is referred to as backtracking. Observe how the recursion stack grows when the function
unfolds by recursive calls and diminishes when they terminate during backtracking. The structure and
working principle of the stack are discussed in detail in §6.9.1.
Recursion stack (or “stack frame”) refers to the entire stack of activation records for a recursive function. It
is dynamic because of growth and diminish of active functions over time. Each time a recursive function
©Partha Bhowmick
Chapter 6. Functions 65
calls itself, a new activation record is pushed onto the stack. The recursion stack grows as more recursive
calls are made and shrinks when the calls return.
The stack is a data structure that holds all the necessary information and
can be implemented by array. It resides in RAM. Data are stored in LIFO
order (Last In, First Out), hence the name "stack". If A1 is pushed onto
the stack first, followed by A2 , then A2 will be popped before A1 . (‘Push’
and ‘pop’ are two stack operations; ‘push’ refers to insertion, while ‘pop’
denotes deletion.)
Example: factorial of n
To understand the stack, let’s go back the computation of n! using tail recursion (Code 6.7). As evident from
Figure 6.2, upon reaching the base case, a recursive call ends, and the program control backtracks to the
previous call. While a recursion unfolds, the stack grows up, and when backtracking starts, it shrinks down,
as shown in Figure 6.2.
The stack maintains the activation records of all the unfinished recursive calls in an orderly manner. The
last record is of the last recursive call, and while backtracking, it is the last record which is first processed.
For example, the record of fact(2) is first processed while backtracking after reaching the base condition
(corresponding to fact(1)).
Let’s now consider a very well-known sequence called Fibonacci sequence, namely tf pnq : n “ 0, 1, 2, . . .u,
defined by the following recurrence:
f p0q “ 0, f p1q “ 1, and f pnq “ f pn ´ 1q ` f pn ´ 2q if n ě 2. (6.2)
A recursive function to compute f pnq for n ě 0 is presented in Code 6.10. Consider the two recursive
calls in the 4th line of Code 6.10: return fib(n-1) + fib(n-2);. For the first call (i.e., fib(n-1)), a new
activation record is pushed onto the recursion stack, which holds the value of n-1 and all auxiliary data. The
same occurs for the second call (i.e., fib(n-2)). These calls, in turn, may invoke further recursive calls, with
similar actions performed each time. In essence, every time a recursive function is called, a new activation
record is created on the recursion stack, storing the argument value and auxiliary data. Once the function
completes, its activation record is popped from the stack. This stack-based mechanism enables the program
to manage multiple levels of recursion effectively.
A demonstration of Code 6.10 for n “ 4 is shown through its recursion tree in Figure 6.3. Each node
in the tree represents a function call, and the directed edges illustrate the recursive calls between functions.
The tree starts with the initial call for n “ 4 at the root and branches out to show all subsequent calls made
by that function. For a function that calls itself recursively, the recursion tree reveals multiple levels of the
function, demonstrating how each call generates further calls. This tree helps visualize how the problem is
divided into sub-problems and how many times each sub-problem is solved.
The execution time and memory space required for running the recursive code are critical for under-
standing the efficiency of Code 6.10. Observe that the size of the recursion tree is exponential in n, implying
©Partha Bhowmick
66 Chapter 6. Functions
fib(3) fib(2)
6th return = 1 7th return = 0
fib(1) fib(0)
that the time complexity is also exponential. This is because the function fibpnq obtains its value only after
exploring all nodes of the tree. However, the actual space used in the RAM at any point is at most linear
in n, as each call to fibpnq occupies an activation record in the recursion stack, and the maximum depth of
the tree is n.
The function recomputes the values of fibpn ´ kq multiple times for k ě 1 due to overlapping sub-
problems. For example, fibpn ´ 2q is computed twice, fibpn ´ 3q is computed four times, and so on. This
redundant computation contributes to the exponential runtime. This redundancy can be completely elimi-
nated by using an iterative approach, which results in a linear runtime in n.
The Tower of Hanoi problem was invented by the French mathematician Édouard Lucas in 1883. He was
inspired by a legend about a temple in India where monks were tasked with moving 64 golden disks from
one peg to another. According to the legend, once all the disks were moved, the world would come to an
end. The problem became famous for illustrating recursion and algorithmic problem solving.
The computational problem goes as follows: Given three pegs A, B, C, and n disks of different sizes
stacked on peg A in decreasing order of their sizes from bottom to top, the task is to move all the disks from
peg A (source peg) to peg C (destination peg), using peg B (auxiliary peg). The constraints are:
1. Only one disk can be moved at a time.
2. A larger disk cannot be placed on top of a smaller disk.
The Tower of Hanoi problem does not admit tail recursion, as mentioned earlier in §6.9. This is because,
after moving the top n ´ 1 disks from peg A to peg B, you must move the largest disk (currently the sole disk
in A) to peg C. After that, the entire set of n ´ 1 disks on peg B needs to be moved to peg C. This process
requires additional work after each recursive call, which prevents the function from being tail recursive. The
fundamental steps for solving the Tower of Hanoi problem, illustrated in Figure 6.4, are as follows:
A B C A B C A B C A B C
1 1
2 1 1 2
2 2
n-1 n-1
n n n-1 n-1 n n
1st recursion Base condition 2nd recursion Final solution
©Partha Bhowmick
Chapter 6. Functions 67
A B C A B C A B C A B C
1
2 2 1
3 3 1 3 2 1 3 2
Start
A B C A B C A B C A B C
1
2 2 1
3 1 3 1 2 3 2 3
End
Figure 6.5: Tower of Hanoi: Sequence of moves while running Code 6.11 with n “ 3.
1. 1st recursion: Move the top n ´ 1 disks from peg A to peg B, using peg C as the auxiliary peg.
2. Base condition: Move Disk n from peg A to peg C.
3. 2nd recursion: Move all n ´ 1 disks from peg B to peg C, using peg A as the auxiliary peg.
The corresponding code is given in Code 6.11, and here is the output for n “ 3:
Enter #disks: 1 Enter #disks: 2 Enter #disks: 3
Disk 1: A → C Disk 1: A → B Disk 1: A → C
Disk 2: A → C Disk 2: A → B
Disk 1: B → C Disk 1: C → B
Disk 3: A → C
Disk 1: B → A
Disk 2: B → C
Disk 1: A → C
14 int main() {
15 int n;
16 printf("Enter #disks: ");
17 scanf("%d", &n);
18 hanoi(n, ’A’, ’C’, ’B’);
19 return 0;
20 }
©Partha Bhowmick
68 Chapter 6. Functions
it calls another recursive function. Below are three examples defined for all positive integers n.
if n “ 1
"
1
Direct recursion: f pnq “
n ¨ f pn ´ 1q otherwise.
if n “ 1
"
1
Indirect recursion: gpnq “
n ¨ hpn ´ 1q otherwise.
if n “ 1
"
1
Indirect recursion: hpnq “
1 ` gpn ´ 1q otherwise.
The function f pnq is an example of direct recursion because it only calls itself. On the contrary, the
function gpnq does not call itself but instead calls hpnq, which, in turn, calls back gpnq. Thus, for both g
and h, it is an example of indirect recursion. Furthermore, since g and h call each other, they form what is
known as mutual recursion or cyclic recursion.
Call graph: In a recursive program, it represents the calling rela-
tionships between functions. This is specially important when there
f g h
are many recursive functions. For example, if f calls f and g, g calls
f , g, and h, and h calls only itself, the call graph will look as shown
aside.
if n “ 0
$
& 0
feven pnq “ 1 if n “ 2 (6.3)
feven pn ´ 2q ` fodd pn ´ 1q otherwise.
%
if n “ 1
$
& 1
fodd pnq “ 2 if n “ 3 (6.4)
fodd pn ´ 2q ` feven pn ´ 1q otherwise.
%
A solution using tail recursion is shown in Code 6.12. This code is revised to make it free from recursions.
The revised code uses only iterations (loops), which is shown in Code 6.13. For index n ranging from 1 to
3, the respective values obtained by running either of these codes are as follows:
n 1 2 3 4 5 6 7 8 9 10
f 1 1 2 3 5 8 13 21 34 55
©Partha Bhowmick
Chapter 6. Functions 69
Code 6.12: Computing the n-th number in a Fibonacci-like sequence using tail recursion.
fibonacci-like-sequence-1-tailRec.c
1 #include <stdio.h>
2
6 int main() {
7 int n;
8 printf("Enter the index (n): ");
9 scanf("%d", &n);
10 if (n % 2 == 0)
11 printf("f_even(%d) = %d\n", n, f_even(n));
12 else
13 printf("f_odd(%d) = %d\n", n, f_odd(n));
14 return 0;
15 }
16
17 int f_even(int n) {
18 if (n == 0)
19 return 0; // Base case
20 else if (n == 2)
21 return 1; // Base case
22 else
23 return f_even(n - 2) + f_odd(n - 1); // Recursive case
24 }
25
26 int f_odd(int n) {
27 if (n == 1)
28 return 1; // Base case
29 else if (n == 3)
30 return 2; // Base case
31 else
32 return f_odd(n - 2) + f_even(n - 1); // Recursive case
33 }
History: The Fibonacci sequence has its roots in the work of the Italian mathematician Leonardo of Pisa,
known as Fibonacci, who introduced the sequence to Western mathematics in his book Liber Abaci (1202).
Although the sequence had appeared earlier in Indian mathematics, Fibonacci used it to model the growth of
a rabbit population. The sequence starts with 0 and 1, and each subsequent number is the sum of the two
preceding ones, forming the series: 0, 1, 1, 2, 3, 5, 8, 13, . . . . Fibonacci’s work with this sequence has since found
applications in various fields of science and nature.
Applications: In computer science, Fibonacci numbers are used in algorithms like Fibonacci search and for
optimizing dynamic programming solutions. They also appear in data structures such as Fibonacci heaps. In
biology, the sequence describes phenomena such as the arrangement of leaves on a stem, the branching of trees,
or the reproduction patterns of bees. In finance, Fibonacci retracement levels are used in technical analysis
to predict stock market movements. Moreover, the sequence appears in architecture and art, where it is often
associated with the golden ratio, as the ratio of consecutive Fibonacci numbers approaches the golden ratio
ϕ « 1.618.
Similar sequences: Several other sequences exhibit similar recursive patterns. The Lucas sequence, for
example, starts with 2 and 1 but follows the same recursive relation. The Tribonacci sequence generalizes
the Fibonacci rule by summing the three preceding numbers instead of two. Similarly, the Padovan and Per-
rin sequences follow related patterns, highlighting the broad applicability and fascination with such recursive
relationships in mathematics and natural phenomena.
©Partha Bhowmick
70 Chapter 6. Functions
Code 6.13: Computing the n-th number in a Fibonacci-like sequence using an iterative method, designed
by removing the tail recursions in Code 6.12. fibonacci-like-sequence-1-iter.c
1 #include <stdio.h>
2
6 int main() {
7 int n;
8 printf("Enter the index (n): ");
9 scanf("%d", &n);
10 if (n % 2 == 0)
11 printf("f_even(%d) = %d\n", n, f_even(n));
12 else
13 printf("f_odd(%d) = %d\n", n, f_odd(n));
14 return 0;
15 }
16
17 int f_even(int n) {
18 int prev_even = 0, prev_odd = 1, current_even = 0, current_odd = 1;
19
30 return current_even;
31 }
32
33 int f_odd(int n) {
34 int prev_even = 0, prev_odd = 1, current_even = 0, current_odd = 1;
35
46 return current_odd;
47 }
©Partha Bhowmick
Chapter 6. Functions 71
While passing an array to a function, the name of the array is used as an argument. Now, beware of the
catch here—since the name of the array is the argument, the values of the array elements are not passed to
the called function. Rather, the array name is interpreted as the address of the first element of the array,
and so what is passed is basically the first element’s address. In this regard, the way it is passed differs from
that for ordinary variables.
Repeating again, the argument carries the address of the first element of the array. When any element
of that array has to be accessed inside the called function, its address is calculated by the compiler using the
technique discussed in §5.2. Clearly, this address is address is same to both the caller and the called for any
element of the array, and it is also accessible to both of them. Hence, any change made to any element inside
the called function is eventually reflected in the calling function, once the called function ends. In Code 6.14,
for example, the elements of a 5-element array are squared in the called function, and when printed from
the caller, the squared elements will be printed as desired.
6 int main(){
7 int a[] = {3, -2, 1, 2, 1};
8 doSqr(A, 5);
9 for(int i=0; i<5; i++)
10 printf("%d ", a[i]); // will print 9 4 1 4 1
11 return 1;
12 }
Macros are a powerful feature in C language that allows you to define constants or expressions that can
be reused throughout the program. A macro serves as a placeholder for the specified value or expression.
It is created by the #define directive. During preprocessing, which occurs before the compilation stage,
the preprocessor scans through the code and substitutes every occurrence of the macro with the value or
expression it represents.
For example, consider the macro definition: #define PI 3.14159. Whenever the macro PI is used in
the code, the preprocessor will automatically replace it with 3.14159. This substitution happens at every
instance where PI appears, excepting the format string in printf. This is done by the programmer to ensure
consistency and to reduce the risk of errors associated with manual input of constant values.
Code 6.15 illustrates shows how you can define and use a macro for π. In this code, we compute the
perimeter and the area of a circle, given by 2πr and πr2 , respectively, where r is the radius of the circle.
The #define directive in C not only allows you to define simple constants but also enables the creation
of macros with arguments, which are similar to functions. These macros can take one or more arguments,
and during preprocessing, the arguments are replaced with the values passed when the macro is invoked.
This capability allows you to create flexible and reusable code snippets.
©Partha Bhowmick
72 Chapter 6. Functions
4 int main() {
5 float radius, perimeter, area;
6
14 return 0;
15 }
In this example, the macro sqr takes one argument, x. When you use sqr in your code, the preprocessor
replaces it with the expression ((x)*(x)). Here is how you can use it:
is replaced with:
This substitution ensures that the macro works correctly, even if the argument n is an expression rather than
a simple variable. For example, if you write SQR(a+b), the macro expands to ((a+b)*(a + b)), ensuring
that the entire expression is squared.
You have to be careful while defining a macro. For example, if you write #define SQR(x) x*x, then
SQR(a+b) will result in a+b*a+b, which is incorrect.
As you understand now, macros with arguments have definite advantages in coding. First, they simplify
complex expressions by encapsulating them in a single, easy-to-use macro. Second, they are efficient because
©Partha Bhowmick
Chapter 6. Functions 73
// Calculate the distance between two points (x1, y1) and (x2, y2)
#define DIS(x1, y1, x2, y2) sqrt(((x2)-(x1)) * ((x2)-(x1)) + ((y2)-(y1)) * ((y2)-(y1)))
they are expanded inline, avoiding the overhead of function calls. Third, they allow you to create reusable
code snippets that can be adapted to different inputs. Some typical use case for macros are given in Table 6.1.
Following is a program where the macro SWAP(a, b, type) is used.
1 #include <stdio.h>
2
6 int main() {
7 // Declare and initialize variables
8 int x = 10, y = 20;
9 double p = 3.14, q = 2.71;
10 char c1 = ’A’, c2 = ’B’;
11
12 // Swap integers
13 printf("Before swapping: x = %d, y = %d\n", x, y);
14 SWAP(x, y, int);
15 printf("After swapping: x = %d, y = %d\n\n", x, y);
16
17 // Swap doubles
18 printf("Before swapping: p = %.2f, q = %.2f\n", p, q);
19 SWAP(p, q, double);
©Partha Bhowmick
74 Chapter 6. Functions
22 // Swap characters
23 printf("Before swapping: c1 = %c, c2 = %c\n", c1, c2);
24 SWAP(c1, c2, char);
25 printf("After swapping: c1 = %c, c2 = %c\n", c1, c2);
26
27 return 0;
28 }
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <time.h>
4
10 int main() {
11 int a, b;
12 int array[SIZE];
13
©Partha Bhowmick
Chapter 6. Functions 75
35 return 0;
36 }
Below is a modified version of the above code in which the seed is an integer r taken in from the user,
rather than using a time-dependent seed. Note that the number of elements is also taken from the user.
1 #include <stdio.h>
2 #include <stdlib.h>
3
6 int main() {
7 int a, b, r, n;
8
9 // Prompt user for the range [a, b], the seed value, and the number of elements
10 printf("Enter the lower bound (a): ");
11 scanf("%d", &a);
12 printf("Enter the upper bound (b): ");
13 scanf("%d", &b);
14 printf("Enter the seed value (r): ");
15 scanf("%d", &r);
16 printf("Enter the number of elements (n): ");
17 scanf("%d", &n);
18
22 // Fixed-size array
23 int arr[100];
24
©Partha Bhowmick
76 Chapter 6. Functions
43 return 0;
44 }
Here are the outputs for two different seeds provided by the user, while the interval [a,b] remains
unchanged. Notice how the outputs differ due to the variation in seeds.
This allows the program to dynamically adjust its behavior based on the provided inputs, making it more
versatile and interactive.
Here is a C program that takes numbers as command-line arguments and prints their square roots to
the terminal:
©Partha Bhowmick
Chapter 6. Functions 77
1 #include <stdio.h>
2 #include <stdlib.h> // Include stdlib.h for atof()
3 #include <math.h> // Include math.h for sqrt()
4
24 return 0;
25 }
Here is another C program where the input file contains some numbers, and the output file will store
their square roots:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <math.h>
4
©Partha Bhowmick
78 Chapter 6. Functions
24 // Read each number, calculate its square root, and write to the output file
25 while (fscanf(inputFile, "%lf", &num) != EOF) {
26 fprintf(outputFile, "Square root of %.2lf is %.4lf\n", num, sqrt(num));
27 }
28
33 return 0;
34 }
1. rPrinting primes up to a given numbers Write a function of the prototype int checkPrime(int x).
It takes as input an integer x, checks whether it’s prime, and returns 1 if so, and 0 otherwise. Scan a
positive integer n from main() and print all primes in r1, ns by calling the above function in a loop.
1 #include <stdio.h>
2 #include <math.h>
3
int main(){
14
15 int n;
16 printf("Enter a positive integer: ");
17 scanf("%d", &n);
18 for(int j=3; j<=n; j++)
19 if(checkPrime(j))
20 printf("%d is prime\n", j);
21 return 0;
22 }
2. n choose r: nr Write two functions of the following prototypes:
“ ` ˘‰
©Partha Bhowmick
Chapter 6. Functions 79
The first function will compute the value of nr “ r!pn´rq! , and to do so, it will call the second one to
` ˘ n!
get the values of n!, r!, and pn ´ rq!. Scan a positive integer n from main() and print the values of nr
` ˘
for r P r0, ns, by calling the function ncr in a loop. For example, for n “ 5, it will print 1, 5, 10, 10, 5, 1.
1 #include <stdio.h>
2
6 int main(){
7 int i, n;
8 printf("Enter n: ");
9 scanf ("%d", &n);
10 for (i=0; i<=n; i+=1)
11 printf ("%d choose %d = %d \n", i, n, ncr(n, i));
12 }
13
3 #include <stdio.h>
4
9 int main(){
10 float a, b, c, x;
11 printf("\nEnter a, b, c, x: ");
12 scanf("%f%f%f%f", &a, &b, &c, &x);
13 printf("f = %f\n", f(a,b,c,x));
14 return 0;
15 }
4. rArithmetic and geometric meanss Write a function to compute and print the arithmetic mean and
the geometric mean of n real numbers, with n and an n-element array as arguments. The value of n
and the array elements will be supplied as input from main(). The math library can be used, but only
for the pow() function.
©Partha Bhowmick
80 Chapter 6. Functions
3 #include <stdio.h>
4 #include <math.h>
5 #define SIZE 1000
6
18 int main(){
19 int n, i;
20 float a[SIZE];
21
22 printf("Enter n: ");
23 scanf("%d", &n);
24 printf("Enter the elements: ");
25
29 computeAMGM(n, a);
30
31 return 0;
32 }
5. rMedians Write a function to compute and return the median of n integers, with n and an n-element
array as arguments. Scanning the value of n along with the array elements, and printing the median,
should all be done from main(). Assume that n is odd and all elements are distinct. Then, exactly
2 elements of the array will be smaller that the median—a fact that can be used to write the function.
n´1
3 #include <stdio.h>
4 #define SIZE 1000
5
©Partha Bhowmick
Chapter 6. Functions 81
31
32 int main(){
33 int a[SIZE], n, m;
34
35 n = readInput(a);
36 m = findMedian(a, n);
37 printf("Median = %d\n", m);
38
39 return 0;
40 }
6. rGCD function, recursives Write a recursive function of the prototype int gcd(int, int) to com-
pute the GCD of two numbers, using Eq. 4.1. Take in two positive integers m, n from main(), call
gcd(m,n) from main(), and print its returned value from main().
3 #include <stdio.h>
4
10 int main(){
11 int m, n;
12 printf("\nEnter two positive integers: ");
13 scanf("%d%d", &m, &n);
14 printf("GCD = %d\n", gcd(m,n));
15 return 0;
16 }
17
7. rGCD function, iteratives Write an iterative version of the GCD function using Eq. 4.1, keeping the
main() same as the recursive version.
1 //GCD function
2
3 #include <stdio.h>
4
©Partha Bhowmick
82 Chapter 6. Functions
7 while(a!=0){
8 c = a;
9 a = b%a;
10 b = c;
11 }
12 return b;
13 }
14
15 int main(){
16 int m, n;
17 printf("\nEnter two positive integers: ");
18 scanf("%d%d", &m, &n);
19 printf("GCD = %d\n", gcd(m,n));
20 return 0;
21 }
22
8. rGenerate all possible permutations of an arrays Write a recursive function to generate all pos-
sible permutations of a random array. The seed, the number of elements, and the range of elements are
taken in as input from the user. The function should generate permutations by swapping each element
with the first element and then recursively permuting the remaining sub-array. This problem inherently
relies on recursion to explore all possible orderings of the array elements.
1 #include <stdio.h>
2 #include <stdlib.h>
3
©Partha Bhowmick
Chapter 6. Functions 83
33
34 int main() {
35 int n, seed, a, b;
36
37 // Take user input for the seed, number of elements, and the range [a, b]
38 printf("Enter the seed: ");
39 scanf("%d", &seed);
40 printf("Enter the number of elements: ");
41 scanf("%d", &n);
42 printf("Enter the range [a, b]: ");
43 scanf("%d %d", &a, &b);
44
65 return 0;
66 }
Here is an output of the above code:
Enter the seed: 1
Enter the number of elements: 3
Enter the range [a, b]: 1 9
Generated array: 2 8 1
Permutations:
2 8 1
2 1 8
8 2 1
8 1 2
1 8 2
1 2 8
1. rValues of a quadratic sequences Write a function to compute the value of x2 ` bx ` c with b and
c as positive integers and x as real, taken as arguments in that order. Scan their respective values from
©Partha Bhowmick
84 Chapter 6. Functions
main(), call that function from main() for every integer value of x ranging from ´10 to 10, and print
every returned value from main(). That is, the function will be called 21 times from main().
2. rFibonacci function, recursives Write a recursive function of the prototype int f(int) to compute
the nth Fibonacci term, f pnq, using Eq. 5.1. Take in a positive integer n from main(), call f(n) from
main(), and print its returned value from main().
3. rFibonacci function, iteratives Write an iterative version of the Fibonacci function using Eq. 5.1,
keeping the main() same as the recursive version.
4. rTwo-element sum as an element in anothers Read in two integers m and n. Assume that both
m and n are in r2, 100s. Take in m integers to an array a[] and n integers to an array b[]. Write a
function that can be called from main to check whether there are two elements in a[] that add up to a
single element in b[]. The arguments of that function should be a[], b[], m, and n.
©Partha Bhowmick
7 | Strings
A character in C programming is an unsigned 8-bit number representing its ASCII (American Standard
Code for Information Interchange) value, ranging from 0 to 255. Some of these characters are keyboard
characters, e.g., the English letters, digits, punctuation marks, brackets, space, mathematical symbols like
+, -, etc., and symbols such as ~, @, etc. Additionally, some characters serve special functions, such as the
newline character denoted by ’\n’. The null character or null terminator, represented by ’\0’, is another
significant special character. Other special characters, which are less commonly used, are listed in Table 7.1
for completeness.
A string is an array of characters ending with a null character, i.e., ’\0’. To be specific, this is called a
null-terminated string. Unless stated otherwise, a string refers to a null-terminated string. In this sense, an
array of characters without ’\0’ is a character array but not a string. In other words, the null character,
also known as the sentinel character, is the last character of a string. For example, India@1947 is a character
array but not a string, while India@1947\0 is both a character array and a string.
Another example is shown in Figure 7.1. The array contains 10 characters: India@47\0B, with the 9th
character being ’\0’. This implies that the string consists of the first 9 characters, i.e., India@47\0, and the
10th byte (’B’) does not belong to the string. Note that the length of the string excludes the null character;
therefore, in this example, the length is 8, not 9.
In short, if the array contains additional characters after the first null character, those characters are
not part of the string. For example, if the array has 10 bytes and its content is India\0@47\0, the string
starting from I is India\0, which consists of 6 characters, including \0. In fact, this array contains two
strings, the second being @47\0.
85
86 Chapter 7. Strings
indexes 0 1 2 3 4 5 6 7 8 9
main memory (RAM) I n d i a @ 4 7 \0 B not an element of string
8D44
8D45
8D46
8D47
8D48
8D49
8D4A
8D4B
8D4C
8D4D
byte addresses
(in hexadecimal)
Figure 7.1: An array of 10 characters, requiring 10 bytes in the memory. Its first and last bytes have the addresses
8D44 and 8D4D, respectively, in hexadecimal number system. Since the 9th (i.e., of index 8) character
is ’\0’, the first 9 characters comprise the string, and the 10th byte, although belongs to the array, is
not a part of the string.
To continue with other examples, "IIT" represents a 4-byte string of length 3, and "hello" is a 6-byte string
of length 5. Each string implicitly ends with the null character ’\0’, which is included in the byte count
but not in the string length, as mentioned earlier. Their respective declarations with optimal storage are as
follows:
To declare a string of characters without initializing it, you have several options in C. Here are the
possible ways:
1. Fixed-size array declaration for 1000 characters:
char s[1000];
2. Using malloc, you can allocate memory dynamically for n characters:
The notation * used in the last two options denotes pointers. Understanding their implementation requires
knowledge of memory management, which will be discussed later.
©Partha Bhowmick
Chapter 7. Strings 87
Consider the string "India@47". It has nine characters in total including the implicit null character
after the character ’7’. An array that will contain just this string and no more characters, can be declared
and initialized as follows.
char s[9] = {’I’, ’n’, ’d’, ’i’, ’a’, ’@’, ’4’, ’7’, ’\0’};
By the above declaration, s[0] gets the character I, s[1] gets ’n’, and so on, and the last cell of the array,
i.e., s[8], stores the null character.
Two other correct declarations are:
char s[] = "India@47\0"; // you must put the string within double quotes
and
char s[] = "India@47"; // you must put the string within double quotes
Note that when you declare a string using the last syntax, the compiler automatically adds the null terminator
to the end of the string. So the array s takes 9 bytes to store the string India@47 along with the null
terminator.
However, if you write char s[9] = {’I’, ’n’, ’d’, ’i’, ’a’, ’@’, ’4’, ’7’};, then the array s
will contain exactly the characters you specified, without an explicit null terminator. In this case, the array
s will have a size of 9 bytes, where the first 8 bytes are used for the characters you specified, and the 9th byte
is uninitialized and contains some ‘garbage value’. Since you did not include the null terminator explicitly,
the array s will not be a valid string if the garbage value at the last byte is not the null character.
That is, the address-of operator & is used by scanf to store a variable’s value directly in its memory location.
However, for a string str (which is basically a null-terminated array of characters), its name itself acts as
a pointer to the first element of the array. That is, str is inherently a pointer pointing to the beginning of
the string, or, equivalently, str contains the address of the first character of the array it represents.
©Partha Bhowmick
88 Chapter 7. Strings
Since scanf needs a pointer to store the input string, using str directly gives the correct memory
address. For example, to read a string up to 24 characters, you can use the following code:
char str[25];
scanf("%s", str);
However, scanf("%s", &str) is incorrect. The reason is that &str has the type char (*)[25], which
is a pointer to an array of 25 characters. But scanf expects a char *, which is a pointer to a single character.
This mismatch in types leads to a compilation error.
As a specific example, consider the following program. It prompts the user to enter a string, reads it
into a character array str, and prints the string to the terminal. The array can hold up to 24 characters
plus the null terminator, meaning the input should be no more than 24 characters long to avoid overflow.
int main(){
char str[25];
printf("Enter the name (at most 24 characters): ");
scanf("%s", str);
printf("Name = %s\n", str);
return 0;
}
The code begins by declaring a character array named str with a size of 25. This array is intended to store
a string (a sequence of characters terminated by the null character). The function call scanf("%s", str) is
used to read a string from the user. The %s format specifier tells scanf to read a string until it encounters
white space (i.e., space, tab, or newline). The function call printf("Name = %s \n", str) is used to display
the string that was input by the user.
Using getchar
int main(){
char line[101], c;
int k = 0;
do{
c = getchar();
line[k++] = c;
}
while (c != ’\n’);
k = k-1;
line[k] = ’\0;;
return 0;
}
To read an entire line of text including spaces, we use getchar() to read each character until a newline char-
acter is encountered. The characters are stored in the line array, and the loop terminates when getchar()
©Partha Bhowmick
Chapter 7. Strings 89
reads the newline character. The last character of the array is then set to ’\0’ to properly terminate the
string.
int main() {
char line[101];
scanf("%[^\n]", line);
return 0;
}
This code uses the format specifier "%[^\n]" in scanf to read a string until a newline character is encoun-
tered, allowing spaces within the string.
Here is another code that reads all characters including newlines and terminates when it encounters the
character #:
int main() {
char line[101];
scanf("%[^#]", line);
return 0;
}
In this code:
• The format specifier %[^#] is used with scanf to read characters until the character # is encountered.
• The # character itself is not included in the line array.
• The null terminator \0 is automatically appended at the end of the string.
• The symbol ^ in the specifier denotes logical not, so all characters except # are accepted.
For example, if the user enters IIT_KGP PDS@2024#Autumn, the scanf will store just this:
IIT_KGP PDS@2024\0.
So, if you write printf("%s", line) after the scanf, it will display IIT_KGP PDS@2024 on the terminal.
The character \0 is not displayed.
©Partha Bhowmick
90 Chapter 7. Strings
(i) strerror: Returns a pointer to the string that describes the error code passed in the argument.
char *strerror(int errnum);
Let’s explore some examples to see how the functions from string.h make it easier to write concise and
efficient code. Here, we’ll focus on three key functions: strlen, strcpy, and strcat.
7.6.1 strlen
Consider finding the length of a string. As mentioned earlier in §7.6, the function strlen measures the
length of a string and returns that value. Recall from §7.1 that the length of a string is defined as the
number of its constituent characters, excluding the null character. The first code snippet demonstrates how
©Partha Bhowmick
Chapter 7. Strings 91
to achieve this without using string.h. The second code snippet shows how to use the strlen function
from string.h to perform the same task.
3 #include <stdio.h>
4
5 int main() {
6 char s[10];
7 printf("Enter a string (up to 9 characters): ");
8 scanf("%s", s);
9
10 int len = 0;
11 while (s[len] != ’\0’)
12 len++;
13 printf("Length of the input string: %d\n", len);
14 return 0;
15 }
3 #include <stdio.h>
4 #include <string.h>
5
6 int main() {
7 char s[10];
8 printf("Enter a string (up to 9 characters): ");
9 scanf("%s", s);
10 int len = strlen(s);
11 printf("Length of the input string: %d\n", len);
12 return 0;
13 }
7.6.2 strcpy
Consider the problem of copying a string s to t. The first code snippet demonstrates how to do this without
using strcpy. The second code snippet shows how to use the strcpy function from string.h to accomplish
the same task.
3 #include <stdio.h>
4 #include <string.h>
5
6 int main() {
7 char s[10], t[10];
8 int len, i;
9 printf("Enter a string (max 9 characters): ");
10 scanf("%9s", s); // for safety against buffer overflow
11
12 len = strlen(s);
©Partha Bhowmick
92 Chapter 7. Strings
1 // using strcpy
2
3 #include <stdio.h>
4 #include <string.h>
5
6 int main() {
7 char s[10], t[10];
8 printf("Enter a string (max 9 characters): ");
9 scanf("%9s", s); // for safety against buffer overflow
10 strcpy(t, s);
11 printf("Copied string: %s\n", t);
12 return 0;
13 }
7.6.3 strcat
Concatenation means appending one string to another. For example, concatenating @47 to India results in
India@47. Consider the problem of concatenating a string t to a string s. The first code snippet demonstrates
how to do this without using strcat. The second code snippet shows how to use the strcat function from
string.h to achieve the same result.
1 #include <stdio.h>
2 #include <string.h>
3
4 int main() {
5 char s[10], t[10];
6 strcpy(s, "India");
7 strcpy(t, "@47");
8
9 int i = strlen(s);
10 for (int j = 0; t[j] != ’\0’; j++) {
11 s[i] = t[j];
12 i++;
13 }
14 s[i] = ’\0’;
15
16 printf("%s\n", s);
17 return 0;
18 }
1 #include <stdio.h>
2 #include <string.h>
3
4 int main() {
©Partha Bhowmick
Chapter 7. Strings 93
1. rScan any characters Scan as input any character other than newline; print it as character and also
print its ASCII value. The user will type in that character followed by a newline.
1 #include <stdio.h>
2
3 int main(){
4 char a;
5 printf("Type any character and press <Enter>: ");
6 scanf("%[^\n]c", &a); // accepts any character followed by newline
7 printf("Scanned = ’%c’ (ASCII value = %d).\n", a, a);
8 return 0;
9 }
2. rScan any two characterss Scan as input two arbitrary characters, one at a time, using two scanf
calls, and finally print them by a single printf at the end. The user will type in each character followed
by a newline.
1 #include <stdio.h>
2
3 int main(){
4 char a, b;
5 printf("Type in the 1st character and press <Enter>: ");
6 scanf("%[^\n]c", &a); // accepts any character followed by newline
7 printf("Scanned = ’%c’.\n", a);
8 printf("Type in the 2nd character and press <Enter>: ");
9 scanf("\n%[^\n]c", &b); // The first \n means the newline entered earlier is skipped
10 printf("Scanned = ’%c’.\n", b);
11 return 0;
12 }
©Partha Bhowmick
94 Chapter 7. Strings
3. rScan integer and characters Scan first as input an integer and then a non-white-space character,
using two scanf calls, and finally print them by a single printf at the end.
1 #include <stdio.h>
2
3 int main(){
4 int n;
5 char a;
6 printf("Type in any integer and press <Enter>: ");
7 scanf("%d", &n);
8 printf("Type in any character and press <Enter>: ");
9 scanf("\n%c", &a); // The first \n means the previous <Enter> is skipped
10 printf("Scanned = %d and ’%c’.\n", n, a);
11 return 0;
12 }
Note: The \n in scanf("\n%c", &a) is used to consume any leftover newline character that may be
in the input buffer from the previous input against scanf("%d", &n). When you input an integer and
press <Enter>, the newline character generated by the <Enter> key remains in the buffer. If you don’t
include \n before %c, the scanf function would read this newline character instead of waiting for a new
character input. The \n effectively skips over any newline characters, ensuring that scanf("%c", &a)
correctly reads the next character entered by the user.
4. rBit flips Given as input any arbitrary character other than a newline, print the new character formed
by flipping each bit excepting the leftmost one. Do it by two methods—once using loop, and once
without loop.
1 #include <stdio.h>
2
3 int main(){
4 char a, b = 0;
5 int i;
6
11 printf("Method 1: ");
12 for(b=0, i=1; i<=64; i=i<<1)
13 b += ((a & i) == 0)? i : 0; // flips the i-th bit
14 printf("Output = ’%c’ (ASCII value = %d)\n", b, b);
15
16 printf("Method 2: ");
17 b = 255 - a; // flips all 8 bits
18 b &= (a<128)? 127 : 255; // retains the leftmost bit of a
19 printf("Output = ’%c’ (ASCII value = %d)\n", b, b);
20
21 return 0;
22 }
5. rString reversals Write a function that takes an alphanumeric string as argument and reverses it. For
example, if it is IIT, then the reverse string created by the function will be TII. Scanning the input
string and printing the reverse one must be done from main(). String library can’t be used.
©Partha Bhowmick
Chapter 7. Strings 95
1 #include <stdio.h>
2 #define MAXLEN 1000
3
7 do
8 n++;
9 while(s[++i]!=’\0’);
10 t[n] = ’\0’;
11
16 int main(){
17 char s[MAXLEN], t[MAXLEN];
18 printf("Enter an alphanumeric string: ");
19 scanf("%s", s);
20 stringReversal(s, t);
21 printf("Reverse = %s.\n", t);
22 return 0;
23 }
iit kgp $1951, Autumn; ... ’&’ "www.ac.in" +-*/~pb*& (@CSE) #_^PDS
The user will input the characters and terminate with a newline, which should not be treated as part
of the string.
3. rOdd characterss Without using the string library, print the characters of an alphanumeric string
occurring in odd positions. Assume that the string has at least one alphanumeric character.
4. rPalindromes Write a function that takes an alphanumeric string as argument and returns 1 if it is a
palindrome, and 0 otherwise. For example, IIT is not a palindrome but ITI is. After a string is taken
in from main(), the function should be called from main() with that string as input, and the value
returned by the function should be printed from main().
♣ 5. rByte reversals Given a byte as input in the form of a character, print the new character formed by
reversing the 7-bit string created from all but the leftmost bit. For example, a becomes C, C becomes a,
b becomes #, # becomes b, etc.
Hint: Let’s walk through the process step by step for the input character ’a’. The ASCII value of
’a’ is 97, which is 01100001 in binary. You can mask this value using 0x7F (= 01111111 in binary),
resulting in 01100001. (Masking here means bit-wise and using the operator &.) Next, reversing the
7 bits of this binary sequence (excluding the leftmost one), producing 1000011. This corresponds to the
ASCII value 67, which is the character ’C’.
©Partha Bhowmick
8 | Pointers
Quick examples
• Declaration: int *p;
This declares p as a pointer to an integer.
• Initialization: int n = 5; p = &n;
The pointer p is initialized to point to n, i.e., p will contain the address of n during execution.
• Modification: *p = 10;
The value at the memory address stored by p is modified to 10.
• Usage: printf("%d", *p);
The value stored at the address pointed to by p is printed.
96
Chapter 8. Pointers 97
We can express the above three statements using two statements as follows:
char a = ’w’; // declaration and initialization
char *p = &a; // declaration and initialization
The variable p here is a pointer to the character a, termed as character pointer. Since the address of a is
denoted by &a, we write char *p = &a; which means:
p points to a
8D40
8D41
8D42
8D47
8D48
address of p address of a
like any data in computer. (in hexadecimal) (in hexadecimal)
2. char pointer (char *): stores the memory address of a character variable.
Example:
char *cp;
This means cp is a pointer to a character, i.e., cp will store the address of a character variable at
runtime. For example: char c = ’A’; cp = &c; assigns the address of c to cp. Now, *cp can be
used to access or modify the value of c. For instance, *cp = ’B’; followed by *cp += 1; will change
the value of c to ’C’. This is because *cp = ’B’; sets the value of c to ’B’. The next statement
*cp += 1; increments the value of c by 1, making it ’C’, since ’C’ is the next character after ’B’ in
the ASCII table.
©Partha Bhowmick
98 Chapter 8. Pointers
3 int main() {
4 int n = 10;
5 int *p; // p is a pointer to an integer; it can store the address of any integer
6 p = &n; // p stores the the address of n
7
3. float pointer (float *): stores the memory address of a floating-point variable.
Example:
float *fp; float f = 5.75; fp = &f;
This means fp is a pointer to a floating-point variable. fp = &f; assigns the address of f to fp, i.e.,
fp will contain the address of a f during execution. *fp can be used to access or modify the value of
f.
4. void pointer (void *): it is a generic pointer that can hold the address of any data type.
Example:
void *vp;
This means vp is a pointer that can point to any data type.
For example: int n = 20; vp = &n; assigns the address of n to vp. To dereference it, you need to
cast it to the correct type: printf(%d, *(int *)vp); prints the value of n.
5. Array pointer: stores the memory address of the first element of an array, i.e., the address of the first
byte of the first element of the array.
Example:
int a[4] = {2, 3, 5, 7};
In this case, a is a pointer to the first element of the array, i.e., a holds the address of a[0].
The array elements can be accessed using pointer arithmetic. For example: *(a + 1) accesses the
second element of the array a, which is 3. a[1] also accesses the second element of the array a.
Arrays and pointers in C are closely related, as array names decay into pointers when passed to
functions. We’ll see more in §8.5.
Pointers allow for direct access and manipulation of data stored in memory. They are used for various
purposes in C programming, some being mentioned below.
1. Passing references (i.e., pointers) as arguments to functions
Pointers allow you to pass the addresses of variables to functions, enabling the called function to access
and modify the actual values in the caller’s context. If only the value is passed without the address, the
original variable remains unchanged.
©Partha Bhowmick
Chapter 8. Pointers 99
Example:
void update(int *p) { *p = 10; } can modify the value of a variable passed by reference, such as
int x = 5; update(&x);, which updates x to 10. On the other hand, if you define the function as
void update(int x) { x = 10; }, and pass x directly, as in update(x);, the value of x will not be
changed in the caller function. This is because the x in the caller and the x in the update function
are different variables, as explained in §6.7.
q a[1] a[9]
RAM 0 0 0
address of a[0] “
3D42
3D43
3D44
3D45
3D46
3D47
3D48
3D49
3D67
3D68
3D69
3D6A
3D6B
base address of a[]
(in hexadecimal)
©Partha Bhowmick
100 Chapter 8. Pointers
int n = 10;
int *p;
p = &n;
*p = 15;
• p = &n; assigns the address of n to the pointer p. Here, the reference operator &n operates on n and
returns the address of n to p.
3. Modification via pointer:
• *p = 15; modifies the value at the address p points to. Thus, n becomes 15. The statement *p = 15
involves dereferencing, which is discussed in §8.4.1.
int *p; means p is a pointer to an integer, which means *p is an integer. Hence, assigning the
number 15 to *p is a valid operation. In fact, *p = n; is also valid for the same reason.
8.4.1 Dereferencing
Dereferencing a pointer p is written as *p. It means using the address stored in p to access the value at that
address. For example, the statement *p = 15; involves dereferencing p, as explained below.
1. p = &n; makes p store the memory address of the variable n.
2. Dereferencing p enables using the address of n (stored in p) to access and change the value of n.
3. *p = 15; changes the value of n to 15 through dereferencing.
To understand more about dereferencing, consider the following piece of code. In this code, the last two
lines are added to the previous code we just discussed about.
int n = 10, k;
int *p;
p = &n;
*p = 15;
*p = *p + 5;
k = *p * 5;
Below is an explanation about the last two lines in the above code.
1. *p = *p + 5;
• This line first dereferences the pointer p, which points to the variable n.
• The expression *p accesses the current value of n. Due to the previous line, n was set to 15.
©Partha Bhowmick
Chapter 8. Pointers 101
• The statement *p = *p + 5; means we take the current value of n (which is 15), add 5 to it, and
store the result back into n. Thus, the new value of n becomes 15 ` 5 “ 20.
2. k = *p * 5;
• This line also dereferences the pointer p, which still points to n.
• After the previous operation, the value of n is now 20.
• The expression *p * 5 calculates 2o ˆ 5 “ 100, which is assigned to the variable k.
8.5.1 Usefulness
Array elements are indexed starting from 0, and indexing is crucial for accessing elements in a 1D array.
Below are some important points regarding array indexes and pointers.
1. Each element of an array is stored sequentially in memory, with the index serving as an offset from the
array’s starting memory address.
2. By specifying an index, we can directly access any array element based on its position. For instance, in
the array arr, the element at index i can be retrieved using arr[i].
3. The expression arr[i] is equivalent to *(arr + i). Both notations access the element at index i. In
arr[i], the index is used to directly retrieve the element. In *(arr + i), pointer arithmetic is used,
where arr is treated as a pointer, and i is added to the base address of the array to access the element
stored at that location.
4. This indexing mechanism provides constant-time access to elements and is essential for iterating over
arrays, performing operations like searching, sorting, or modifying specific elements.
Code 8.17 demonstrates the equivalence between a[1] and *(a + 1). The expression a[1] accesses the
second element of the array, which is 3. Similarly, *(a + 1) accesses the same element, as a + 1 gives the
address of the second element of the array, and the * operator dereferences that address to retrieve the value.
However, *(a + 1) and *a[1] are not the same. *(a + 1) accesses the second element of the array a
using pointer arithmetic to move the pointer to the second element and dereferencing it to get the value
stored at that location. On the other hand, *a[1] attempts to dereference the value stored at a[1]. Since
a[1] is an integer but not a pointer, this would lead to a compilation error or undefined behavior because
dereferencing a non-pointer value is not valid.
Code 8.18 shows how to print addresses using two different ways for the elements in array. The format
"%p" specifies the addresses to be printed as hexadecimal numbers, the first two characters (0x) implying
that they are so.
©Partha Bhowmick
102 Chapter 8. Pointers
The addresses are fixed during execution of the code, so they change from one execution to the other,
as you can see below. Since a is an array of integers and each integer takes 4 bytes, the addresses of two
consecutive elements differ by 4.
$ ./a.out
i = 0: a+i = 0x7fffc2527520, &a[i] = 0x7fffc2527520, a[i] = 2
i = 1: a+i = 0x7fffc2527524, &a[i] = 0x7fffc2527524, a[i] = 3
i = 2: a+i = 0x7fffc2527528, &a[i] = 0x7fffc2527528, a[i] = 5
i = 3: a+i = 0x7fffc252752c, &a[i] = 0x7fffc252752c, a[i] = 7
$ ./a.out
i = 0: a+i = 0x7fff2f412470, &a[i] = 0x7fff2f412470, a[i] = 2
i = 1: a+i = 0x7fff2f412474, &a[i] = 0x7fff2f412474, a[i] = 3
i = 2: a+i = 0x7fff2f412478, &a[i] = 0x7fff2f412478, a[i] = 5
i = 3: a+i = 0x7fff2f41247c, &a[i] = 0x7fff2f41247c, a[i] = 7
©Partha Bhowmick
Chapter 8. Pointers 103
See Code 8.19 for an example of how the sizeof() function impacts pointer arithmetic, and also
see its output therein. The statement pi = pi+10; implies that the address stored in pi increases by
8 pi = pi+10; pc = pc+10;
9 printf("pi = %u, pc = %u \n", (unsigned)pi, (unsigned)pc);
10 return 0;
11 }
©Partha Bhowmick
104 Chapter 8. Pointers
sizeof(int) ˆ10. Given that an integer takes 4 bytes, this results in 4 ˆ 10 “ 40 bytes being added to pi,
effectively shifting the pointer forward by 10 integers in memory. Similarly, the statement pc = pc + 10;
advances the character pointer pc by 10 characters or 10 bytes, since each character occupies 1 byte of
memory.
5 int main(){
6 int i = 1;
7 f(i);
8 printf("i = %d\n", i); // prints 1
9 return 0;
10 }
5 int main(){
6 int i = 1;
7 f(&i);
8 printf("i = %d\n", i); // prints 10
9 return 0;
10 }
As shown in Code 8.20, passing the value of the variable i to the function f does not change the value
of i in main(). This is because the i in f is local to the function f, and the i in main() is a separate
variable, local to main(). However, passing a pointer to i to the function f, as shown in Code 8.21, does
modify the value of i in main(). This works because the pointer contains the address of i in main(),
allowing f to access and modify the original variable.
©Partha Bhowmick
Chapter 8. Pointers 105
To swap the values of two variables, a and b, using the function swap, the correct approach is shown in
Code 8.22. This method uses pointers to the variables. If the values of the variables are passed directly
as arguments, the swap will fail, as demonstrated in Code 8.23.
Code 8.22: Correct code for swapping (arguments passed by pointers). swapFun_correct.c
1 #include <stdio.h>
2
8 int main(){
9 int a = 5, b = 10;
10 swap (&a, &b);
11 printf ("a = %d, b = %d\n", a, b); // prints a = 10, b = 5
12 return 0;
13 }
Code 8.23: Incorrect code for swapping (arguments passed by values). swapFun_incorrect.c
1 #include <stdio.h>
2
8 int main(){
9 int a = 5, b = 10;
10 swap(a, b);
11 printf ("a = %d, b = %d\n", a, b); // prints a = 5, b = 10
12 return 0;
13 }
int x, y;
scanf("%d %d", &x, &y);
printf("%d %d %d", x, y, x + y);
©Partha Bhowmick
106 Chapter 8. Pointers
Such scenarios are more effectively managed using dynamic memory management techniques.
Why not use static arrays? In C, the number of elements in an array must be specified during compi-
lation. This often leads to issues like:
1. Over-allocation, which wastes memory space.
2. Under-allocation, leading to program failure.
Through dynamic memory allocation, the required memory space can be allocated during execution time,
allowing flexibility as the data size changes. C supports this with a set of library functions for dynamic
memory management. Dynamic memory is allocated from the free memory pool, also known as the heap,
during run time.
free(p);
free(q);
This releases the memory pointed to by p and q.
4. realloc: Modifies the size of previously allocated space to accommodate changing needs.
man malloc
NAME
malloc, free, calloc, realloc - allocate and free dynamic memory
SYNOPSIS
#include <stdlib.h>
©Partha Bhowmick
Chapter 8. Pointers 107
DESCRIPTION
The malloc() function allocates size bytes and returns a pointer to
the allocated memory. The memory is not initialized. If size is 0,
then malloc() returns either NULL, or a unique pointer value that can
later be successfully passed to free().
.....
The malloc() function always allocates a block of contiguous bytes. The allocation can fail if sufficient
contiguous memory space is not available. If it fails, malloc returns NULL. So whenever a program calls
malloc, it should check whether the allocation has been done. If not, the program should be exited to
prevent error. This is how the lines of code should be written:
int *p;
p = (int *)malloc(100 * sizeof(int));
if (p == NULL) {
printf("Memory cannot be allocated");
exit(1);
}
int *p;
if ((p = (int *)malloc(100 * sizeof(int))) == NULL) {
printf("Memory cannot be allocated");
exit(1);
}
The same NULL check must be done for calloc and realloc, since they also return NULL if memory
allocation fails.
exit() is a function that immediately terminates the program execution.
Unlike return, which is a statement that simply returns control to the call-
ing function, exit completely exits the program and ends all its activities.
You must #include <stdlib.h> to use exit().
While return is used to return a value from a function or terminate a
function’s execution, exit() is used when the program needs to be stopped
immediately, often due to an error or a critical condition.
1. rFirst upper-case letter in a strings In this problem, we will see a function that returns a pointer.
Here is the problem statement: Given a string as input, find its first upper-case letter, if any. To do
this, we use a user-defined function firstUpper that returns a pointer to that letter if it exists, and
returns NULL otherwise.
©Partha Bhowmick
108 Chapter 8. Pointers
A function should not return a pointer to its local variable, because after the
function returns, the local variable no longer exists, and thus the address
stored in that pointer is invalid.
1 #include <stdio.h>
2
16 p = firstUpper(s); pointer ke sath fuction use kar rahe ho too aise karo
17
23 return 0;
24 }
2. rDynamic array managements Write a program that dynamically manages an array of integers. The
user will input a number of elements, and the program will allocate memory accordingly. The program
should also allow resizing the array if the user decides to add more elements than initially specified.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main() {
5 int *arr, size, newSize, i;
6
17 // Input elements
18 for (i = 0; i < size; i++) {
19 printf("Enter element %d: ", i);
20 scanf("%d", &arr[i]);
21 }
©Partha Bhowmick
Chapter 8. Pointers 109
22
23 // Print elements
24 printf("Array elements: ");
25 for (i = 0; i < size; i++) {
26 printf("%d ", arr[i]);
27 }
28 printf("\n");
29
57 return 0;
58 }
59
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 // Function prototypes
©Partha Bhowmick
110 Chapter 8. Pointers
10 int main() {
11 int **matrix;
12 int rows, cols, newRows, newCols;
13
23 // Print matrix
24 printMatrix(matrix, rows, cols);
25
45 return 0;
46 }
47
48 // Function definitions
49 int** allocateMatrix(int rows, int cols) {
50 int **matrix = (int **)malloc(rows * sizeof(int *));
51 if (matrix == NULL) {
52 printf("Memory allocation failed\n");
53 exit(1);
54 }
55 for (int i = 0; i < rows; i++) {
56 matrix[i] = (int *)malloc(cols * sizeof(int));
57 if (matrix[i] == NULL) {
58 printf("Memory allocation failed\n");
©Partha Bhowmick
Chapter 8. Pointers 111
59 exit(1);
60 }
61 }
62 return matrix;
63 }
64
1. rDynamic Array Initialization and Accesss Write a program that dynamically allocates memory
for an array of integers based on user input. The user specifies the number of elements, and the program
initializes the array with Fibonacci numbers up to that length. Implement a function to print the array.
Use malloc to allocate memory and free to deallocate it.
©Partha Bhowmick
112 Chapter 8. Pointers
2. rSum and Maximum Value Calculations Create a program that allocates memory for an array of
integers based on user input. After filling the array with values, compute and display the sum and
maximum value of the elements. Implement functions for allocation, input, sum and maximum value
computation, and printing. Use calloc for memory allocation and free to deallocate.
3. rDynamic Array Sortings Implement a program that dynamically allocates memory for an array of
integers. The user inputs the number of elements and their values. After sorting the array in ascending
order, print the sorted array. Implement functions for allocation, input, sorting, and printing. Use
malloc to allocate memory and free to deallocate. After sorting is discussed in the class, you can
implement this.
4. rArray Element Replacements Write a program that allocates memory for an array of integers.
After the user inputs values, prompt the user to specify an index and a new value. Replace the value at
the specified index with the new value and print the updated array. Implement functions for allocation,
input, replacement, and printing. Use malloc to allocate memory and free to deallocate.
5. rFrequency Counter of Elementss Create a program that dynamically allocates memory for an array
of integers in the interval ra, bs, where a and b are input. After filling the array with values, compute
the frequency of each unique element in the array and display the results. Implement functions for
allocation, input, frequency counting, and printing. Use malloc for memory allocation and free to
deallocate. You can use an additional dynamic array.
♣ 6. rMaximum Subarray Sums Given an array of integers, the task is to find a/the contiguous subarray
with the maximum sum. Implement a program that uses malloc to allocate memory for the array
depending on the number of elements (given as input). Then, populate the array using user’s input and
find the maximum subarray sum. You shouldn’t use more than one loop. Ensure that the program frees
the allocated memory using free.
A contiguous subarray of an array consists of consecutive elements of the array. Clearly, if all elements
are positive, then the maximum subarray sum equals the sum of all elements. Otherwise, it will be
a proper subarray. For example, in the array [-18, -9, 16, -11, 7, 15, -23, 20, -21, 17], the
maximum subarray sum is 27, and it corresponds to the subarray [16, -11, 7, 15].
♣ 7. rLongest Increasing Subsequences Given an array of distinct integers, determine the length of the
longest increasing subsequence. If using nested loops, ensure that there are exactly two loops: one inside
the other. Your program should dynamically allocate memory for the array using malloc, based on the
number of elements provided by the user, and deallocate the memory using free.
A subsequence is a sequence derived from another sequence by deleting some or no elements without
changing the order of the remaining elements.
For example, the array [18, 9, 16, 11, 7, 15, 23, 20, 21, 17] has a unique longest increasing
subsequence: [9, 11, 15, 20, 21], which has a length of 5. Any other increasing subsequence has
length less than 5.
♣ 8. rEqual-Sum Partitions Given an array of single-digit integers, determine if it can be partitioned
into two subsets with equal sum. Your program should print Yes if it is possible, and No otherwise.
Dynamically allocate memory for the array using malloc, based on the number of elements provided by
the user, and deallocate the memory using free.
A partition of an array refers to dividing it into two subsets such that they are disjoint and their sums are
equal. For example, the array [-3, 5, -1, 0, -3] can be partitioned into [-3, 5, -3] and [-1, 0]
where both subsets have a sum of ´1. But [-3, 5, -1, 0] doesn’t admit any equal-sum partition.
©Partha Bhowmick
as if you put func(int a[12]) then that dimension is ignored
we get the next row first elemnt adress by b+1 because of row major memeory mapping
* means app array me ghuse .
9 | Two-dimensional arrays
In Chapter 5, we discussed how a 1D array essentially stores a list of elements. Many applications, however,
require storing data in the form of a table. For this, we need a two-dimensional array. For example, to store
a table containing the marks of n students in k subjects, we use a 2D array where each row represents a
student and each column represents a subject. The table below illustrates an example with n “ 4 students
and k “ 5 subjects. In this array, the rows correspond to students, and the columns represent the subjects.
71 82 90 63 76
68 75 80 70 72
(9.1)
88 74 85 76 80
50 65 68 40 70
9.1 Examples
Here are some common examples of tables that can be stored in 2D arrays:
1. Multiplication Table: A table showing the products of pairs of numbers (e.g., a 10-by-10 table showing
products from 1 ˆ 1 to 10 ˆ 10).
2. Logarithm Table: A table used to find the logarithms of numbers to a certain base (e.g., base 10). It
typically contains logarithmic values for various numbers.
3. Periodic Table of Elements: The periodic table can be stored in a 2D array, with rows representing
periods and columns representing groups.
4. Gray-scale Image: A 2D array can represent a gray-scale image, where each element of the array holds
the pixel intensity value.
5. Seating Arrangement: Seating plans for events, classrooms, trains, or airplanes can be represented by a
2D array, where each element denotes a seat.
6. Chess Board: A chessboard layout can be represented as an 8 ˆ 8 matrix, where each element denotes
a piece or an empty space.
7. Sudoku Puzzle: A 9 ˆ 9 grid can represent a Sudoku board, where each cell contains numbers or
placeholders for solving the puzzle.
8. Matrix (Math): Any mathematical matrix, which is essentially a 2D array of numbers, such as for linear
algebra problems.
9. Game Board (e.g., Tic-Tac-Toe): A 3-by-3 array can represent a Tic-Tac-Toe game board, where each
cell holds a player’s mark or remains empty.
10. Distance Table: A 2D array can represent all inter-node distances (e.g., all inter-airport distances in
India) in a ‘graph’.
113
114 Chapter 9. Two-dimensional arrays
A 2D array is defined using two indexes: the first for row and the second for column. The syntax for declaring
is as follows:
type arrayName[rows][columns];
If its name is arr, it has m rows and n columns, and it stores integers, then it should be declared as follows:
int arr[m][n];
Let’s consider the table given in (9.1). To store it in a 2D array named marks, we write the following
declaration:
int marks[4][5];
Each element is denoted by marks[i][j] in which the first index i denotes the row (student), and
the second index j denotes the column (subject). They are referred to as row index and column index,
respectively.
Similar to a 1D array, indexing for each dimension starts from 0. Since marks has 4 rows and 5 columns,
the row indexes range from 0 to 3, and the column indexes range from 0 to 4. Thus, the marks of the 1st
student in the 1st subject is marks[0][0] = 71. Similarly, the marks of the 2nd student in the 3rd subject
is marks[1][2] = 80.
Here are some more examples:
int marks[2000][5];
char Sudoku[9][9];
(9.2)
float sales[12][25];
double matrix[100][100];
There are several other ways to declare 2D arrays, which will be discussed in §9.8. Also note that,
instead of simple elements such as numbers or characters, a 2D array can also store more complex data
types, such as structures, which we will study later (in the chapter on structures).
Q1 Write the amount of memory space consumed for each array declared in (9.2).
Q2 You have 1000 two-dimensional points with integer coordinates. Write a declaration for a 2D array that will
contain their px, yq coordinates.
Q3 You have 1000 three-dimensional points with integer coordinates. Write a declaration for a 2D array that
will contain their px, y, zq coordinates.
Q4 Consider a special version of Question 2 in which for all 1000 points, all the coordinates are integers in
r0, 127s. Can you store them in a 2D array that will be smaller than the one used for Question 2? Justify.
Q5 You have n circles with integer centers and integer radii. Write a declaration for a 2D array that will contains
their centers and radii. How many bytes are needed for this array?
Q6 Write five practical applications (each within 25 words) where 2D arrays can be used. Write for each of
them how the arrays should be declared.
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 115
9.3 Initialization
After declaring a 2D array, we can initialize it later by filling it with elements taken as input during execution.
Alternatively, we can initialize a 2D array at the time of declaration. The syntax for this is shown below for
the array marks from (9.1).
int marks[4][5] = {
{71, 82, 90, 63, 76},
{68, 75, 80, 70, 72},
{88, 74, 85, 76, 80},
{50, 65, 68, 40, 70}
};
int marks[4][5] = {{71, 82, 90, 63, 76}, {68, 75, 80, 70, 72}, {88, 74, 85, 76, 80},
{50, 65, 68, 40, 70}};
Code 9.24 provides an example where a 4-by-5 2D array is declared, initialized using user input, and
used to compute the total marks of 4 students.
1 #include <stdio.h>
2
3 #define ROWS 4
4 #define COLS 5
5
6 int main(){
7 int marks[ROWS][COLS], row, col, total;
8
22 return 0;
23 }
Q7 Revise Code 9.24 so that it prints the average marks of each student and the average marks for each subject.
©Partha Bhowmick
116 Chapter 9. Two-dimensional arrays
9.4 Operations
Each element of the 2D array can be treated as a usual variable and operated on using the usual operands
for its corresponding datatype. For example, these are all valid for the array marks:
Code 9.25 provides an example where a 2D array named marks is first declared and initialized, and then
its elements are modified depending on the user’s choice.
1 #include <stdio.h>
2
3 int main() {
4 int marks[4][5] = {
5 {71, 82, 90, 65, 76}, {68, 75, 80, 70, 72},
6 {88, 74, 85, 76, 80}, {50, 65, 68, 40, 70}
7 };
8
12 do {
13 printf("Enter row (0-3) and column (0-4) of the element to view: ");
14 scanf("%d %d", &row, &col);
15
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 117
Eventually, this enables the 2D array to be treated as a 1D array. (So cute! Isn’t it?) As an example,
let’s rewrite the array marks given in (9.1):
j=0 1 2 3 4
i=0 71 82 90 63 76
1 68 75 80 70 72
2 88 74 85 76 80
3 50 65 68 40 70
The above 2D array is simply equivalent to the following 1D array with 20 elements.
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
71 82 90 63 76 68 75 80 70 72 88 74 85 76 80 50 65 68 40 70
The reason is that the addresses of the elements in this 1D array adhere to the same rule. For the 2D array,
we have
address of marks[i][j] = &marks[i][j] = &marks[0][0] + (i * 5 + j) * 4
which is also the address of the element with index i*5+j in the 1D array. For example, the element in the
2D array with row index i = 3 and column index j = 1 is 65. For this element, we have i*5+j = 16, and
that’s the index of the same element in the 1D array.
Now, let us consider the general scenario in which k is the size (measured in terms of bytes) of each
element of any 2D array. It is given by the operator sizeof() discussed in Chapter 8. For example, for
an integer variable, it is given by sizeof(int), which is basically 4 bytes in standard computers. So, if an
integer array has r rows and c columns, then the total space allocated for it will be 4 ˆ r ˆ c bytes. For
example, the space allocated for the 4-by-5 array marks will be 4 ˆ 4 ˆ 5 “ 80 bytes. For a character array,
the space required will be r ˆ c bytes, as each character takes just one byte.
Now, let us see how to find the address of an arbitrary element of a 2D array from its base address.
Suppose A is a 2D array with x as its base address, and c as the number of its columns. Then the address
of A[i][j] can be calculated as x + (i * c + j) * k, or,
&A[i][j] “ &A[0][0] + (i * c + j) * k. (9.4)
As an example, the actual addresses of the elements in marks are displayed below. These addresses are
obtained by running Code 9.26. These addresses usually vary with each execution, as reflected here.
Addresses are very likely to change with each execution because memory is
actually allocated only at runtime. The allocation is done by the operating
system as per the available free space in the memory during that particular
execution.
©Partha Bhowmick
118 Chapter 9. Two-dimensional arrays
1 #include <stdio.h>
2
3 #define ROWS 4
4 #define COLS 5
5
6 int main(){
7 int marks[ROWS][COLS], row, col, total;
8
13 return 0;
14 }
Q8 You have 1000 two-dimensional points with integer coordinates. Can you store them in a 1D array? If
possible, write a declaration for that 1D array. Is this a better scheme compared to the 2D array (Question 2)?
Justify.
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 119
11 int main(){
12 int A[3][4];
13 ...
14 f1(A); // the address of the 1st element of A is passed to f1
15 ...
16 return 0;
17 }
To understand the above concept for a specific problem, let us refer back to Code 9.24. In that code,
we have seen how a 4-by-5 2D array can be declared and subsequently used in main() for specific tasks
like initialization and computing the total marks. We now modify that code to make it modular by writing
user-defined functions and calling them from main() to perform the same tasks. The modified code is given
in Code 9.28. Observe in this code that the argument passed to either of fillArray and printTotalMarks
is the array name marks, which basically works as a pointer to the first element of marks. This is just similar
to the convention followed for 1D arrays, as discussed in Chapter 8.
©Partha Bhowmick
120 Chapter 9. Two-dimensional arrays
1 #include <stdio.h>
2
3 #define ROWS 4
4 #define COLS 5
5
25 int main(){
26 int marks[ROWS][COLS];
27 fillArray(marks);
28 printTotalMarks(marks);
29 return 0;
30 }
• void freeArray(int **array, int rows): This function frees the dynamically allocated memory for
the 2D array. It takes the pointer to the 2D array and the number of rows as arguments. It first frees
each individual row, then frees the array itself. In main(), it is called as freeArray(marks, m) after the
array has been used.
• int main(): The main() function prompts the user for the number of rows (students) and columns
(subjects), allocates the 2D array using allocateArray, calls fillArray to populate the array, and then
calls printTotalMarks to display the total marks for each student. After that, it calls freeArray to
release the allocated memory.
1 #include <stdio.h>
2 #include <stdlib.h>
3
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 121
43 int main(){
44 int m, n;
45 printf("Enter number of rows (students): ");
46 scanf("%d", &m);
47 printf("Enter number of columns (courses): ");
48 scanf("%d", &n);
49
A 2D array can be declared in several ways, as shown in Code 9.30. The meanings are as follows:
1. int A[MAXROW][MAXCOL]; ñ A is a statically allocated 2D array with fixed dimensions. This refers to
memory allocated at compile time, contrasting with dynamically allocated arrays that are created at
runtime.
2. int (*B)[MAXCOL]; ñ B is a pointer to an array of MAXCOL integers. The parentheses are necessary to
bind the pointer to the array of integers, not just to an individual integer.
©Partha Bhowmick
122 Chapter 9. Two-dimensional arrays
3. int *C[MAXROW]; ñ C is an array of MAXROW pointers to integers. Each element of C can point to the
start of a separate array of integers.
4. int **D; ñ D is a pointer to a pointer to an integer. This is typically used for dynamic memory
allocation for 2D arrays.
5. The last three arrays support dynamic memory allocation; the most commonly used style is int **D;.
When properly allocated memory, any of them can be used to represent a MAXROW-by-MAXCOL array.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define MAXROW 4
5 #define MAXCOL 5
6
7 int main(){
8 int A[MAXROW][MAXCOL];
9 int (*B)[MAXCOL];
10 int *C[MAXROW];
11 int **D;
12 ...
13 return 0;
14 }
Let’s now see how memory is allocated dynamically for MAXROW-by-MAXCOL arrays using B, C, and D.
1. int (*B)[MAXCOL]; ñ B is a pointer to an array of MAXCOL integers.
So, it can be allocated MAXROW rows in the following way:
B = (int (*)[MAXCOL])malloc(MAXROW * sizeof(int[MAXCOL]));
2. int *C[MAXROW]; ñ C is an array of MAXROW int pointers. Therefore, C itself cannot be allocated
memory. The individual rows of C should be allocated memory.
int i;
for (i=0; i<MAXROW; ++i)
C[i] = (int *)malloc(MAXCOL * sizeof(int));
3. int **D; ñ D is a pointer to an int pointer. So, D is dynamic in both directions.
First, it should be allocated memory to store MAXROW int pointers, each meant for a row of the 2D array.
Each row pointer, in turn, should be allocated memory for MAXCOL int data.
D D[0] D[0][0]
int i;
D = (int **)malloc(MAXROW * sizeof(int *)); D[1]
for (i=0; i<MAXROW; ++i) D[2]
D[i] = (int *)malloc(MAXCOL * sizeof(int)); D[3]
D[3][4]
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 123
Note the following points regarding the problems stated in this section and in §9.11.
1. By m-by-n or m ˆ n array (or matrix), we mean a 2D array with m rows and n columns.
2. Unless mentioned, assume that m, n ď 10.
3. Unless mentioned, assume that all elements are integers.
1. rMatrix addition: Version 1s Compute the addition of two matrices with compatible dimensions and
elements as input, store it in a new matrix, and print its elements on the terminal.
If A and B are two compatible matrices (i.e., have m rows and n columns each), then in their sum
matrix C, the element at row i and column j is denoted by Crisrjs and evaluated as Arisrjs ` Brisrjs.
Here is an example:
» fi » fi » fi
1 3 2 1 3 2 1 3 4 5 3 4
A ` B “ –3 2 1 3fl ` –1 3 2 1fl “ –4 5 3 4fl
2 1 3 2 2 1 3 2 4 2 6 4
1 #include<stdio.h>
2
3 int main(){
4 int m, n, a[10][10], b[10][10], c[10][10], i, j;
5
29 return 0;
30 }
Q10 How many scalar additions are used in Code 9.31? (A scalar addition means the addition between two
numbers.)
©Partha Bhowmick
124 Chapter 9. Two-dimensional arrays
2. rMatrix addition: Version 2s Rewrite Version 1 of matrix addition with user-defined functions for
input of the elements of the matrices, for adding the matrices, and for printing the result.
1 #include<stdio.h>
2
26 int main() {
27 int m, n, a[10][10], b[10][10], c[10][10];
28
36 return 0;
37 }
3. rMatrix addition: Version 3 (with dynamic memory allocation)s Rewrite Version 2 of matrix
addition with user-defined functions with the provision for taking input for number of rows and columns
in main() and for memory allocation of the matrices.
In Code 9.33, you should understand the use of freeMatrix function that frees allocated memory for
a 2D array:
(i) In freeMatrix function, memory is freed in two stages.
First, each row’s memory is freed using free(matrix[i]);.
Second, the memory allocated for the array of row pointers is freed using free(matrix);.
(ii) Using free(matrix); directly only frees the memory allocated for the array of pointers,
leaving the memory for the rows still allocated. This causes memory leaks, as the memory
is no longer needed but remains occupied. Such leaks can degrade performance, particularly
when the program needs to allocate other large arrays later.
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 125
Code 9.33: Matrix addition: Version 3 (with dynamic memory allocation). matrixDynAddFun.c
1 #include <stdio.h>
2 #include <stdlib.h>
3
23 void addMatrices(int **a, int **b, int **c, int m, int n){
24 for (int i = 0; i < m; i++)
25 for (int j = 0; j < n; j++)
26 c[i][j] = a[i][j] + b[i][j];
27 }
28
36 void freeMatrix(int **matrix, int m){ // free allocated memory for matrix with m rows
37 for (int i = 0; i < m; i++)
38 free(matrix[i]); // free all cells of row i
39 free(matrix); // free m row-pointers
40 }
41
42 int main(){
43 int m, n;
44 printf("Enter #rows and #columns of the matrices: ");
45 scanf("%d%d", &m, &n);
46 int **a = allocateMatrix(m, n);
47 int **b = allocateMatrix(m, n);
48 int **c = allocateMatrix(m, n);
49
©Partha Bhowmick
126 Chapter 9. Two-dimensional arrays
4. rMatrix multiplication: Version 1s Compute the multiplication of two matrices with compatible
dimensions and elements as input, store it in a new matrix, and print its elements on the terminal.
Consider static memory allocation and do not use user-defined functions.
If A and B are two compatible matrices (i.e., with sizes m ˆ n and n ˆ p, respectively), then in their
product matrix C, the element at row i and column j is denoted by Crisrjs and evaluated as
n
ÿ
Crisrjs “ Arisrks ¨ Brksrjs.
k“1
Code 9.34: Matrix multiplication: Version 1 (without dynamic memory allocation). matrixMult.c
1 #include<stdio.h>
2
3 int main(){
4 int m, n, p;
5 int a[10][10], b[10][10], c[10][10];
6 int i, j, k;
7
28 printf("Output matrix:\n");
29 for(i=0; i<m; i++, printf("\n")) // see a correct nuance here: printf("\n") [laugh]
30 for(j=0; j<p; j++)
31 printf("%3d ", c[i][j]);
32 printf("\n");
33
34 return 0;
35 }
Q11 How many scalar multiplications are used in Code 9.34? (A scalar multiplication means the multipli-
cation between two numbers.)
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 127
5. rSaddle points An element x is said to be a saddle point in a 2D array if it is smallest in its row and
largest in its column. Given a 2D array with 4 rows and 5 columns, find whether it has any saddle
point. Write a C program with dynamic memory allocation. It need not have any user-defined function.
Assume that all elements are distinct.
In the example below, A has no saddle point, but B has exactly one saddle point: Br3sr0s “ 16.
» fi » fi
3 8 7 6 4 1 2 3 4 5
—14 5 9 10 11ffi —6 7 8 9 10ffi
A“— –13 17 2
ffi B“— ffi
15 16fl –11 12 13 14 15fl
12 18 19 1 20 16 17 28 19 20
1 #include <stdio.h>
2
3 int main(){
4 int m = 4, n = 5; // Fixed for a 4-by-5 matrix
5 int a[m][n];
6 int saddleFound = 0; // Flag to check if a saddle point is found
7 int i, j, k, minRowIndex, isSaddlePoint;
8
29 if (isSaddlePoint)
30 saddleFound = 1,
31 printf("\nSaddle point: a[%d][%d]: %d\n", i, minRowIndex, a[i][minRowIndex]);
32 }
33
34 if (!saddleFound)
35 printf("\nNo saddle point found.\n");
36
37 return 0;
38 }
Q12 At most how many comparisons are used in Code 9.35? (Comparison means element-to-element com-
parison.) At most how many comparisons will be used if Code 9.35 is generalized for an m ˆ n matrix?
Q13 Can you revise Code 9.35 to find saddle points in a different way? How?
©Partha Bhowmick
128 Chapter 9. Two-dimensional arrays
6. r4-adjacent local maxs An element x in a 2D array is said to be a local max if x is larger than all its
four adjacent elements — left, right, above, and below. If x lies on the array boundary, then it it may
be disregarded due to insufficient adjacency. Given such an array with m ě 3 rows and n ě 3 columns,
find all its local maxima.
3 #include<stdio.h>
4 #define MAX 10
5
24 if(!found)
25 printf("None");
26 printf("\n");
27 }
28
29 int main(){
30 int m, n, a[MAX][MAX];
31 printf("\nEnter #rows & #columns: ");
32 scanf("%d%d", &m, &n);
33
34 readArray(a, m, n);
35 adj4Max2D(a, m, n);
36
37 return 0;
38 }
Q14 How many comparisons are used in Code 9.36? (Comparison means element-to-element comparison.)
Q15 At most how many local maxima can be present in the 2D array? Justify.
Q17 How many comparisons will be needed in the modified code of Question 16?
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 129
7. rImage fadings An image of width c and height r is a 2-dimensional array with r rows and c columns
in which each element represents the color of a pixel. For a gray-scale image, the color is in gray shade,
expressed as an integer ranging from 0 (absolute black) to 255 (absolute white). It is stored as a PGM
(portable gray map) file, with the following content:
(i) Line 1: P2
(ii) Line 2: values of c and r (in this order)
(iii) Line 3: 255
(iv) Line 4 to Line p3 ` r ˆ cq: values of the pixel colors in row-major order
Optionally, it may contain one or more comment lines (e.g. # created by ...) just after Line 1. We
assume that in our PGM files, there is no comment line.
Given a PGM image as input, our task is to fade the image and to save it as PGM and PNG files
(Figure 9.1). Below are the commands to run your code on two input files: m1a.pgm and m1.pgm.
gcc a07-0.c
./a.out < m1a.pgm > m1a_0.pgm
convert m1a_0.pgm m1a_0.png
The sign < instructs to take input from the file m1a.pgm, and the sign > instructs to write the output to
the file m1a_0.pgm. To convert from PGM to PNG, the convert command in Linux is used.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(){
5 char imageType[3]; // imageType is in the 1st line
6 int r, c, maxColor; // r = #rows, c = #columns, maxColor = 255
7 unsigned char **a; // image array
8
29 return 1;
30 }
©Partha Bhowmick
130 Chapter 9. Two-dimensional arrays
Figure 9.1: Image fading. Left image has 77 rows and 59 columns. Right image has 549 rows and 549 columns.
The color black (value 0) in the input image is changed to the value 127 in the output image.
Q18 Modify exactly one statement of Code 9.37 so that the modified code inverts an image and saves it as
a PNG file.
For example, the image m1.pgm in Figure 9.1, after being inverted, will look as follows:
©Partha Bhowmick
Chapter 9. Two-dimensional arrays 131
a target sum t, find all its submatrices that sum up to t. Assume that 2 ď m, n ď 50. A submatrix
is defined as a contiguous rectangular block within the matrix. In the example below, there are 4
submatrices that sum up to t “ 7. The last submatrix contains just one element (7).
Input matrix:
1 -2 1 -3 2
-2 2 4 5 -6
0 0 -3 -2 7
Output submatrices:
-2 1 -3 1 -3 2 4 5 -6 7
2 4 5 4 5 0 -3 -2 7
♣ 7. rMaximum-Sum Path in a Grids You are given a 2D grid of size m ˆ n where each cell contains a
positive integer. Assume that 2 ď m, n ď 50. You start from the bottom-left cell and need to reach the
top-right cell. You can only move rightward or upward at each step. Your task is to find a/the path
that maximizes the sum of the numbers along the path.
For example, for the following grid, the maximum sum is 26.
1 2 5 4 1
2 3 6 2 1
5 2 1 3 4
Application: This problem can be applied in resource optimization, such as finding the most
profitable path in a grid-based system, and in game development, where it helps to determine
the highest-scoring path through a grid of values.
♣ 8. rMinimum-Sum Path without Obstacless Given a 2D grid where some cells may contain obstacles,
represented by the value 0, and other cells contain positive integers less than 10. Assume that the width
and height of the grid are at most 10. Find the minimum cost to reach the bottom-right cell from the
top-left cell while avoiding obstacles. You are allowed to move right, down, or diagonally (right-down).
The goal is to compute the least-cost path from the start to the destination, considering only valid
moves.
For example, for the following grid, the least-cost path has a cost of 10.
1 1 2 1 3
0 3 1 0 1
2 1 5 9 4
Application: This problem is relevant in robotics for path planning, where a robot needs to
navigate through a grid-like environment while avoiding obstacles and minimizing travel cost.
©Partha Bhowmick
10 | Searching in 1D array
Code 10.38: Linear search of a number key in a 1D array a having n numbers. linearSearch.c
132
Chapter 10. Searching in 1D array 133
2. Worst case: This occurs when the search takes the maximum number of comparisons. There are two
worst-case scenarios:
(i) Unsuccessful search: The search key is not found in the entire array, i.e., key ‰ a[i] for all i
from 0 to n-1.
(ii) Successful search: The match occurs at the very last position of the array, i.e., key “ a[n-1].
In both cases, n comparisons are required, which results in a worst-case time complexity of Opnq (pro-
nounced “big O of n”).
For any linear function (e.g., n{2`100 or 8n`5) or linear-dominant function
(e.g., n{5 ` 2 log n ` 100), we disregard constants and non-dominant terms
and say the time complexity is Opnq. The notation Op¨q captures the overall
growth rate of the function.
3. Average case: In this scenario, we calculate the number of comparisons across all possible positions where
key could be found. For the case key “ a[i], the number of comparisons is i+1 since key is compared
with a[0], a[1], . . . , a[i] until the match is found. The total number of comparisons for all n cases is
n-1 n
ÿ ÿ npn+1q
pi+1q “ i“ .
i“0 i“1
2
Code 10.39: The main() from where the linear search is called. linearSearchMain.c
1 #include <stdio.h>
2
5 int main() {
6 int a[1000]; // Declare the array
7 int n, key, result;
8
20 if (result != -1)
21 printf("Key found at index %d\n", result);
22 else
23 printf("Key not found in the array\n");
24
25 return 0;
26 }
©Partha Bhowmick
134 Chapter 10. Searching in 1D array
Binary search is applicable only if the array is sorted, i.e., its elements are arranged in increasing or decreasing
order. If the elements are not distinct but arranged in non-increasing or non-decreasing order (e.g., the array
{3, 5, 5, 6, 7, 7, 7, 9} is in non-decreasing order), then also binary search can be done.
The basis idea is as follows:
1. Look at the middle of the array.
2. If the key is not at the middle, ignore half of the array, and repeat the process with the other half.
Thus, in every step, the search space is halved (that’s why it is said to be ‘binary’), and hence the searching
converges very fast.
Binary search can be implemented in several ways, which are given in Codes 10.40, 10.41, and 10.42.
For Code 10.40, a demonstration is presented in Figure 10.1. If the search key is found, then its index is
returned; otherwise -1 is returned to the caller function. The convention of returning the index, and not the
element, is same as in linear search.
14 if (key == a[left])
15 return left;
16 else
17 return -1;
18 }
Q20 Observe that exactly four elements (37, 14, 20, 36) are accessed by Code 10.40 while searching for the key 36,
as shown in Figure 10.1. Calculate the number of accesses for keys 10, 14, 25. Do it for both Code 10.40
and Code 10.41.
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
disc-
14 20 36 37 45 51 65 82 14 20 36 37 45 discarded
51 65 82 14 20
arded 36 37 45 discarded
51 65 82
36 ď a[3]
looooomooooon 36 ‰ 14 and looooomooooon
looomooon 36 ą a[1] 36 “ a[2] ùñ return 2
loooooooooooooooomoooooooooooooooon
1st iteration 1st iteration 2nd iteration 2nd iteration
Figure 10.1: Execution of binary search (Code 10.40) with key = 36 returns the index 2. Only the accessed elements
are shown; the rest are dimmed or hidden, as they are irrelevant to the search function.
©Partha Bhowmick
Chapter 10. Searching in 1D array 135
Q21 Consider the array shown in Figure 10.1. For each element in the array, treated as a search key, determine
which keys will require the maximum number of comparisons when using Code 10.40.
9 if (key == a[mid])
10 return mid;
11
18 return -1;
19 }
1 #include <stdio.h>
2
17 int main() {
18 int a[] = {14, 20, 36, 37, 45, 51, 65, 82}; // Sorted array
19 int n = sizeof(a) / sizeof(a[0]); // Size of the array
20 int key = 36;
21 int result = binarySearchRec(a, 0, n - 1, key);
22
23 if (result != -1)
24 printf("Element %d found at index %d.\n", key, result);
25 else
26 printf("Element %d not found in the array.\n", key);
27 return 0;
28 }
©Partha Bhowmick
136 Chapter 10. Searching in 1D array
Q22 What is the base case for the recursive binary search function to terminate?
Q23 Describe the difference in memory usage between iterative and recursive binary search.
Q24 Given the following array and key, use the recursive binary search to find the key’s index:
a[] = {3, 7, 12, 18, 22, 27}, key = 21.
©Partha Bhowmick
Chapter 10. Searching in 1D array 137
1. rDeletion from an unsorted arrays Given an unsorted array a with n distinct elements, delete an
element x if it exists in a. Assume that n is 100.
For example, if the initial array is {14, 10, 36, 17, 45, 51, 40, 82}, and the element 10 is deleted,
then the new array will be {14, 36, 17, 45, 51, 40, 82}.
1 #include <stdio.h>
2
21 int main() {
22 int n, arr[MAX_SIZE], x, i;
23
46 return 0;
47 }
©Partha Bhowmick
138 Chapter 10. Searching in 1D array
2. rInsertion in a sorted arrays Given a sorted array a with n (potentially non-distinct) elements,
insert a new element x such that a remains sorted after the insertion. Assume n is less than 100, and a
can accommodate up to 100 elements. Also assume that x is not present in the array before insertion.
Use binary search to find the position where x has to be inserted.
For example, if the initial array is {14, 20, 36, 37, 45, 51, 65, 82}, and the element 27 is inserted,
then the new array will be {14, 20, 27, 36, 37, 45, 51, 65, 82}.
1 #include <stdio.h>
2
17 for (int i = *n; i > pos; i--) // Shift elements to the right
18 a[i] = a[i - 1];
19
25 int main() {
26 int a[100], n, x;
27 printf("Enter number of elements (at most 99): ");
28 scanf("%d", &n);
29
43 return 0;
44 }
3. rDeletion from a sorted arrays Given a sorted array a with n (potentially non-distinct) elements,
delete an element x if it exists in a such that a remains sorted after the deletion. Use linear search to
find the position of x. Assume that n is 100.
©Partha Bhowmick
Chapter 10. Searching in 1D array 139
For example, if the initial array is {14, 20, 36, 37, 45, 51, 65, 82}, and the element 20 is deleted,
then the new array will be {14, 36, 37, 45, 51, 65, 82}.
1 #include <stdio.h>
2
14 if (pos == -1)
15 return n; // If element is not found, return the original size
16
23 int main() {
24 int a[100], n, x;
25 printf("Enter number of elements (at most 99): ");
26 scanf("%d", &n);
27
41 return 0;
42 }
Q25 What are the best, worst, and average-case time complexities for inserting an element into a sorted
array? Assume binary search is used to find the insertion position.
Q26 What are the best, worst, and average-case time complexities for deleting an element from a sorted
array? Assume binary search is used to find the element to delete.
Q27 Will the time complexities for insertion and deletion be worse if linear search is used instead of binary
search? Explain why or why not.
©Partha Bhowmick
140 Chapter 10. Searching in 1D array
Write C programs with appropriate user-defined functions for the following problems. Elements in the input
array need not be distinct, unless mentioned.
1. rFind a pair with a given sum in an unsorted arrays Given an unsorted array a of n elements
and a target sum target, find a pair of elements in the array that adds up to target. If no such pair
exists, return -1. The function should run in quadratic time (i.e., Opn2 q time) in the worst case.
For example, if a = {9, 15, 5, 16, 2}, target = 20, the output will be (5, 15).
Q28 Provide arguments about the worst-case time complexity of your algorithm.
2. rFind a pair with a given sum in a sorted arrays Given a sorted array a of n elements and a
target sum target, find a pair of elements in the array that adds up to target. If no such pair exists,
return -1. The function should run in linear time (i.e., Opnq time) in the worst case.
For example, if a = {2, 5, 9, 15, 16}, target = 20, the output will be (5, 15).
Q29 Provide arguments about the worst-case time complexity of your algorithm.
3. rInterval search in a sorted arrays Given a sorted array a of n (possibly non-distinct) elements, and
given two integers p and q, where p ď q, find all the elements of a that lie within the interval [a, b].
For example, if a = {2, 5, 6, 9, 15, 16} and a = 5, b = 10, then the output will be {5, 6, 9}.
Q30 Provide arguments about the worst-case time complexity of your algorithm.
♣ 5. rTernary search in a sorted arrays Write a function ternarySearch that searches for a target
element (key) in an array sorted in ascending order. The function should have the prototype:
int ternarySearch(int arr[], int left, int right, int key);
The function should return the index of the key if it is found in the array. If the key is not present in
the array, the function should return -1.
It should perform the following steps:
(i) Divide the array: In each iteration, divide the search interval into three parts. This is done
by computing two mid-points: mid1 and mid2. The array is then split into three segments based
on these mid-points.
(ii) Determine the search region: Compare the target element with the values at mid1 and mid2.
Based on these comparisons, decide which segment of the array the target element might be in.
This helps in narrowing down the search region efficiently.
(iii) Recursively Search the Region: Recurse on the segment where the target element might be
located. This process continues until the element is found or the search interval is reduced to an
empty range.
Q32 What will be the worst-case time complexity of your algorithm? Is this time complexity better than
that of binary search? Justify.
©Partha Bhowmick
Chapter 10. Searching in 1D array 141
♣ 6. rExponential search in a sorted arrays In this problem, you are required to implement a search
function to find a target element (key) in a sorted array. Assume that the array is sorted in ascending
order. The function should perform the following steps:
(i) Determine the range: Begin at the start of the array and repeatedly double the index to find
a range in which the target element might be located. For instance, if the target element is less
than the value at the current index, then the element must lie in the range from the previous
index to the current index.
(ii) Binary search within the range: Once the range is identified, use Binary Search to find the
exact position of the target element within this range.
Exponential search is particularly efficient for unbounded or infinite lists.
The time complexity is Oplog nq, similar to binary search.
a b c b a
aa bb
aba aca aba bcb
abba
abcba
Your task is to find the number of palindromic subsequences in a given sequence. You need not print
these subsequences.
Your code should treat two subsequences as different only if the original indices of their constituent
characters are different, even if they consist of the same sequence of characters. For example, in the 1st
row of the above table, a appears twice; its first occurrence is for the first a in abcba, while its second
occurrence is for the second a in abcba. Similarly, in the 3rd row of the above table, aba appears twice;
its first occurrence is for the first b in abcba, while its second occurrence is for the second b in abcba.
♠ 8. rMax length of monotonic subsequences A subsequence of a sequence is derived by deleting some
or none of the elements from the original sequence without changing the order of the remaining elements.
A sequence or subsequence is monotonic if its characters are in non-decreasing order when read from
left to right.
You are given a sequence s consisting of 0, 1, and 2 only. Your task is to find the maximum length
(maxlen) of a monotonic subsequence in s such that it contains at least one from each of 0, 1, and 2.
You need not print that subsequence. Here are some examples:
• s = 2011: maxlen = 0
• s = 2012: maxlen = 3
• s = 001120201: maxlen = 6
• s = 1021002110210011012210: maxlen = 11
• s = 10210021102100110122101021002110210011012210: maxlen = 20
©Partha Bhowmick
11 | Sorting
Sorting means ordering. Given an array a[] with n distinct elements arranged in an arbitrary order, the
task of sorting involves rearranging them in increasing or decreasing order. If the elements are not distinct,
the ordering will be non-decreasing or non-increasing. After sorting, the elements are usually stored in the
same array a[] or in a different array.
For example, if the given array is {5, 2, 8, 5, 3}, then after sorting in non-decreasing order, we get
{2, 3, 5, 5, 8}. If we sort the same array in non-increasing order, we get {8, 5, 5, 3, 2}.
Henceforth, we will assume that sorting refers to arranging elements in increasing or non-decreasing
order. Specifically, the array a[] is considered sorted if and only if a[0] ď a[1] ď a[2] ď ¨ ¨ ¨ ď a[n-1].
This assumption does not affect any subsequent reasoning or arguments. With minor adjustments, any
sorting algorithm can be adapted to handle decreasing or non-increasing order as well. So, we proceed with
the following definition of sorted array:
Q33 Write a code that checks whether a given array a[] is sorted or not. How many comparisons will be needed
by your code if the array has n elements, which are not necessarily distinct?
The subarray a[0], . . . , a[i] is called a tail-max prefix if and only if a[i] “ max a[j] : 0 ď j ď i .
(
tail name se apta chal raha ki last mai largest anan chayhiye
The above definition plays a crucial role in determining whether an array is sorted, as stated below.
142
Chapter 11. Sorting 143
The heart of Bubble Sort is Fact 11.1. It basically establishes the Tail-Max property for each prefix, starting
from the longest prefix (i.e., the entire array). For every prefix, it iteratively checks every two consecutive
elements and swaps them if they are not in the correct order. As a result, each prefix ultimately becomes a
tail-max prefix.
13 if (!swapped) // No swap => prefix is sorted => all its prefixes are sorted
14 break; // Returns to the caller, e.g. main()
15 }
16 }
©Partha Bhowmick
144 Chapter 11. Sorting
An implementation of Bubble Sort is given in Code 11.46, and its demonstration is shown in Fig-
ure 11.1. Its outer for-loop considers all prefixes, starting from the longest one. For each prefix, the inner
for-loop examines every two consecutive elements and swaps them if needed. If, for a particular prefix
{a[0],...,a[i]}, no swap is needed, the flag swapped is found to be 0 (i.e., false) after the checking is com-
plete for {a[0],...,a[i]}, indicating that {a[0],...,a[i]} is sorted. This, in turn, implies that all the
prefixes of {a[0],...,a[i]} are also sorted. Since the suffix {a[i+1],...,a[n-1]} is already sorted from
previous iterations of the outer for-loop, the algorithm terminates successfully with no further iterations.
Thus, the swapped flag in the code enhances the efficiency of the algorithm.
During execution, Bubble Sort implicitly partitions the original array into two parts: the unsorted part,
which lies at the front end and gradually diminishes in size, and the sorted part that follows, as illustrated
in Figure 11.1. The sorted part contains the largest elements of the prefixes processed so far. The algorithm
begins with an empty sorted part and continues until the unsorted part becomes empty.
Time Complexity
Best case: The best-case scenario occurs when the array is already sorted, resulting in a time complexity
of Opnq since no swaps are required.
Worst case: In the worst-case scenario, the array is sorted in reverse order, leading to a time complexity
of Opn2 q as all elements must be compared and swapped.
Average case: On average, Bubble Sort performs about n2 {4 comparisons and swaps, resulting in an average
time complexity of Opn2 q.
Comments
Bubble Sort is a straightforward sorting algorithm that repeatedly traverses the list, compares adjacent
elements, and swaps them if they are in the wrong order. The algorithm derives its name from the way
lighter elements “bubble” to the top of the array with each pass, while heavier elements settle at the bottom.
Although Bubble Sort is an in-place sorting algorithm, it is not efficient for large datasets due to its
quadratic time complexity. Despite its inefficiency compared to more advanced algorithms, this algorithm is
studied primarily for historical interest.
Q34 What will be the number of comparisons for Bubble Sort, as per Code 11.46, for an array of 100 distinct
elements if the array is already sorted in increasing order? What happens if it is already sorted in decreasing
order? What will be these two values if the flag swapped is not used?
Q35 Can you initialize with i = 0 in Code 11.46 and suitably adjust the initialization of j so that your code is
correct?
Q36 Can you write a provable fact similar to Fact 11.1 in which ‘prefix’ is replaced by ‘suffix’ ? Can you use it
to design a sorting algorithm?
Q37 Given an array of 10 elements, the task is to find its smallest 5 elements. Write a code for this by modifying
Code 11.46.
©Partha Bhowmick
Chapter 11. Sorting 145
Q38 Justify whether true or false: “There is no practical way to optimize the performance of Selection Sort with
a flag for early termination.”
Q39 Justify whether true or false: “Selection Sort is a stable sorting algorithm.”
Q40 Under what circumstances might Selection Sort be preferred over more efficient algorithms?
©Partha Bhowmick
146 Chapter 11. Sorting
Figure 11.2: Selection Sort on the array {5, 2, 8, 6, 5, 3} (Min “ smallest element in the unsorted part).
An implementation of Selection Sort is given in Code 11.48. To illustrate the behavior of the Selec-
tion Sort algorithm, a demonstration of the algorithm is given in Figure 11.2. Although Selection Sort is an
in-place sorting algorithm, it is not stable. It can be made stable but that requires advanced data structures.
The core operation is selecting the smallest element (from the unsorted part), which gives Selection Sort
its name. Despite its simplicity, this algorithm provides a good starting point for understanding basic sorting
principles.
Proof of correctness
The correctness of Selection Sort can be proved through an inductive argument:
• Base Case: After the first pass, the smallest element is correctly placed in the first position.
• Inductive Step: Assume that the first k elements are sorted after k iterations. In the pk`1q-th iteration,
the algorithm selects the smallest element from the unsorted portion and swaps it with the first unsorted
element. Thus, the first k ` 1 elements are sorted.
Since the unsorted portion decreases by one element in each iteration and the sorted portion grows, eventually
the entire array is sorted.
©Partha Bhowmick
Chapter 11. Sorting 147
Time Complexity
Best case: The best-case scenario occurs when the array is already sorted. However, Selection Sort does
not take advantage of this and still performs Opn2 q comparisons. Thus, the best-case time complexity is
Opn2 q.
Worst case: In the worst case, the array is sorted in reverse order, but the number of comparisons remains
the same. Therefore, the worst-case time complexity is also Opn2 q.
Average case: In the average case, Selection Sort performs approximately n2 {2 comparisons, leading to an
average time complexity of Opn2 q.
Q41 Justify whether true or false: “Selection Sort’s primary inefficiency is the large number of comparisons, even
when the array is already sorted.”
Q42 Deduce the result that the average-case time complexity of Selection Sort is Opn2 q.
Q43 Explain why Selection Sort is not a stable sorting algorithm. Provide an example to support your answer.
Q44 Compare Selection Sort with Bubble Sort. What are the advantages and disadvantages of each?
Q45 Under what conditions might Selection Sort be preferred over more efficient algorithms like Merge Sort or
Quick Sort?
©Partha Bhowmick
148 Chapter 11. Sorting
Q46 What is the worst-case time complexity of Code 11.49? When does it occur?
Can you modify Code 11.49 so that its worst-case time complexity is improved? How?
©Partha Bhowmick
Chapter 11. Sorting 149
©Partha Bhowmick
150 Chapter 11. Sorting
Proof. (Forward) We prove by contradiction. Suppose that PSR holds for every prefix-suffix pair but the
array is not sorted. Then, there must exist at least two elements a[i] and a[j] such that i < j and
a[i] > a[j]. Since i < j, we can construct a prefix-suffix pair (p[], s[]) such that p[] contains a[i]
and s[] contains a[j]. This specific pair violates PSR, resulting to contradiction.
(Converse) If the array is sorted, then a[i] ď a[j] for every pair (i, j) with i < j. So, for every prefix-
suffix pair, PSR holds.
As an illustration, consider two arrays: {2, 3, 5, 5, 8} and {5, 2, 8, 5, 3}, the former being
sorted, but the latter not. For the sorted array, PSR holds for every prefix-suffix pair. For instance, take
the prefix {2, 3, 5} and suffix {5, 8}. Notice that every element of the prefix is at most as large as every
element of the suffix. On the contrary, for the unsorted array, if we take the prefix {5, 2, 8} and suffix
{5, 3}, the prefix element 8 is greater than the suffix element 5, which violates PSR.
PS-Sort
Based on Fact 11.3, we can design an algorithm for sorting, named PS-Sort (‘P’ for prefix, ‘S’ for suffix; we
are sorting based on prefix-suffix pairs, whence the name), as shown in Code 11.50. It has a nested loop.
The outer loop iterates over each possible prefix of the array, starting from the smallest one. The inner loop
the last element a[i] of the prefix p[] with every subsequent element a[j] in the suffix s[], where j > i.
The formal proof of correctness is given below.
©Partha Bhowmick
Chapter 11. Sorting 151
Proving that PS-Sort follows Fact 11.3 suffices. Let’s prove by induction on the index i that works as a
variable of the outer loop.
Basis: For i = 0, the prefix p[] consists of the single element a[0], and the suffix s[] consists the rest.
The inner loop compares a[0] with all elements of s[], ensuring that if any element of s[] is smaller than
a[0], they are swapped, thus ensuring PSR.
Hypothesis: Assume that PSR is true for every prefix-suffix pair up to iteration i - 1.
Inductive Step: By hypothesis, PSR holds for every prefix-suffix pair up to iteration i - 1, which we refer
to as previous iteration. We need to prove that PSR holds for the prefix-suffix pair at iteration i (current
iteration). The current prefix includes a[i] as its last element, which happened to be the first element of
the previous suffix. The inner loop ensures a[i] in the current prefix is the smallest element of the previous
suffix, so that no element of the current suffix is smaller than a[i]. During this process, a[i] is compared
with each element a[j] of the set {a[i+1],...,a[n-1]}, and if a[j] is smaller, the two are swapped. This
guarantees that PSR is maintained for the current prefix-suffix pair.
The outer loop runs from i = 0 to i = n - 2, which is Opnq. For each iteration of the outer loop, the inner
loop runs from j = i + 1 to j = n - 1, and so it has n ´ i ´ 1 iterations. Summing up, the total number
of iterations in the PS-Sort algorithm is
How is PS-Sort?
If you examine carefully, the algorithm PS-Sort is essentially a variant of the Selection Sort algorithm, but
with a different mechanism for locating and placing the smallest element in the correct position. In the
Selection Sort algorithm, for each iteration, the smallest element in the unsorted portion of the array is
identified and swapped with the first unsorted element, ensuring that the new prefix is sorted after each
iteration. However, in PS-Sort, the last element of the current prefix is compared with all other elements in
the suffix, and swaps are performed whenever necessary to maintain the Prefix-Suffix Relation (PSR). This
approach results in multiple swaps, in contrast to Selection Sort, which only swaps the smallest element once
per iteration.
Despite the difference in implementation, both algorithms have the same time complexity of O n2 , but
` ˘
Selection Sort typically performs fewer swaps since it minimizes them to one per iteration of the outer loop,
whereas PS-Sort may swap elements more frequently.
Nevertheless, the idea of using Prefix-Suffix Relation (PSR) can be used to improve the average-case
time complexity to O pn log nq. To achieve this, we need the following fact, which is even deeper than the
previous one.
©Partha Bhowmick
152 Chapter 11. Sorting
Proof. (Forward) We proceed by induction on n, with the base case n = 1, where the proof is trivial. Assume
as the inductive hypothesis that the fact holds when the array has 2 to n - 1 elements.
In the inductive step, let PSR hold for some prefix-suffix pair (p[], s[]) in an array of size n. By
the inductive hypothesis, p[] and s[] are sorted, as each contains at most n - 1 elements and admits PSR
recursively. Further, since PSR holds for (p[], s[]), no element of p[] is larger than any element of s[].
Thus, the entire array is sorted.
(Converse) Assume the array is sorted. By Fact 11.3, PSR holds for every prefix-suffix pair (p[], s[]),
including recursively defined prefix-suffix pairs within p[] and s[]. This confirms the converse.
Fact 11.4 underpins the principle of Quick Sort. Based on this principle, Quick Sort utilizes the divide-and-
conquer technique to sort an array or subarray. Here are the main steps:
1. Divide: Choose an element called pivot from the array. This can be the first, last, or a random element.
We choose the last element in Code 11.51. Partition the array into three parts: prefix, pivot, suffix, so
that no element of prefix is larger than the pivot and no element of suffix is smaller than the pivot.
2. Conquer: Recursively apply Quick Sort to the prefix and the suffix. (The pivot is already in its correct
position.)
3. Combine: Because the prefix and the suffix are already sorted, and the pivot is sandwiched between
them, no work is needed to combine them! The entire array is now sorted.
The divide-and-conquer paradigm involves three steps at each level of the recursion:
1. Divide the problem into smaller sub-problems.
2. Conquer the sub-problems by solving them recursively. Define the basis of recursion
so that when a sub-problem is small enough, the solution is trivial.
3. Combine the sub-problem solutions to get the solution for the original problem.
The Quick Sort code is presented in Code 11.51, and its demonstration on two arrays is illustrated in
Figure 11.4. Its recursion tree for one array is given in Figure 11.5.
Consider the array or subarray a[low],...,a[high]. If it is the original array a[] containing n ele-
ments, then the indices low and high are 0 and n - 1, respectively. A subarray typically represents either
a prefix or suffix of the original array or of a larger prefix or suffix. Quick Sort has several variations, which
are minor modifications of one another. In Code 11.51, the pivot is always the last element of the array or
subarray to be partitioned into a prefix and suffix. Moreover, the pivot is neither included in the prefix nor
the suffix. In some variants, the pivot is the first element and is placed in the prefix if the suffix is nonempty,
and in the suffix otherwise.
©Partha Bhowmick
Chapter 11. Sorting 153
8 int partition(int a[], int left, int right){ // CLRS Book: Edition 3
9 int pivot = a[right]; // Choose the last element as pivot
10 int i = left-1; // Index of the smaller element
11
31 void quickSort(int a[], int left, int right){ // CLRS Book: Edition 3
32 if (left < right){
33 int pivot = partition(a, left, right);
34 quickSort(a, left, pivot - 1); // Sort prefix
35 quickSort(a, pivot + 1, right); // Sort suffix
36 }
37 }
38
39 int main(){
40 int a[100], n;
41 printf("Enter the number of elements (at most 100): ");
42 scanf("%d", &n);
43
53 return 0;
54 }
©Partha Bhowmick
154 Chapter 11. Sorting
Figure 11.4: Demonstration of Quick Sort on two arrays. Note that in Step 2 and Step 4 of the first array, no swaps
occur because the pivot is the largest element. As a result, their suffixes are empty, and the pivot does
not belong to either the prefix or suffix. Thus, the prefix, followed by the pivot, and then the suffix,
forms the array or subarray after each partition. The ‘array state’ column shows the arrangement of
elements in the entire array after each step.
Original Array
quickSort
{2, 8, 7, 1, 3, 5, 6, 4}
Prefix Suffix
quickSort pivot quickSort
{2, 1, 3} 4 {7, 5, 6, 8}
Prefix Prefix
quickSort pivot quickSort pivot
{2, 1} 3 {7, 5, 6} 8
Figure 11.5: The recursion tree of Quick Sort for the input array {2, 8, 7, 1, 3, 5, 6, 4}.
The symbol stands for empty prefix or suffix.
An interesting fact: If you collect the elements of leaf nodes (pivots are also leaves) from left to right,
you get the sorted sequence.
Comments
Quick Sort is an in-place algorithm but uses the recursion stack. It does not admit tail recursion, so it
cannot be implemented as an iterative function, as seen in previous algorithms such as Bubble Sort and
Insertion Sort. However, by utilizing a user-defined stack, the recursion can be avoided, although this adds
some complexity to the implementation.
©Partha Bhowmick
Chapter 11. Sorting 155
7 while (i <= mid && j <= right){ // Merging the prefix and suffix
8 if (a[i] <= a[j])
9 temp[k++] = a[i++];
10 else
11 temp[k++] = a[j++];
12 }
13
33 int main(){
34 int n, a[100]; // Assume that there are at most 100 elements
35 ... // Take as input n and the elements
36 mergeSort(a, 0, n - 1);
37 ... // Print and do other things if needed
38 return 0;
39 }
©Partha Bhowmick
156 Chapter 11. Sorting
Before
Step partition or left right mid Prefix Suffix After merging
(index) (index) (index)
merging
Input array: {2, 8, 7, 1, 3, 5, 6, 4}
{2, 8, 7, 1, 3,
1 0 7 3 {2, 8, 7, 1} {3, 5, 6, 4} -
5, 6, 4}
2 {2, 8, 7, 1} 0 3 1 {2, 8} {7, 1} -
3 {2, 8} 0 1 0 {2} {8} {2, 8}
4 {7, 1} 2 3 2 {7} {1} {1, 7}
5 {2, 8, 1, 7} 0 3 1 {2, 8} {1, 7} {1, 2, 7, 8}
6 {3, 5, 6, 4} 4 7 5 {3, 5} {6, 4} -
7 {3, 5} 4 5 4 {3} {5} {3, 5}
8 {6, 4} 6 7 6 {6} {4} {4, 6}
9 {3, 5, 4, 6} 4 7 5 {3, 5} {4, 6} {3, 4, 5, 6}
{1, 2, 7, 8, 3, {1, 2, 3, 4, 5,
10 0 7 3 {1, 2, 7, 8} {3, 4, 5, 6}
4, 5, 6} 6, 7, 8}
Input array: {5, 2, 8, 5, 3}
1 {5, 2, 8, 5, 3} 0 4 2 {5, 2, 8} {5, 3} -
2 {5, 2, 8} 0 2 1 {5, 2} {8} -
3 {5, 2} 0 1 0 {5} {2} {2, 5}
4 {2, 5, 8} 0 2 1 {2, 5} {8} {2, 5, 8}
5 {5, 3} 3 4 3 {5} {3} {3, 5}
6 {2, 5, 8, 3, 5} 0 4 2 {2, 5, 8} {3, 5} {2, 3, 5, 5, 8}
©Partha Bhowmick
Chapter 11. Sorting 157
Time Complexities
Algorithm In-place Stable Best Case Worst Case Average Case Auxiliary Space
Selection Sort 3 7 Opn2 q Opn2 q Opn2 q Op1q
Bubble Sort 3 3 Opnq Opn2 q Opn2 q Op1q
Insertion Sort 3 3 Opnq Opn q
2
Opn q
2
Op1q
Merge Sort 7 3 Opn log nq Opn log nq Opn log nq Opnq
Quick Sort 3 7 Opn log nq Opn2 q Opn log nq Oplog nq
Heap Sort 3 7 Opn log nq Opn log nq Opn log nq Op1q
Each algorithm has its own strengths and weaknesses, and the choice of which to use depends on the
nature and size of the input data, and the specific application requirements. Among the algorithms listed
above, only Merge Sort requires an auxiliary array, while the others perform in-place sorting, meaning they
sort the data without needing extra space proportional to the input array size. However, it is important
to note that Quick Sort also utilizes a stack to manage recursive calls, which results in additional space
usage, typically Oplog nq on average. There are certain advanced techniques to make Merge Sort work in-
place, but those are not easily implementable. Quick Sort and Heap Sort stand out for their intriguing
analyses and tricky implementations. Both algorithms are widely studied for their elegance and practical
efficiency. Similarly, Merge Sort has received significant attention for its stability and effective performance,
particularly in external sorting scenarios. The key attributes of a sorting algorithm are evaluated based on
their properties, as well as their time and space complexities. The important ones are as follows:
• Comparison-based sorting: These algorithms rely on comparing elements to determine their order. It
can be proved that the worst-case time complexity for such algorithms cannot be reduced below Opn log nq.
• In-place sorting: An in-place algorithm sorts data without requiring additional space proportional to
the input size. It typically uses only a constant amount of extra space, making it memory-efficient.
• Stable sorting: A sorting algorithm is stable if it preserves the relative order of elements with equal
keys. For i < j, if a[i] and a[j] are equal in the input array, a stable sort ensures that a[i] appears
before a[j] in the output.
Example: Consider an array of pairs {(5,A), (1,B), (5,C)}. After sorting by the first value (key),
a stable algorithm would return {(1,B), (5,A), (5,C)}, preserving the order of (5,A) and (5,C).
Table 11.1 provides a concise comparison of the above sorting algorithms based on their type (comparison-
based, in-place, stable) and their time complexities across best, worst, and average cases. This allows for an
easy selection of the most appropriate algorithm depending on the nature of the data and the requirements
of the task at hand. By comparing attributes such as stability and efficiency, the table helps highlight the
trade-offs involved, especially for large datasets.
Sorting algorithms may be recursive (e.g., Quick Sort, Merge Sort) or iterative (e.g., Bubble Sort). Recursive
algorithms often use function calls to divide the problem into smaller sub-problems, while iterative ones rely
on loops to process elements.
Both Quick Sort and Merge Sort can be implemented iteratively but the coding is complex. To implement
Quick Sort iteratively, an explicit stack is needed to manage the subarrays, making it iterative. Merge Sort
can also be implemented iteratively by using a bottom-up approach, where the array is repeatedly merged
in pairs until fully sorted.
©Partha Bhowmick
158 Chapter 11. Sorting
©Partha Bhowmick