Padma Reddy - Data Structures and Applications - A Simple and Systematic Approach
Padma Reddy - Data Structures and Applications - A Simple and Systematic Approach
1.1 Introduction
In this chapter, let us discuss about the definition of data structures and see how the
data structures are classified. Basic terminology and concepts are defined along with
examples. The way the data is represented, organized, managed and structured is
crucial part of the data structures. The data to be manipulated can be used
individually or in a group under one common name and data can be of different types.
Note: Let us take an example relating to the last difference. Consider the statement
“My daughter is seven years old”. I can represent may daughter’s age as “seven in
words” or “7 in figures” or “VII in roman” or “111 in binary” or “1111111 in unary”
and so on. All these representations we call as data. Even though representation
changes, my daughter’s age has not changed. So, we say that information does not
change and data representation may change.
Note: Even though normally we use data and information interchangeably, they are
totally different and they are not synonyms.
The data are represented by the state of electronic switches. A switch with ON state
represents 1 and a switch with OFF state represents 0 (zero). Thus, all digital
computers use binary number system to represent the data. So, the data that we input
to the computer is converted into 0’s and 1’s. The letters, digits, punctuation marks,
sound and even pictures are represented using 1’s and 0’s. Thus, computers represent
data using binary digits 0 and 1 i.e., in binary number system.
Systematic Approach to Data Structures using C - 1.3
For example, consider the string “ABC01” that we type from the keyboard. This data
can be represented in the computer as shown below.
Characters typed: A B C 0 1
ASCII values 41 42 43 30 31
(Binary values) 0100 0001 0100 0010 0100 0011 0011 0000 0011 0001
All the quantities are measured in some units. For example, length may be measured
in meters or feet. On similar lines, to measure computer memory, we require units.
Now, let us see “How does the data represented using 1’s and 0’s can be grouped or
measured?” The data represented can be grouped or measured using following units:
Bit = 0 or 1
Nibble = 4 bits
Byte = 8 bits
Units of data
Kilobyte = 1024 bytes
Megabyte = 1024 Kilobytes
Gigabyte = 1024 Megabytes
Terabyte = 1024 Gigabytes
attributes
entity
student Name Usn Phone_no branch
MONALIKA 101 9900170827 CSE
Attribute values
Now, let us see “In what way the attributes, entities and entity sets are related to
fields, records and files?” The way the data are organized into the hierarchy of fields,
records and files reflect the relationship between attributes, entities and entity sets.
Now, let us see “What is a field? What is a record? What is a file”
Definition: A field is a single elementary unit of information representing an attribute
of an entity. A record is a collection of field values of a given entity. A file is a
collection of records of the entities in a given entity set.
Integers: An integer is a whole number without any decimal point or a fraction part.
No extra characters are allowed other than ‘+’ and ‘–‘ sign. If ‘+’ and ‘–‘ are present,
they should precede the number. The integers are normally represented in binary or
hexadecimal. All negative numbers are represented using 2’s complement. Based on
the sign, the integers are classified into:
Unsigned integer
Signed integer
Floating point number: The floating point constants are base 10 numbers with
fraction part such as 10.5. All negative numbers should have a prefix ‘–‘. A positive
number can have an optional ‘+’ sign. No other extra characters are allowed. The
floating point constants can be represented using two forms as shown below:
Fractional form
Floating point
notations Scientific notation
(Exponent notation)
Fractional form: A floating point number represented using fractional form has an
integer part followed by a dot and a fractional part. We can omit the digits before the
decimal point or after the decimal point. For example, 0.5, -0.99, -.6, -9., +.9 etc
are all valid floating point numbers.
Exponent form (Scientific notation): The floating point number represented using
scientific notation (also called exponential notation) has three parts namely:
mantissa e/E exponent.
1.6 Introduction to data structures and arrays
Ex1: 9.86 E 3 imply 9.86x103
Ex2: 9.86 e -3 imply 9.86x10-3
where
The mantissa can be an integer or a floating point number represented using
fractional notation.
The letter e or E should follow mantissa.
The exponent should follow e or E. The exponent has to be an integer with
optional ‘+’ or ‘–‘ sign.
For example,
6.698274e2 means 6.698274 x 102
-0.36e-54 means -0.36 x 10-54
Observe from the table that character ‘A’ has an ASCII value 41, the character ‘B’
has an ASCII value 42 and character ‘C’ has an ASCII value 43. So, if we type the
text “ABC” from the keyboard, to the computer, it appears as shown below:
A B C (Characters)
41 42 43 (ASCII values)
0100 0001 0100 0010 0100 0011 (Binary values )
Systematic Approach to Data Structures using C - 1.7
Pointer: A pointer is a special variable which contains address of a memory location.
Using this pointer, the data can be accessed.
For example, assume that a program contains four occurrences of a constant 3.1459.
During the compilation process, four copies of 3.1459 can be created as shown below:
c 3.1459 a
The variables b, c and d are called pointers since they contain address of variable a.
For example, arrays, structures, stacks, queues, linked lists, trees, graphs, files
etc., are some of the non- primitive data structures.
Now, let us see “What are the different types of non-primitive data structures?” The
non-primitive data structures are classified as shown below:
Definition: The data structure where its values or elements are not stored in a
sequential or linear order is called non-linear data structures. Unlike linear data
structures, here, the logical adjacency between the elements is not maintained and
hence elements cannot be accessed if we go in sequential order. In non-linear data
structures, a data item (or an element) could be attached to several other data items.
For example, graphs, trees, files are all non-linear data structures.
Now, let us “Explain non-linear data structures?” All the non-linear data structures
such as arrays, stacks, queues, linked lists, graphs, trees and files are discussed below:
Arrays: Definition: An array is a special and very powerful data structure in C
language. An array is a collection of similar data items. All elements of the array
share a common name. Each element in the array can be accessed by the subscript
(or index). Array is used to store, process and print large amount of data using a
single variable.
Ex 1: Set of integers, set of characters, set of students, set of pens etc. are
examples of various arrays.
10 15 20 25 30
A[0] A[1] A[2] A[3] A[4]
Systematic Approach to Data Structures using C - 1.9
Ex 3: An array of 5 characters is pictorially represented as shown below:
Stack: A stack is a special type of data structure (linear data structure) where
elements are inserted from one end and elements are deleted from the same end.
Using this approach, the Last element Inserted is the First element to be deleted
Out, and hence, stack is also called Last In First Out (LIFO) data structure.
The stack s={a0, a1, a2,……an-1) is pictorially represented as shown below:
Insert Delete The elements are inserted into the
stack in the order a0, a1, a2,……an-1.
That is, we insert a0 first, a1 next and
so on. The item an-1 is inserted at the
an-1 Top of stack end. Since, it is on top of the stack, it
is the first item to be delted. The
various operations performed on
a2 stack are:
Insert: An element is inserted
a1
from top end. Insertion operation
a0 Bottom of stack is called push operation
Delete: An element is deleted from top end only. Deletion operation is called pop
operation.
Overflow: Check whether the stack is full or not.
Underflow: Check whether the stack is empty or not.
Queue: A queue is a special type of data structure (linear data structure) where
elements are inserted from one end and elements are deleted from the other end.
The end at which new elements are added is called the rear and the end from
which elements are deleted is called the front. Using this approach, the First
element Inserted is the First element to be deleted Out, and hence, queue is also
called First In First Out (FIFO) data structure.
For example, consider the queue shown below having the elements 10, 50 and 20:
q
10 50 20
0 1 2 3 4
front rear
The items are inserted into queue in the order 10, 50 and 20. The variable q is
used as an array to hold these elements
1.10 Introduction to data structures and arrays
Item 10 is the first element inserted. So, the variable front is used as index to
the first element
Item 20 is the last element inserted. So, the variable rear is used as index to
the last element
Linked lists: A linked list is a data structure which is collection of zero or more
nodes where each node is connected to the next node. If each node in the list has
only one link, it is called singly linked list. If it has two links one containing the
address of the next node and other link containing the address of the previous
node it is called doubly linked list. Each node in the singly list has two fields
namely:
info – This field is used to store the data or information to be manipulated
link – This field contains address of the next node.
The pictorial representation of a singly linked list where each node is connected to
the next node is shown below:
first
20 30 10 60 \0
The above list consists of four nodes with info fields containing the data items 20,
30, 10 and 60.
Graphs: A graph is a non-linear data structure which is a collection of vertices
called nodes and the edges that connect these vertices. Formally, a graph G is
defined as a pair of two sets V and E denoted by
G = (V, E)
where V is set of vertices and E is set of edges. For example, consider the graph
shown below:
5
4 E = { (1, 6), (1, 2), (2, 3), (4, 3), (5, 3), (5, 6), (6, 4) }
2 0 is set of edges
Note:|V| = |{1, 2, 3, 4, 5, 6}| = 6 represent the number of
3 vertices in the graph.
|E| = |{(1, 6), (1, 2), (2, 3), (4, 3), (5, 3), (5, 6), (6, 4) }| = 7 represent the
number of edges in the graph.
Systematic Approach to Data Structures using C - 1.11
Trees: A tree is a set of finite set of one or more nodes that shows parent-child
relation such that:
There is a special node called the root node
The remaining nodes are partitioned into disjoint subsets T1, T2, ……Tn, n ≥ 0
where T1, T2, T3 ……Tn which are all children of root node are themselves
trees called subtrees.
Ex1 : Consider the following tree. Let us identify the root node and various
subtrees:
The tree has 8 nodes : A, B, C, D, E,
root A
F, G and H.
subtrees The node A is the root of the tree
We normally draw the trees with root
B C D at the top
The node B, C and D are the children
E F G H of node A and hence there are 3
subtrees identified by B, C and D
The node A is the parent of B, C and D whereas D is the parent of G and H
Binary tree: A binary tree is a tree which is collection of zero or more nodes and
finite set of directed lines called branches that connect the nodes. A tree can be
empty or partitioned into three subgroups namely root, left subtree and right
subtree.
Root – If tree is not empty, the A
first node is called root node.
left subtree – It is a tree which is
connected to the left of root. B D
Since this tree comes under root,
it is called left subtree.
right subtree – It is a tree which C E F I
is connected to the right of root.
Since this tree comes under the H J
root, it is called right subtree.
Each of the operations can be performed one after the other. For example, given an
item, we may have to traverse the list and during this process we can search for the
item in the list. If the item is present in the list, we may delete that item and insert
another item in that place. Then we may have to sort the resulting list and display the
sorted list.
Exercises
1) What is data? What is information?
2) What are the differences between data and information?
3) Define the terms: entity, attribute, entity set, field, record, file
4) Define data structures and types of data structures
5) What are primitive data structures? Explain
6) What are non-primitive data structures? Explain
7) Explain the different types of non-primitive data structures
8) What are the different types of number systems that are commonly used?
9) What are linear data structures? What are non-linear data structures? Explain
10) What are the operations performed on various data structures?
Chapter 2: Arrays
What are we studying in this chapter?
Arrays: Definition and representation of linear arrays in memory
Dynamically allocated arrays (Discussed in chapter 3)
Array operations
Traversing, Inserting and deleting
Searching and sorting
Multi-dimensional arrays
Application of arrays:
Polynomials (discussed in structures and unions – chapter 5)
sparse matrices (discussed in structures and unions – chapter 5)
2.1 Introduction
In this section, let us see array concepts in detail. First, let us see “What is an array?”
Ex 1: Set of integers, set of characters, set of students, set of pens etc. are examples of
various arrays.
10 15 20 25 30
A[0] A[1] A[2] A[3] A[4]
Now, the question is “How to access these elements?” Since an array is identified by
a common name, any element in the array can be accessed by specifying the subscript
(or an index). For example,
0th item 10 can be accessed by specifying A[0]
1st item 20 can be accessed by specifying A[1]
2nd item 30 can be accessed by specifying A[2]
3rd item 40 can be accessed by specifying A[3]
4th item 50 can be accessed by specifying A[4]
Now, let us see “How to declare and define a single dimensional array?” As we
declare and define variables before they are used in a program, an array also must be
declared and defined before it is used. The declaration and definition informs the
compiler about the:
Type of each element of the array
Name of the array
Number of elements (i.e., size of the array)
The compiler uses this size to reserve the appropriate number of memory locations so
that data can be stored, accessed and manipulated when the program is executed. A
single dimensional array can be declared and defined using the following syntax:
data type such as int, float, char etc.
name of the array
expression must be evaluated to integer
type array_name [ int_expression ]; // semicolon is must at the end
Number of items = ub – lb + 1
=9 -5 +1
=5
Observe that in C language, array index always starts from 0 whereas in Pascal
language, array index can start from any integer (even negative indexing is possible in
Pascal). Now, let us see “How to obtain the location of a[j] in a single dimensional
array?” Let us take the following array declaration in Pascal language:
a : array[5..9] of integer; // Here, lower bound : LB = 5
// upper bound : UB = 9
where
x is the distance from base address upto jth row
2.4 Arrays
Calculation of x: Given the base address, the distance from base address can be
obtained as shown below:
Expressing in
terms of index
Address of each row
Given any element a[j], its address can be calculated using the above relation and
the time taken to calculate location is same. So, the time taken to access a[5], a[6],
a[7], a[8] and a[9] remains same.
The time taken to locate any element a[j] is independent of j. That is, irrespective
of j, the time taken to locate an array element a[j] remains same.
This is very important property of linear arrays. (Linked lists discussed in chapters
8 and 9 do not have this property.
Systematic Approach to Data Structures using C - 2.5
Example 2.1: A car manufacturing company uses an array car to record number of
cars sold each year starting from 1965 to 2015. Rather than beginning the array index
from 0 or 1, it is more useful to begin the array index from 1965 as shown below:
500 504 508 512 516 …….. 700
Example 2.2: Consider the linear arrays AAA(5:50), BBB(-5:10) and CCC(1:18)
(a) Find the number of elements in each array
(b) Suppose Base(AAA) = 300 and w = 4 words per memory cell for AAA. Find
the address of AAA[15], AAA[35] and AAA[55]
Solution:
(a) The number of elements in each array can be calculated using the following
relation:
Number of elements = ub – lb + 1
So, number of elements of array AAA = 50 – 5 + 1 = 46
Number of elements of array BBB = 10 – (-5) + 1 = 16
Number of elements of array CCC = 18 – 1 + 1 = 18
(b) It is given that Base(AAA) = 300, w = 4, lb = 5
We know that Loc(a[i]) = Base(a) + w *(i – lb)
Loc(AAA[15]) = 300 + 4 * (15 – 5) = 340
Loc(AAA[35]) = 300 + 4 * (35 – 5) = 420
Loc(AAA[55]) cannot be computed since 55 exceeds ub = 50
2.6 Arrays
The various operations that can be performed on arrays are shown below:
Traversing
Inserting
Deleting
searching
sorting
2.3.1 Traversing
Now, let us see “What is traversing an array?” Visiting or accessing each item in the
array is called traversing the array. Here, each element is accessed in linear order
either from left to right or from right to left.
In this section, let us see “How to read the data from the keyboard and how to display
data items stored in the array?”
We can easily read, write or process the array items using appropriate programming
constructs such as for-loop, while-loop, do-while, if-statement, switch-statement etc.
Consider the declaration shown below:
int a[5];
Here, memory for 5 integers is reserved and each item in the array can be accessed by
specifying the index as shown below:
Using a[0] through a[4] we can access 5 integers.
Note: In general, Using a[0] through a[n-1] we can access n data items.
Once we know how to access each location in the memory, next question is “How to
store the data items in these locations which are read from the keyboard?”
This is achieved by reading n data items from the keyboard using scanf() function
which is available in C library as shown below:
Systematic Approach to Data Structures using C - 2.7
scanf(“%d”, &a[0]);
scanf(“%d”, &a[1]);
scanf(“%d”, &a[2]);
………………
………………
scanf(“%d”,&a[n-1]);
So, in C language, if we want to read n data items from the keyboard, the following
statement can be used:
Similarly to display n data items stored in the array, replace scanf() by printf()
statement as shown below:
for (i = 0; i < n; i++)
{
printf(“%d”, a[i]);
}
Now, the function to create an array by reading n elements can be written as shown
below:
Example 2.3: Functions to read n items into an array
Now, let us see “How to insert an item into an unsorted array based on the position?”
Design: An item can be inserted into the array by considering various situations as
shown below:
Step 1: Elements are present (Invalid position): This case can be pictorially
represented as shown below:
Item = 60
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 1
pos
Can you insert an item at 8th position onwards in the above array? No, we cannot
insert since, it is invalid position. That is, if pos is greater than 7 or if pos is less than
0, the position is invalid. The code for this case can be written as shown below:
if (pos > n || pos < 0) // When pos is greater than number of items
{
1 printf (“Invalid position\n”);
return n; // No insertion and return number of items
}
Systematic Approach to Data Structures using C - 2.9
Step 2: Make room for the item to be inserted at the specified position: Consider
the following list with 7 elements and item 60 to be inserted at position 3.
Item = 60
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 pos
We have to make room for the item to be inserted at position 3. This can be done by
moving all the elements 30, 80, 70 and 90 from positions 6, 5, 4, 3 into new positons
7, 6, 5, 4 respectively towards right by one position as shown below:
2
Item = 60
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 pos
This is possible using the following assignment statements in the order specified:
a[7] = a[6];
a[6] = a[5];
a[5] = a[4];
a[4] = a[3]
In general, a[i+1] = a[i] for i = 6 down to 3
for i = n-1 down to pos
Now, the code for above activity can be written as shown below:
for (i = n-1; i >= pos; i--)
{
2 a[i+1] = a[i];
}
After executing, the above statement, the array contents can be pictorially represented
as shown below:
Item = 60
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 pos
2.10 Arrays
Step 3: Insert item at the specified position: The code for this case can be written as
shown below:
3 a[pos] = item;
Now, the contents of array can be written as shown below:
Item = 60 3
a 50 40 20 60 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 pos
Step 4: Update number of elements in above array: The code for this case can be
written as shown below:
4 return n + 1;
Now, the complete function to insert an item at the specified position can be written
as shown below:
Example 2.5: Function to insert an item at the specified position in the array
Design: An item can be deleted from the array by considering various situations as
shown below:
Step 1: Elements are present (Invalid position): This case can be pictorially
represented as shown below:
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 1
pos
Can you delete an item from 7th position onwards in the above array? No, we cannot
delete since, it is invalid position. That is, if pos is greater than or equal to 7 or if pos
is less than 0, the position is invalid. The code for this case can be written as shown
below:
if (pos >= n || pos < 0 ) // When pos >= number of items
{
1 printf (“Invalid position\n”);
return n; // No deletion and return number of items
}
Step 2: Display the item to be deleted: Consider the following list with 7 elements
and let the position pos is 3.
a 50 40 20 90 70 80 30
0 1 2 3 4 5 6 7 8 9
n=7 pos
The item at position pos can be accessed by writing a[pos] and it can be displayed
using the printf() function as as shown below:
a 50 40 20 70 80 30
0 1 2 3 4 5 6 7 8 9
n=6
Step 4: Update number of elements in above array: After deleting an item, the
number of items in the array should be decremented by 1. It can be done using the
following statement:
4 return n - 1;
Now, the complete function to delete an item from the specified position can be
written as shown below:
Example 2.6: Function to delete an item from the specified position in the array
Systematic Approach to Data Structures using C - 2.13
int delete_at_pos (int a[], int n, int pos)
{
int i;
if (pos >= n || pos < 0) // When pos >= number of items
{
1 printf (“Invalid position\n”);
return n; // No deletion and return number of items
}
2 printf(“Item deleted = %d\n”, a[pos]);
for (i = pos + 1; i < n; i++) // Move elements towards left
{
3 a[i - 1] = a[i];
}
4 return n - 1; // Decrement the number of items in array
}
Example 2.7: Design, Develop and Implement a menu driven Program in C for the
following Array operations
1. Creating an Array of N Integer Elements
2. Display of Array Elements with Suitable Headings
3. Inserting an Element (ELEM) at a given valid Position (POS)
4. Deleting an Element at a given valid Position(POS)
5. Exit.
Support the program with functions for each of the above operations.
#include <stdio.h>
#include <process.h>
void main()
{
int choice, a[10], n, item, pos;
2.14 Arrays
for (;;)
{
printf("1:Creat an array 2: Display \n");
printf("3:Insert at position 4: Delete at position \n”);
printf(“5:Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the number of elements\n");
scanf("%d", &n);
printf(“Enter %d elements\n”, n);
create_array(a, n);
break;
case 2:
printf(“The contents of the array are\n”);
display_array(a, n);
break;
Definition: More often programmers will be working with large amount of data and it
may be necessary to arrange them in ascending or descending order. This process of
arranging the given elements so that they are in ascending order or descending order
is called sorting. For example, consider the unsorted elements:
Before writing the algorithm or the program let us know the answer for “What is the
concept used in bubble sort (Why it is also called sinking sort)?”
Design: Once we know what is sorting, the next question is “How to sort the elements
using bubble sort?”
Step 2: Return type: Our intention is only to sort the numbers. Hence, we are not
returning any value. So,
Step 3: Designing body of the function: This is the simplest and easiest sorting
technique. In this technique, the two successive items A[i] and A[i+1] are exchanged
whenever A[i] = A[i+1]. For example, consider the elements shown below:
A[0] = 50 40 40 40 40 30 30 30 20 20 10
A[1] = 40 50 30 30 30 40 20 20 30 10 20
A[2] = 30 30 50 20 20 20 40 10 10 30 30
A[3] = 20 20 20 50 10 10 10 40 40 40 40
A[4] = 10 10 10 10 50 50 50 50 50 50 50
Given 50 sinks to 40 sinks to 30 sinks to 20 sinks
array bottom bottom bottom to bottom
after pass 1 after pass 2 after pass 3
In the first pass 50 is compared with 40 and they are exchanged since 50 is greater
than 40.
Next 50 is compared with 30 and they are exchanged since 50 is greater than 30.
If we proceed in the same manner, at the end of the first pass the largest item
occupies the last position.
On each successive pass, the items with the next largest value will be moved to
the bottom and thus elements are arranged in ascending order.
Note: Observe that after each pass, the larger values sinks to the bottom of the array
and hence it is called sinking sort. The following figure below shows the output of
each pass.
A[0] = 50 40 30 20 10
Note: Observe that at the end of each
A[1] = 40 30 20 10 20
pass smaller values gradually “bubble”
A[2] = 30 20 10 30 30 their way upward to the top (like air
bubbles moving to surface of water)
A[3] = 20 10 40 40 40
and hence called bubble sort.
A[4] = 10 50 50 50 50
Algorithm BubbleSort(a[], n)
for j 1 to n – 1 do // Perform n-1 passes
for i 0 to n-j-1do // To compare items in each pass
if ( a[i] > a[i+1] ) // If out of order exchange
temp a[i]
a[i] a[i+1]
a[i+1] temp
end if
end for
end for
2.18 Arrays
Note: All variables other than parameters should be declared as local variables i.e.,
the variables i, j and temp should be declared as int data type in function.
The C equivalent for the above algorithm can be written as shown below:
Example 2.10: C program to read n numbers, sort them using bubble sort
#include <stdio.h>
void main()
{
int a[20]; /* Array of integers to be sorted */
int n; /* Number of elements in array a and b */
int i; /* Index used to access elements in a and b */
Systematic Approach to Data Structures using C - 2.19
Input
bubble_sort ( a, n); 10 20 30 40 50
a[0] [1] [2] [3] [4]
printf(“The sorted elements are”); The sorted elements are
for ( i = 0; i < n; i++) 10 20 30 40 50
{
printf("%d\n",a[i]);
}
}
Advantages of bubble sort
Very simple and easy to program
Straight forward approach
Disadvantages of bubble sort
It runs slowly and hence it is not efficient. More efficient sorting techniques are
present
Even if the elements are sorted, n-1 passes are required to sort.
Definition: More often we will be working with large amount of data. It may be
necessary to determine whether a particular item is present in the large amount of
data. This process of finding a particular item in the large amount of data is called
searching. The two important and simple searching techniques are shown below:
Linear search
Binary search
2.20 Arrays
10 15 20 25 30
a[0] a[1] a[2] a[3] a[4]
Successful search: If search key is 25, it is present in the above list and we return
its position 3 indicating “Successful search”.
Unsuccessful search: If search key is 50, it is not present in the above list and we
return -1 indicating “Unsuccessful search”
Step 1: Identify parameters to function: We have to search for key item in an array a
consisting of n elements. So, input must be key, array a and n. So,
Step 2: Return type: We are returning position of key item if found, otherwise, we
return -1. Note that the position is integer and -1 is also integer. So,
Step 3: Designing function body: Let a[0], a[1],…..etc. be an array of n elements. Let
us take an example. Assume 10 is the key item to be searched in the list shown in
figure. The list consists of items 50, 40, 30, 60 and 10 in order. In the worst scenario,
key item may have to be compared with all the elements in the array. Observe from
following figure that, key item 10 has to be compared with a[0], a[1], a[2], a[3] and
a[4] as shown below:
Systematic Approach to Data Structures using C - 2.21
key = 10
50 40 30 60 10
a[0] a[1] a[2] a[3] a[4]
i i i i i (Note: i = 0, 1, 2, 3, 4)
i
During searching if key == a[0] for (i = 0; i < 5; i++)
or if key == a[1] {
or if key == a[2] if (key == a[i] ) return i;
or if key == a[3] }
or if key == a[4]
the search is successful
But, once the value of i is greater than or equal to 5, it is an indication that item is not
present and we return -1. The code for this can be written as:
Note: The terminal condition in the for loop i.e., i < 5 can be replaced by i < n for a
general case. Now, the code can be written as:
Note: All variables other than parameters should be declared as local variables i.e.,
the variable i which is of type int must be declared inside the function definition.
#include <stdio.h>
/* Include: Example 2.11: Function to implement linear search */
if (pos == -1)
printf("Item not found\n”); Item not found
else
printf("Item found\n"); Item found
}
Systematic Approach to Data Structures using C - 2.23
Step 2: Return type: We are returning position of key item if found, otherwise, we
return -1. Note that the position is integer and -1 is also integer. So,
return type : int
2.24 Arrays
Step 3: Designing function body: Let key is the element to be searched in the array a
consisting of n elements. In the array a, element 10 in position 0 is the first element
and element 90 in position 8 is the last element as shown below:
key
50
10 20 30 40 50 60 70 80 90
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8]
low high
So, initial values are: low = 0 and high = 8
=9–1
= n – 1 (general)
So, in general, initial values are:
low = 0
high =n–1
Step 4: If low is the position of the first element and high is the position of the last
element, the position of the middle element can be obtained using the statement:
10 20 30 40 50 60 70 80 90
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8]
30
10 20 30 40 50 60 70 80 90
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8]
Case 2: key towards right of mid: If key is greater than the middle element, the right
part of array has to be compared from mid + 1 to high as shown in figure below:
i.e., low to high
10 20 30 40 50 60 70 80 90
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8]
Finally, when the value of low exceeds the value of high indicates that key is not
present in the array and return -1 using the following statement:
return -1; /* Search unsuccessful */
low = 0; /* Initialization */
high = n-1;
#include <stdio.h>
It is necessary to obtain the middle element which is possible only if the elements
are stored in the array. If the elements are stored in linked list, this method cannot
be used.
Now, we discuss 2-dimensional arrays. Before proceeding further, let us answer the
question “What are 2-dimensional arrays? When are 2-dimensional arrays used?”
Definition: Arrays with two sets of square brackets [][] are called 2-dimensional
arrays. A two-dimensional array is used when data items are arranged in row-wise
and column wise in a tabular fashion. Here, to identify a particular item we have to
specify two indices (also called subscripts): the first index identifies the row number
and second index identify the column number of the item.
Column subscript
Row subscript
Array name
Now, let us see “How a 2-dimensional array can be initialized?” Consider the
initialization statement shown below:
int a[4][3] = {
{11, 22, 33},
{44, 55, 66},
{77, 88, 99},
{10, 11, 12}
};
The declaration indicates that array a has 4 rows and 3 columns. The pictorial
representation of this 2-dimensional array is shown below:
columns
0 1 2
0 11 22 33
1 44 55 66
rows Matrix A
2 77 88 99
3 10 11 12
Now, let us see “What is row major order?” In row major order, the elements are
stored row by row one row at a time. For example, consider the following 2-
dimensional matrix:
int a[4][3] = {
{11, 22, 33},
{43, 55, 66},
{77, 88, 99},
};
Assume that address of first byte of a (usually called base address) is 2000 and size of
integer is 2 bytes. The above matrix can be stored in memory using row-major order
as shown below:
Now, let us see “What is column major order?” In column major order, the elements
are stored column by column one column at a time. For example, consider the
following 2-dimensional matrix:
int a[4][3] = {
{11, 22, 33},
{43, 55, 66},
{77, 88, 99},
};
Assume that address of first byte of a (usually called base address) is 2000 and size of
integer is 2 bytes. The above matrix can be stored in memory using column-major
order as shown below:
Systematic Approach to Data Structures using C - 2.31
Observe that in C language, array index always starts from 0 whereas in Pascal
language, array index can start from any integer (even negative indexing is possible in
Pascal).
Now, let us see “How to obtain the address of a[i][j] when array elements are stored
in row-major order?” Let us take the following array declaration in Pascal language:
a : array[3..5, 4..7] of integer; // size of array is 3 x 4
The pictorial representation of above array can be written as shown below:
columns
lb2 ub2
rows 4 5 6 7
lb1 = 3
4 Matrix A
ub1 = 5
where
x is the displacement of ith row from base address
y is the displacement of jth column from ith row
2.32 Arrays
Finding x (displacement of ith row from base address): The row-major order
representation for the above matrix assuming base address as 3000 can be written as
shown below:
Address of Address of Consider
each item each row displacement
a[3,4] 3000 3000 = 3000 + 0 0 = 8 * 0 = 8 * (3 – 3)
a[3,5] 3002
Row - 3
a[3,6] 3004
a[3,7] 3006
a[4,4] 3008 3008 = 3000 + 8 8 = 8 * 1 = 8 * (4 – 3)
a[4,5] 3010
Row - 4
a[4,6] 3012
a[4,7] 3014
a[5,4] 3016 3016 = 3000 + 16 16 = 8 * 2 = 8 * (5 – 3)
a[5,5] 3018
`= 8 * (i – lb1)
Row - 5 a[5,6] 3020 = 2 * 4 (i – lb1)
a[5,7] 3022
In general, displacement = w * (ub2 – lb2 + 1) * (i – lb1)
= w * (ub2 – lb2 + 1) (i – lb1)
Finding y (displacement of jth column from ith row): Consider any row and
corresponding addresses. Let us
=w consider
* (ub2 row
– lb24 +elements as shown below:
1) (i – lb1)
Displacement
The 2-dimensional array elements stored using row major order assuming base
address = 3000 is pictorially represented as shown below:
3000
It is given that:
a[2][3]
a[2][4] 3002 Row-1
a : array[2..4,3..6] of integer;
a[2][5] 3004
a[2][6] 3006 So, lb1 = 2, ub1 = 4
a[3][3] 3008
lb2 = 3, ub2 = 6
a[3][4] 3010 Row-2
a[3][5] 3012 w = 2 (size of integer)
a[3][6] 3014
a[4][3] 3016 We need to find location of a[3][5]
a[4][4] 3018 Row-3 So, i = 3, j = 5
a[4][5] 3020
a[4][6] 3022
Loc a[3][5] = 3012 (observe from the figure that location of a[3][5] is 3012
Now, let us see “How to obtain the address of a[i][j] when array elements are stored
in column-major order?” Let us take the following array declaration in Pascal
language:
Displacement
columns
lb2 ub2
rows 3 4 5 6
lb1 = 2
3 Matrix A
ub1 = 4
Observe from the following figure that location a[3][5] is indeed 3014 where the 2-
dimensional array elements are stored using column major order:
a[2][3] 3000
a[3][3] 3002 Col-1
a[4][3] 3004
a[2][4] 3006
a[3][4] 3008 Col-2
a[4][4] 3010
a[2][5] 3012
a[3][5] 3014 Col-3
a[4][5] 3016
a[4][4] 3018
a[4][5] 3020 Col-4
a[4][6] 3022
Systematic Approach to Data Structures using C - 2.37
Exercises
1) What is an array? How to obtain the location of a[j] in a single dimensional array?
2) A car manufacturing company uses an array car to record number of cars sold
each year starting from 1965 to 2015. Rather than beginning the array index from
0 or 1, it is more useful to begin the array index from 1965 as shown below:
500 504 508 512 516 …….. 700
6) How to insert an item into an unsorted array based on the position? Write the
function for the same
7) How to delete an item from an unsorted array based on the position? Write the
function for the same.
8) What is sorting? Write a function to sort the elements using bubble sort?
10) What is linear search? Write a function to search for an element using linear
search
11) What is binary search? What is the necessary condition for binary search? Write a
function to search for an element using linear search
2.38 Arrays
12) What are 2-dimensional arrays? Write a function to read and print two-
dimensional array
13) What is row major order? Show how 2 dimensional array is represented in
memory using row major order. Obtain the location of a[i][j] using row major
order.
14) What is column major order? How to obtain the address of a[i][j] when array
elements are stored in column-major order?
3.1 Pointers
As we store any data or information in our memory, the computer stores data in
computer memory as shown below:
Addresses
The computer memory is divided into number Data
of cells called memory locations. Each location
0 10
can hold only one byte of data.
1 20
Each location is associated with address. In the 2 30
figure, addresses of memory locations ranges 3 40 Memory
from 0 to 65535. ….. ……. locations
We cannot change these addresses assigned by 65533 50
the system and hence they are constants; But, 65534 60
we can only use them to store data. These 65535 70
addresses are called pointer constants. In these memory locations, the data can be
stored. Here,10, 20, 30, 40, …..70 represent the data.
Consider the following declaration:
int i = 100, j = 200, k = 300;
Note: Address of variable can be accessed by prefixing the address operator & with
the variable. For example, address of i denoted by &i is 65520, address of j denoted
by &j is 65524, address of k denoted by &k is 65528
Now, let us see “What are the steps to be followed to use pointers?” The following
sequences of operations have to be performed by the programmer:
Now, let us see “What is a NULL pointer?” A NULL pointer is defined as a special
pointer value that points to ‘\0’ (nowhere) in the memory. If it is too early in the code
to assign a value to the pointer, then it is better to assign NULL (i.e., \0 or 0) to the
pointer. For example, consider the following code:
Here, the pointer variable p is a NULL pointer. This indicates that the pointer
variable p does not point to any part of the memory. The value for NULL is defined
in the header file “stdio.h”. The programmer can access the data using the pointer
variable p if and only it does not contain NULL. The error condition can be checked
using the following statement:
if (p == NULL)
printf(“p does not point to any memory\n”);
else {
printf(“Access the value of p\n”);
……..
}
3.4 Pointers
During compilation, the compiler will allocate 10 locations (each location consisting
of 4 bytes say) for the variable a. In the worst case, 10 elements can be inserted.
Inserting less than 10 elements leads to underutilization of allocated space and more
than 10 elements cannot be inserted.
Disadvantages of static memory allocation
The memory space to be allocated is fixed during compilation. Hence, the
memory space cannot be altered during execution time
Leads to underutilization if more memory space is allocated
Leads to overflow if less memory is allocated
The static nature imposes certain limitations and can find their applications only
when the data is fixed and known before processing.
Note: All the above disadvantages can be overcome using dynamic memory
allocation. If there is an unpredictable storage requirement, then static allocation
technique is not used. This is the point where the concept of dynamic allocation
comes into picture.
Note: ptr points only to the first byte of memory block of 10 bytes reserved using
malloc() function. But, the allocated memory is not initialized.
Now, let us see “How to read the data into allocated memory?” Because of type cast
(int *), every two bytes are used to store an integer. So, totally 5 integers can be read
and stored in the allocated memory of 10 bytes using the following statement:
If we input 10, 30, 20, 60 and 50 after executing the above statement, these numbers
are stored in memory as shown below:
Now, let us see “How to access the data from allocated memory?” The contents of
the above memory locations can be accessed using *(ptr+i) with the help of de-
reference operator or using ptr[i] or i[ptr] using array notation. The equivalent
statements are:
10 30 20 60 50
for (i = 0; i < 5; i++)
printf(“%d ”, *(ptr+i) );
Example 3.2: Program showing the usage of malloc() function (dynamic arrays)
3.8 Pointers
#include <stdio.h> TRACING
#include <stdlib.h>
void main() Execution starts from here
{
int i,n;
int *ptr;
Inputs
printf("Enter the number of elements\n"); Enter the number of elements
scanf("%d",&n); 5
ptr = (int *) malloc (sizeof(int)* n);
/* If sufficient memory is not allocated */
if (ptr == NULL)
{
printf("Insuffient memory\n");
return;
}
/* Read N elements */
printf("Enter N elements\n"); Enter N elements
for (i = 0; i < n; i++)
scanf("%d", ptr+i); 10 20 30 40 50
printf("The given elements are\n");
for (i = 0; i < n; i++) The given elements are
printf("%d ", *(ptr+i)); 10 20 30 40 50
}
This function is used to allocate multiple blocks of memory. Here, calloc – stands for
contiguous allocation of multiple blocks and is mainly used to allocate memory for
arrays. The number of blocks is determined by the first parameter n. The size of each
block is equal to the number of bytes specified in the parameter i.e., size. Thus, total
number of bytes allocated is n*size and all bytes will be initialized to 0. The syntax is
shown below:
where
ptr is a pointer variable of type data_type
data_type can be any of the basic data type or user defined data type
n is the number of blocks to be allocated
size is the number of bytes in each block
Now, let us see “What does this function return?” The function returns the following
values:
On successful allocation, the function returns the address of first byte of allocated
memory. Since address is returned, the return type is a void pointer. By type
casting appropriately we can use it to store integer, float etc.
If specified size of memory is not available, the condition is called “overflow of
memory”. In such case, the function returns NULL.
void function_name()
{
……..
ptr = (data_type *) calloc(n, size);
if (ptr == NULL)
{
printf(“Insufficient memory\n”);
exit(0);
}
……..
……..
}
Example 3.3: What will happen if the following program segment is executed?
int *ptr;
ptr = (int *) calloc (5, sizeof(int));
3.10 Pointers
Solution: The compiler reserves the space for the variable ptr in the memory. No
initialization is done if ptr is a local variable (It is initialized to NULL if it is global
variable). Now, using the function calloc() we can request specified number of
blocks of memory to be reserved. The specified blocks of memory may be allocated
or may not be allocated. Now, let us discuss both the situations.
Since 5*sizeof(int) = 5*2 = 10 bytes of free memory space is not available, the
function calloc(), cannot allocate memory and hence the function returns NULL and
is copied into ptr. The memory status after execution of above statement is shown
below:
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
NULL
ptr Free
Used by other programs
memory
Since ptr contains NULL, it indicates that memory has not been allocated and the
user should not use ptr to access the data. Instead, if ptr is NULL appropriate
message has to be displayed using the following statement:
if (ptr == NULL)
{
printf(“Insufficient memory\n”);
return;
}
If ptr is not NULL, it indicates that memory has been allocated successfully and
allocated memory can be used to store the data. This situation is discussed below:
Systematic Approach to Data Structures using C - 3.11
Case 2: Successful allocation of memory: Consider the following memory status:
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
?
ptr Free memory
Now, let us execute the statement:
ptr = (int *) calloc (5, sizeof(int));
Since 5*sizeof(int) = 5*2 = 10 bytes of free memory space is available, the function
calloc(), allocates 5 blocks of 2 bytes each, initializes each byte to 0 and returns the
address of the first byte. This returned address is stored in pointer variable ptr as
shown below:
Note: ptr points only to the first byte and the allocated memory is initialized to 0’s.
Now, let us see “How to read the data into allocated memory?” Because of type cast
(int *), every two bytes are used to store an integer. So, totally 5 integers can be read
and stored in the allocated memory of 10 bytes using the following statement:
If we input 10, 30, 20, 60 and 50 after executing the above statement, these numbers
are stored in memory as shown below:
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
0100 10 30 20 60 50
ptr 5 integers are stored Free memory
Now, let us see “How to access the data from allocated memory?”
3.12 Pointers
The contents of the above memory locations can be accessed using *(ptr+i) with the
help of de-reference operator or using ptr[i] or i[ptr] using array notation. The
equivalent statements are:
10 30 20 60 50
for (i = 0; i < 5; i++)
printf(“%d ”, *(ptr+i) );
The program below shows how to find maximum of n numbers which uses calloc()
function. This program prints the largest of n elements along with its position. Note
that the value of n is supplied only during execution time. This is straightforward
program and it is self-explanatory.
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *a, i, j, n;
printf("Enter the no. of elements\n");
scanf("%d",&n);
Now, let us see “What is the difference between malloc() and calloc()?” Even though
malloc() and calloc() are used to allocate memory, there is significant difference
between these two techniques. The difference between these two lies in the way the
memory is allocated and initialized along with the number of parameters passed to
them.
malloc calloc
1. The syntax of malloc() is: 1. The syntax of calloc is:
ptr = (data_type*) malloc(size); ptr = (data_type *) calloc(n, size);
The required number of bytes to Takes two arguments: n number of
be allocated is specifies as blocks to be allocated size is
argument i.e., size in bytes number of bytes to be allocated for
each block.
2. Allocates a block of memory of 2. Allocates multiple blocks of
size bytes memory, each block with the same
size
3. Allocated space will not be 3. Each byte of allocated space is
initialized initialized to zero
4. Since no initialization takes place, 4. calloc() is slightly more
time efficiency is higher than computationally expensive because
calloc(). of zero filling but, occasionally,
more convenient than malloc()
3.14 Pointers
5. This function can allocate the 5. This function can allocate the
required size of memory even if required number of blocks
the memory is not available contiguously. If required memory
contiguously but available at cannot be allocated contiguously, it
different locations. returns NULL.
if (ptr == NULL)
{ /* Memory is not allocated */
printf(“Insufficient memory\n”);
return;
}
Now, let us see “What does this function return?” The function returns the following
values:
On successful allocation, the function returns the address of first byte of allocated
memory.
If specified size of memory cannot be allocated, the condition is called “overflow
of memory”. In such case, the function returns NULL.
Case 1: Reducing the size of the allocated memory. Consider the following
memory status
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
0100
ptr allocated by Free Used by other
malloc() or calloc() memory programs
Assume, ptr points to the address of the first byte of memory block. After executing
the statement:
ptr = (int *) realloc(ptr, 3*sizeof(int) )
only 3*2 = 6 bytes are re-allocated starting from 0100 and last block of memory
starting from 0106 is de-allocated and the resulting memory status is shown below:
Observe from the above memory status that memory is deleted from the end of block
there by free memory space is increased.
3.16 Pointers
Case 2: Extend the allocated memory without changing the starting address.
Consider the following memory status
only 4*2 = 8 bytes are re-allocated starting from 0100 and the resulting memory
status is shown below:
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
0100
ptr re-allocated by Free Used by other
realloc() memory programs
Observe from the above memory status that free memory space is reduced by re-
allocating the extra memory.
Case 3: Extend the allocated memory by changing the starting address. Consider
the following memory status.
4*2 = 8 bytes should be re-allocated. But, the existing memory block cannot be
extended because there is no free space at the end of the current block. So, realloc
Systematic Approach to Data Structures using C - 3.17
allocates a completely new block, copies the existing memory contents to the new
block and deletes the old allocation as shown below:
0098 0100 0102 0104 0106 0108 0110 .. ...... 9999
0100 1 0 2 0 ? ? ? ?
ptr Free Used by re-allocated
memory other by realloc
programs
de-allocated
by realloc
After copying the data from old block to new block, the remaining locations of new
block are not initialized and hence are represented using ??. Thus, the function
realloc() guarantees that re-allocating the memory will not destroy the original
contents of memory.
Example: 3.5: C program showing the usage of realloc() function.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char *str;
/* allocate memory for the string */
str = (char *) malloc(10);
strcpy(str, “Computer”);
str = (char *) realloc(str, 40);
strcpy(str, “Computer Science and Engineering”);
}
3.2.4 free(ptr)
This function is used to de-allocate (or free) the allocated block of memory which is
allocated using the functions calloc(), malloc() or realloc(). It is the responsibility of a
programmer to de-allocate memory whenever it is not required by the program and
initialize ptr to NULL. The syntax is shown below:
3.18 Pointers
#include <stdlib.h> /* Prototype definition of free() is available */
……..
free(ptr);
ptr = NULL;
…….
…….
Here, ptr is a pointer to a memory block. Now, let us see how this function works. For
example, consider the following memory configuration where ptr points to a memory
block containing 200 integers ranging from 01 to 0200.
0 1 0 2 0 3 0 4 ….. 0200
200 integers Free
ptr memory
free(ptr);
0 1 0 2 0 3 0 4 ….. 0200
Free memory
ptr
This shows the memory allocated for 200 integers is de-allocated and is available as
part of the free memory (heap area). Observe from the above figure that even after
freeing the memory, the pointer value stored in ptr is not changed. It still contains the
address of the first byte of the memory block allocated earlier. Using the pointer ptr
even after the memory has been released is a logical error.
Note: These logical errors are very difficult to debug and correct. So, immediately
after freeing the memory, it is better to initialize to NULL as shown below:
………
/* allocate the memory */
……..
/* de-allocate the memory */
free (ptr);
ptr = NULL;
Systematic Approach to Data Structures using C - 3.19
Example: 3.6: Program to shown the problems that occur when free() is not used.
1. #include <stdlib.h>
2.
3. void main()
4. { a
5. int *a;
6.
7. a = (int *) malloc(sizeof(int)); a
8. *a = 100; 100
9.
10. a = (int *) malloc(sizeof(int)); 200
11. *a = 200;
12. }
Now, let us see “What will happen if the above program is executed?” The various
activities done during execution are shown below:
When control enters into the function main, memory for the variable a will be
allocated and will not be initialized.
When memory is allocated successfully by malloc (line 7), the address of the first
byte is stored in the pointer a and integer 100 is stored in the allocated memory
(line 8).
But, when the memory is allocated successfully by using the function malloc in
line 10, address of the first byte of new memory block is copied into a (shown
using dotted lines.)
Observe that the pointer a points to the most recently allocated memory, thereby
making the earlier allocated memory inaccessible. So, memory location where the
value 100 is stored is inaccessible to any of the program and is not possible to free so
that it can be reused. This problem where in memory is reserved dynamically but not
accessible to any of the program is called memory leakage.
So, care should be taken while allocating and de-allocating the memory. It is the
responsibility of the programmer to allocate the memory and de-allocate the memory
when no longer required.
Note: Observe the following points:
It is an error to free memory with a NULL pointer
It is an error to free a memory pointing to other than the first element of an
allocated block
It is an error to refer to memory after it has been de-allocated
3.20 Pointers
Note: Be careful, if we dynamically allocate memory in a function. We know that
local variables vanish when the function is terminated. If ptr is a pointer variable
used in a function, then the memory allocated for ptr is de-allocated automatically.
But, the space allocated dynamically using malloc, calloc or realloc will not be de-
allocated automatically when the control comes out of the function. But, the allocated
memory cannot be accessed and hence cannot be used. This unused un-accessible
memory results in memory leakage.
3.3 Pointers can be dangerous
“Pointers can be dangerous: Justify your answer?” The pointers are dangerous in
following situations:
We may attempt to access an area of memory that is out of range of our program.
For example, consider the statements:
p a
int *p; 10
int a = 10; Junk address
p a
p = &a; 10
Observe that p contains address of a. So, *p refers to value of a. But, *(p-1) refers
to the value in previous location which does not exist. So, we are accessing
memory location which is outside our program area which eventually may crash
the program.
The pointer may not contain address of legitimate variable (memory location). For
example, consider the following program segment:
In the previous section, we have seen how to allocate memory for single dimensional
array dynamically.
int a; p2 p1 a
Garbage Garbage
int *p1;
value value
int **p2;
p2 p1 a
Garbage Garbage
a = 10; value 10
value
3.22 Pointers
p2 p1 a
p1 = &a; Garbage
value 10
p2 p1 a
p2 = &p1;
10
If we frequently allocate the memory space, then it is better to use functions as shown
below where n is number of items.
Example 3.8: Functions to allocate memory for integers, floats, chars and doubles.
if ( x == NULL) if ( x == NULL)
{ {
printf (“Insufficient memory\n”); printf (“Insufficient memory\n”);
exit(0); exit(0);
} }
return x; return x;
} }
In step1, memory is allocated for variable p by the compiler and this memory
location contains junk value. Hence, p is dangling pointer.
In step2, the function MALLOC_INT(5) is invoked and allocates 5 locations
where each location consists of 4 bytes (total 5*4 = 20 bytes). Here, 4 is
sizeof(int). The address of the first byte of allocated space is copied into variable
p. Now, p contains valid address and hence it is not a dangling pointer.
Case 2: To allocate memory for n floating point numbers, call the function:
Now, the question is “Can we write a generalized single function to allocate memory
for n integers or n characters etc.?” Answer is no. It is not possible to write a
generalized function to allocate memory for n elements of any data type in C. But, we
can write a generalized macro in place of a generalized function. The macro definition
to allocate memory for n elements of any data type can be written as shown below:
Example 3.9: Macro to allocate memory for one or more items of any data type
#define MALLOC(p, n, type) \
p = (type*) malloc ( n * sizeof(type) ); \
if (p == NULL) \
{ \
printf("Insufficient memory\n"); \
exit(0); \
}
Observe the following points:
Macros are normally written in a single line.
Since, the body of the macro consists of more number of statements, for
readability purpose we may write the body of macro in multiple lines. The symbol
backslash (denoted by \) is must at the end of each line.
The symbol \ is an instruction given to the pre-processor that all the lines
following \ are continuation of the first line of that macro definition.
#include <stdio.h>
#include <stdlib.h>
// Include : Example 3.9: MACRO to allocate memory for any number of items
p1 p2
MALLOC(p1, 1, int);
MALLOC(p2, 1, int);
p1 p2
*p1 = 10;
10 20
*p2 = 20;
sum = 10 + 20
sum = *p1 + *p2; sum = 30
Now, let us see “How two dimensional arrays can be represented in C?” A two-
dimensional array is represented as one-dimensional array of pointers where each
pointer contains address of one-dimensional array. For example, consider the
following declaration:
int a[3][5];
5 -columns
[0] [1] [2] [3] [4]
3 -rows a[0]
It is a single-dimensional array a[1]
which can hold three pointers. a[2]
In general, we use
Stage 2: Allocate memory for each row with specified number of columns. We can
allocate memory for 5 columns with respect to row x[0], x[1] and x[2] using
MALLOC() three times as shown below.
By using both stages, we can write the code (that is, program statements) in general,
as shown below:
**a with two stars is used to store address of two-dimensional array dynamically
***a with three stars is used to store address of three-dimensional array dynamically
Note: Allocation of memory for arrays can be done using static allocation and
dynamic allocation as shown below:
3.28 Pointers
Note: It is same as example 3.11. But, using for loop, we initialize each location to 0
#define MAX_ROWS 5
#define MAX_COLS 10
void main()
{
int **a; // Holds address of 0th row and 0th column of matrix A
int rows, cols; // Number of rows and columns
printf("Enter the size of the matrix\n");
scanf("%d %d", &rows, &cols);
// check for invalid rows and columns
if (rows < 0 || cols < 0)
{
printf("Invalid matrix size\n");
exit(0);
}
a = make2dArray(rows, cols); // create matrix A dynamically
printf("The initialized matrix\n"); // The initialized matrix
print_array(a, rows, cols); // 0 0 0
} // 0 0 0
Systematic Approach to Data Structures using C - 3.31
3.4.4.3 Matrix Addition using dynamic allocation
Now, let us “Write a program to add two matrices using dynamically allocated
arrays” The header for the function should be:
void add(int **a, int **b, int **c, int rows, int cols);
The function is similar to the way to add two matrices using arrays. The complete
function can be written as shown below:
Example 3.16: Function to add two matrices that are created dynamically
void add(int **a, int **b, int **c, int rows, int cols)
{
int i; // row index
int j; // column index
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
c[i][j] = a[i][j] + b[i][j] ; // can also be written as
} // *(c[i]+ j) = *(a[i] + j) + *(b[i] + j)
}
}
The complete C program to add two matrices created dynamically is shown below:
Example 3.17: Program to add two matrices that are created dynamically
#include <stdio.h>
#include <stdlib.h>
#define MAX_ROWS 5
#define MAX_COLS 10
Include : Example 3.9 : Macro to allocate memory dynamically
Include : Example 3.11 : To create a 2-d array dynamically
Include : Example 3.12 : To display elements of 2-d array created dynamically
3.32 Pointers
Include : Example 3.13 : To read the elements into 2-d array created dynamically
Include : Example 3.16: To add the elements of 2 matrices created dynamically
void main()
{
int **a; // Holds address of 0th row and 0th column of matrix A
int **b; // Holds address of 0th row and 0th column of matrix B
int **c; // Holds address of 0th row and 0th column of matrix C
int rows, cols; // Number of rows and columns
The program to use the above macro can be written as shown below:
#include <stdio.h>
#include <stdlib.h>
// Include : Example 3.18: MACRO to allocate memory for any number of items
void main() Memory representation and tracing
{ p1 p2
int *p1, *p2;
int sum;
junk address junk address
p1 p2
CALLOC(p1,1,int);
CALLOC(p2,1,int);
p1 p2
*p1 = 10;
*p2 = 20;
10 20
If we frequently re-allocate the memory space using various data types, then we can
write a macro as shown below:
Example 3.20: Macro to re-allocate memory for one or more items of any data type
#define REALLOC(p, n, type) \
p = (type*) realloc (p, n*sizeof(type) ); \
if (p == NULL) \
{ \
printf("Insufficient memory\n"); \
exit(0); \
}
The program to use the above macro can be written as shown below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Include : Example 3.20: MACRO to reallocate memory for any number of items
void main()
{
char *str;
9) What is the purpose of using calloc? What does this function return?
10) What will happen if the following program segment is executed?
int *ptr;
ptr = (int *) calloc (5, sizeof(int));
20) Write a function to read elements into 2-d array created dynamically
3.36 Pointers
21) Write a function to initialize allocated memory locations to 0 using malloc()
22) Write a function to add two matrices that are created dynamically
24) Write a program to find transpose of a matrix using dynamically allocated arrays
25) Write a macro to allocate memory for one or more items of any data type using
calloc
26) Write a macro to re-allocate memory for one or more items of any data type
Chapter 4: Strings
What are we studying in this chapter?
Basic terminology
Storing
Pattern matching algorithms
Programming examples
All the above character sets are used to communicate with the computer. Now, let us
see “What is a string?”
Null-character
V I V E K A N A N D A \0
0 1 2 3 4 5 6 7 8 9 10 11
Note that array starts from zero and each location holds one character starting from
index 0 to 10. The string always ends with NULL character denoted by ‘\0’. The
string can be displayed on the screen using the following program:
#include <stdio.h>
VIVEKANANDA
void main()
{
printf(“VIVEKANANDA\n”);
}
4.2 Data structures using C
0 R A M A
1 K R I S H N A
2 M I T H I L
3
4
The above records are accessed pointers. Some of the disadvantages using this
approach are shown below:
1) Time is wasted in reading the entire record even if more spaces are present
2) Certain records may require more space than available to store a string
3) If the length reserved for string is too small, it is not possible to store the larger
data. (see the above figure).
4) If the length reserved for string is too large, too much memory is wasted.
5) Once the string is defined, the length of a string cannot be changed. It is
"fixed" for the duration of program execution.
For example, the strings “RAMA”and “KRISHNA” are stored using in C language
as shown below:
string delimiter
R A M A \0
5 bytes
string delimiter
K R I S H N A \0
8 bytes
M I T H I L \0
Now, let us see “What are operations that can be performed on strings?” The normal
operations that can be performed on strings are:
Substring
Indexing
Concatenation
Length
Indexing: The process of finding the position of pattern string in a given text t is
called indexing. It is also called pattern matching. If the paatern string is present
in text t, the position of the first occurrence of the pattern string is returned
otherwise, 0 is returned.
Let, text t = “RAMA IS THE KING OF AYODHYA”, the pattern string pattern is
“KING”. Then,
INDEX(t, pattern) = 13
Concatenation: The process of appending the second string to the end of first
string is called concatenation. We can denote the concatenation symbol by “+”.
For example, let first string is s1=”SEETA” and the second string s2 =” RAMA”.
Then,
s1+ s2 = “SEETA RAMA”
Length: The number of characters in a string is called length of the string. For
example, Length(“RAMA”) = 4
Working: This function returns the length of the string str i.e., it counts all the
characters up to ‘\0’ except ‘\0’. So, an empty string has length zero. Now, let us see
“How to write user defined function?” Consider the string “SVIT”
str
S V I T \0
0 1 2 3 4
i
To start with, the index variable i is zero. Now, keep incrementing value of index
variable i as long as str[i] is not equal to ‘\0’ using the following statement:
i = 0;
while (str[i] != ‘\0’)
i++;
Once control comes out the loop, the index variable i gives the length of the string.
The complete C function is shown below:
return i;
}
Design: Copy each character of source string src to destination string dest as long as
character is not ‘\0’. This is pictorially represented as shown below:
4.6 Data structures using C
dest src
dest src
0 S S 0 dest[0] = src[0]
1 V V 1 dest[1] = src[1]
2 I I 2 dest[2] = src[2]
3 T T 3 dest[3] = src[3]
4 \0 \0 4
In general, dest[i] = src[i]
where initial value of i = 0
Observe from above figure that as long as src[i] != ‘\0’ it is required to copy src[i] to
dest[i] and increment i after copying each character. The partial code can be written
as shown below:
i = 0;
while (src[i] != ‘\0’) /* Copy as long as ‘\0’ is not encountered */
{
dest[i] = src[i];
i++;
}
It is the responsibility of the programmer to attach null character ‘\0’ at the end of the
string. The equivalent code for this is shown below:
dest[i] = ‘\0’;
Example 4.2: Function to copy string src to string dest is shown below:
void my_strcpy(char dest[], char src[])
{
int i = 0;
while (src[i] != ‘\0’) /* Copy the string till we get NULL character */
{
dest[i] = src[i];
i++;
}
Working: The function copies all the characters of string s2 to the end of string s1.
The delimiter of string s1 is replaced by the first character of s2. Only condition is
that the size of s1 should be sufficiently large enough to store a string whose length is
s1 + s2.
For example, the memory representation for the variables s1 and s2 before executing
strcat is shown below:
s1 V I V E K \0 ? ? ? ? s2 R A M A \0 ? ? ?
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
s1 V I V E K R A M A \0 s2 R A M A \0 ? ? ?
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
Now, let us see “How to implement strcat() function?” Let us design our own
function strcat and call this function as my_strcat.
Design: This is very simple and straightforward approach. Given two strings s1 and
s2 perform the following activities:
Obtain the position of the null character of str1. This can be done using the
following statements:
i = 0;
while (str1[i] != ‘\0’) i++;
Copy the second string to the end of the first string
j = 0;
while ( str2[j] != '\0')
{
str1[i++] = str2[j++];
}
Attach null character at the end using the statements:
str1[i++] = ‘\0’;
So, the complete C function to implement my_strcat is below:
4.8 Data structures using C
i = 0;
s1 s2
0 S == S 0 s1[0] == s2[0]
1 V == V 1 s1[1] == s2[1]
2 I == I 2 s1[2] == s2[2]
3 T == T 3 s1[3] == s2[3]
4 \0 == \0 4
In general, s1[i] == s2[i]
where initial value of i = 0
Strings 4.9
Given the two strings s1 and s2, the comparison starts with first character of each
string and continues as long as they are equal and increment the index i by 1 to point
to the next character. During this process, if end of the character is encountered, with
respect to s1, control comes out of the loop. The equivalent statement for this can be
written as shown below:
i = 0;
while (s1[i] == s2[i])
{
if (s1[i] == ‘\0’) break; / * Check if it is end of string */
The control comes out of the loop if corresponding two characters differ or null
character ‘\0’ is encountered. Since the characters are represented by numerical
values (ASCII), let us take the difference of corresponding characters which results in
zero, positive or negative numbers using the following statement:
return s1[i] – s2[i];
i = 0;
while (s1[i] == s2[i]) /* As long as two strings are equal */
{
if (s1[i] == ‘\0’) break; /* Check if end of string */
i++; /* Point to next character of each string */
}
Working: This function reverses all characters in the string str except the terminating
NULL character ‘\0’. The original string is lost. Now, let us see “How to design user
defined function for string reversal?”
Step 1: Identify parameters to function: We have to reverse the given string say src
and assume the reversed string is in dest. So,
Step 3: Designing function body: The source string characters have to be accessed
and should be copied in reverse order in the destination string. This can be done as
shown below:
A [0] dest
I [1] dest
D [2] dest
N [3] dest
I [4] dest
\0 [5] dest
Note: Other than parameters src and dest other variables used should be declared as
local variables inside the function as shown below:
n, i : declare as int
The complete function is shown below:
Definition: Given a string called pattern p with n characters and another string called
text with m characters where n m. It is required to search for the pattern string p in
the text string t. If search is successful return the position of the first occurrence of
pattern string p in the text string t. Otherwise, return -1. This process of searching for
a pattern string in a given text string is called pattern matching.
Now, let us see “What is brute force pattern matching algorithm?” In this pattern
matching problem, we align pattern string to the beginning of the text string. If there
is no match, we shift the pattern string towards right by one position and the
procedure is repeated till we get the end of the text string. During this procedure, if
pattern string is present, we return the position of pattern string in the text string.
Now, let us see how to “Design the algorithm for pattern matching using brute force
method”
Design: This procedure can be interpreted as sliding a template which contains the
pattern string and slide over text string as long as the string shown in shaded region
matches. The sequence of steps to be followed while searching for a pattern string
“UNCLE” in the text string “FUN-UNCLE” is shown below:
Strings 4.13
k
0 1 2 3 4 5 6 7 8
Note: The characters ‘F’ and ‘U’ are
t F U N - U N C L E compared and they are not equal. So,
p U N C L E slide the pattern string towards right
0 1 2 3 4 as shown below:
j
(a)
k
0 1 2 3 4 5 6 7 8 Note: The characters ‘N’ and ‘U’ are
t F U N - U N C L E compared and they are not equal. So,
p U N C L E slide the pattern string towards right as
0 1 2 3 4 shown below:
j
(c)
k
t 0 1 2 3 4 5 6 7 8 Note: The characters ‘–’ and ‘U’ are
F U Np - U N C L E compared and they are not equal. So,
U N C L E slide the pattern string towards right as
0 1 2 3 4 shown below:
j
(d)
k
0 1 2 3 4 5 6 7 8 Note: The pattern string found and
t F U N - U N C L E position of k has to be returned
p U N C L E
0 1 2 3 4
j
(e)
In figure (e), each character of pattern string has to be compared with corresponding
character in the text string. If there is a mismatch we return the position from where the
pattern string is matched. Otherwise, we return -1 indicating there is no match. The code
can be written as shown below:
4.14 Data structures using C
The pattern string can be compared with text string from position k onwards using the
following code:
Example 4.6: C function to search for pattern in text string from specific position
n = strlen(p);
for (j = 0, i = k; j < n; j++) // search for pattern from position k
{
if (t[i] == p[j])
{
i++;
continue; // partial pattern found and continue
}
break; // pattern not present from position k
}
if (j == n) return k; // Pattern found
Observe from figures (a) through (e) that “sliding the pattern string towards right is
nothing but incrementing the index k of the text string by 1”. Also observe that, the
index value k = 4 is the last position in text string that can match a pattern string.
Beyond this position, there are not enough number of characters in text string to
compare with pattern string. So, k range from 0 to 4. This can be written as shown
below:
k = 0 to 4
k = 0 to 9 – 5 (Expressing 4 in terms of length of text and pattern string)
In general, k = 0 to m – n where m is length(t) and n is length(p). Now, the code for
pattern search can be written as shown below:
m = strlen(t);
n = strlen(p);
for (k = 0; k <= m – n; k++) // slide the pattern towards right by 1 position
{
Insert example 4.6 string comparison from specified position
}
return -1; // pattern string not found in text string
Strings 4.15
Example 4.7: Function to search for pattern string in the text string
int pattern_match(char p[], char t[])
{
int m, n, i, j, k, flag;
m = strlen(t);
n = strlen(p);
for (k = 0; k <= m - n; k++) // shift the pattern towards right
{
flag = strcmp_from_spcefic_pos (p, t, k);
Now, the complete program to read a text string t, pattern string p and search for the
pattern string in text string can be written as shown below:
Example 4.8: C program to search for pattern string in the text string
#include <stdio.h>
#include <string.h>
// Include: Example 4.6: Function to search for pattern p in text string t at position k
// Include: Example 4.7: Function to search for pattern p in text string
void main()
{
char t[20], p[10];
int pos;
printf(“Enter the text string : “); gets(t);
printf(“Enter pattern string : “); gets(p);
pos = pattern_match(p, t);
if (pos == -1)
printf(“Pattern string not found\n”);
else
printf(“Pattern string found at pos = %d\n”, pos);
}
4.16 Data structures using C
4.5.2 Brute force pattern matching algorithm by checking end indices first
Now, let us see “What is brute force pattern matching algorithm by checking end
indices first?” In this pattern matching problem, we align pattern string to the
beginning of the text string. First we compare the last character of the pattern string
with the corresponding character in the text string. If there is a match, we proceed to
match the remaining characters of the pattern string with that of text string using the
brute force technique from the beginning of the pattern string.
Design: First, align the pattern string to the beginning of the text string as shown
below:
k m=9 k is the position in text string from
0 1 2 3 4 5 6 7 8 where the comparison starts
t F U N - U N C L E j is the positon in the pattern string
p U N C L E n=5 from where the comparison starts.
0 1 2 3 4
j
Apart from the index variables, we require the end position in the text string and end
position in the pattern string. Now, the pattern string and text string along with
various index values are shown below:
k et So, initial values are:
0 1 2 3 4 5 6 7 8 m=9 m = strlen(t);
t F U N - U N C L E
p
n = strlen(p);
U N C L E n=5
0 1 2 3 4
et = ep = n – 1;
j ep k=0
The sequence of steps to be followed while searching for a pattern string “UNCLE”
in the text string “FUN-UNCLE” is shown below:
k et
0 1 2 3 4 5 6 7 8 m=9 Note: The characters ‘U’ and ‘E’
t F U N - U N C L E at positon 4 in text string and at
p U N C L E n=5 positon 4 in pattern string are not
0 1 2 3 4 equal. So, slide the pattern string
j ep towards right as shown below:
(a)
k et Note: The characters ‘N’ and ‘E’ at
0 1 2 3 4 5 6 7 8 m=9 positon 5 in text string and at positon
t F U N - U N C L E 4 in pattern string are not equal. So,
p U N C L E n=5 slide the pattern string towards right
0 1 2 3 4
as shown below:
j ep
(b)
Strings 4.17
k et
t 0 1 2 3 4 5 6 7 8
Note: The characters ‘L’ and ‘E’ at
F U Np - U N C L E positon 7 in text string and at positon
U N C L E 4 in pattern string are not equal. So,
0 1 2 3 4 slide the pattern string towards right
j ep as shown below:
(d)
k et
0 1 2 3 4 5 6 7 8 Note: The pattern string found and
t F U N - U N C L E
p
position of k has to be returned
U N C L E
0 1 2 3 4
j ep
(e)
Observe from figures (a) through (e) that “sliding the pattern string towards right is
nothing but incrementing the index values k and et of the text string by 1”.
Also observe that, the index value et = 8 is the last position in text string that can
match a pattern string. Beyond this position, there are not enough number of
characters in text string to compare with pattern string. So, final value of et must be
less than the length of the text string. That is,
et < m;
and each time et and k are incremented by 1. Now, the code for pattern search can be
written as shown below:
m = strlen(t);
n = strlen(p);
et = ep = n – 1;
k=0
4.18 Data structures using C
Example 4.9: Function to search for pattern string in the text string
int pattern_match(char p[], char t[])
{
int m, n, flag, i, j, k, et, ep;
m = strlen(t);
n = strlen(p);
et = ep = n – 1; // To compare last character of pattern
for (k = 0; et < m; k++, et++) // move the pattern string towards right
{
if (t[et] != p[ep]) continue; // compare last character of pattern with
// corresponding character in text string
flag = strcmp_from_spcefic_pos (p, t, k);
Example 4.10: C program to search for pattern string in the text string
#include <stdio.h>
#include <string.h>
// Include: Example 4.6: Function to search for pattern from the specified position
void main()
{
char t[20], p[10];
int pos;
printf(“Enter the text string : “); gets(t);
printf(“Enter pattern string : “); gets(p);
pos = pattern_match(p, t);
if (pos == -1)
printf(“Pattern string not found\n”);
else
printf(“Pattern string found at pos = %d\n”, pos);
}
In this section, we write a more efficient program with the help of pattern matching
table. The pattern matching table can be easily created using the following finite
automaton.
x x a
accept
start x a b a
0 1 2 3 p
a
x
Note: In the above diagram, x represent any other scanned symbol.
The above finite automaton which accepts any string consisting of the substring
“aaba”. To accept any specific pattern we have to construct a finite automaton and
then we have to write the program.
void main()
{
char text[20];
int flag = 0;
int i, state = 0, pattern = 4;
break;
case 1:
if (text[i] == 'a')
state = 2;
else
state = 0;
break;
case 2:
if (text[i] == 'a')
state = 2;
else if (text[i] == 'b')
state = 3;
else
state = 0;
break;
Strings 4.21
case 3:
if (text[i] == 'a')
{
flag = 1;
}
else
{
state = 0;
}
break;
} // end switch
} // end for
if (flag)
printf("Pattern string found at: %d " , i - pattern);
else
printf("Pattern string not found");
}
Statement problem: Given a text string t, pattern string p and replace string r, it is
required to search for every occurrence of pattern string p in a text string t with
replace string r. Consider the following strings where t is the text string, p is the
pattern string and r is the replace string:
p B E
t T O B E O R T O B E O K
r H A V E
It is required to replace every occurrence of “BE” in the text string t with replace
string “HAVE” and the resultant string to be obtained is shown below:
d T O H A V E O R T O H A V E O K
Design: The steps to replace the pattern string in the text string with replace string are
shown below:
4.22 Data structures using C
i
t T O B E O R T O B E O K m = 17
j
r H A V E
z
d
k
Step 2: Search for the pattern p in the text string and results in the following scenario:
p B E n=2
i
t T O B E O R T O B E O K m = 17
j
z
H A V E r
d
k
Step3: Copy the characters from text and replace string into destination string:
Observe that all the items in the text string t from position j till (i – 1) have to be
copied into destination string d. This can be done using the following code:
while (j < i) d[k++] = t[j++]; // shown as thick lines in above figure
Now, copy all the characters from replace string r into destination string using the
following code:
z = 0;
while (z < strlen(r) ) d[k++] = r[z++]; // shown as dotted lines in above figure
Strings 4.23
update i to point to immediate right of pattern string in the text string and assign i to j
using the following code:
i += strlen(p);
j = i;
The final code for this step can be written as shown below:
Step 4: Since pattern string p is not present in ith position in text string t, we
increment i by 1 so that pattern string is shifted towards right by 1 position. As we
increment i by 1 we should see that it should not exceed m-n. Now, the code can be
written as shown below:
while (i <= m - n)
{
if ( strcmp_from_spcefic_pos (p, t, i) != -1)
{
/* copy test string till the position of pattern string */
while (j < i) d[k++] = t[j++];
Step 5: Copy the remaining characters from string t from positon j till the end into
destination string. The code can be written as shown below:
Example 4.11: Search for pattern and replace with replace string in a text string
i = j = k = 0;
m = strlen(t);
n = strlen(p);
Strings 4.25
while (i <= m - n)
{
if ( strcmp_from_spcefic_pos (p, t, i) == -1)
{
/* copy test string till the position of pattern string */
while (j < i) d[k++] = t[j++];
/* copy replace string into destination */
z = 0;
while(z < strlen(r)) d[k++]= r[z++];
i = i + strlen(p); // update i to point beyond pattern
j = i; // save the index value
}
else
i++; // shift the pattern towards right
}
// copy the remaining characters from string t from position j till the end
while (j < m) d[k++] = t[j++];
d[k] = '\0'; // Null terminate the string
}
Now, the complete program in C to search for the pattern in text string t and replace
with replace string r can be written as shown below:
#include <stdio.h>
#include <string.h>
// Include: Example 4.6: Function to search for pattern p from the specific position
// Include: Example 4.11: Function my_search_replace()
void main()
{
char t[40], p[10], r[10], d[40];
printf("Enter the text string t: "); gets(t);
printf("Enter the pattern string p: "); gets(p);
printf("Enter the replace string r: "); gets(r);
my_search_replace( p, t, r, d);
printf("The final string after replacing : %s\n", d);
}
4.26 Data structures using C
Now, let us see how to “Design the algorithm for pattern matching using KMP
method?” The two steps to be followed using this technique are:
Step 1: Create a failure function for the pattern to be searched
Step 2: Search for the pattern using the above failure function
Create a failure function for the pattern to be searched: Now, let us obtain the
failure function for the pattern “ABABD”
Step 1: Given the pattern string “ABABD” obtain the proper prefixes and proper
suffixes for the strings “A”, “AB”, “ABA”, “ABAB” and “ABABD”. Refer
columns 1, 2 and 3 in the table in the next page.
Step 2: For every substring in step 1, obtain a longest proper prefix which is same
as the longest proper suffix.
Step 3: Find the length of the longest proper prefix which is same as the longest
proper suffix
Strings 4.27
So, the failure function for the corresponding pattern string can be written as shown
below:
0 1 2 3 4
p A B A B D pattern string
f 0 0 1 2 0 failure function
0 1 2 3 4
Now, let us see “How to construct failure function for a given pattern?” This can be
done using the following relations:
Let i and j are two index variables with 0 and 1 values initially. Let f[0] = 0 where
f s a failure function.
If p[i] is same as p[j] then assign i + 1 to f[j]. The code for this case can be written
as shown below:
if (p[i] == p[j])
{
f[j] = i + 1
i++, j++
}
If p[i] is not equal to p[j] and if i is zero, then assign i to f[j] and increment j by 1.
The code for this can be written as shown below:
if (i == 0)
{
f[j] = i
j++
}
4.28 Data structures using C
If p[i] is not equal to p[j] and if i is not zero, then assign f[i-1] to i. The code for
this case can be written as shown below:
i = f[i-1]
Now, the complete code for the failure function can be written as shown below:
f[i] = 0;
while ( j < strlen(p))
{
if (p[i] == p[j])
f[j] = i+1, i++, j++;
else if (i == 0)
f[j++] = i;
else
i = f[i-1];
}
}
Search for the pattern using failure function: For searching the pattern string in the
text string, first we align patter to the beginning of text string as shown below:
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13
t A B A D A B A B E A B A B D
p A B A B D
0 1 2 3 4
j
Initialization: Since we have to compare the first character of text string and first
character of pattern string we have to initialize i to 0 and j to 0 as shown below:
i=0
j=0
Case 1: When corresponding characters are equal: If t[i] is same as p[j] then we
increment i by 1 and j by 1 so as to compare the next characters. The code for this
case can be written as shown below:
Strings 4.29
if (t[i] == p[j)
{
i++;
j++;
}
Case 2: When corresponding characters are not equal: Consider the following
scenario:
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13
t A B A D A B A B E A B A B D
p B B A B D
0 1 2 3 4
j
Note that t[i] is not equal to p[j] in the above scenario. So, we have to slide the pattern
string towards right as shown below:
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13
t A B A D A B A B E A B A B D
p B B A B D
0 1 2 3 4
j
Observe that the value of j has not been changed. But, the value of i is incremented by
one. It indicates that incrementing the value of i by 1 implies the pattern string is
moved towards right by one position. The code corresponding to this can be written
as shown below:
if (j == 0) i++;
Case 3: When corresponding characters are not equal: Consider the following
scenario:
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13
t A B A D A B A B E A B A B D
p A B A B D
0 1 2 3 4
j
Observe that the A and A enclosed within the oval in the above figure are matched.
But, A is the proper prefix and A is the proper suffix (shown using the arrow mark). It
4.30 Data structures using C
means that the proper prefix A in the pattern and A in the 2nd position of text string
are one and the same. So, shift the pattern string towards right so that A in the 2 nd
position of text string and the proper prefix A in the 0th position of pattern string are
aligned as shown below:
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13
t A B A D A B A B E A B A B D
p A B A B D
0 1 2 3 4
j
Now, there is no need to compare two A’s shown in oval shape in the above figure.
Observe that the value of j should be 1. This can be done very easily by looking at the
scenario shown in the first figure of case 3. In that figure mismatch occurs when j is 3
(see the figure in the previous page). Now, take the previous character i.e., A in
position 2. Now, take the failure value in position 2 in failure function f. That is f[2]
whose value is 1 has to be copied into j. The code for this case can be written as
shown below:
if (j != 0) j = f [j-1];
All the statements in three cases should be repeatedly executed as long as i is less than
string length of text string and j is less than string length of pattern string. Now, the
code can be written as shown below:
Whenever control comes out of the loop, if j is equal to the string length of pattern
string, there is a match and return the position of the pattern string in the text string.
Otherwise, return -1 indicating pattern not found. The code for this case can be
written as shown below:
Strings 4.31
if ( j == strlen(p))
return i – strlen(p); // Pattern string found. Return its position
else
return -1; // Pattern string not found
Now, the complete function to search for the pattern string in a text string using KMP
method can be written as shown below:
Example 4.14: Pattern match using Knuth, Moris and Pratt method
int pattern_match(char p[], char t[], int f[])
{
int i = 0, j = 0;
Now, the complete program to search for the pattern string in a text string using KMP
algorithm is shown below:
Example 4.15: C Program to search for the pattern in a given text using KMP method
#include <stdio.h>
#include <string.h>
int i, pos;
char t[40], p[20];
int f[20];
failure(f, p);
if (pos == -1)
printf("Pattern string not found\n");
else
printf("Pattern string found at pos: %d", pos);
}
Exercises
1) What is a string? How strings are stored in memory?
2) What are operations that can be performed on strings?
3) What is pattern matching? Design an algorithm to search for pattern string p in
text string t from position i using straight forward method (brute force method)
4) Design a function to search for the string in a pattern string using pattern matching
table.
5) Design the function to search for a pattern string p in the text string t and replace it
with replace string r
6) Design a KMP algorithm for pattern matching
7) Design brute force pattern matching algorithm by checking end indices first
8) Design functions to implement following C string functions:
a) strlen b) strcpy c) strcat d) strrev e)strcmp
Chapter 5: Structures and unions
What are we studying in this chapter?
Structures and self-referential structures
Unions
Polynomials
Sparse matrices
5.1 Structures
We know that array is collection of same type of data. But, in real world we often
encounter a situation where we need to have collection of data of different types. In
such situations, we use structures in C. Now, we shall see “What is a structure?”
Ex: The structure definition to hold the student information such as name,
roll_number and average_marks can be written as shown below:
In the above structure, name, roll_number and average_marks are the fields of the
structure. They are also called members of the structure.
Even though the size of structure is 22 bytes, no space is reserved for the above
structure.
Memory is reserved only if the above definition is associated with variable. That
is, once the structures are defined, they have to be declared. Then only, 22 bytes
of memory space is reserved.
5.2 Structures and unions
Once we know how to define the structure, the next question is “How to declare a
structure?” As variables are declared before they are used in the function, the
structures are also should be declared before they are used. A structure can be
declared using three different ways as shown below:
Tagged structures
Structure variables
Type defined structures
5.2.1 Tagged Structure
Definition: The structure definition with tag name is called tagged structure. The tag
name is the name of the structure. The syntax of tagged structure is shown below:
struct tag_name
{
type1 member1;
type2 member2;
curly braces …… ……
…… ……
}; Note: semicolon is must at the end
For example, consider the following structure definition:
struct student
{
char name[10]; 10 bytes
int roll_number; 4 bytes
float average_marks; 8 bytes
}; Total: 22 bytes
Here, student is the tag name. By defining the above structure, memory will not be
reserved for any of the members. To allocate the memory for the structure, we have to
declare the variable as shown below:
Systematic Approach to Data Structures using C - 5.3
struct student cse, ise ;
Once the structure variables are declared, the compiler allocates memory for the
structure variables. So, 22 bytes are reserved for the variable cse and another 22 bytes
are reserved for the variable ise. The size of the memory allocated is the sum of size
of individual members.
Let us see “How to define the structure along with structure variables?” The syntax of
structure definition and declaration using structure variables is shown below:
struct
{
type1 member1;
type2 member2;
curly braces …… ……
…… ……
} v1, v2,…..vn ; Note: semicolon is must at the end of variables
structure variables
struct
{
char name[10]; 10 bytes
int roll_number; 4 bytes
float average_marks; 8 bytes
} cse, ise; Total: 22 bytes
Observe that 22 bytes of memory is allocated for the variable cse and another 22
bytes of memory is allocated for the variable ise.
Note: We avoid this way of defining and declaring the structure variables because of
the following reasons:
5.4 Structures and unions
Without a tag, it is not possible to declare variables in later stages inside the
functions.
It is not possible to use them as parameters in the function, because all the
parameters have to be declared.
We can define them only in the beginning of the program. In such situations, they
are treated as global variables. In the structured programming it is better to avoid
the usage of global variables.
Definition: The structure definition associated with keyword typedef is called type-
defined structure. This is the most powerful way of defining the structure. This can be
done using two methods:
typedef struct
{
type1 member1;
type2 member2;
curly braces …… ……
…… ……
} TYPE_ID; Note: semicolon is must after TYPE_ID
where
typedef is the keyword added to the beginning of the definition
struct is the keyword which tells the compiler that a structure is being defined.
member1, member2,….. are called members of the structure. They are also
called fields of the structure.
The members are declared within curly braces.
The closing brace must end with type definition name (TYPE_ID in the syntax
shown) which in turn ends with semicolon. Note that TYPE_ID is not a
variable, instead it is a user-defined data type.
Since STUDENT is the type created by the user using the keyword typedef, it can be
called as user-defined data type. From this point onwards we can use STUDENT as
data type and declare the variables. For example, consider the following declaration:
This statement declares that the variables cse and ise are variables of type STUDENT.
Method 2: Here, we use the tag for the structure and then we obtain the user-defined
data type using the keyword typdef. For example, consider the structure definition:
/* Structure definition */
struct student Note: student is the tag name
{
char name[10];
int roll_number;
float average_marks;
}; /* No memory is allocated for structure */
The user-defined data type can be obtained using the keyword typedef as shown
below:
typedef struct student STUDENT; /* STUDENT is user-defined data type */
Using the user-defined data type STUDENT, we can declare the variables as shown
below:
/* Structure declaration */
STUDENT cse, ise; /* Memory is allocated for the variables */
Note: The word student which is written using fully lowercase letters is the tag name
of the structure whereas STUDENT which is written using fully capital letters is the
user-defined data type.
5.6 Structures and unions
Now, let us see “How are structures initialized?” The structures can be initialized
various ways:
Method 1: Specify the initializers within the braces and separated by commas when
the variables are declared as shown below:
struct employee
Memory representation
{
char name[20]; name M O N A L I K A \0
int salary; salary 10950
int id;
id 2001
} a = {"MONALIKA", 10950, 2001};
a
initializers
Method 2: Specify the initializers within the braces and separated by commas when
the variables are declared as shown below:
/* structure definition */
struct employee
{ Note: The compiler will not reserve
char name[20]; memory for structure definition
int salary;
int id;
};
Note: By looking at the declaration for variable a, the compiler reserves the space for
the variable a, whose size is equal to the sizes of the individual data members of the
structure. Then, the data is copied into appropriate fields as shown below:
name M O N A L I K A \0 10 bytes
salary 10950 4 bytes
id 2001 4 bytes
a Total : 18 bytes
Systematic Approach to Data Structures using C - 5.7
Example 5.1: Show the memory representation for the following structure
declaration: struct employee
{
char name[20];
int salary, id;
} a, b = {"MITHIL", 10950, 2001};
Solution: The compiler reserves the space for both variables a and b. Observe that the
variable a is not initialized whereas the variable b is initialized. The size of each
variable is the sum of sizes of individual data members.
Note: Observe that variable a is not initialized. But, only the variable b is initialized.
Now, let us see some important points to remember when we initialize the structures.
The members of the structure cannot be initialized in the definition. For example,
struct employee
{
char name[20] = “RAMA”; Note: Initialization within structure as
int salary = 20000; shown results in syntax error
int id = 25;
};
The initial values are assigned to members of the structure on one-to-one basis.
The values are assigned to various members in the order specified from the first
member. For example, the following statement:
struct employee a = {"RAMA", 10950, 2001};
is valid. Here, the string “RAMA” will be assigned to the first member, 10950 is
assigned to the second member and 2001 will be assigned to the third member.
5.8 Structures and unions
During partial initialization the values are assigned to members in the order
specified and the remaining members are initialized with default values. For
example, in the initialization statement
struct employee a = {"RAMA"};
observe that the string “RAMA” will be assigned to structure member name. The
other members of the structure namely salary and id are initialized to zero by
default.
During initialization, the number of initializers should not exceed the number of
members. It leads to syntax error. For example, for the following statement
struct employee a = {“Rama”,10950, 2001,10.2};
the compiler issues a syntax error saying “too many initializers”.
During initialization, there is no way to initialize members in the middle of a
structure without initializing the previous members. For example,
struct employee a = {10950, 2001};
is invalid. Because, without initializing the first member namely name, we are
trying to initialize last two members. In this case, the number 10950 will be
copied into name field, the number 2001 will be copied into salary field and the
result is unpredictable.
Now, let us see “How structure members are accessed?” The members of a structure
can be accessed by specifying the variable followed by dot operator followed by the
name of the member. For example, consider the structure definition and initialization
along with memory representation as shown below:
Now, the question is “How to display the various members of a structure?” The
various values can be accessed and printed as shown below:
Programming statements Output
printf(“%s\n”, a.name); MITHIL
printf(“%d\n”, a.salary); 10950
printf(“%d\n”, a.id); 2001
Once we know how to display the members of a structure, let us see “How to read the
values for various members of a structure?” We know that format specifications such
as %s %d %d are used to read a string, an integer and a float. The same format
specifications can be used to read the members of a structure. For example, we can
read the name of an employee, the salary and id as shown below:
gets(a.name);
scanf(“%d”, &a.salary);
scanf(“%d”, &a.id);
2021
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2022
2034
8 4 8
bytes bytes 1 bytes
byte
21 bytes
Observe that the values of the members are stored in increasing address locations in
the order specified in the structure definition.
That is, address of name < address of id < address of salary and the address of
So, address of member id = starting address of member name + size of member name
= 2000 + 8
= 2008
Address of member sex = starting address of member id + size of member id
= 2008 + 4
= 2012
Address of member salary = starting address of member sex + size of member sex
= 2012 + 1
= 2013
Note: Observe that some members have odd addresses and some members have
even addresses. For example, members such as name, id and sex have even
addresses whereas the member salary has odd address. In such situation,
microprocessor accesses the data stored in even addresses faster than the data stored
in odd addresses. So, it is the responsibility of the compiler to allocate the memory
for members such that they have even addresses so that the data can be accessed
very fast. This leads to slack bytes.
Systematic Approach to Data Structures using C - 5.11
Now, let us see “What are slack bytes?”
Definition: The optimized compilers will always assign even addresses to the
members of the structure so that the data can be accessed very fast. The even
addresses may be multiples of 2, 4, 8 or 16. This introduces some unused bytes or
holes between boundaries of some members. These unused bytes or holes between
boundaries of members of the structure are called slack bytes. The slack bytes do not
contain any valid information and are useless and waste the memory. But, it results in
faster accessing of data stored in members.
Some optimized compilers assign the addresses such that the addresses of each
member will be multiple of the size of largest data type of that structure. For example,
if the data types of members of a structure are char, int, float and double, then a
member whose type is double occupy more space. Assuming sizeof(double) is 8
bytes, then the address of each member is multiple of 8 as shown below:
slack slack
name id bytes sex bytes salary
M I T H I L \0 10950 M 9999.99
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2022
2034
8 4 4 7 8
bytes bytes bytes 1 bytes bytes
byte
32 bytes
Observe that the starting address of each member is divisible by 8. But, we can access
only the specified number of bytes of a member. The extra bytes that are not used are
slack bytes. The shaded region represents the slack bytes.
Note: The size of the structure is 32 bytes which is greater than the sum of sizes of
each member. So, when slack bytes are used, the size of a structure is greater than or
equal to the sizes of its individual members.
Related data items of dissimilar data types can be logically grouped under a
common name and all the items can be accessed using a common name.
Can be used to pass arguments so as to minimize the number of function
arguments.
When more than one data has to be returned from the function, then structures can
be used.
Extensively used in applications involving database management
To make the program more readable.
5.3 Union and its definition
In this section, we shall see another concept called union which is similar to
structures. Let us see “What is a union?”
Definition: A union is similar to a structure which is also collection of data items of
similar or dissimilar data types which are identified by unique names using
identifiers. Each identifier is called a field or a member. All the members of the union
share the same memory space. Thus, all the identifiers of union have the same
addresses. At any given point during execution, only one member is active.
Now, let us see “How union is declared and used in C?" The general format
(syntax) of a union definition is shown below:
union tag_name
{
type1 member1;
type2 member2;
curly braces …… ……
…… ……
}; Note: semicolon is must
Ex: The union definition to hold the various informations such as integer, char and
double values can be written as shown below:
// union definition
typedef union members of union Here, i, d and c are the fields of
{ the union. They are also called
int i; members of the union.
double d; No space is reserved for the
char c; above union.
} item;
Systematic Approach to Data Structures using C - 5.13
x
c
1 byte
item x;
i
4 bytes
d
8 bytes
By looking at the above declaration, the compiler will reserve the memory whose
size is that of the largest member. Since double is the largest data type of the
member of the union, sizeof(double) = 8 bytes of memory is reserved for the
variable x.
Observe that all the members have the same starting address and hence they share
the same allocated space
Now, let us consider two programs to show that union and structures behave
differently.
#include <stdio.h>
void main()
{
typedef union
{
int marks;
char grade;
float percentage;
} STUDENT;
STUDENT x;
5.14 Structures and unions
Output
x.marks = 100;
printf("Marks : %d\n",x.marks);
Marks: 100
x.grade = 'A'; Grade: A
printf("Grade : %c\n",x.grade); Percentage: 99.5
x.percentage = 99.5;
printf("Percentage: %f\n", x.percentage);
}
Note: It is observed from the above output that only one member of union can hold a
value at a time. It is not possible to access all the members simultaneously. So, the
variable of type STUDENT can be treated as integer variable or char variable or float
variable. Now, consider the same program with structure.
#include <stdio.h>
void main()
{
typedef struct
{
int marks;
char grade;
float percentage;
} STUDENT;
Systematic Approach to Data Structures using C - 5.15
STUDENT x;
x.marks = 100;
x.grade = 'A';
x.percentage = 99.5; Output
Note: It is observed from the above output that the all the members of a structure can
hold individual values at a time. It is possible to access all the members of a structure
simultaneously. Now to answer the question “What is the difference between a
structure and union?”
Structure Union
1. The keyword struct is used to define a 1. The keyword union is used to define
structure a union.
2. When a variable is associated with a 2. When a variable is associated with a
structure, the compiler allocates the union, the compiler allocates the
memory for each member. The sizeof memory by considering the size of the
structure is greater than or equal to largest member. So, size of union is
the sum of sizes of its members. The equal to the size of largest member
smaller members may end with
unused slack bytes (see section 2.4.5)
3. Altering the value of a member will 3. Altering the value of any of the
not affect other members of the member will alter other member
structure values.
4. The address of each member will be 4. The address is same for all the
in ascending order This indicates that members of a union. This indicates
memory for each member will start at that every member begins at offset
different offset values zero.
Now, let us consider a polynomial with only one variable and see “How to represent
each term of the polynomial?” Each term consists of a co-efficient multiplied by a
variable raised to a power. So, each term can be represented by a structure consisting
of 2 fields namely:
cf (representing coefficient)
px (power of x)
The structure definition for a term of a polynomial can be written as shown below:
typedef struct
{
int cf; // used to hold the co-efficient
int px; // used to hold power of x
} POLY;
Now, consider the following declaration along with its memory representation:
typedef struct
{
int cf; // used to hold the co-efficient
int px; // used to hold power of x
} POLY;
POLY p[10];
Using the above declaration, we have an array of 10 terms where each term has four
fields. Consider the following polynomial:
8 x6 + 3 x3 + 6 x2 + 3 x + 4
8 6 3 3 6 2 3 1 4 0
cf px cf px cf px cf px cf px
p[0] p[1] p[2] p[3] p[4]
Now, let us see “How to write a function to read a polynomial consisting of n terms”
Consider the following polynomial with 5 terms:
8 x6 + 3 x 3 + 6 x2 + 3 x + 4
Read the coefficient and power of x of each term and copy into cf and px field as
shown below:
scanf(“%d %d”, &cf, &px);
for i = 0 to 4
p[i].cf = cf; i = 0 to 5 – 1
p[i].px = px i = 0 to n – 1
return -1;
}
Once we know how to search for a term of polynomial 1 in polynomial 2, the general
procedure to add two polynomials is shown below:
Now, the above algorithm can be written using C statements as shown below:
Example 5.7: Function to add two polynomials
5.20 Structures and unions
int add_poly (POLY p1[], int m, POLY p2[], int n, POLY p3[])
{
int i, k, cf1, px1, pos, sum;
k = 0;
for ( i = 0; i < m; i++)
{
cf1 = p1[i].cf;
px1 = p1[i].px; /* Access each item of poly 1 */
#include <stdio.h>
typedef struct
{
int cf;
int px;
} POLY;
// Include : Example 5.4: To read a polynomial
// Include : Example 5.5: To display a polynomial
// Include : Example 5.6: To search for term of poly 1 in poly 2
// Include : Example 5.7: To add two polynomials
// Include : Example 5.8: To copy remaining terms in second polynomial
void main()
{
POLY p1[20], p2[20], p3[40];
int m, n, k;
printf(“Enter total terms in Poly 1:”); scanf(“%d”, &m);
read_poly(p1, m);
5.22 Structures and unions
printf(“Enter total terms in Poly 2:”); scanf(“%d”, &n);
read_poly(p2, n);
Input
Enter total terms in Poly 2: 5 p2: 8x7 + 4x6 + 7x3 + 3x2 + 4x n = 5 terms
cf, px : 8 7
cf, px : 4 6
cf, px : 7 3
cf, px : 3 2
cf, px : 4 1
Output:
Poly 1: 6x^6 + 7x^3 + 8x^1 + 8
Poly 2: 8x^7 + 4x^6 + 7x^3 + 3x^2 + 4x
------------------------------------------------------------
Poly 3: 10x^6 + 14x^3 + 12x^1 + 8 + 8x^7 + 3x^2
typedef struct
{
int cf;
int px;
int py;
int pz;
} POLY;
p[i].cf = cf;
p[i].px = px;
p[i].py = py;
p[i].pz = pz;
}
}
return -1;
}
k = 0;
Systematic Approach to Data Structures using C - 5.25
for ( i = 0; i < m; i++)
{
cf1 = p1[i].cf;
px1 = p1[i].px; /* Access each item of poly 1 */
py1 = p1[i].py;
pz1 = p1[i].pz;
pos = search(px1, py1, pz1, p2, n); /* get position of px1 in poly 2 */
k++;
}
void main()
{
POLY p1[20], p2[20], p3[40];
int m, n, k;
printf(“Enter total terms in Poly 1:”); scanf(“%d”, &m);
read_poly(p1, m);
printf(“Enter total terms in Poly 2:”); scanf(“%d”, &n);
read_poly(p2, n);
Output
Enter total terms in Poly 1:4
cf, px, py, pz:6 3 4 5
cf, px, py, pz:2 2 0 2
cf, px, py, pz:3 0 2 1
cf, px, py, pz:4 0 0 0
Enter total terms in Poly 2:5
Systematic Approach to Data Structures using C - 5.27
cf, px, py, pz:6 4 4 5
cf, px, py, pz:2 3 4 5
cf, px, py, pz:6 0 0 3
cf, px, py, pz:7 0 3 0
cf, px, py, pz:5 0 0 0
row[1] 11 0 31 row[1] 11 0 22 0
Now, let us see “What is the disadvantage of a sparse matrix?” The sparse matrix
contains many zeroes. If we are manipulating only non-zero values, then we are
wasting the memory space by storing unnecessary zero values. This disadvantage can
be overcome by storing only non-zero values thus saving the space.
Now, let us see “How sparse matrix can be represented by storing only non-zero
values?”
We know that any element in the matrix can be uniquely defined using the triples
<row, col, val>. Thus, a sparse matrix can be created using the array of triples as
shown below:
typdef struct
{
int row;
int col;
int val;
} TERM;
row[1] 11 0 22 0
row [2] 0 0 0 0
row [3] 20 0 0 50
row [4] 0 15 0 25
Solution: The declaration to represent the above sparse matrix can be written as:
typdef struct
{
int row;
int col;
int val;
} TERM;
The non-zero elements in the above matrix along with row and col position can be
represented using a single dimensional array starting from a[1] as shown below:
row col val
a[0] 5 4 8 5 x 4 is size and 8 non-zero values in given matrix
a[1] 0 0 10
row 0
a[2] 0 3 40
a[3] 1 0 11
a[4] 1 2 22 row 1 Note: Since row 2 in given matrix has all
a[5] 3 0 20 0 entries, they are not considered. Hence,
row 3 in this representation row 2 is missing.
a[6] 3 3 50
a[7] 4 1 15
row 4
a[8] 4 3 25
5.30 Structures and unions
The various information can be accessed using as shown below:
The size of the matrix using : a[0].row, a[0].col
The number of non-zero elements using : a[0].val
The row index of a non-zero element : a[j].row
The column index of a non-zero element : a[j].col for j = 1 to a[0].col
The index of non-zero element : a[j].val
Now, let us see “How to read the elements of sparse matrix?” This can be done using
the following code:
The above code represent the normal way of reading a 2-dimensional array. But, if we
represent the sparse matrix using triples, the above code can be written as shown
below:
a[0].row = m, a[0].col = n, k = 1;
Example 2.34: How do you represent the following sparse matrix using triples in a
single dimensional array? Also show the transpose of the given matrix represented as
a triple
row[1] 11 0 22 0
row [2] 0 0 0 0
row [3] 20 0 0 50
row [4] 0 15 0 25
Solution: All non-zero elements represented in the form of a triple can be represented
as shown below:
To get the transpose we exchange row, col indices along with val as shown below:
5.32 Structures and unions
Input sparse matrix Transposed sparse matrix
stored as triple stored as triples
row col val row col val
a[0] 5 4 8 4 5 8 b[0] interchange size
a[1] 0 0 10 0 0 10 b[1]
a[2] 0 3 40 0 1 11 b[2] row 0
a[3] 1 0 11 0 3 20 b[3]
a[4] 1 2 22 1 4 15 b[4] row 1
a[5] 3 0 20 2 1 22 b[5] row 2
a[6] 3 3 50 3 0 40 b[6]
a[7] 4 1 15 3 3 50 b[7] row 3
a[8] 4 3 25 3 4 25 b[8]
Now, let us “Design the algorithm to transpose a given matrix represented as triples in
a single dimensional array” Initially, the size of the matrix must be reversed. This can
be done by interchanging rows and columns as shown below:
b[0].row = a[0].col
b[0].col = a[0].row
b[0].val = a[0].val
We find the transpose of given matrix by accessing col index of each non-zero
element in array a and store in array b as shown below:
b[k].row = a[j].col // make column as row
b[k].col = a[j].row // make row as column
b[k].val = a[j].val
k++; // to add the next entry
To obtain the index j, we start searching for 0th column, 1st column, 2nd column, 3rd
column and 4th column from a[1].col to a[8].col. The corresponding code can be
written as shown below:
for j = 1 to 8
for j = 1 to 8 for j = 1 to 8 for j = 1 to 8
{
{ { {
if (a[j]. col == 0)
if (a[j]. col == i) if (a[j]. col == i) if (a[j]. col == i)
{
{ { {
b[k].row = a[j].col
b[k].row = a[j].col b[k].row = a[j].col b[k].row = a[j].col
b[k].col = a[j].row b[k].col = a[j].row b[k].col = a[j].row b[k].col = a[j].row
b[k].val = a[j].val
b[k].val = a[j].val b[k].val = a[j].val b[k].val = a[j].val
k++;
k++; k++; k++;
}
} } }
}
} } }
Search for 0th column
Search for 1st column Search for 2nd column Search for 3rd column
Systematic Approach to Data Structures using C - 5.33
In general, we search for ith column as shown below:
for (j = 1; j <= 8; j++) // where 8 = a[0].val
{
if (a[j]. col == i) for i = 0, 1, 2, 3
{
b[k].row = a[j].col
b[k].col = a[j].row
b[k].val = a[j].val
k++;
}
}
Definition: A matrix where all non-zero entries can occur on or below the main
diagonal (lower triangular matrix) or non-zero entries can occur on or above the main
diagonal (upper triangular matrix) is called triangular matrix. Since only zero
elements are present either on upper part of the diagonal or on the lower part of the
diagonal, the triangular matrix is a sparse matrix.
10
11 10
6 5 -5
20 24 15 8
Now, the question is “How to represent lower triangular matrix?” The lower
triangular matrix can be represented using:
2-dimensional array
1-dimensional array
10 0 0 0
11 10 0 0
6 5 -5 0
20 24 15 8
Since all elements above diagonal are 0’s, and we are not manipulating 0’s, it is waste
to store 0’s in the memory. It results in wastage of memory space.
Systematic Approach to Data Structures using C - 5.35
1-d representation: Consider the lower triangular matrix shown below:
Number of elements in each row
a11 =1
a21 a22 =2
10 6
11 10 22
15 8 diagonal elements
Elements below the diagonal
Now, the question is “How to represent tridiagonal matrix using one-dimensional
array?” The n x n matrix tridiagonal array can be written as shown below:
a11 a12
So, k = 3i – 6 + 4 + j – i
k = 2i + j - 2
Thus, it is clear from the above diagram that B[9] is indeed a[4,3]
Systematic Approach to Data Structures using C - 5.39
Exercises
1) What is a structure? How to declare a structure?
13) How sparse matrix can be represented by storing only non-zero values?
15) Write a function to find the transpose of a matrix which is stored as a triple
20) How do you represent the following sparse matrix using triples in a single
dimensional array? Also show the transpose of the given matrix represented as
a triple
row[1] 11 0 22 0
row [2] 0 0 0 0
row [3] 20 0 0 50
row [4] 0 15 0 25
Chapter 6: STACKS
What are we studying in this chapter?
Stack definition, operations
Array representation of stacks
Stacks using dynamic arrays
Applications:
Polish notations
Infix to postfix conversion (Reverse polish notation)
evaluation of postfix expression
Recursion
Factorial, GCD, Fibonacci Sequence, Tower of Hanoi
Ackerman's Recursive function, maze problem
Implementation of multiple stacks
6.1 Introduction
In this section, let us see “What is a stack?”
Definition: A stack is a special type of data structure (linear data structure) where
elements are inserted from one end and elements are deleted from the same end.
Using this approach, the Last element Inserted is the First element to be deleted
Out, and hence, stack is also called Last In First Out (LIFO) data structure. The
stack s={a0, a1, a2,……an-1) is pictorially represented as shown below:
The elements are inserted into the stack
Insert in the order a0, a1, a2,……an-1. That is,
Delete
we insert a0 first, a1 next and so on. The
item an-1 is inserted at the end. Since, it
is on top of the stack, it is the first item
an-1 Top of stack to be delted. The various operations
performed on stack are:
Insert : An element is inserted from
top end. Insertion operation is called
a2 push operation
a1 Delete: An element is deleted from
a0 Bottom of stack top end only. Deletion operation is
called pop operation.
6.2 Stacks
Note: We have seen that, normally all the plates in a hotel/cafeteria are placed one
above the other. The plates are inserted from the top only. If a plate is required, the
top most plate is selected. This process of inserting the plates at the top and removing
the plates from the top is called stacking of plates.
Let us see how stacks are used in the field of computer science. Before proceeding
further, let us see “What are the various operations that can be performed on stacks?”
The various operations that can be performed on stacks are shown below:
Push (Inserting an element on the top of stack)
Stack operations Pop (Deleting an element from the top of stack)
display (Display contents of stack)
6.1.1 Push operation
Definition: Inserting an element into the stack is called push operation. Only one item
is inserted at a time and item has to be inserted only from top of the stack.
Example 6.1: Stack contents after inserting 4 items 30, 20, 25 and 10 one after the
other with a STACK_SIZE 4.
STACK_SIZE = 4
top
3 3 3 3 3 10
top
2 2 top 2 2 25 2 25
top 1
1 1 20 1 20 1 20
0 0 30 0 30 0 30 0 30
top
-1 s s s s s
Empty stack insert 30 insert 20 insert 25 insert 10
When items are being inserted into the stack, we may get stack overflow. Now, let us
see “What is stack overflow?”
Systematic approach to Data Structures using C - 6.3
Definition: When elements are being inserted, there is a possibility of stack being
full. Once the stack is full, it is not possible to insert any element. Trying to insert an
element, even when the stack is full results in overflow of stack.
For example, consider the stack shown in figure 6.1 with STACK_SIZE 4.
After inserting 30, 20, 25 and 10 there is no space to insert any item. Then we say that
stack is full. Trying to insert an element into the stack when the stack is full is called
overflow of stack.
Now, let us see “How to implement push operation using arrays (static allocation
technique)?”
Design: Before inserting any element, we should ask the question “Where and how
the item has to be inserted?” If we know the answer for this question we have the
push function ready. So, let us consider the situation where two elements 30 and 20
are already inserted into the stack as shown in figure 6.2.a. with top = 1.
Suppose, we have to insert an element say item. Now, let us ask “Where this item has
to be inserted?”
We know that it has to be inserted at top = 2. For this to happen, we have to increment
top by 1 (Fig. 6.2.b). This is achieved using the statement:
Then we ask “How item has to be inserted at top position?” This is achieved by
copying item to s[top] (Fig 6.2.c) using the following statement:
void push()
{
/* Check for overflow of stack */
if (top == STACK_SIZE – 1)
{
printf(“Stack overflow\n”);
return;
}
Note: The array s, the variable top and the variable item are global variables and
should be declared before all the functions. As far as possible let us avoid the usage of
global variables in this book.
Let us rewrite the above code by passing parameters. It is clear from the above code
that as the item is inserted, the contents of the stack identified by s and the index top
pointing to topmost element are changed and so they should be passed as parameters
(pass by reference) as shown below:
Systematic approach to Data Structures using C - 6.5
Example 6.3: C function to insert an integer item (by passing parameters)
void push(int item, int *top, int s[])
{
/* Check for overflow of stack */
if (*top == STACK_SIZE – 1)
{
printf(“Stack overflow\n”);
return;
}
*top = *top + 1; /* Increment top by 1 */ s[++(*top)] = item;
s[*top] = item; /* Insert into stack */
}
Note: By comparing functions shown in 6.3 and 6.4 note that body of the function has
not been changed. But, the type of formal parameters changes.
Example 6.5: Perform pop operation when stack already contains 30, 20, 25, and 10.
top
3 10 3 3 3 3
top
2 25 2 25 top 2 2 2
1 20 1 20 1 20 top 1 1
0 30 0 30 0 30 0 30 top 0
-1 -1 -1 -1 -1
Stack full After After After After
deleting 10 deleting 25 deleting 20 deleting 30
(stack empty)
Figure 6.3 Sequence of Deletion operations
The items can be deleted one by one as shown in figure 6.3. The contents of stack and
the top which contains the position of topmost elements are also shown. When items
are being deleted, we may get stack underflow. Now, let us see “What is stack
underflow?”
Definition: When elements are being deleted, there is a possibility of stack being
empty. When stack is empty, it is not possible to delete any item. Trying to delete an
element from an empty stack results in stack underflow.
For example, in the figure 6.3, after deleting 10, 25, 20 and 30 there are no
elements in the stack and stack is empty. Deleting an element from the empty stack
results in stack underflow.
Now, let us see “How to implement pop operation using arrays (static allocation
technique)?”
Design: Let us access an item from the top of the stack. This can be achieved by using
the following statement:
item_deleted = s[top]; /* Access the topmost item */
and then decrementing top by one as shown below:
top = top – 1; /* Update the position of topmost item */
The above two statements can also be written using single statement as shown below:
item_deleted = s[top--]; Note: item_deleted = s[--top]; is wrong.
Systematic approach to Data Structures using C - 6.7
Each time, the item is deleted, top is decremented and finally, when the stack is
empty the value of top will be –1. In this situation, it is not possible to delete any item
from the stack. The code to delete an item from stack can be written as shown below:
Now, let us write the above function without using global variables and by passing
appropriate parameters. We know that to access the top item, we need the index top
and the stack s. So, we have to pass top and s as the parameters. As the value of top
changes every time the item is deleted, top can be used as a pointer variable. The
complete C function (by passing parameters) is shown below:
The following function shows how to delete a character item from the stack.
In the display procedure, if the stack already has some items, all those items are
displayed one after the other. If no items are present, the appropriate error message is
displayed. Let us design the display function.
Design: Assume that the stack contains three elements as shown below:
3
top
2 25
1 20
0 30
-1 s
Usually, the contents of the stack are displayed from the bottom to top. So, first item
to be displayed is 30, next item to be displayed is 20 and final item to be displayed is
25. This can be achieved using the following statements:
Design Output
printf(“%d\n”, s [0] ); 30
printf(“%d\n”, s [1] ); 20
printf(“%d\n”, s [2] ); 25
But, the above statement should not be executed when stack is empty i.e., when top
takes the value –1. So, the above code can be modified to include this error condition
and can be written as shown below:
Example 6.9: C function to display the contents of stack (using global variables)
void display()
{
int i;
In the previous sections we have seen how the stacks can be implemented using
global variables and by passing parameters. The program to implement stack
operations such as push, pop and display can be written as shown below:
Example 6.11: C function to display contents of the stack (by passing parameters)
#include <stdio.h>
#include <process.h>
#define STACK_SIZE 5
/* Global variables */
int top; /* index to hold the position of the top most item */
int s[10]; /* Holds the stack items */
int item; /* Item to be inserted into the stack */
void main()
{
int choice; /* user choice for push, pop and display */
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
push();
break;
case 2:
item = pop();
if (item == 0)
printf("Stack is empty\n");
else
printf("Item deleted = %d\n",item);
break;
case 3:
display();
break;
default:
exit(0);
}
}
}
0 1 2 3 4 5
M A D A M \0 str
stack
Example 6.13: Design, Develop and Implement a menu driven Program in C for the
following operations on STACK of Integers (Array Implementation of Stack with
maximum size MAX)
a. Push an element on to Stack
b. Pop an element from Stack
c. Demonstrate how Stack can be used to check Palindrome
d. Demonstrate Overflow and Underflow situations on Stack
e. Display the status of Stack
f. Exit
#include <stdio.h>
#include <process.h>
#include <string.h>
#define STACK_SIZE 5
/* Insert: Example 6.3 : function push */
/* Insert: Example 6.7 : function pop */
/* Insert: Example 6.10 : function display */
/* Insert: Example 6.12 : function is_palindrome */
void main()
{
int item, top, s[10]; /* stack variables */
char str[20]; /* Used to check for palindrome */
int choice, flag;
6.14 Stacks
top = -1; /* Indicates stack is empty to start with */
for (;;)
{
printf("1:Push 2:Pop\n");
printf("3:Display 4:Palindrome\n”);
printf("5:Exit\n");
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
break;
case 2:
item = pop(&top, s);
if (item == 0)
printf("Stack is empty\n");
else
printf("Item deleted = %d\n", item);
break;
case 3:
display(top, s);
break;
case 4:
printf(“Enter the string\n”);
scanf(“%s”, str);
flag = is_palindrome(str);
Systematic approach to Data Structures using C - 6.15
if (flag == 0)
printf(“The string is not a palindrome\n”);
else
printf(“The string is a palindrome\n”);
break;
default:
exit(0);
}
}
}
Once we know the definition of a stack and how stack is implemented in C, let us
concentrate on “What are the applications of stack?” The various applications in
which stacks are used are:
Conversion of expressions: When we write mathematical expressions in a
program, we use infix expressions. These expressions will be converted into
equivalent machine instructions by the compiler using stacks. Using stacks, we
can efficiently convert the expressions from infix to postfix, infix to prefix etc.
Evaluation of expressions: An arithmetic expression represented in the form of
either postfix or prefix can be easily evaluated.
Recursion: A function which calls itself is called recursive function. Some of the
problems such as tower of Hanoi, problems involving tree manipulations etc., can
be implemented very efficiently using recursion. It is a very important facility
available in variety of programming languages such as C, C++ etc.,
Other applications: There are so many other applications where stacks can be
used: For example, to find whether the string is a palindrome, to check whether a
given expression is valid or not and so on.
First, let us see “What is an expression? What are the various types of expressions?”
6.16 Stacks
Definition: The sequence of operators and operands that reduces to a single value
after evaluation is called an expression. The operands consist of constants and
variables whereas the operators consist of symbols such as +, -, *, / and so on. The
operators indicate the operation to be performed on the operands specified. The
expressions can be represented as shown below:
Infix expression
Representation
Postfix expression
of expressions
Prefix expression
In the expression, a + b
operand1 operand2
operator
a and b are operands and the symbol + is an operator.
Now, let us see “What is postfix expression?”
Definition: In an expression, if an operator follows the two operands (i.e., operator
comes after the two operands), the expression is called postfix expression. No,
parenthesis is allowed in postfix expressions. The postfix expression is always un-
parenthesized. It is also called suffix expression or reverse polish expression. For
example,
ab+
Now, let us see “What is prefix expression?”
Definition: In an expression, if an operator precedes the two operands (i.e., operator
comes before the two operands), the expression is called prefix expression. No,
parenthesis is allowed in prefix expressions. The prefix expression is always un-
parenthesized. It is also called polish expression. For example,
+ab
Systematic approach to Data Structures using C - 6.17
6.3.1 Precedence and associativity of the operators
While evaluating or converting an expression into other forms, we should know the
precedence of operators and the associativity of operators. So, let us see “What is
precedence of operators?”
Definition: The rules that determine the order in which different operators are
evaluated are called precedence rules or precedence of operators.
Normally we associate values to determine the order in which the operators have to be
evaluated. Highest precedence operators will have the highest value and lowest
precedence operators will have the least value. The table below shows arithmetic
operators along with priority values.
Description Operator Priority Associativity
Exponentiation ($, ^) 6 Right to Left
Multiplication (*) 4 Left to Right
Note: The symbol ‘$’ or ‘^’ is considered as the operator to perform exponentiation
and should be given first precedence since it has higher priority value. The addition or
subtraction is given the least precedence since it has least priority value as shown in
above table.
Now, “What if different operators have the same precedence?” During evaluation, if
two or more operators have the same precedence, then precedence rules are not
applicable. Instead, we go for associativity of the operators. Now, we shall see “What
is associativity?
Definition: The order in which the operators with same precedence are evaluated in
an expression is called associativity of the operator. In such case, precedence rules are
not considered.
8 + 4 + 3
[First add 8 and 4 to get 12]
12 + 3
[Next add 12 and 3 to get 15]
15
Observation: In the above expression, same operator “+” is used twice and hence
both operators have the same priority and the precedence rules are not applicable.
Then the question is “How to evaluate?” Normally, we do the evaluation as shown
below:
First we compute 8 + 4 to get 12
Next we compute 12 + 3 to get 15 thus moving from Left to Right.
Since evaluation is done from left to right one after the other, we say that the operator
“+” is left associative.
Definition: In an expression, if two or more operators have the same priority and are
evaluated from left-to-right, then the operators are called left associative operators.
We normally denote using L → R. The process of evaluating from left to right is
called left associativity.
Example 6.15: What is the result of 2$3$2? if ‘$’ represent exponentiation operator?
Note: Instead of using symbol ‘$’ we can use symbol ‘^’ to denote exponentiation.
not 64. So, to get the answer 512, the computer has to evaluate the expression 2$3$2
as shown below:
2$3$2 First evaluate 3 $ 2 (i.e., 32 = 9)
2$ 9 Then evaluate 2$9 (i.e., 29 = 512)
512
Observation: In the above expression, same operator “$” is used twice and both
operators have the same priority and the precedence rules are not applicable. Then the
question is “How to evaluate?” Normally, we do the evaluation as shown below:
Systematic approach to Data Structures using C - 6.19
First we compute 3$2 to get 9
Next we compute 2$9 to get 512 thus moving from right to left.
Since evaluation is done from right to left one after the other, we say that the operator
“$” is right-to-left associative (also called right associative).
Definition: In an expression, if two or more operators have the same priority and if
they are evaluated from right-to-left, then the operators are called right associative
operators.
T4 F +
T3 E ^ F + (Replacing T4 by T3 E ^)
A T2 + E ^ F + (Replacing T3 by A T2 +)
6.20 Stacks
A T1 D * + E ^ F + (Replacing T2 by T1 D *)
ABC– D*+E^F+ (Replacing T1 by B C –)
T5 T3 +
T4 N + P Q / (Replacing T5 by T4 N + and T3 by P Q /
T2 M – N + P Q / (Replacing T4 by T2 M –)
X T1 $ M – N + P Q / (Replacing T2 by X T1 $)
X Y Z $ $ M – N + P Q / (Replacing T1 by Y Z $)
Hence, equivalent postfix expression is X Y Z $ $ M – N + P Q /
Systematic approach to Data Structures using C - 6.21
Now, let us discuss “How to write a program to convert a given infix expression to its
equivalent postfix expression?”
Design: The following precedence table is used while converting from infix to
postfix.
Symbols Stack precedence Input precedence Associativity
function F function G
+,- 2 1 Left
*, / 4 3 Left
$ or ^ 5 6 Right
operands 8 7 Left
( 0 9 Left
) - 0 -
# -1 -
The two functions F and G using the above precedence table can be written as shown
below:
Example 6.18: C function which returns stack and input precedence values
int F(char symbol) int G(char symbol)
{ {
switch(symbol) switch(symbol)
{ {
case '+': case '+':
case '-': return 2; case '-': return 1;
case '*': case '*':
case '/': return 4; case '/': return 3;
case ‘^’: case ‘^’:
case '$': return 5; case '$': return 6;
case '(': return 0; case '(': return 9;
case '#': return -1; case ')': return 0;
default: return 8; default: return 7;
} }
} }
6.22 Stacks
Note: Observe the following points from the above table/function with respect to
precedence:
The operators ‘+’ and ‘–‘ have the same precedence and are least precedence
operators.
The operators ‘*’ and ‘/’ also have the same precedence and have the precedence
higher than ‘+’ and ‘-‘.
The operator ‘$’ or ‘^’ indicates exponential operator and has a precedence higher
than ‘*’ and ‘\’ but it is right associative operator.
Note: Observe the following points from the above table/function with respect to
associativity.
If an operator is left associative, stack precedence value is greater than input
precedence value.
If an operator is right associative, stack precedence value is less than the input
precedence value.
Procedure: General procedure to convert from infix to postfix form is shown below:
As long as the precedence of symbol on top of the stack is greater than the
precedence of input symbol, pop an item from the stack and place it in the postfix
expression. The code for this statement can be of the form:
if ( F(s[top]) != G(symbol) )
s[++top] = symbol; /* Push symbol on the stack */
else
top--; /* Drop an element from stack */
Note: The above steps have to be performed for each input symbol in the infix
expression.
Systematic approach to Data Structures using C - 6.23
Note: Before conversion starts, stack is empty denoted by symbol ‘#’ on the stack and
output is empty. The formal version of the algorithm can be written as:
Example 6.19: C function to convert from infix expression into postfix expression
#include <stdio.h>
#include <string.h>
/* Include: Example 6.18: Precedence functions F and G */
/* Include: Example 6.19: Function to convert from infix to postfix */
void main()
{
char infix[20];
char postfix[20];
Input and output
printf("Enter a valid infix expression\n"); Enter a valid infix expression
scanf("%s",infix); ((a+b)*c-(d-e))^(f+g)
/* Convert infix to postfix expression */ The postfix expression is
infix_postfix(infix, postfix); ab+c*de--fg+^
Example 6.21: The complete trace of the program for the expression:
(A+(B–C)*D)
Stack s[top] Symbol F(s[top]) > G(symbol) Postfix
+ T4 F
+ ^ T3 E F (Replacing T4 by ^ T3 E)
+ ^ + A T2 E F (Replacing T3 by + A T2)
+ ^ + A * T1 D E F (Replacing T2 by * T1 D )
+ ^ +A*–BC D E F (Replacing T1 by – B C)
+ T5 T3
+ + T4 N / P Q (Replacing T5 by + T4 N and T3 by / P Q )
+ + – T2 M N / P Q (Replacing T4 by – T2 M )
+ + – $ X T1 M N / P Q (Replacing T2 by $ X T1 )
+ + – $ X $YZ M N /PQ (Replacing T1 by $ Y Z )
On similar lines, we can convert all infix expressions into their equivalent prefix
expressions. Now, let us discuss “How to write a program to convert a given infix
expression to its equivalent prefix expression?”
Design: To obtain a prefix expression from infix expression, the procedure that is
followed while converting from infix to postfix expression is used with the following
modifications:
The precedence functions are different from that of the precedence functions
shown earlier (given in infix to postfix conversion) The precedence functions used
to obtain a prefix expression are shown below:
Reverse the infix expression and follow the same procedure that we have used to
convert from infix to postfix
The prefix expression is obtained by reversing the result.
6.28 Stacks
Example 6.24: C function which returns stack and input precedence values
/* Stack precedence function F */ /* Input precedence function G */
int F(char symbol) int G(char symbol)
{ {
switch(symbol) switch(symbol)
{ {
case '+': case '+':
case '-': return 1; case '-': return 2;
case '*': case '*':
case '/': return 3; case '/': return 4;
case ‘^’: case ‘^’:
case '$': return 6; case '$': return 5;
case ')': return 0; case '(': return 0;
case '#': return -1; case ')': return 9;
default: return 8; default: return 7;
} }
} }
Note: Observe the following points from the above table/function with respect to
precedence:
The operators ‘+’ and ‘–‘ have the same precedence and are least precedence
operators.
The operators ‘*’ and ‘/’ also have the same precedence and have the precedence
higher than ‘+’ and ‘-‘.
The operator ‘$’ or ‘^’ indicates exponential operator and has a precedence higher
than ‘*’ and ‘\’ but it is right associative operator.
Note: Observe the following points from the above table/function with respect to
associativity.
If an operator is left associative, stack precedence value is less than input
precedence value.
If an operator is right associative, stack precedence value is greater than the input
precedence value.
Systematic approach to Data Structures using C - 6.29
Procedure: General procedure to convert from infix to prefix form is shown below:
As long as the precedence value of the symbol on top of the stack is greater than
the precedence value of the current input symbol, pop an item from the stack and
place it in the prefix expression. The code for this statement can be of the form:
if ( F(s[top]) != G(symbol) )
s[++top] = symbol; /* Push symbol on the stack */
else
top--; /* Drop an element from stack */
Note: These two steps have to be performed for each input symbol in the infix
expression.
Example 6.25: C function to convert from infix expression into prefix expression
void infix_prefix(char infix[], char prefix[])
{
int top; /* Points to top of the stack */
char s[30]; /* Acts as storage for stack elements */
int j; /* Index for prefix expression */
int i; /* Index to access infix expression */
char symbol; /* Holds scanned char from infix expression */
top = -1; /* Stack is empty */
s[++top] = ‘#’; /* Initialize stack to # */
j = 0; /* Points to first char of prefix expression */
6.30 Stacks
if ( F(s[top]) != G(symbol) )
s[++top] = symbol; /* push the input symbol */
else
top--; /* discard ‘(‘ from stack */
}
Example 6.26: C program to convert from infix expression into prefix expression
#include <stdio.h>
#include <string.h>
Note: The precedence values of the operands in functions G and F in example 6.22
are 7 and 8. The same values are used in example 6.28. This is because the order of
the operands in the prefix, postfix and infix expressions are same. Only the order of
the operators changes. So, care should be taken while taking the precedence values
for the operators. For example, consider the following expressions:
a+b*c ( Infix expression )
abc*+ ( Postfix expression )
+a*bc ( Prefix expression )
If we look at above expressions, the operands a, b and c appear in the same sequence
whereas the operators are not in same sequence. So, the precedence value of the
operands is same, but the precedence values of operators are different while
converting from infix to postfix and from infix to prefix.
Let us see “What is the problem in evaluating the infix expressions? What is the need
for evaluating postfix/prefix expressions?” The evaluation of infix expression is not
recommended because of the following reasons:
Evaluation of infix expression requires the knowledge of precedence of
operators and the associativity of operators.
The problem becomes complex, if there are parentheses in the expression
because they change the order of precedence.
During evaluation, we may have to scan from left to right and right to left
repeatedly thereby complexity of the program increases.
6.32 Stacks
All these problems can be avoided if the infix expression is converted to its
corresponding postfix or prefix expression and then evaluate. Evaluation of a postfix
expression or prefix expression is very simple.
Now, let us see “How to evaluate the postfix expression?” The postfix expression can
be evaluated using the following procedure:
Scan the symbol from left to right
If the scanned symbol is an operator, pop two elements from the stack. The
first popped element is operand2 and the second popped element is operand1.
This can be achieved using the statements
op2 = s[top--]; /* First popped element is operand2 */
op1 = s[top--]; /* Second popped element is operand1 */
#include <stdio.h>
#include <math.h>
#include <string.h>
/* Function to evaluate */
double compute(char symbol, double op1, double op2)
{
switch(symbol)
{
case '+': return op1 + op2; /* Perform addition operation */
case '$':
case '^': return pow(op1, op2); /* Compute power */
}
}
void main()
{
double s[20]; /* Place for stack elements */
double res; /* Holds the result of partial or final result */
double op1; /* First operand */
double op2; /* Second operand */
Output
Enter the postfix expression
23/
The result is 0.666667
Enter the postfix expression
23+
The result is 5.00000
Example 6.29: Give the tracing to evaluate the following postfix expression
AB C–D*+E$ F+
corresponding to the infix expression ( ( A + ( B – C ) * D ) $ E + F ) with following
values assigned: A = 6, B = 3, C= 2, D = 5, E = 1, F = 7
Solution: After substituting the given values, the resulting postfix expression is:
632–5*+1$7+
Systematic approach to Data Structures using C - 6.35
Note: If ‘6’ is an operand its integer value is ‘6’-‘0’ i.e., 6. This integer value will be
pushed on to the stack.
Note: The assumption is that the input consists of only non-negative, single digit
integer numbers and the expression is valid.
Example 6.30: Apply the evaluation algorithm and trace for the valid postfix
expression
ABC+*CBA-+*
Solution: Substituting the values for the variables we have the following postfix
expression:
123+*321-+*
6.36 Stacks
The complete tracing is shown below:
result = 20
Example 6.31: Apply the evaluation algorithm discussed in section 6.3.13 and trace
for the valid postfix expression
AB+C–BA+C$-
Solution: Substituting the values for the variables we have the following postfix
expression:
12+3–21+3$–
result = -27
Now, let us see “How to insert an element into a stack using dynamic array?” Assume
s is pointer to an integer, top is an index to top of the stack and initial size of the stack
denoted by SIZE is 1.
Design: Before inserting, we check whether sufficient space is available in the stack.
We know that if top value is same as SIZE – 1, then stack is full. The code for this
condition can be written as shown below:
if (top == SIZE – 1)
{
printf(“Stack overflow\n”);
return;
}
But, once the stack is full, instead of returning the control, we can increase the size of
array using realloc() function and the above code can be modified as shown below:
6.38 Stacks
if (top == SIZE – 1)
{
printf(“Stack Full: update size, insert item\n ”);
SIZE++;
s = (int *) realloc(s, SIZE*sizeof(int) );
}
When above condition fails, it means we can insert an item. If the above condition is
true, space is not sufficient. So, using realloc() we increase the size by 1 and we can
insert an item. To insert an item, we have to increment top by 1 as shown below:
top = top + 1; /* Increment top by 1 */
Then, the item can be inserted on top of the stack using:
s[top] = item; /* Insert into stack */
Now, the complete function can be written as shown below:
Note: The functions pop() and display() are same as we discussed earlier.
Now, the complete C program to implement stack using dynamic arrays is shown
below:
Systematic approach to Data Structures using C - 6.39
Example 6.33: C program to implement stacks using dynamic arrays
#include <stdio.h>
#include <stdlib.h>
for(; ;)
{
printf("1:Push 2:pop \n”);
printf(“3:Display 4:Exit\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("Enter the item\n");
scanf("%d", &item);
push(item,&top, a);
break;
case 2:
item = pop(&top, a);
if (item == -1)
printf("Stack is empty\n");
else
printf("Item deleted = %d\n", item);
break;
case 3: display(top, a);
break;
6.40 Stacks
default: exit(0);
}
}
}
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Let us divide the array into n stacks where n is the number entered by the user. In our
example, let us say n is 4. Now, 4 empty stacks can be pictorially represented as
shown below:
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Now, let us see, “How to find the initial top value for each stack?” Note the initial
values of each stack:
Express in terms of starting
these values index of each stack
Initial value of top of stack_0 = top[0] = -1 = 0 –1=5* 0 –1
Initial value of top of stack_1 = top[1] = 4 = 5 –1=5* 1 –1
Initial value of top of stack_2 = top[2] = 9 = 10 – 1 = 5 * 2 –1
Initial value of top of stack_3 = top[3] = 14 = 15 – 1= 5 * 3 –1
Initial value of top of stack_4 = top[4] = 19 = 20 – 1= 5 * 4 –1
i.e., top[j] = 5 * j –1
i.e., top[j] = 20/4* j – 1
i.e., top[j] = MAX_SIZE / n * j – 1 for j = 0 to 4
j = 0 to n
Now, the code to find the initial top value of each stack can be written as shown
below:
for ( j = 0; j <=n ; j++)
{
top[j] = MAX_SIZE / n * j – 1;
}
Now, the question is “How to find the initial value of boundary of each stack?”
Observe from the 4 stacks given in the previous page that initial top value of each
stack is same as initial boundary value. The code for this case can be written as
shown below:
for ( j = 0; j <=n ; j++)
{
boundary[j] = top[j];
}
Now, the above two for loops can be combined into a single for-loop as shown below:
6.42 Stacks
#define MAX_SIZE 20
int s[MAX_SIZE];
Design: Let us consider an array with 4 stacks where five elements are inserted into
each stack. This can be pictorially represented as shown below:
if (top[i] == boundary[i+1]
{
printf(“Stack %d is full\n”, i);
return;
}
top++;
s[top] = item;
Now, top is replaced by top[i]. Now, the above set of activities can be written by
replacing top by top[i] as shown below:
Now, let us see “How to check for underflow in multiple stacks?” Consider the
following figure where 4 stacks are used using only one single-dimensional array:
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Since no elements are present in any of the stack, all stacks are empty. Observe the
following facts: 3
When top[0] is same as boundary[0] stack_0 is empty 2
When top[1] is same as boundary[1] stack_1 is empty 1
When top[2] is same as boundary[2] stack_2 is empty 0
When top[3] is same as boundary[3] stack_3 is empty
-1 s
In general, when top[i] is same as boundary[i] stack_i is empty. This can be written as
shown below:
if (top[i] == boundary[i]) return -1; // indicates stack is empty
return s[top[i]--];
int pop() int pop(int s[], int top[], int boundary[], int i)
{ {
if (top[i] == boundary[i]) if (top[i] == boundary[i])
return -1; // stack is empty return -1; // stack is empty
if (top[i] == boundary[i])
{
printf(“Stack %d is empty\n”,i);
return;
}
When control comes out of the above if-statement, it means that stack is not empty.
The contents of multiple stacks can be written as shown below:
Observe from above figure that contents of stack[i] starts from “boundary[i] + 1”
and goes up to top[i]. So, the contents of the stack_i can be displayed using the
following statements:
Now, the complete function to display stack_i can be written as shown below:
6.46 Stacks
Example 6.36: Display contents of stack using global variables and by passing
parameters
The complete program to perform operations such as push, pop and display using
global variables is shown below:
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 20
int s[MAX_SIZE];
break;
6.48 Stacks
case 3:
display();
break;
default:
exit(0);
}
}
}
The complete program to perform operations such as push, pop and display by
passing parameters is shown below:
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 20
#define MAX_STACKS 10 // Maximum number of stacks
void main()
{
int choice; /* user choice for push, pop and display */
int item;
int s[MAX_SIZE];
int boundary[MAX_STACKS];
int top[MAX_STACKS] ;
int n; // Number of stacks entered by the user
int i, j;
for (;;)
{
printf(“Stack number: “);
for (j = 0; j < n; j++) printf(“%d “, j);
break;
case 2: item = pop(s, top, boundary,i);
if (item == -1)
printf("Stack is empty\n");
else
printf("Item deleted = %d\n",item);
break;
case 3:
display(top, s, i, boundary);
break;
default:
exit(0);
}
}
}
6.50 Stacks
6.6 Recursion
Direct recursion: A recursive function that invokes itself is said to have direct
recursion. For example, the factorial function calls itself (detailed explanation is given
later) and hence the function is said to have direct recursion.
return n*fact(n-1);
}
Indirect recursion: A function which contains a call to another function which in
turn calls another function which in turn calls another function and so on and
eventually calls the first function is called indirect recursion. It is very difficult to
read, understand and find any logical errors in a function that has indirect
recursion. For example, a function f1 invokes f2 which in turn invokes f3 which in
turn invokes f1 is said to have indirect recursion. This is pictorially represented as
shown below:
void f1() void f2() void f3()
{ { {
…… …… F1();
…… F3(); …… Indirect recursion
f2(); …… ……
} } }
Systematic approach to Data Structures using C - 6.51
Now, the question is “How to design recursive functions?” Every recursive call must
solve one part of the problem using base case or reduce the size (or instance) of the
problem using general case. Now, let us see “What is base case? What is a general
case?”
Definition: A base case is a special case where solution can be obtained without
using recursion. This is also called base/terminal condition. Each recursive function
must have a base case. A base case serves two purposes:
1) It acts as terminating condition.
2) The recursive function obtains the solution from the base case it reaches.
Definition: In any recursive function, the part of the function except base case is
called general case. This portion of the code contains the logic required to reduce the
size (or instance) of the problem so as to move towards the base case or terminal
condition. Here, each time the function is called, the size (or instance) of the problem
is reduced.
Note: A recursive function should never generate infinite sequence of calls on itself.
An algorithm exhibiting this sequence of calls will never terminate and hence it is
called infinite recursion and will crash the system.
5! = 5 * 4!
4! = 4 * 3! Decompose the
3! = 3 * 2! problem from
2! = 2 * 1! top to bottom
1!= 1 * 0!
1! = 1 * 0! = 1
2! = 2 * 1! = 2 Compute the
3! = 3 * 2! = 6 solution from
4! = 4 * 3! = 24 bottom to top
5! = 5 * 4! = 120
6.52 Stacks
Observe that computation of 5! is postponed and it is decomposed into computing
factorials of 4, 3, 2, 1 as shown below till we get 0! which is 1.
5! = 5 * 4!
4! = 4 * 3! In general,
n! = n * (n-1)! // general case
3! = 3 * 2!
2! = 2 * 1!
1!= 1 * 0!
0! = 1
n! = 1 if n = 0 // base case
Thus, by looking at the above base case and general case, the recursive definition to
find factorial of n can be written as shown below:
n!= 1 if n == 0 1 if n == 0
or n! =
n! = n * (n-1)! otherwise n * (n-1)! otherwise
The above definition can also be written as shown below:
1 if n == 0
F(n) = Output: 5 * 4 * 3 *2 * 1
n * F(n-1) otherwise
Using the above recursive definition, we can write the function as shown below:
Example 6.39: Recursive C function to find the factorial of N
int fact(int n)
{
if ( n == 0 ) return 1; /* factorial of n when n = 0 */
return n*fact(n-1); /* factorial of n when n > 0 */
}
The above function can be invoked as shown below:
Systematic approach to Data Structures using C - 6.53
Example 6.40: C program to compute factorial of n
#include <stdio.h>
/* Include: Example 6.39: Function to compute factorial of n */
void main()
{
int n;
float res; Input
printf("Enter n \n"); Enter n
scanf("%d %d",&n, &r); 5
res = fact(n); Output
printf("%d! = %d\n", n, res) ; 5! = 120
}
So, the general rules that we are supposed to follow while designing any recursive
algorithm are:
Determine the base case. Careful attention should be given here, because: when
base case is reached, the function must execute a return statement without a call to
recursive function.
Determine the general case. Here also careful attention should be given and see
that each call must reduce the size of the problem and moves towards base case
and must reach base case
Combine the base case and general case into a function.
void main()
{
int n;
n = fact (3); a
printf("%d\n”, n);
}
3 2 1 0
int fact(int n) int fact(int n) int fact(int n) int fact(int n)
{ { { {
if ( n == 0 ) if ( n == 0 ) if ( n == 0 ) if ( n == 0 )
b c d 1
return 1; return 1; return 1; return 1;
return n* fact(n-1); return n* fact(n-1); return n* fact(n-1); return n* fact(n-1);
} 3*2 } 2*1 } 1*1 }
2 1
h g f e
F(0) = 0 + F(-1)
F(1) = 1 + F(0)
F(2) = 2 + F(1)
F(3) = 3 + F(2)
F(4) = 4 + F(3)
0 if n == -1
F(n) =
n + F(n-1) otherwise
// 4 + 3+ 2 + 1 + 0 where n = 4 // 0 + 1 + 2 + 3 + 4 where n = 4
int F(int n) int F(int n)
{ {
if ( n == -1 ) return 0; OR if ( n == -1 ) return 0;
Note: If we exchange the terms in the expression n + F(n-1) shown using dotted
rectangular box as: F(n-1) + n, we get the sum of the series: 0 + 1 + 2 + 3 + 4.
Note: By inserting an array a as the parameter and changing n to a[n] for the above
recursive relation, we get sum of following series:
0 if n == -1
F(a,n) =
a[n] + F(a, n-1) otherwise
Example 6.42: Recursive function to find sum of array elements from a[n-1] to a[0]
// sum = a[4] + a[3] + a[2] + a[1] + a[0] // sum = a[0] + a[1] + a[2] + a[3] + a[4]
float F(float a[], int n) float F(float a[], int n)
{ {
if ( n == -1 ) return 0; if ( n == -1 ) return 0;
0 if n == -1
F(a,n) =
print(a[n]) , F(a, n-1) otherwise Output: 50 40 30 20 10
Example 6.44: Recursive function to print array elements from a[0] to a[n-1]
Now, let us see “What is GCD of two numbers?” What is the recursive definition to
compute GCD of two numbers?”
Definition: The GCD of two given numbers is the largest integer that divides both of
them. GCD of two numbers is defined only for positive integers but, not defined for
negative integers and floating point numbers.
Now, let us “Write a recursive function to find GCD of two numbers using Euclid’s
algorithm” The recursive solution for Euclid’s algorithm can be obtained as shown
below:
Design recursive algorithm: The procedure to obtain GCD(6, 10) using Euclid’s
algorithm is shown below:
M N R ←M % N
6 10 6 ← 6 % 10 1
10 6 4 ← 10 % 6
6 4 2 ←6%4
4 2 0 ←4%2
2 0 stop when n is zero
GCD(10, 6) =
= GCD (6, 4)
= GCD (6, 10 % 6 )
Now, the recursive definition to compute GCD of two numbers M and N using
EUCLID’s algorithm can be written as shown below:
M if N = 0 Base case
GCD(M, N) =
GCD(N, M % N) otherwise General case
Systematic approach to Data Structures using C - 6.59
Using the above recursive definition, we can write the recursive function as shown
below:
#include <stdio.h>
void main()
{
int m, n, res;
Input
printf(“Enter the value of m and n\n”); Enter the value of m and n
scanf(“%d %d”,&m, &n); 10 6
res = gcd(m, n); res = 2
Output
printf(“gcd(%d, %d) = %d\n”, m, n, res); gcd(10, 6) = 2
}
To write a recursive definition, we should know the base case and general case which
can be computed as shown below:
fibonacci
numbers
F[0] = 0 F[n] = 0 if n = 0
F[1] = 1 F[n] = 1 if n = 1
F[2] = F[1] + F[0] = 1 + 0 = 1 Base case
F[3] = F[2] + F[1] = 1 + 1 = 2
F[4] = F[3] + F[2] = 2 + 1 = 3
General case
In general, F[n] = F[n-1]+F(n-2) F(n) = F(n-1)+F(n-2) otherwise
Now, using the above base case and general case, the recursive definition to find nth
Fibonacci number can be written shown below:
0 if n = 0
F (n) = 1 if n = 1
F(n-1) + F(n-2) otherwise
The algorithm to find the nth Fibonacci number can be written using recursive C
function as shown below:
int F(int n)
{
if ( n == 0 ) return 0; /* Base case: 1st number */
#include <stdio.h>
/* Include: Example 6.48: To compute nth Fibonacci number */
void main()
{
int n;
Input
printf("Enter n\n"); Enter n
scanf("%d",&n); 6
Output
printf("fib(%d) = %d\n”,n,F(n)); Fib(6) = 8
}
The function F() can also be used to generate n Fibonacci numbers as shown below:
#include <stdio.h>
/* Include: Example 6.48: To compute nth Fibonacci number */
void main()
{
int i, n;
Input
printf("Enter n\n"); Enter n
scanf("%d",&n); 6
Output
printf(“%d Fibonacci numbers are\n”, n); 6 Fibonacci numbers are
for (i = 0; i < n; i++) fib(0) = 0
{ fib(1) = 1
printf("fib(%d) = %d\n”,i,fib(i)); fib(2) = 1
fib(3) = 2
} fib(4) = 3
} fib(5) = 5
Let us see, “What is tower of Hanoi problem?” In this problem, there are three pegs
say A, B and C. The different diameters of n discs are placed one above the other
6.62 Stacks
through the peg A and the discs are placed such that always a smaller disc is placed
above the larger disc. The two pegs B and C are empty. All the discs from peg A are
to be transferred to peg C using peg B as temporary storage. The rules to be followed
while transferring the discs are
Only one disc is moved at a time from one peg to another peg
Smaller disc is on top of the larger disc at any time.
Only one peg can be used to for storing intermediate discs
pegs
The various actions to be taken during this transfer of discs results in following cases:
Case 1: When there are no discs: This situation occurs when n is 0. When there are
no discs i.e., when n is 0, no action takes place and we simply return. The code for
this can be written as shown below:
if ( n == 0) return;
Systematic approach to Data Structures using C - 6.63
Case 2: When there are some discs: Suppose there are 4 discs in the source peg.
The initial set up when n is 4 is shown below:
pegs
Now, it is required to transfer all n discs from source to destination recursively. This
can be done using following sequence of operations:
Step 1: Move n-1 discs recursively from source to temp. In our example we have to
move 3 discs from source to temp. Observe that temp has to be 4th parameter when the
function is called since temp is the destination. This results in following scenario:
This activity can be achieved by calling the function transfer() recursively as shown
below:
transfer (n-1, s, d, t); // transfer n-1 discs from source to destination
Step 2: Move nth disc from source to destination. This results in following scenario:
This activity can be achieved by moving the nth disc from source s to destination d.
This can be achieved using the following statement:
printf("Move disc %d from %c to %c\n", n, s, d); // Move nth disc from source to destination
Step 3: Move n-1 discs recursively from temp to destination. In our example we have
to move 3 discs from temp to destination recursively. Observe that temp has to be 2nd
parameter when the function is called since temp is the source peg. This results in
following scenario:
6.64 Stacks
This activity can be achieved by calling the function transfer() recursively as shown
below:
The C function to implement tower of Hanoi problem can be written as shown below:
n 1 if k = 0 or n = k
Ck =
n-1 n-1
Ck-1 + Ck otherwise
Note: The value of n cannot be 0. The above recurrence relation can also be written
as
1 if k = 0 or n = k
C(n, k) =
C(n-1, k-1) + C(n-1, k) for n > k > 0
For the above recursive relation, we can write the recursive function as shown below:
return C(n – 1, k – 1) + C (n – 1, k );
}
6.66 Stacks
The C program to compute binomial co-efficient can be written as shown below:
N+1 if M = 0 Case 1
A(M, N) = A (M-1, 1) if N = 0 Case 2
A(M – 1, A(M, N-1)) otherwise Case 3
In the above recursive relation, Case 1 is the base case and Case 2 and Case 3 are
general cases. Using Ackermann’s function, A(1,2) can be evaluated as shown below:
For the above recursive relation, we can write the recursive function as shown below:
Systematic approach to Data Structures using C - 6.67
Example 6.55: C function to compute value of Ackermann’s function
The C program to invoke the above function to compute and print the value of
Ackermann’s function can be written as shown below:
Example 6.56: C Program to compute and print the value of Ackermann’s function
#include <stdio.h>
/* Include: Example 6.55: C function to compute value of Ackermann’s function */
void main()
{
int m, n, res;
printf("Enter M and N\n"); // Enter N and K
scanf("%d %d", &m, &n); // 1 2
Now, let us see “What is system stack? How the system stack is used to process a
function call?” A stack used by a program at run-time (that is when the program is
being executed) is called system stack. The system stack is used when functions are
invoked and executed:
When a function is called, a structure called an activation record is created on top
of the stack
When a function is terminated, the activation record is deleted from the top of the
system stack.
6.68 Stacks
Now, let us see “What is an activation record?”
Analogy: The above facts can be explained using the following analogy: We post the
letter by writing the sender’s address to whom it is to be delivered and we also write
our address (also called return address). The receiver stores the return address written
on the letter and uses this address to communicate back. On similar lines, control is
transferred to or from the function.
Step 5: When main is being executed, if the function a1() is invoked, a new
stack frame or activation record is created consisting of:
return address of main()
local variables of a1()
current frame pointer which is on top of the stack
This new stack frame is pushed onto the stack.
Systematic approach to Data Structures using C - 6.69
Note: Observe that previous frame pointer of activation record which is on top of the
stack points to the activation record of main().
Step 7: When return statement in a1() is executed, the activation record on top of
the stack is removed. Using the removed activation record, we can get the
return address of main using which the control is transferred to main.
Step 8: Finally, when body of the function main is completely executed, the return
address of OS is obtained from the activation record and control is
transferred to OS.
Mazes have been very interesting subject for many years. In this section, let us
develop a program that runs a maze. Now, let us see “What is a maze?”
entrance
Definition: A maze is a confusing
network of paths through which it is 0 0 0 0 0 1
very difficult to find one’s way. The 1 1 1 1 1 0
experimental psychologists train the rats 1 0 0 0 0 1
to search mazes for food. A maze
0 1 1 1 1 1
problem is a nice application of stack.
1 0 0 0 0 1
Before developing a program let us see 1 1 1 1 1 0
“How maze is represented?” A maze is 1 0 0 0 0 1
represented as a two dimensional array
0 1 1 1 1 1
in which
zeros represent the open paths 1 0 0 0 0 0 exit
An example for a simple maze with long path is shown in above figure.
Note: Observe from the above maze that, the rat starts at the top left corner of the
maze and leaves at bottom right of the maze.
6.70 Stacks
Now, let us see “What is the disadvantage of representing a maze of size is m x n?” If
(i, j) is the position of the rat in a maze, the following figure shows the possible
moves along with new positions:
N N – North
NW NE
(i-1, j) NW – North West
(i-1, j-1) (i-1, j+1) NE – North East
W – West
W (i, j-1) (i, j) (i, j+1) E E – East
SW – South West
(i+1, j-1) (i+1, j+1) S – South
(i+1, j) SE – South East
SW SE
S
Let (i, j) is the current position of rat in the maze where i is the row index and j is the
column index. Now, the rat moves in 8 directions as shown in above figure. The new
position of rat in the maze can be obtained as shown below:
North (vertical upwards) (i –1, j)
South (vertical downwards) (i+1, j)
West (horizontal right) (i, j + 1)
East (horizontal left) (i, j – 1)
North east (vertical upwards and horizontal right) (i – 1, j + 1)
North west(vertical upwards and horizontal left) (i – 1, j – 1)
South east (vertical downwards and horizontal right) (i + 1, j + 1)
South west (vertical downwards and horizontal left) (i + 1, j – 1)
N N – North
NW (i-1, j) NE NW – North West
(i-1, j-1) (i-1, j+1) NE – North East
W – West
W (i, j-1) (i, j) (i, j+1) E E – East
SW – South West
(i+1, j-1) (i+1, j+1) S – South
(i+1, j) SE – South East
SW SE
S
Observe from above figure that either we add -1 or 0 or 1 to (i, j) to get the new
position. These values that we add to find the new position are called offset values.
The offset values of various moves can be obtained as shown below:
(N) North position = (i-1, j) = (i-1, j+0) Offset value = -1, 0
(NE)North east position = (i-1, j+1) Offset value = -1, 1
(E) East position = (i, j+1) = (i+0, j+1) Offset value = 0, 1
(SE)South east position = (i+1, j+1) Offset value = 1, 1
(S)South position = (i+1, j) = (i+1, j+0) Offset value = 1, 0
(SW)South west position = (i+1, j-1) Offset value = 1, -1
(W)West position = (i, j-1) = (i+0, j-1) Offset value = 0, -1
(NW)North west position= (i-1, j-1) Offset value = -1, -1
The above 8 directions can be represented using the
numbers from 0 to 7 along with above offset values in the
form of a table as shown below:
Name dir move[dir].vert move[dir].horiz
N 0 -1 0
NE 1 -1 1
E 2 0 1
SE 3 1 1
S 4 1 0
SW 5 1 -1
W 6 0 -1
NW 7 -1 -1
6.72 Stacks
The above table can be initialized using array of structures as shown below:
typedef struct
{
int vert; /* 0: No vertical movement, 1:Vertical down, -1:Vertical up */
int horiz; /* 0: No horizontal movement, 1:Horizontal right */
} offsets; /* -1: Horizontal left */
Initialization: There is always an entrance for the rat at position (1, 1). So, mark this
initial position of rat and save it on the stack using the following statements as shown
below:
mark[1][1] = 1; /* Mark the path through (1, 1) */
pos.row = 1, pos.col = 1; /* Rat starts from (1, 1) */
pos.dir = 0; /* dir : 0 – Movement is north */
Now, take the valid position of rat in the maze which is on top of the stack using the
statements:
pos = s[top--]; /* Obtain the position of rat from stack*/
Now, from the current position (row, col) we can find the next valid position of rat in
the maze at a particular direction using the following code:
If the above position is same as exit point, then we have the path and we stop
searching. The code for this can be:
s[++top] = pos;
row = nextRow, col = nextCol, dir = 0;
}
If above condition fails, then we have to go in the next direction by incrementing the
value of dir as shown below:
++dir;
The complete maze program along with output is shown below:
#include <stdio.h>
typedef struct
{
int vert;
int horiz;
} offsets;
offsets move[8] = {{-1, 0}, {-1, 1},{0, 1},{1, 1}, {1, 0},{1, -1},{0, -1},{-1, -1}};
typedef struct
{
int row;
int col;
int dir;
} element;
#define EXIT_ROW 12
#define EXIT_COL 15
6.74 Stacks
int maze[20][20]= { // Maze for the rat move */
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1},
{1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1},
{1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1},
{1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1},
{1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1},
{1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1},
{1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1},
{1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1},
{1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
};
void main()
{
int row, col, nextRow, nextCol, dir, found = FALSE, top, i;
int mark[20][20] = {{0}};
element s[200], pos;
mark[1][1] = 1;
pos.row = 1, pos.col = 1; /* Rat starts from (1, 1) */
pos.dir = 0; /* dir : 0 – Movement is north */
s[++top] = pos;
row = nextRow, col = nextCol, dir = 0;
}
else
++dir;
}
}
if (found)
{
printf("Path is\n");
printf("Row Col\n");
for (i = 0; i <= top; i++)
{
printf("%3d %3d\n",s[i].row, s[i].col);
}
printf("%3d %3d\n", row, col);
printf("%3d %3d\n",EXIT_ROW, EXIT_COL);
}
else
printf("Maze does not have a path\n");
}
Output
Path is:
(1, 1) (2, 2) (1, 3) (1, 4) (1, 5) (2, 4)
(3, 5) (3, 4) (4, 3) (5, 3) (6, 2) (7, 2)
(8, 1) (9, 2) (10, 3) (10, 4) (9, 5) (8, 6)
(8, 7) (9, 8) (10, 8) (11, 9) (11, 10) (10,11)
(10,12) (10,13) (9, 14) (10,15) (11,15) (12,15)
By looking at the above positions, we can find the path from entry to exist and is
shown using dotted lines in the following maze.
6.76 Stacks
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
entrance 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1
1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1
1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1
1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1
1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1
1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1
1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1
1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1
1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1
1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1
1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1 exit
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
Maze 2: Initialize the matrix maze in the above program change symbolic constants
EXIT_ROW and EXIT_COL as shown below:
int maze[20][20] =
{
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 1, 1},
{1, 1, 1, 1, 1, 1, 0, 1},
{1, 1, 0, 0, 0, 0, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 1},
{1, 1, 0, 0, 0, 0, 1, 1}, 9 rows
{1, 1, 1, 1, 1, 1, 0, 1},
{1, 1, 0, 0, 0, 0, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 1},
{1, 1, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1}
}; 6 columns
#define EXIT_COL 6
#define EXIT_ROW 9
1, 1, 1, 1, 1, 1, 1, 1
entrance 1, 0, 0, 0, 0, 0, 1, 1
1, 1, 1, 1, 1, 1, 0, 1
1, 1, 0, 0, 0, 0, 1, 1
1, 0, 1, 1, 1, 1, 1, 1
1, 1, 0, 0, 0, 0, 1, 1
1, 1, 1, 1, 1, 1, 0, 1
1, 1, 0, 0, 0, 0, 1, 1
1, 0, 1, 1, 1, 1, 1, 1
1, 1, 0, 0, 0, 0, 0, 1 exit
1, 1, 1, 1, 1, 1, 1, 1
Exercises
1) What is a stack? What are the various operations that can be performed on
stacks?
2) What is push operation? What is stack overflow?
3) How to implement push operation using arrays (static allocation technique)?
4) What is pop operation? What is stack underflow?”
5) How to implement pop operation using arrays (static allocation technique)?
6) Write a C function to display the contents of stack (using global variables)
7) Write a C function to display contents of the stack (by passing parameters)
8) What is a palindrome? Write a C function to check for a palindrome using
stack
9) What are the applications of stack?
10) What is an expression? What are the various types of expressions?
11) What is postfix expression? What is prefix expression?
12) How to write a program to convert a given infix expression to its equivalent
prefix expression?
13) What is the problem in evaluating the infix expressions? What is the need for
evaluating postfix/prefix expressions?
14) How to evaluate the postfix expression?
15) Given the following expressions give their postfix and prefix forms
i. (A+B)*(D-C)
ii. X$Y * Z – M + N + P | Q | (R+S) (VTU-July/Aug 2004)
16) Obtain the prefix expressions and postfix expressions for the following.
6.78 Stacks
a) A+B-C*D
b) (A+B)*(C+D)$(A+B)
17) Convert the following prefix expressions to corresponding infix expressions.
a) –A+*$CDB/-FE*GHI
b) ^-*+ABC-DE+FG
18) Convert the following postfix expressions to corresponding infix expressions.
a) AB^C*D-EF/GH+/+
b) AB+C*DE—FG+^
19) Write an algorithm for evaluating a valid postfix expression. Trace the same
on
i. AB+C–BA+C$-
ii. ABC+*CBA-+*
for given value A=1, B=2, C=3
20) How to implement stacks using dynamic arrays?
21) How to implement multiple stacks using a single dimensional array?
22) What is recursion? What are the various types of recursion?
23) How to design recursive functions? What is base case? What is a general case?
24) Write a recursive C function to find factorial of a number
25) Write a recursive C function to find the sum of series
26) Write a recursive function to find sum of array elements from a[n-1] to a[0]
27) Write a recursive function to print array elements from a[0] to a[n-1]
28) What is GCD of two numbers? What is the recursive definition to compute
GCD of two numbers?
29) What are Fibonacci numbers? What is the recursive definition to compute a
Fibonacci number?
30) What is tower of Hanoi problem? Write a recursive function for the same
31) Write a C function to compute value of Ackermann’s function
32) What is system stack? How the system stack is used to process a function call?
33) What is an activation record? How the control is transferred to or from the
function with the help of activation record?
34) What is a maze problem? How maze is represented?
35) What is the disadvantage of representing a maze of size is m x n
36) Write a C program for maze problem
Chapter 7: QUEUES
What are we studying in this chapter?
Definition, array representation
Queue operations
Queue variants: Circular queue, priority queue, double ended queue
Circular queues using dynamic arrays, multiple queues
Programming examples - 7 hours
Definition: A queue is a special type of data structure (an ordered collection of items)
where elements are inserted from one end and elements are deleted from the other
end. The end at which new elements are added is called the rear and the end from
which elements are deleted is called the front. Using this approach, the First element
Inserted is the First element to be deleted Out, and hence, queue is also called First In
First Out (FIFO) data structure.
Now, let us see “What are the different types of queues?” Since a queue elements are
stored in linear order, it can be implemented using arrays and linked lists. Based on
the method of insertion and deletion the queues are classified as shown below:
In this chapter we concentrate on how each type of queue can be represented using
arrays. In the next chapter we see how a queue can be represented using linked lists.
For example, consider the queue shown below having the elements 10, 50 and 20:
q
10 50 20
0 1 2 3 4
front rear
The items are inserted into queue in the order 10, 50 and 20. The variable q is
used as an array to hold these elements
Item 10 is the first element inserted. So, the variable first is used as index to the
first element
Item 20 is the last element inserted. So, the variable rear is used as index to the
last element
Two more items can be inserted into above queue.
Now, let us see “what are the various operations that can be performed on a queue?”
The various operations that can be performed on stacks are shown below:
Insert (An element is inserted from the rear end)
Queue operations Delete (An element is deleted from the front end)
Display (Display the status of queue and queue contents)
Data Structures using C - 7.3
In a queue, an item is always inserted at the rear end. Now, let us see “How to insert
an item into the queue?”
Design: The various steps to be followed while inserting the elements into queue are
shown below:
Step 1: Consider the queue shown below. Can we insert any element into the queue?
No, it is not possible because queue is full.
Observe that whenever rear value is QUEUE_SIZE = 5
equal to “QUEUE_SIZE – 1” insertion is
q
not possible. The code for this can be
written as shown below: 10 20 30 40 50
0 1 2 3 4
if (rear == QUEUE_SIZE - 1) front rear
{
printf(“Queue is full\n”); Rear insertion not possible
return;
}
In a queue, an item is always removed from the front end. Now, let us see “How to
delete an item from the queue?”
Design: The various steps to be followed while deleting an element from queue are
shown below:
Step 1: Now, let us see “How to check whether queue is empty or not?”
q q
10 20 30 10 20 30
0 1 2 3 4 0 1 2 3 4
front rear front
rear
3 items are present in queue only one item is present
Step 2: When above condition fails, we can delete an item from front end of queue.
For this to happen, we have to access and return the first element and increment value
of front by 1 as shown below:
return q[front++];
Now, the complete function to delete an element from the front end of the queue can
be written as shown below:
Example 7.2: Function to delete an element from the front end of queue
or int *q
int Delete_Front() int Delete_Front(int *front,int *rear,int q[])
{ {
if (front > rear) return -1; if ( *front > *rear) return -1;
Note: The variables front, rear and Note: The variables front, rear and q have
q are global. So, they can be to be passed as parameters. Since the
accessed in all the functions. contents of front, rear and q are changed
they should be treated as pointers.
7.2.3 Display queue items
Now, let us see “How to display the elements present in queue?”
Design: The various steps to be followed while displaying the elements of from
queue are shown below:
Step 1: Check for empty queue. This can be done using the following code:
if (front > rear)
{
printf(“Que is empty\n”);
return;
}
7.6 Queues
Step 2: If elements are present in queue control comes out of the above if statement.
Assume that the queue contains three elements as shown in figure.
20 25 10
-1 0 1 2 3 4
front rear
The contents of queue can be displayed as shown below:
Output
printf(“%d\n”, s [0] ); 20
printf(“%d\n”, s [1] ); 25
printf(“%d\n”, s [2] ); 10
#include <stdio.h>
#include <process.h>
#define QUE_SIZE 5
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
7.8 Queues
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
Insert_Rear();
break;
case 2:
item = Delete_Front();
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted = %d\n”, item);
break;
case 3:
Display();
break;
default:
exit(0);
}
}
}
#include <stdio.h>
#include <process.h>
#define QUE_SIZE 5
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
Insert_Rear(item, &rear, q);
break;
case 2:
item = Delete_Front(&front, &rear, q);
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted = %d\n”, item);
break;
case 3:
Display(front, rear, q);
break;
default:
exit(0);
}
}
}
7.10 Queues
The above situation arises when 5 elements say 10, 20, 30, 40 and 50 are inserted and
then deleting first two items 10 and 20. Now, if we try to insert an item we get the
message
“Queue overflow”
Note: In the above situation, rear insertion is denied even if space is available at the
front end. This is because in our function InsertQ() (see example 7.1) before inserting
an element, we test whether rear is equal to QUEUE_SIZE – 1. If so, we say “Queue
is full and cannot insert”. This is a disadvantage. This disadvantage can be overcome
using two methods:
Method 1: Shift left: After deleting the element from the front, shift all remaining
elements to the left. This can be pictorially represented as shown below:
0 1 2 3 4 0 1 2 3 4
10 20 30 40 50 20 30 40 50
After inserting 10, 20, 30, 40, 50 After deleting 10, shift remaining
elements to the left
0 1 2 3 4 0 1 2 3 4
30 40 50 40 50
When an item is deleted, all the items towards right are moved to left by one position
and rear is decremented by 1. It is costly method and hence not recommended.
Data Structures using C - 7.11
Method 2: Using circular representation: In a queue, we increment rear by 1 and
then insert the item as shown below:
rear = rear + 1;
q[rear] = item;
In circular representation, we increment rear by 1 as usual and then perform the
modulus operation using operator ‘%’ as shown below:
rear = (rear + 1) % QUE_SIZE;
where QUE_SIZE is symbolic constant which represent maximum number of
elements in the queue and it can be defined as shown below:
#define QUE_SIZE 5
The significance of % operator is clear from the following example.
Insert: Assume QUE_SIZE is 5 and 5 elements are inserted into queue. The circular
representation along with linear array are shown below:
3 2
40 30 q 10 20 30 40 50
rear 4 50 20 1 0 1 2 3 4
10
0
front rear
front
Delete: In the above queue, item 10 is the first element. So, during deletion, the item
10 has to be deleted. This is achieved by incrementing front by 1 so that front
contains the index of the second element. This is pictorially represented as shown
below:
3 2
40 30 q 20 30 40 50
20 1 front 0 1 2 3 4
rear 4 50
front rear
0
Insert: Now if we want to insert an item 60, we have to increment rear by 1. In the
above figure, the value of rear is 4. If we increment rear by 1, its value will be 5. But,
we have assumed that queue is circular. So, after incrementing by 1, it should be 0
instead of 5. This is achieved by taking modulus as shown below:
rear = (rear + 1) % QUE_SIZE
7.12 Queues
After executing the above statement, the value of rear will be 0 so that item 60 can be
inserted at 0th position as shown below:
3 2
40 30 q 60 20 30 40 50
4 50 20 1 front 0 1 2 3 4
60
rear front
0
rear
Now, if we display the contents of queue, the output will be 20, 30, 40, 50, 60. This is
because 20 is the first element in the queue and 60 is the last element in the queue.
Definition: In circular queue, the elements of a given queue can be stored efficiently
in an array so as to “wrap around” so that rear end of the queue is followed by the
front of queue. The pictorial representation of a circular queue and its equivalent
representation using an array are given side by side in figure below:
rear
q 10 20 30
3 2
30 0 1 2 3 4
4 20 1 front rear
10
0
front
Data Structures using C - 7.13
This circular representation allows the entire array to store the elements without
shifting any data within the queue. This is an efficient way of implementing queues.
Empty queue: The circular representation and its equivalent array representation
when queue is empty is shown below:
3 2
q
-1 0 1 2 3 4
4 1
rear
front rear
front 0
When queue is empty, front = 0 and rear = -1. But, in circular queue, just before
index 0 we have index 4. So, instead of rear = -1, we can write rear = 4 also. Thus,
empty queue is represented by following initialization statements:
front = 0
rear = -1;
The above statements indicating empty queue can also be represented as shown
below:
front = 0
rear = 4; // rear = 4. In general, rear = QUE_SIZE – 1
Example 7.6: Show the contents of circular queue after performing each of the
following operations”
a) Empty queue
b) Insert 10
c) Insert 20 and 30
d) Insert 40 and 50
e) Insert 60
f) Delete two items
g) Insert 60 and 70
h) Insert 80
Solution: The queue and its representation after performing each of the above
operations can be written as shown below:
3 2
q
-1 0 1 2 3 4
4 1
rear
front rear
front 0
3 2
q 10
0 1 2 3 4
4 1
10 front
rear
front 0 rear
Step 3: Inserting 20 and 30: Increment rear by 1 and insert 20. Again increment
rear by 1 and insert 30 as shown below:
rear
3 2
30 q 10 20 30
0 1 2 3 4
4 20 1
10 front rear
front 0
Step 4: Inserting 40 and 50: Increment rear by 1 and insert 40. Again increment
rear by 1 and insert 50 as shown below:
3 2
40 30 q 10 20 30 40 50
0 1 2 3 4
rear 4 50 20 1
10 front rear
0
front
Step 5: Inserting 60: Queue is full. It is not possible to insert any element into queue.
So, contents of queue have not been changed.
Data Structures using C - 7.15
3 2 q
40 30 10 20 30 40 50
0 1 2 3 4
rear 4 50 20 1
10 front rear
front 0 Queue is full
Step 6: Delete: An item has to be deleted always from the front end. So, 10 is deleted
and contents of queue after deleting 10 is shown below:
3 2
40 30 q 20 30 40 50
0 1 2 3 4
rear 4 50 20 1 front
front rear
0
Step 7: Delete: An item has to be deleted always from the front end. So, 20 is deleted
and contents of queue after deleting 20 is shown below:
3 2
40 30 front q 30 40 50
0 1 2 3 4
1
rear 4 50
front rear
0
Step 8: Inserting 60: Incrementing rear by 1, its value is 0 (because of its circular
representation) and insert 60 at 0th location as shown below:
3 2
40 30 front q 60 30 40 50
0 1 2 3 4
4 50 1
60 rear front
0
rear
Step 9: Inserting 70: Increment rear by 1 and insert 70 as shown below:
3 2
40 30 front q 60 70 30 40 50
0 1 2 3 4
4 50 70 1
rear
60 rear front
0
7.16 Queues
Step 10: Inserting 80: Queue is full. It is not possible to insert any element into
queue. So, contents of queue has not been changed.
3 2
40 30 front q 60 70 30 40 50
0 1 2 3 4
4 50 70 1 rear
60 rear front
0
Queue is full
7.4.1 InsertQ()
Now, let us see “How to implement insert function using arrays (static allocation
technique)?”
Design: The various steps to be followed while inserting the elements into queue are
shown below:
Step 1: Check for overflow: Before inserting, we check whether sufficient space is
available in the queue. This can be achieved using the following code:
if (count == QUEUE_SIZE)
{
printf(“Queue is full\n”);
return;
}
Step 2: Insert item: Increment rear by 1 and then take the mod operation and then
insert the item as shown below:
rear = (rear + 1) % QUEUE_SIZE;
q[rear] = item;
7.4.2 DeleteQ()
Now, let us see “What are the various steps to be followed while deleting an
element?” The following steps are followed:
Step 1: Check for underflow: Before deleting an element from queue, we check
whether sufficient queue is empty or not. This can be achieved using the statement:
Example 7.8: Function to delete an item from the front end of circular queue
7.4.3 DisplayQ()
Now, let us see “What are the various steps to be followed while displaying the
elements of circular queue?” The following steps are followed:
Step 1: Check for underflow: This is achieved using the following statement:
if (count == 0)
{
printf(“Queue is empty\n”);
return;
}
Step 2: Display: Display starts from the front index. After displaying q[front] we
have to update front by 1 (That is by incrementing front by 1 and then taking the
modulus). The procedure is repeated for count number of times. This is because,
count contains the number of items in queue. The code for this can be written as:
Data Structures using C - 7.19
for (i = 1, f = front; i < = count; i++)
{
printf(“%d\n”, q[f]);
f = (f + 1) % QUE_SIZE;
}
So, the complete function to display the contents of queue is shown below:
Example 7.9: Function to display the contents of circular queue
void display() void display(int front, int q[], int count)
{ {
int i, f; int i, f;
if ( count == 0 ) if ( count == 0 )
{ {
printf("Q is empty\n"); printf("Q is empty\n");
return; return;
} }
printf("Contents of queue is\n"); printf("Contents of queue is\n");
for ( i = 1, f = front; i <= count; i++) for ( i = 1, f = front; i <= count; i++)
{ {
printf("%d\n",q[f]); printf("%d\n",q[f]);
f = (f + 1) % QUE_SIZE; f = (f + 1) % QUE_SIZE;
} }
} }
#include <stdio.h>
#include <process.h>
#define QUE_SIZE 5
int item, front, rear, count, q[QUE_SIZE];
front = 0;
rear = -1;
count = 0; /* queue is empty */
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d",&choice);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
InsertQ();
break;
case 2:
item = DeleteQ();
if (item == -1)
{
printf(“Queue is empty\n”);
break;
}
printf(“Item deleted = %d\n”, item);
break;
case 3:
display();
break;
default:
exit(0);
}
}
}
Data Structures using C - 7.21
The C program to implement circular queue by passing parameters is shown below:
#include <stdio.h>
#include <process.h>
#define QUE_SIZE 5
/* Include: Example 7.8: Function to delete an item from the front end */
void main()
{
int choice, item, front, rear, count, q[QUE_SIZE];
front = 0;
rear = -1;
count = 0; /* queue is empty */
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
break;
case 2:
item = DeleteQ(&front, q, &count);
7.22 Queues
if (item == -1)
{
printf(“Queue is empty\n”);
break;
}
Now, let us see “What is a double ended queue (or dequeue)? and “What are the
various operations that can be performed on dequeues?”
Definition: A Dequeue is a special type of data structure in which insertions are done
from both ends and deletions are done at both ends. The operations that can be
performed on deques are shown below:
Insert an item from front end
Insert an item from rear end
Operations performed on dqueues Delete an item from front end
Delete an item from rear end
Display the contents of queue
Note: The three operations Insert_Rear, Delete_Front and display operations have
already been discussed in section 7.2. In this section, other two operations i.e., insert
an item at the front end and delete an item from the rear end are discussed.
Data Structures using C - 7.23
7.5.1 Insert at the front end
Now, let us see “How to implement insert front function using arrays (static allocation
technique)?”
Design: Before inserting any element, we should ask the question “Where and how an
item has to be inserted?” If we know the answer for this question we have the
insert front function ready. So, let us consider various situations shown in figure
below:
Case 1: Queue empty: When queue is empty, an item can be inserted at the front end
first by incrementing r by 1 and then insert an item.
-1 0 1 2 3 4 -1 0 1 2 3 4
10 20 30 40 10 10
r f f, r
Before insert After insert
(a) (b)
Case 2: Some items are deleted: Consider the following situation where 10, 20 and
30 were inserted earlier and 10 and 20 have been deleted from the front end. Now,
there is only one item in queue. Here, an item can be inserted by decrementing the
front index f by 1 and then inserting an item at that position as shown below:
0 1 2 3 4 0 1 2 3 4
10 20 30 40 10 20 30 40
f, r f r
After deleting 2 items After inserting 20
(a) (b)
7.24 Queues
The equivalent code for this can be written as shown below:
/* Insert at the front end if possible: Case 2 (Fig a)*/
if ( f != 0 )
{
q[--f] = item;
return;
}
Case 3: Some items are inserted (not deleted): Consider the following situation
where 10, 20 and 30 were inserted into queue.
0 1 2 3 4
10 20 30 40
f r
queue after inserting 10, 20, 30
Now, Observe that, in the above situation it is not possible to insert an any item at the
front end and we should display the appropriate message. This is achieved using the
following statement:
printf(“Front insertion not possible\n”);
The complete C function to insert an item at the front end is shown below:
Example 7.13: Function to insert an item at the front end (by passing parameters)
void Insert_Front(int item, int q[], int *front, int *rear)
{
if ( *front == 0 && *rear == -1 ) /* Case 1: Insert when Q empty */
{
q[++(*rear)] = item;
return;
}
if ( *front != 0 ) /* Case 2: Insert when items are present */
{
q[--(*front)] = item;
return;
}
printf("Front insertion not possible\n");
} /* Case 3: Insertion not possible at front end */
Now, let us see “How to implement delete rear operation using arrays (static
allocation technique)?” Consider the following situation where 3 items are already
inserted into queue and one item is deleted.
0 1 2 3 4 0 1 2 3 4
10 20 30 40 10 20 40
f r f r
After inserting 3 items After deleting from rear
(a) (b)
Design: Item has to be deleted from rear end. This can be achieved by accessing the
rear element q[r] as shown below:
printf(“Item deleted = %d\n”, q[r]); /* Access and print rear item */
and then decrementing r by one as shown below:
r = r – 1; /* Update position of rear item */
7.26 Queues
The above two statements can also be written using single statement as shown below:
Observe that as each item is deleted, the index variable r is decremented so that it
always contains the position of last item (see figure). Finally, when the queue is
empty the value of f will be greater than r. Once f is greater than r, it is not possible
to delete any item because queue is empty. This condition is called underflow of
queue. Hence, the above statement has to be executed only if queue is not empty and
the code to delete an item from rear end of queue can be written as shown below:
Example 7.14: Function to delete an item from the rear end (Using global variables)
int Delete_Rear()
{
int item;
if (front > rear) front = 0, rear = -1; /* Reset to initial state of empty queue */
return item;
}
The above function can be written by passing the parameters as shown below:
Example 7.15: Function delete rear (replace f by front, r by rear (passing parameters)
return item;
}
Data Structures using C - 7.27
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
Insert_Front();
break;
7.28 Queues
case 2:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
Insert_Rear();
break;
case 3:
item = Delete_Front();
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted from front = %d\n”, item);
break;
case 4:
item = Delete_Rear();
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted from rear = %d\n”, item);
break;
case 5:
Display();
break;
default:
exit(0);
}
}
}
#include <stdio.h>
#include <process.h>
#define QUE_SIZE 5
/* Include: Example 7.1: Function to insert an item at the rear end */
Data Structures using C - 7.29
/* Include: Example 7.2: Function to delete an item at the front end */
/* Include: Example 7.13: Function to insert an item at the front end */
/* Include: Example 7.15: Function to delete an item at the rear end */
/* Include: Example 7.3: To display contents of queue */
void main()
{
int choice, item, front, rear, q[10];
front = 0;
rear = -1;
for (;;)
{
printf("1:Insert_front 2:Insert_rear\n");
printf("3:Delete_front 4:Delete_rear\n");
printf("5:Display 6:Exit\n");
printf("Enter the choice\n");
scanf("%d",&choice);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
Insert_Front(item, q, &front, &rear);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
Insert_Rear(item, &rear, q);
break;
case 3:
item = Delete_Front(&front, &rear, q);
7.30 Queues
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted from front = %d\n”, item);
break;
case 4:
item = Delete_Rear(q, &front, &rear);
if (item == -1)
printf(“Queue is empty\n”);
else
printf(“Item deleted from rear = %d\n”, item);
break;
case 5:
Display(front, rear, q);
break;
default:
exit(0);
}
}
}
Now, let us see “How to implement priority queues?” There are various methods of
implementing priority queues using arrays.
It is left as an exercise to the reader to implement this. Now, let us implement priority
queues using another technique.
Design 2: The second technique is to insert the items based on the priority. In this
technique, we assume the item to be inserted itself denotes the priority. So, the items
with least value can be considered as the items with highest priority and items with
highest value can be considered as the items with least priority. So, to implement
priority queue, we insert the elements into queue in such a way that they are always
ordered in increasing order. With this technique the highest priority elements are at
the front end of the queue and lowest priority elements are at the rear end of queue.
Hence, while deleting an item, always delete from the front end so that highest
priority element is deleted first. The function to insert an item at the appropriate place
is shown below:
Note: To insert an element into appropriate place so that elements are arranged in
ascending order, we use the insertion sort technique.
7.32 Queues
Example 7.18: Function to insert an item at the correct place in priority queue
Note: To implement priority queue, insert an item such that items are arranged in
ascending order. But, always delete an item from the front end. The functions
delete_front() and display() remains unaltered. The complete program to implement
priority queue is shown below:
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
Insert_Item(item, q, &rear);
break;
case 2:
item = Delete_Front(&front, &rear, q);
if (item == -1)
printf (“Queue is empty\n”);
else
printf(“Item deleted = %d\n”, item);
break;
case 3:
Display(, front, rear, q);
break;
default:
exit(0);
}
}
}
7.34 Queues
Note: We can input in any order. But, the items are inserted at the appropriate
position and are arranged in ascending order. The item at 0th position is having
highest priority; the item at 1st position is having next highest priority and so on.
Finally, the item at the end of the queue is having the least priority. So, if we remove
from front, an item with highest priority is removed.
Note: The priority queue need not have the numbers or characters. They can represent
complex structures and elements can be ordered based on some common property. In
this sense, a stack can be considered as a priority queue. If we take the time as an
ordering on the elements, an element that is inserted first has to spend more time in
the stack whereas the element which is inserted recently will spend less time in the
stack. This is because stack is a last in first out data structure. So, while deleting, the
element that has spent less time is removed first.
Even an ordinary queue can also be considered as a priority queue where the
priority is based on the order in which they are inserted. Here, the first element
inserted into queue is the first element to go out of the queue. So, an item that is
inserted first can be considered as the item with highest priority and so it is removed
first from the queue.
Multiple queues can be easily implemented using array of linked list (see section
8.9.4).
Data Structures using C - 7.35
7.9 Circular Queue using Dynamic Arrays
Now, let us see “What is the disadvantage of using arrays to implement queue
operations?” The disadvantage of using arrays to implement queue are shown below:
If arrays are used to implement queues, then maximum size of the queue
(QUE_SIZE) should be known during compilation.
Once the size of the queue is fixed during compilation, it is not possible to alter
the size of the queue during execution. This disadvantage we can overcome using
dynamic arrays.
Now, the various operations that can be performed on circular queue using dynamic
arrays can be implemented as shown below:
Now, let us see “How to insert an element into a circular queue using dynamic
array?” Assume q is pointer to an integer, two index variables front and rear are
initialized to 0 and -1 respectively. Another variable count is initialized to 0 which
indicate queue is empty. Initial size of the queue denoted by SIZE is 1.
if (count == SIZE )
{
printf(“Q Full: update size, insert item\n ”);
SIZE++;
Case 1: The front index is less than the rear index: Consider the following circular
queue with five elements whose capacity SIZE is 5:
3 2
40 30 q 10 20 30 40 50
0 1 2 3 4
rear 4 50 20 1
10 front rear
front 0
After increasing SIZE by 1 using realloc(), the contents of circular queue is shown
below:
q 10 20 30 40 50
0 1 2 3 4 5
front rear
Now, an item can be inserted as usual into circular queue in usual manner by
incrementing rear by 1, insert the item and then update count by 1.
Case 2: The front index is greater than the rear index: Consider the following
circular queue with five elements whose capacity SIZE is 5:
front
3 2
40 30 q 60 70 30 40 50
0 1 2 3 4
4 50 70 1 rear
60 rear front
0
After increasing SIZE by 1 using realloc(), the contents of circular queue is shown
below:
q 60 70 30 40 50
0 1 2 3 4 5
rear front
Data Structures using C - 7.37
Now, to get the proper circular queue configuration, slide the elements 30, 40, 50 in
above queue, to the right by one position using the following statements so that queue
looks as shown below:
QUE_SIZE = 6
q 60 70 30 40 50
q[5] = q[4]
q[4] = q[3] 0 1 2 3 4 5
q[3] = q[2] rear front
if (count == SIZE )
{
printf(“Q Full: update size, insert item\n ”);
SIZE++;
q = (int *) realloc(q, SIZE*sizeof(int) );
void InsertQ()
{
int i;
if (count == QUE_SIZE)
{
printf("Q Full: update size, item insert\n");
QUE_SIZE++;
The complete C program to implement circular queue using dynamic arrays by using
global variables is shown below:
#include <stdio.h>
#include <stdlib.h>
Data Structures using C - 7.39
int QUE_SIZE = 1; // Global variable
if (item == -1)
{
printf(“Queue is empty\n”);
break;
}
printf(“Item deleted = %d\n”, item);
break;
7.40 Queues
case 3:
display();
break;
default:
exit(0);
}
}
}
The function written in example 7.20 ccan be written by passing parameters as shown
below:
Example 7.22: Function to delete an item from the front end of circular queue
void InsertQ(int item, int *front, int *rear, int *q, int *count)
{
int i;
if (*count == QUE_SIZE)
{
printf(“q Full:update size,insert item\n”);
QUE_SIZE ++;
q = (int *) realloc (q, QUE_SIZE * sizeof(int) );
if (*front > *rear)
{
for (i = QUE_SIZE – 2; i >= *front; i--)
{
q[i+1] = q[i];
}
(*front)++;
}
}
*rear = (*rear + 1) % QUE_SIZE;
q[*rear] = item;
(*count)++;
}
Data Structures using C - 7.41
#include <stdio.h>
#include <stdlib.h>
/* Include: Example 7.8: Function to delete an item from the front end */
void main()
{
int choice, item, front, rear, count, *q;
front = 0;
rear = -1;
count = 0; /* queue is empty */
for (;;)
{
printf("1:Insert 2:Delete\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
break;
7.42 Queues
case 2:
item = DeleteQ(&front, q, &count);
if (item == -1)
{
printf(“Queue is empty\n”);
break;
}
1. What is a queue? What are the various operations that can be performed on
queues?
4. What is the disadvantage of an ordinary queue? How you can overcome this
disadvantage?
7. What is a double ended queue or deque? What are the operations that can be
performed on double-ended queues?
12. A Circular queue the size of which is 5 has 3 elements 10,40,25, where F = 2 and
R = 4. After inserting 50,60, what is the value of F and R? Trying to insert an
element 30 at this stage what will happen? Delete 2 elements from the queue and
insert 100. Show the sequence of steps with necessary diagrams with the value of
F and R.
15. Explain how elements can be arranged in ascending order in priority queue while
inserting? After arranging them in ascending order, what other functions are
necessary to implement priority queues?
7.44 Queues
16. Show the contents of circular queue after performing each of the following
operations
a) Empty queue
b) Insert 10
c) Insert 20 and 30
d) Insert 40 and 50
e) Insert 60
f) Delete two items
g) Insert 60 and 70
h) Insert 80
Chapter 8: Linked Lists
What are we studying in this chapter?
Definition, Representation of linked lists in Memory
Linked list operations: Traversing, Searching, Insertion, Deletion.
Linked Stacks and Queues
Doubly Linked lists
Circular linked lists
header linked lists.
Applications of Linked lists, Polynomials, Sparse matrix representation.
Memory allocation; Garbage Collection.
Programming Examples
8.1 Introduction
In the previous chapters, we have seen various types of linear data structures such as
stacks, queues and their representations and implementations using sequential
allocation technique i.e., using arrays. Let us see, “What are various advantages of
using arrays?” The advantages of using arrays in implementing various data structures
are shown below:
Data accessing is faster: Using arrays, the data can be accessed very efficiently
just by specifying the array name and the corresponding index. For example, we
can access ith item in the array A by specifying A[i]. The time taken to access a[0]
is same as the time taken to access a[10000].
Simple: Arrays are simple to understand and use.
Now, let us see “What are the disadvantages of arrays?” Even though arrays are very
useful in implementing various data structures, there are some disadvantages:
The size of the array is fixed: The size of the array is fixed during compilation
time only.
Insertion and deletion operations involving arrays is tedious job: Consider an
array consisting of 200 elements. If item has to be inserted at 1st position, all 200
elements must be moved to their next immediate positions, making room for the
new item. Then item has to be inserted at the 1st position. Similarly, deleting an
item at a given position consumes time.
The above disadvantages can be overcome using linked lists.
8.2 Linked Lists
Definition: A linked list is a data structure which is collection of zero or more nodes
where each node is connected to one or more nodes. If each node in the list has only
one link, it is called singly linked list. If it has two links one containing the address of
the next node and other link containing the address of the previous node it is called
doubly linked list. Each node in the singly list has two fields namely:
info – This field is used to store the data or information to be manipulated
link – This field contains address of the next node.
The pictorial representation of a singly linked list where each node is connected to the
next node is shown below:
first
20 30 10 60 \0
The above list consists of four nodes with info fields containing the data items 20, 30,
10 and 60.
Note: Given the address of a node, we can easily obtain addresses of subsequent
nodes using link fields. Thus, the adjacency between the nodes is maintained by
means of links.
Analogy: A linked list can be compared to train having zero or more coaches.
The people sitting inside each coach represent the data part of the linked list
The connection between the coaches using iron links represent the address field of
linked list. The link field contains address of the next node.
As we can move from one coach to other coach, we can go from one node to other
node.
As coaches can be attached or detached easily, in linked list also a node can be
inserted or a node can be deleted.
Now, let us see “What are the different types of linked list?” The linked lists are
classified as shown below:
Data Structures using C - 8.3
Definition: A singly linked list is a collection of zero or more nodes where each node
has two or more fields but only one link field which contains address of the next
node. Each node in the list can be accessed using the link field which contains address
of the next node.
For example, a singly linked list consisting of the items 50, 20, 45, 10 and 80 is
shown below:
2004 1020 5012 1008 6016
50 20 45 10 80 \0
info link
Note: The variable first contains address of the first node. The link
first field of the last node contains \0 (NULL) indicating it is the last node.
Note: Observing the addresses of nodes i.e., 2004, 1020, 5012, 1008 and 6016, we
know that they are physically far apart (they are not adjacent. One node starts at
2004, other starts at 1020 and so on). But, using link field we can obtain each node
in the list in the order specified and hence we say they are logically adjacent.
8.4 Linked Lists
The link field of each node contains address of the next node. For example,
consider the second node. The link field of second node contains 5012 which is
address of the next node. It is denoted by an arrow originating at the link field and
ending at next node.
The variable first contains the address of the first node of the list. So, the entire list
can be identified by the name first.
Using the variable first, we can access any node in the list.
The link field of a last node contains \0 (null).
Definition: An empty linked list is a pointer variable which contains NULL (\0). This
indicates that linked list does not exist. For example, an empty list can be written as
shown below:
first \0
The nodes in a linked list are self-referential structures. So, let us see “What are self-
referential structures? How to define a node in C language?”
struct node
{
int info;
struct node *link;
};
Observe from the above structure definition that a node has two fields:
info – It is an integer field which contains the information.
link – It is a pointer field and hence it should contain the address. The link field
contains the address of the next node in the list whose type is same as the link
field.
Now, let us see “How to define a self-referential structure?” The self-referential
structure can be defined as shown below:
Data Structures using C - 8.5
Example 8.1: Structure definition of a node along with declaration
Note: Both the declarations are same, since NODE and struct node * can be
interchangeably used because of above typedef.
8.1.5 Create an empty list
Now, let us see “How to create an empty list?” An empty list is can be created by
assigning NULL to a self-referential structure variable.
For example, consider the following code:
struct node
{
int info;
struct node *link
};
x = (data_type *) malloc(size);
On successful allocation, the function returns the address of first byte of allocated
memory. Since address is returned, the return type is a void pointer. By type
casting appropriately we can use it to point to any desired data type.
It x is not NULL it means, a node is successfully created and we can return the node
using the statement:
return x;
Example 8.2: C Function to get a new node from the availability list
NODE getnode()
{
NODE x;
Data Structures using C - 8.7
x = ( NODE ) malloc(sizeof(struct node)); /* allocate the memory space */
Note: The return type of getnode() is NODE and it is defined in previous section.
Step 1: get a node: A node which is identified by variable first with two fields: info
and link fields can be created using getnode() function (described in previous section)
as shown below:
first
*first
first = getnode();
info link
Note: Observe from the above figure that:
Using the variable first we can access the address of the node
Using *first we can access entire contents of node
Using (*first).info or first->info, we can access the data stored in info field
Using (*first).link or first->link, we can access the link field.
Step 2: Store the item: The data item 10 can be stored in the info field using the
following statement:
first
*first
first->info = 10;
or 10
(*first).info = 10; info link
After executing the above statement, the data item 10 is stored in info field of first as
shown in above figure.
8.8 Linked Lists
Step 3: Store NULL character: After creating the above node, if we do not want link
field to contain address of any other node, we can store ‘\0’ (NULL) in link field as
shown below:
first->link = NULL; first
*first
or
(*first).link = NULL; 10 \0
info link
Thus, using above three steps we can create a node with specified data item as
shown in above figure.
In this section, let us see “How to insert a node at the front end of the list?”
Data Structures using C - 8.9
Design: To design the function easily, let us consider a linked list with 4 nodes. Here,
pointer variable first contains address of the first node of the list as shown below:
first
20 30 10 60 \0
Let us try to insert the item 50 at the front end of the above list. The sequence of steps
to be followed are shown below:
1 temp = getnode()
Step 2: Copy the item 50 into info field of temp using the following statement:
2 temp->info = item;
temp first
2 50 20 30 10 60 \0
Step 3: Copy address of the first node of the list stored in pointer variable first into
link field of temp using the statement:
3 temp->link = first;
Step 4: Now, a node temp has been inserted and observe from the figure that temp is
the first node. Let us always return address of the first node using the statement:
4 return temp;
The complete function is shown below:
Example 8.3: C Function to insert an item at the front end of the list
The above function should be called in the calling function as shown below:
After executing the above statement, the variable first in the calling function contains
address of the first of the list as shown below:
first first
50 20 30 10 60 \0
The variable first shown using dotted lines contains the address of the first node of the
list before insertion whereas the variable first shown using thick lines contains the
address of the first node of the list after insertion.
Thus, if the function insert_front() is called n times, we have a list with n nodes.
Note: Thus, repeatedly inserting an element at the front end of the list we can create a
linked list.
Consider the following singly linked list where the variable first contains address of
the first node of the list.
first
1004 1008 1048 1026
20 30 10 60 \0
We need to find the address of the last node. For this to happen, we have to start from
the first node. Instead of updating first, let us use another variable say cur. To start
with, the variable cur should contain the address of the first node. This is achieved
using the statement:
cur = first;
8.12 Linked Lists
first
1004 1008 1048 1026
20 30 10 60 \0
cur link
Note: What is first->info and cur->info? It is 20. What is first->link and cur->link? It
is 1008 (which is the address of the next node)
Let us update cur so that it contains address of the next node. This can be done by
copying cur->link (i.e, 1008) to cur using the following code:
cur = cur->link;
After executing the above instruction, cur contains address of the next node as shown
below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
If we execute the instruction “cur = cur->link” again, cur contains address of the next
node as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
If we execute the instruction “cur = cur->link” again, cur contains address of the next
node as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
Note: Observe that, now the variable cur contains address of the last node of the list.
Data Structures using C - 8.13
Now the question is “When we say that given node is the last node of the list?” If link
field of a node contains NULL, then that node is the last node of that list. In the above
list, cur->link is NULL. So, cur is the last node of the above list.
So, if cur contains the address of the first node, keep updating cur as long as link field
of cur is not NULL as shown in figure below:
while ( cur->link != NULL)
{
cur = cur->link; cur = cur->link
}
Now, given the variable first which contains address of the first node, we can find
address of the last node as shown below:
8.2.4 How to find last node and last but one in the list
Consider the following list:
first
1004 1008 1048 1026
20 30 10 60 \0
prev \0 cur
If cur contains address of the first node of the list, what is the previous node? The
previous node does not exit and so we say prev is NULL. The code for the above
situation can be written as shown below:
prev = NULL;
cur = first;
Now, we know how to update cur to get the address of the last node. The code for this
can be written as shown below: (see previous section)
8.14 Linked Lists
Now, before updating cur inside the loop using “cur = cur->link”, let us copy cur to
prev. The above code can be modifies as shown below:
After the loop, the variable cur contains address of the last node and the variable prev
contains address of the prev node. Now, the complete code to find the address of the
last node and last but one node is shown below:
The given linked list after executing the above code can be written as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
prev cur
Note: The variable first contains address of the first node, the variable cur contains
address of the last node, the variable prev contains address of last but one node.
Case 1: List is empty: If the list is empty, it is not possible to display the contents of
the list. The code for this can be written as shown below:
if (first == NULL)
{
printf(“List is empty\n”);
return;
}
Case 2: List is exiting: Consider the linked list shown below with four nodes where
the variable first contains address of the first node of the list.
first
1004 1008 1048 1026
20 30 10 60 \0
Initialization: Use another variable say cur to point to the beginning of the list. This
can be done by copying first to cur as shown below:
cur = first;
first
1004 1008 1048 1026
20 30 10 60 \0
cur
Display: Now, display info field of cur node and update cur as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
Now, display info field of cur node and update cur as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
Now, display info field of cur node and update cur as shown below:
first
1004 1008 1048 1026
20 30 10 60 \0
cur
Now, display info field of cur node and update cur as shown below:
Finally, observe that cur is NULL indicating no more nodes are there to display.
Observe that the following statements are repeatedly executed:
printf(“%d “, cur->info);
cur = cur->link;
Data Structures using C - 8.17
as long as cur is not NULL. Once cur is NULL, the displaying of all the nodes is
over. The code for this can be written as shown below:
Now, the complete C function to display the contents of the list is shown below:
Now, let us see “How to delete a node from the front end of the list?”
Design: A node from the front end of the list can be deleted by considering various
cases as shown below:
8.18 Linked Lists
Case 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display “List is empty” and return NULL. The code for this can
be written as shown below:
if (first == NULL)
{
printf (“List is empty\n”);
return NULL;
}
Case 2: List is exiting: Consider the list with five nodes where the variable first
contains address of the first node of the list.
first
50 20 45 10 80 \0
Given the address of the first node of the list, we need to know the address of the
second node of the list. This is because, after deleting the first node, the second node
will be the first node of the list. The sequences of steps to be followed while deleting
an element are shown below:
Step 1: Use a pointer variable temp and store the address of the first node of the list as
shown in figure below. This is achieved using the following statement:
1 temp = first;
Now, the list can be written as shown below where temp and first points to first node.
first temp
1
50 20 45 10 80 \0
Step 2: Update the pointer temp so that the variable temp contains address of the
second node. This is achieved using the statement:
2 temp = temp->link;
Now, the list can be written as shown below where temp points to second node.
Data Structures using C - 8.19
first temp
50 20 45 10 80 \0
Step 3: Note that variable first points to first node of the list and temp points to the
second node of the list. Now, display info field of the node first node to be deleted
and de-allocate the memory as shown below:
50 20 45 10 80 \0
3
Step 4: Once the node first is deleted, observe that node temp is the first node (see
above list). So, return temp as the first node to the calling function using the
statement:
return temp;
Now, the complete algorithm in C can be written as shown below:
Example 8.5: C function to delete an item from the front end of the list
NODE delete_front(NODE first)
{
NODE temp;
if ( first == NULL ) /* Check for empty list */
{
printf("List is empty cannot delete\n");
return NULL; // We can replace NULL with first also
}
temp = first; /* Retain address of the node to be deleted */
8.20 Linked Lists
50 50 \0
info link info link info link
Step 2: If list is empty, the above node can be returned as the first node of the list.
This can be done using the statement:
if (first == NULL) return temp;
Step 3: If the list is existing, we have to insert temp at the end of the list
first temp
20 30 10 60 \0 50 \0
Existing list Node to be inserted
To insert at the end, we have to find address of the last node. The code to find the
address of the last node can be written (for details see section 8.2.3) as shown below:
Data Structures using C - 8.21
/* Find the address of the last node */
cur = first;
while (cur->link != NULL)
{
cur = cur->link;
}
The pictorial representation of the list after executing the above code can be written as
shown below:
20 30 10 60 \0 50 \0
Step 4: Insert node at the end: Looking at the above list, we can easily insert temp
at the end of cur. This is achieved by copying temp to cur->link as shown below:
cur->link = temp; 1
The list obtained after executing the above code can be written as shown below:
Step 5: Observe from the above list that first contains address of the first node of the
list. So, we return first
return first; 2
Now, the complete list after inserting at the rear end is shown below:
first 2
20 30 10 60 50 \0
8.22 Linked Lists
The complete C function to insert an item at the rear end of the list is below:
Example 8.6: Function to insert an item at the rear end of the list
NODE insert_rear(int item, NODE first)
{
NODE temp; /* Points to newly created node */
NODE cur; /* To hold the address of the last node */
Now, let us see “How to delete a node from the rear end of the list?”
Design: A node from the rear end of the list can be deleted by considering various
cases as shown below:
Case 1: List is empty: If the list is empty, it is not possible to delete the contents of
the list. In such case we display appropriate message and return. The code for this can
be written as shown below:
Data Structures using C - 8.23
if (first == NULL)
{
printf(“List is empty\n”);
return NULL;
}
Case 2: List contains only one node: Consider a list with single node shown in
figure below:
first \0 first
empty list
10 \0
Before deleting After deleting
Note: If link field of first contains NULL, it indicates that there is only one node.
If only one node is present, it can be deleted using free() function. Then, we return
NULL indicating list is empty. The code for this case is shown below.
first
50 20 30 10 60 \0
Step 1: To delete the last node, we should know the address of the last node and last
but one node. For this reason, we use two pointer variables: cur and prev. Initially,
cur points to the first node and prev points to \0 (null). This is achieved using the
following statements:
prev = NULL;
cur = first;
8.24 Linked Lists
first cur
\0 50 20 30 10 60 \0
prev
Step 2: Now, update cur and prev so that cur contains address of the last node and
prev contains address of the last but one node. This can be achieved by using the
following statements (see section 8.2.4 for detailed explanation).
After executing the above loop, the variable cur contains address of the last node and
prev contains address of last but one node as shown in figure below:
Step 3: To delete the last node pointed to by cur, the function free() is used as shown
below:
printf(“Item deleted = %d\n”, cur->info); // Item deleted = 60
3
free(cur);
After executing the above statements, the last node is deleted and the list is shown
below:
Step 4: Once the last node is deleted (the node shown using cross symbol in above
figure), the node pointed to by prev should be the last node. This is achieved by
copying NULL to link field of prev as shown below:
4 prev->link = NULL; /* Node pointed to by prev is the last node */
Data Structures using C - 8.25
After executing the above statement, the list shown in previous step can be written as
shown below:
first prev
50 20 30 10 \0
4
Step 5: Finally return address of the first node.
Now, the linked list as seen from the calling function is shown below:
5 first
50 20 30 10 \0
The complete C function to delete a node from the rear end of the list is shown below:
Example 8.7: Function to delete an item from the rear end of the list
/* Obtain address of the last node and just previous to that */ Case 3
prev = NULL;
cur = first;
while( cur->link != NULL )
{
prev = cur;
cur = cur->link;
}
printf(“The item deleted is %d\n”,cur->info);
free(cur); /* delete the last node */
prev->link = NULL; /* Make last but one node as the last node */
return first; /* return address of the first node */
}
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *link;
};
Data Structures using C - 8.27
typedef struct node* NODE;
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.3: Function to insert an item at the front end of the list*/
/* Include: Example 8.4: Function to display the contents of the list */
/* Include: Example 8.5: Function to delete an item from the front end of the list */
void main()
{
NODE first;
int choice, item;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2:Delete_Front\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_front (item, first);
break;
case 2:
first = delete_front(first);
break;
case 3:
display(first);
break;
default:
exit(0);
}
}
}
8.28 Linked Lists
Note: In the function main(), we can use insert_rear() and delete_rear() functions and
still implement stack operations using linked list.
We know that queue is a special type of data structure where elements are inserted at
one end and elements are deleted at the other end. It is a FIFO data structure as we
have already seen in previous chapter. That is, if an element is inserted at front end,
an element has to be deleted from rear end. If an element is inserted at rear end, an
element has to be deleted from the front end. Thus, a queue can be implemented
using the following functions:
insert_front()
insert_rear()
delete_rear() OR
delete_front()
display()
display()
The C program to implement queues using singly linked list with the help of
insert_rear(), delete_front() and display() functions is below:
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.4: Function to display the contents of the list */
/* Include: Example 8.5: Function to delete an item from the front end of the list */
/* Include: Example 8.6: Function to insert an item at the rear end of the list*/
void main()
{
NODE first;
int choice, item;
Data Structures using C - 8.29
first = NULL;
for (;;)
{
printf("1:Insert_Front 2:Delete_Front\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_rear (item, first);
break;
case 2:
first = delete_front(first);
break;
case 3:
display(first);
break;
default:
exit(0);
}
}
}
Note: In the above function main(), we can use insert_front() and delete_rear()
functions and still implement queue operations using linked list.
Note: Let us see “What is enqueuer operation? What is dequeuer operation?”
Inserting an element into queue is called enqueue operation and deleting an element
from queue is called dequeue operation. But, there is a difference between dequeue
operation and dequeue. Simply dequeue means is a double ended queue.
8.5 Double ended queue using linked lists
We know that double ended queue is a special type of data structure where elements
can be inserted either from front end or rear end and elements can be deleted from
front end and rear end. That is, insertions and deletions are possible from both ends.
8.30 Linked Lists
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.3: Function to insert an item at the front end of the list*/
/* Include: Example 8.4: Function to display the contents of the list */
/* Include: Example 8.5: Function to delete an item from the front end of the list */
/* Include: Example 8.6: Function to insert an item at the rear end of the list*/
/* Include: Example 8.7: Function to delete an item from the rear end of the list*/
void main()
{
NODE first;
int choice, item;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2:Insert_Rear\n");
printf(“3:Delete_Front 4:Delete_Rear\n”);
printf("5:Display 6:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
Data Structures using C - 8.31
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_front (item, first);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_rear (item, first);
break;
case 3:
first = delete_front(first);
break;
case 4:
first = delete_rear(first);
break;
case 5:
display(first);
break;
default:
exit(0);
}
}
}
Now, let us see “How to find the length of linked list or number of nodes present in
the linked list?” The solution is very simple and straight forward technique. Now, tell
me what the following code does?
8.32 Linked Lists
cur = first;
while (cur != NULL) /* As long as no end of list */
{
printf(“%d\n”, cur->info); /* Display the info of a node */
cur = cur->link; /* point cur to the next node */
}
The above code displays the info field of each node in the list. But, we are supposed
to find the number of nodes in the list. This can be achieved very easily by replacing
printf statement by count++ and initialize count to 0 before the loop. Now, the
complete function can be written as shown below:
When control comes out of the loop, if cur is still NULL then key not found.
Otherwise, key found. The partial code to check for an item in the list can be written
as shown below:
cur = first;
while (cur != NULL) /* As long as no end of list */
{
if (key == cur->info) break; /* key found */
cur = cur->link; /* point cur to the next node */
}
if (cur == NULL) /* If end of list, key not found */
{
printf(“Search is unsuccessful\n”);
return;
}
printf(“Serach is successful\n”); /* Yes, key found */
If item not found, instead of displaying the message “Item not found” return
NULL.
Now, let us see “How to delete a node whose info field is specified?”
Design: To design the function easily, let us follow the sequence of steps shown
below:
Step 1: Check for empty list. The equivalent code can be written as shown below:
if (first == NULL)
{
printf(“List empty, Search fails\n”);
return NULL;
}
Step 2: Check for key in the first node in the list. If key matches with the first node of
the list, the node at the front end of the list has to be deleted as shown below:
first
key = 50 50 20 30 10 60 \0
Now, save the address of first node into cur using the statement:
cur = first; 1
and update first to point to the next node using the statement:
2 first
50 20 30 10 60 \0
1
cur
8.36 Linked Lists
Now, delete the first node pointed to by cur. This can be done using the statement:
free (cur); 3
return first; 4
After executing the above two statements, the list can be pictorially represented as
shown below:
4 first
50 20 30 10 60 \0
3
cur
Thus, if key matches with first node of the list, the first node can be deleted and the
corresponding code can be written as shown below:
if ( key == first->info )
{
cur = first; /* Save the address of the first node */
first = first->link; /* Point first to second node in the list */
free(cur); /* Delete the first node */
return first; /* Return second node as the first node */
}
Step 3: Search for the key: Control comes to step 3, if key is not present in the first
node of the list. It may be present or may not be present in subsequent nodes. So, we
have to search for key in the list. The search code given in previous section is repeated
for convenience.
cur = first;
while (cur != NULL) /* As long as no end of list */
{
if (key == cur->info) break; /* If found go out of the loop */
prev = cur; /* Save the address of cur node */
cur = cur->link; /* point cur to the next node */
}
Data Structures using C - 8.37
if (cur == NULL) /* If end of list, key not found */
{
printf(“Search is unsuccessful\n”);
return first;
}
printf(“Serach is successful\n”); /* Yes, key found */
Note: Observe that the statement:
prev = cur;
is added inside the while loop, just before updating cur to the next node. This is
necessary to delete the cur node.
Now, if 10 is the item to be deleted, after executing the above statements, we get the
message “Search is successful” and at this instant, the linked list looks as shown
below:
Observe that cur contains address of the node to be deleted and prev contains address
of predecessor of the node to be deleted.
Step 4: Delete the node: Before deleting the cur node we need to adjust the pointers.
That is, copy “cur->link” to “prev->link” as shown below:
prev->link = cur->link; 5
8.38 Linked Lists
After executing the above statement, prev->link points to node containing 60 and
thus, cur node is isolated from the list and the linked list can be pictorially represented
as shown below:
Now, the node cur can be removed using free() function as shown below:
free (cur); 6
Step 5: Finally return the address of the first node. The code for this is shown below.
return first; 7
Definition: Concatenation of two lists is nothing but joining the second list at the end
of the first list.
Design: Concatenation of two lists is possible if two lists exist. If one of the lists is
empty, return the address of the first node of the non-empty list as shown below:
Once, control comes out of second if-statement it means both lists are existing. The
pictorial representation of both lists are shown below:
first second
50 20 30 10 \0 50 20 \0
If both lists exist, obtain the address of the last node of the first list. The code to
obtain address of the last node of the first list is shown below:
cur = first;
while (cur->link != NULL) /*Traverse till the end */
cur = cur->link;
Once control comes out of the loop, pointer cur contains address of the last node of
the first list (see the figure below).
Now, attach address of the first node of the second list identified by second to cur
node using the following code:
cur->link = second;
The resulting list after executing the above statement is shown below:
Now, first contains the address of the first node of the concatenated list and return this
node to the calling function using the statement:
return first;
Data Structures using C - 8.41
The corresponding C function is shown below:
cur->link = sec; /* Attach first node of second list to end of first list */
return first; /* Return the first node of the concatenated list */
}
first
10 20 30 40 50 \0
If we display the above list, the output is: 10 20 30 40 50. After reversing the list, the
list is shown below:
first
10 \0 20 30 40 50
Design: Assume that the given list is divided into two sub lists such that the first list
with two nodes is reversed and the second list with three nodes is yet to be reversed as
shown below:
cur first
10 \0 20 30 40 50 \0
With this situation in mind, let us try to reverse the list to be processed.
Step 1: Obtain the address of the second node of the list to be reversed. This can be
achieved using:
temp = first->link;
After executing the above statement, the list can be written as shown below:
10 \0 20 30 40 50 \0
Step 2: Attach the first node of the list to be reversed, to the front end of the partially
reversed list. This can be achieved using:
first->link = cur;
After executing the above statement, the list can be written as shown below:
cur first temp
10 \0 20 30 40 50 \0
Data Structures using C - 8.43
Step 3: The pointer variable cur always should contain address of the first node of the
reversed list and first should contain address of first node of the list to be reversed.
This can be achieved using the following statements:
cur = first;
first = temp;
After executing the above statement, the list can be written as shown below:
cur first
10 \0 20 30 40 50 \0
The complete C function to insert an item into an ordered linked list is below:
Definition: A linked list in which the items are stored in some specific order is an
ordered linked list. The elements in an ordered linked list can be in ascending or
descending order or based on key information. A key is one or more fields within a
structure that are used to identify the data. For example, an ordered linked list shown
below:
first
10 20 30 40 \0
first
10 20 30 40 \0
Here, the variable first contains address of the first node of the list.
Data Structures using C - 8.45
After inserting any item into the above list, the order of list should be maintained i.e.,
the items in the list should be arranged in ascending/descending order only. We
should see that the variable first always contains the address of the first node of the
list. To insert an item into the list, the sequence of steps to be followed are:
Step 1: Create a node to be inserted and insert the item using the following
statements:
temp = getnode();
temp->info = item;
temp->link = NULL;
Step 2: If the node temp is inserted into the list for the first time (i.e., into the empty
llist), then return temp itself as the first node using the following code.
if (first == NULL) return temp;
When control comes out of the above if statement, it means that the list is already
existing and node temp has to be inserted at the appropriate place so that after
inserting temp, the list must be arranged in ascending order. This results in following
two cases: item has to be inserted at the front end or in the middle.
Step 3: Inserting an item at the front end of the list: This case occurs, when the node
to be inserted is less than the first node of the list as shown below:
temp first
5 \0 10 20 30 40 \0
item = 5
Suppose, item is 5. Can you tell me where it has to be inserted? It has to be inserted at
the front end of the list so that the list is arranged in ascending order. This can be
done by copying first into link field of temp as shown below:
temp->link = first.
5 10 20 30 40 \0
8.46 Linked Lists
After inserting as shown in figure, we return temp since temp is the new first node of
the list as shown below:
return temp;
The above two statement should be executed if and only if item is less than the first
node of the list. Now, the code can be written as shown below:
Step 4: Inserting somewhere in the middle of the list: Suppose item 35 has to be
inserted into the list shown below:
first
item = 35 10 20 30 40 \0
5 \0
temp
To create an ordered list, node temp has to be inserted in between 30 and 40. So, let
us find the appropriate place so that the list remains in order. The code to find the
appropriate place can be written as shown below:
prev = NULL;
cur = first;
while (cur != NULL && item > cur->info)
{
prev = cur;
cur = cur=>link
}
Data Structures using C - 8.47
Now, after executing the above code, the linked list can be written as shown below:
5 \0
temp
Step 5: Insert at middle/end: By looking at the above list, observe that temp has to
be inserted between prev and cur. The corresponding code is shown below:
After executing the above statements, the linked list can be written as shown below:
temp
Step 6: Always we return the address of the first node as shown below:
Now, the complete function to create an ordered linked list can be written as shown
below:
struct node
{
int info;
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.3: Function to insert an item at the front end of the list*/
/* Include: Example 8.4: Function to display the contents of the list */
/* Include: Example 8.11: Function to find the length of the linked list */
/* Include: Example 8.12: Function to search for key item in the list */
/* Include: Example 8.14: Function to delete a node whose info field is specified */
/* Include: Example 8.16: Function to reverse a list */
void main()
{
NODE first;
int choice, item, key;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2:Length of list\n");
printf(“3:Serach 4:Delete item\n”);
printf("5:Reverse 6:Display\n");
printf(“7: Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_front (item, first);
break;
8.50 Linked Lists
case 2:
printf("Length of list = %d\n”, Length(first));
break;
case 3:
printf(“Enter the item to be searched\n”);
scanf(“%d”, &key);
search(key, first);
break;
case 4:
printf(“Enter the item to be deleted\n”);
scanf(“%d”, &key);
first = delete_info(key, first);
break;
case 5:
first = reverse(first);
break;
case 6:
display(first);
break;
default:
exit(0);
}
}
}
Definition: A header node in a linked list is a special node whose link field always
contains address of the first node of the list. Using header node, any node in the list
can be accessed. The info field of header node usually does not contain any
information and such a node does not represent an item in the list. Sometimes, useful
information such as, number of nodes in the list can be stored in the info field. If the
list is empty, then link field of header node contains \0 (null).
Data Structures using C - 8.51
Example 1: An empty list using header is represented as shown below:
Head
\0
If we display the above list, list is empty.
Example 2: The non-empty list using header node is represented as shown below:
Head
20 30 10 40 50 \0
Example 3: The non-empty list using header node with valid information in header
node is represented as shown below:
Head
5 20 30 10 40 50 \0
In the above list, the info field of the header node contains the number of nodes in the
list.
Now, let us see “What are the advantages of a list with a header node?” The
advantages of a header node are shown below:
Simplifies Insertion and deletion operations
Avoid the usage of various cases such as “if only one node is present what to do”
Designing of program will be very simple
Circular lists with header node are frequently used instead of ordinary linked lists
because many operations can be easily implemented
8.52 Linked Lists
20 30 10 40 \0
An item 50 can be inserted at the front end of the list using the following sequence of
steps:
Step 1: Create a node: This can be done by using getnode() function and copying item
into the info field as shown below:
temp = getnode()
1
temp->info = item;
The list after executing above statements can be written as shown below:
temp
1
Head 50
20 30 10 40 \0
Step 2: Obtain the address of the first node: The first node can be accessed using
“head->link” and copy this into first as shown below:
2 first = head->link;
Now, the variable first contains address of the first node and the linked list can be
written as shown below:
temp
Head 50 first
2
20 30 10 40 \0
Data Structures using C - 8.53
Step 3: Make the new node created as the first node: This can be done by inserting
the node temp between head and first. This can be achieved using the following code:
3 head->link = temp;
4 temp->link = first;
The list obtained after executing the above statements can be written as shown below:
temp
Head 50 first
3 4
20 30 10 40 \0
Step 4: Finally return the address of the header node using the following statement:
return head;
The list as seen from the calling function can be written as shown below:
Head
50 20 30 10 40 \0
4 temp->link = first;
return head; /* Return the header node */
}
8.54 Linked Lists
Now, let us see “How to insert an item at the rear end?” Consider the circular list
with a header node shown in figure below.
Head
20 30 10 40 \0
An item 50 can be inserted at the rear end of the list using the following sequence of
steps:
Step 1: Create a node: This can be done by using getnode() function and copying item
into the info field as shown below:
temp = getnode()
1 temp->info = item;
temp->link = NULL
The list after executing above statements can be written as shown below:
Head temp
1
20 30 10 40 \0 50 \0
Step 2: Obtain the address of the first node: The first node can be accessed using
“head->link” and copy this into cur as shown below:
2 cur = head->link;
Now, the variable cur contains address of the first node and the linked list can be
written as shown below:
Head cur temp
2
20 30 10 40 \0 50 \0
Data Structures using C - 8.55
Step 4: Obtain the address of the last node: This can be done by updating cur
repeatedly to contain address of the next node till cur->link is not NULL. This can be
achieved using the following code:
Once control comes out of the loop, the variable cur contains address of the last node.
Now, the linked list obtained can be written as shown below:
Head cur temp
3
20 30 10 40 \0 50 \0
Step 4: Make the new node created as the last node: This can be done by inserting the
node temp after cur. This can be done by copying temp to link field of cur node as
shown below:
4 cur->link = temp;
The list obtained after executing the above statements can be written as shown below:
Step 4: Finally return the address of the header node using the following statement:
return head;
The list as seen from the calling function can be written as shown below:
Head
20 30 10 40 50 \0
Now, the complete code to insert an element at the rear end of the list can be written
as shown below:
8.56 Linked Lists
return head;
}
if (head->link == NULL)
{
1 printf(“List is empty\n”);
return head;
}
When control comes out of the above if-statement, it means list is already existing
and it can be pictorially represented as shown below:
Data Structures using C - 8.57
Head
20 30 10 40 50 \0
Step 2: Get the address of the first node and its previous:
The link field of node head is the address of the first node and head is the previous
node. The first node and its predecessor can be obtained using the following
statements:
prev = head; // Previous to first node
2
cur = head->link; // Obtain the first node
After executing the above statements, the linked list looks as shown below:
20 30 10 40 50 \0
Step 3: Get the address of last node and its previous node: Keep updating prev and
cur as long as link field of cur is not NULL. The code corresponding to this can be
written as shown below:
After executing the above part of the code, the linked list can be written as shown
below:
20 30 10 40 50 \0
8.58 Linked Lists
Step 4: Make last but one node as the last node: This is achieved by copying NULL
to link field of prev node. This can be done using the statement:
4 prev->link = NULL;
After executing the above statement, the last node cur is isolated and the linked list
looks as shown below:
20 30 10 40 \0 50 \0
4
Note that prev is the last node of the above list.
Step 5: Delete the last node: First, display the info field of last node and then delete it.
This can be done using the statement:
After executing the above statements, the linked list looks as shown below:
20 30 10 40 \0 50 \0
Step 6: Return the header node: This can be done using the statement:
return head;
The final list as seen from the calling function is shown below:
Head
20 30 10 40 \0
Thus, the last node of the list can be removed. The code to delete an element from the
rear end can be written as shown below:
Data Structures using C - 8.59
Example 8.21: Function to delete an element from the rear end
if (head->link == NULL)
{
printf(“List is empty\n”);
1
return head;
}
while (cur->link != NULL) // Obtain address of last node and last but one */
{
3 prev = cur;
cur = cur->link;
}
4 prev->link = NULL; // Make last but one node as the last node
return head;
}
Step 1: Check for an empty list: An empty list is pictorially represented as shown
below:
Head
1
\0
The code for the above case can be written as shown below:
8.60 Linked Lists
if (head->link == NULL)
{
printf(“List is empty\n”);
1
return head;
}
When control comes out of the above if-statement, it means list is already existing
and it can be pictorially represented as shown below:
Head
20 30 10 40 50 \0
Step 2: Get the address of the first node: The link field of node head is the address of
the first node and it can be obtained using the following statement:
Head first
2
20 30 10 40 50 \0
Step 3: Get the address of the second node: The link field of node first is the address
of the second node and it can be obtained using the following statement:
After executing the above instruction, the linked list looks as shown below:
20 30 10 40 50 \0
Step 5: Make second node as the first node: This can be done by copying second into
link field of head using the statement:
Data Structures using C - 8.61
20 30 10 40 50 \0
4
Step 6: Remove the first node: The node first can be removed using the following
code:
printf(“Item deleted = %d\n”, first->info); // Item deleted = 20
5 free(first);
After executing the above instruction, the linked list looks as shown below:
Step 7: Return the header node: This can be done using the following code:
return head;
Now, the linked list as seen from the calling function is shown below:
Head
30 10 40 50 \0
The final function to delete an element from the front end can be written as shown
below:
if (head->link == NULL)
{
1 printf(“List is empty\n”);
return head;
}
return head;
}
The double ended queue can be implemented using singly linked list with a header
node using five functions: insert_front(), insert_rear(), delete_front(), delete_rear()
and display() functions. The C program to implement deque is shown below:
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.19: Function to insert at the front end of the list */
/* Include: Example 8.20: Function to insert at the front end of the list */
/* Include: Example 8.21: Function to delete an element from the rear end */
/* Include: Example 8.22: Function to delete an element from the front end */
/* Include: Example 8.4: C function to display the contents of linked list */
void main()
{
NODE head;
int choice, item;
head = getnode();
head->link = NULL;
for (;;)
{
printf("1:Insert_Front 2:Insert_Rear\n");
printf(“3:Delete_Front 4:Delete_Rear\n”);
printf("5:Display 6:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
head = insert_front (item, head);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
head = insert_rear (item, head);
break;
case 3:
head = delete_front (item, head);
break;
8.64 Linked Lists
case 4:
head = delete_rear (item, head);
break;
case 5:
display(head->link);
break;
default:
exit(0);
}
}
}
Now, let us “Design, Develop and Implement a menu driven Program in C for the
following operations on Singly Linked List (SLL) of Student Data with the fields:
USN, Name, Branch, Sem, PhNo”
1. Create a SLL of N Students Data by using front insertion.
2. Display the status of SLL and count the number of nodes in it
3. Perform Insertion and Deletion at End of SLL
4. Perform Insertion and Deletion at Front of SLL
5. Demonstrate how this SLL can be used as STACK and QUEUE
6. Exit
typedef struct
{
int usn;
char name[20];
char branch[20];
int sem;
char phone[20];
struct
} STUDENT;
The design details are given in section 8.2. Creating a list is nothing but repeated
insertion of items into the list. The insert front function (example 8.3) and insert rear
function (example 8.6) are modified to incorporate the above details.
Data Structures using C - 8.65
The C function to insert a node at the front end can be written as shown below:
Example 8.24: C Function to insert an item at the front end of the list
if (first == NULL) return temp; /* Insert a node for the first time */
The C function to insert a node at the rear end can be written as shown below:
Example 8.25: C Function to insert various items at the rear end of the list
if (first == NULL) return temp; /* Insert a node for the first time */
8.66 Linked Lists
The function to delete an element from the front end given in example 8.5 can be
modified as shown below:
Example 8.26: C function to delete an item from the front end of the list
NODE delete_front(NODE first)
{
NODE temp;
if ( first == NULL ) /* Check for empty list */
{
printf("student list is empty cannot delete\n");
return NULL; // We can replace NULL with first also
}
temp = first; /* Retain address of the node to be deleted */
temp = temp->link; /* Obtain address of the second node */
printf("Delete student record: USN = %d\n", first->usn);
free(first); /* delete the front node */
Example 8.27: Function to delete an item from the rear end of the list
prev->link = NULL; /* Make last but one node as the last node */
Example 8.27: Program to implement dequeues using singly linked list (student)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int usn;
char name[20];
char branch[20];
int sem;
char phone[20];
} STUDENT;
Data Structures using C - 8.69
struct node
{
int usn;
char name[20];
char branch[20];
int sem;
char phone[20];
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.24: Function to insert an item at the front end of the list */
/* Include: Example 8.25: Function to insert an item at the rear end of the list */
/* Include: Example 8.26: Function to delete an item from the front end of the list */
/* Include: Example 8.27: Function to delete an item from the rear end of the list */
/* Include: Example 8.28: Function to display the contents of linked list */
void main()
{
NODE first;
int choice;
STUDENT item;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2: Insert_Rear\n");
printf("3:Delete_Front 4: Delete_Rear\n");
printf(“5:Display 6: Exit\n”);
switch(choice)
{
case 1:
printf("usn :"); scanf(“%d”,&item.usn);
printf("name :"); scanf(“%s”,item.name);
printf("branch :"); scanf(“%s”,item.branch);
printf("semester :"); scanf(“%d”,&item.sem);
printf("phone :"); scanf(“%s”,item.phone);
first = insert_front (item, first);
break;
case 2:
printf("usn :"); scanf(“%d”,&item.usn);
printf("name :"); scanf(“%s”,item.name);
printf("branch :"); scanf(“%s”,item.branch);
printf("semester :"); scanf(“%d”,&item.sem);
printf("phone :"); scanf(“%s”,item.phone);
first = insert_rear (item, first);
break;
case 3:
first = delete_front(first);
break;
case 4:
first = delete_rear(first);
break;
case 5:
display(first);
break;
default:
exit(0);
}
}
}
8.9 Other operations on linked list
In this section, let us see how to implement the following operations on linked lists:
Remove duplicate elements in the list
Union and intersection operation on two lists
Multiple stacks
Multiple queues
Data Structures using C - 8.71
The first two operations require that an item has to be searched in the list. If item is
present in the list, we return 1, otherwise, we return 0. The code already designed in
section 8.6.2 (i.e., example 8.12) can be modified only in returning the values using
the return statement. The modified code can be written as shown below:
Now, let us see, how the above function is helpful in designing the solution for next
three operations discussed.
8.9.1 Remove duplicate elements
In this section, let us remove duplicate elements in the given linked list. This can be
done as shown below:
Step 1: Empty list: If list is empty return NULL indicating the resulting list is also
empty. The code for this case can be written as shown below:
if (first == NULL) return NULL;
Step 2: List is existing: If the list is existing then duplicate elements have to be
removed. This can be explained by considering the following list:
40 20 30 40 30 40 \0
8.72 Linked Lists
Instead of using the variable first, let us use another variable a which points to the
beginning of the list as shown below:
first
a
40 20 30 40 30 40 \0
The above situation can be achieved by executing the following statement:
a = first;
Each item in the above list has to be accessed and a new list without duplicates has to
be created. Each item in the list can be accessed using the following code:
a = first;
while (a != NULL)
{
a->info; // access each item in the list
a = a->link; // get the address of the next node
}
Now, as you access using a->info, search for a->info in the second list b. If not
found, insert a->info at the end of the list b. Now, the above partial code can be
written as shown below:
a = first;
while (a != NULL)
{
/* search for a->info in list b */
found = search(a->info, b) ;
/* If a->info not found in b, insert a->info at the end of list b */
if (found == 0) b = insert_rear(a->info, b);
a = a->link;
}
Step 3: Return final list: This can be done by returning b which contains address of
the list. This can be done using the statement:
return b;
Now, the final code can be written as shown below:
Data Structures using C - 8.73
Example 8.29: Function to remove duplicate elements from the list
first
40 20 30 50 \0
second
60 20 30 50 40 70 \0
8.74 Linked Lists
40 20 30 50 60 70 \0
The above activity can be achieved using the following three steps:
Step 1: Add all elements of first list to the resultant list: This can be done by
accessing each item from first list and adding at the end of the resultant list identified
by variable third. The pictorial representation of three lists now can be written as
shown below:
first
First list
40 20 30 50 \0
second
Second list
60 20 30 50 40 70 \0
third
Resultant list
40 20 30 50 \0
The code for this activity can be written as shown below:
40 20 30 50 \0
second
Second list
60 20 30 50 40 70 \0
third
Resultant list (Union of two lists)
40 20 30 50 60 70 \0
This can be done using the statements shown in dotted lines in example 8.29 in
previous section. But, replace the variable first by second. The code is re-written as
shown below:
/* search for each item of 2nd list in 3rd list. If not found add into 3rd list */
a = second; /* existing list */
while (a != NULL) /* Traverse entire second list */
{
flag = search(a->info, third); /* search for item in new list b */
/* if not found insert into resultant list*/
if (flag == 0) third = insert_rear(a->info, third);
a = a->link; /* access next item in the list a */
}
return third; /* return the resultant result */
}
The intersection of two linked lists is much easier. To start with initialize resultant list
to NULL. This can be done as shown below:
third = NULL;
Traverse the first list till the end. During this process search for each item of the first
list in the second list. If the element is present then add the element to the resultant
Data Structures using C - 8.77
list. This can be done by changing the if-statement inside the loop given in example
8.29 shown using dotted lines (section 8.9.1). In the if-statement check for flag to 1.
The code is re-written with modification as shown below:
Now, given the two lists we can get the third list which is intersection of first two
lists using the above code as shown below:
first
First list
20 10 40 80 \0
0
second
Second list
60 20 30 50 40 70 \0
third
Resultant list (Intersection of two lists)
20 40 \0
Now, the functions to remove duplicate elements, to find union and intersection of
two lists can be invoked using function main which can be written as shown below:
Example 8.32: Program to remove duplicate items, find union and intersection of 2 lists
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
typedef struct node* NODE;
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.4: Function to display the contents of the list */
/* Include: Example 8.6: Function to insert an item at the rear end of the list*/
/* Include: Example 8.28: Function to search for key in the list*/
Data Structures using C - 8.79
void main()
{
NODE first, second, third;
int choice, item, i, n;
for (;;)
{
printf("1:Create first list 2:Create second list\n”);
printf(“3:Remov duplicates of list 1 4:Remov duplicates of list 2\n”);
printf("5:Union of two lists 6:Intersection of two lists\n");
printf("7:Exit\n”);
first = NULL;
break;
case 2:
printf("Enter the number of nodes in second list\n");
scanf("%d", &n);
second = NULL;
8.80 Linked Lists
case 3:
printf(“The first list before removing duplicates\n”);
display(first);
first = remove_duplicate (first);
printf(“The first list after removing duplicates\n”);
display(first);
break;
case 4:
printf(“The second list before removing duplicates\n”);
display(second);
second = remove_duplicate (second);
printf(“The second list after removing duplicates\n”);
display(second);
break;
case 5:
printf(“The first list \n”);
display(first);
printf(“The second list \n”);
display(second);
third = union_of_list (first, second);
printf(“The union of two lists \n”);
display(third);
break;
case 6:
printf(“The first list \n”);
display(first);
Data Structures using C - 8.81
printf(“The second list \n”);
display(second);
third = intersection_of_list (first, second);
printf(“The intersection of two lists \n”);
display(third);
break;
default:
exit(0);
}
}
}
Now, let us “Implement multiple queues using array of queues with the help of singly
linked lists”
Design: Multiple queues can be implemented using array of linked lists as shown
below:
Let us fix the number of queues to 5. This can be done using the statement:
#define MAX_QUES 5
A queue can be implemented using linked list as shown in previous section. The
structure definition for a node can be written as:
struct node
{
int info;
struct node *link;
};
NODE a[MAX_QUES];
8.82 Linked Lists
Initially, all queues are empty. This can be done by assigning NULL to each of the
queue stored in the array as shown below:
To insert an item into ith queue: Invoke the function insert_rear() (example 8.6)
using item and a[i] as shown below:
To delete an item from ith queue: Invoke the function delete_front() (example 8.5)
using the following statement:
To display contents of ith queue: Invoke the function display() (example 8.4) using
the following statement:
display(a[i]);
Example 8.33: Program to implement multiple queues using singly linked lists
Data Structures using C - 8.83
#include <stdio.h>
#include <stdlib.h>
#define MAX_QUES 5
struct node
{
int info;
struct node *link;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.6: Function to insert an item at the rear end of the list */
/* Include: Example 8.5: Function to delete an item from the front end of the list */
/* Include: Example 8.4: Function to display the contents of the list */
void main()
{
NODE a[MAX_QUES];
int choice, item, i;
for (;;)
{
printf(“Enter queue number: 0, 1, 2, 3, 4 : “);
scanf(“%d”, &i);
switch ( choice )
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
a[i] = insert_rear(item, a[i]);
break;
case 2:
a[i] = delete_front(a[i]);
break;
case 3:
display(a[i]);
break;
default:
exit(0);
}
}
}
Now, let us “Implement multiple stacks using array of singly linked lists”
Let us fix the number of stacks to 5. This can be done using the statement:
#define MAX_STACKS 5
A stack can be implemented using linked list as shown in previous section. The
structure definition for a node can be written as:
struct node
{
int info;
struct node *link;
};
NODE a[MAX_STACKS];
Initially, all stacks are empty. This can be done by assigning NULL to each of the
stack stored in the array as shown below:
To insert an item into ith stack: Invoke the function insert_front() (example 8.3)
using item and a[i] as shown below:
a[i] = insert_rear(item, a[i]);
To delete an item from ith stack: Invoke the function delete_front() (example 8.5)
using the following statement:
a[i] = delete_front( a[i] );
To display contents of ith stack: Invoke the function display() (example 8.4) using
the following statement:
display(a[i]);
Now, the complete algorithm in C can be written as shown below:
8.86 Linked Lists
Example 8.34: Program for multiple stacks using array of singly linked lists
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
typedef struct node* NODE;
#define MAX_STACKS 5
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 8.3: Function to insert an item at the front end of the list*/
/* Include: Example 8.5: Function to delete an item from the front end of the list */
/* Include: Example 8.4: Function to display the contents of the list */
void main()
{
NODE a[MAX_STACKS];
int choice, item, i;
for (;;)
{
printf(“Enter stack number: 0, 1, 2, 3, 4 : “);
scanf(“%d”, &i);
case 3:
display(a[i]);
break;
default:
exit(0);
}
}
}
Definition: The process of reserving the memory space to store the data is called
memory allocation. The memory space is allocated for variables in the following
situations:
For all global variables and static variables, memory is allocated during program
startup. The BSS segment in the memory contains all global variables and static
variables that are initialized to zero or do not have explicit initialization in source
code. When the program is terminated, the memory is freed and returned to the
free space available.
For all local variables, memory is allocated as and when the control enters into
the function. The memory space for all local variables is reserved on the stack
area in memory. Before control goes out of the function, the memory reserved for
all local variables is freed and returned to the free space available.
Memory can be allocated during run time (execution time) dynamically using
memory allocation functions such as malloc(), calloc() and realloc() in C
8.88 Linked Lists
language. The memory space for all dynamically variables is reserved in the heap
area. Once, memory space is used and no longer required, we can free the
memory space using free() function in C language.
When the memory is allocated dynamically and when it is not freed when not
required, this memory space cannot be used by any program. Thus memory space is
wasted. If too much memory space is wasted, soon the system may crash. So, it is
necessary to identify the unused space and return this unused space to the operating
system so that it can be allocated other programs which require memory space to store
the data. For this reason, we require garbage collector. Now, let us see “What is
garbage collector?”
EXERCISES
22. What are the shortcomings of a singly linked list? How these are eliminated in
circular list?
23. Write a C function to find the length of the linked list
24. Write a C function to search for key item in the list
25. Write a C function to delete a node whose info field is specified
26. Write a C function to create an ordered linked list?
27. What is a header node? What are the advantages of a list with a header node?”
28. How to implement dequeue operations using C functions.
29. Write a C function to remove duplicate elements from the list
30. Write a C function to find union of two lists
31. Write a C function to find intersection of two lists
32. Write a C function to implement multiple queues using array of queues with the
help of singly linked lists
33. Write a C function to implement multiple stacks using array of singly linked lists
34. What is garbage collection? How it is done?
Chapter 9: Circular and doubly linked lists
In a singly linked list that we have discussed earlier, note that link field of last node
contains \0 (null) indicating there are no more nodes from this point onwards.
Now, let us see “What are the disadvantages of singly linked lists?” The various
disadvantages of singly linked lists are listed below:
In a singly linked list, there is only one link field and hence traversing (visiting
each node) is done only in one direction. So, given the address of a node x, only
those nodes which follow x are reachable but, the nodes that precede x are not
reachable.
Note: Since traversing is only in one direction, singly linked lists are also called
one way lists.
To delete a designated node cur, address of the first node of the list should be
provided. This is necessary because, to delete node cur, the predecessor of this
node has to be found. For this, search has to begin from the first node of the list.
These disadvantages can be overcome using special type of list called circular list.
Definition: A circular singly linked list is a singly linked list where the link field of
last node of the list contains address of the first node. The pictorial representation of a
circular list is shown below:
first
20 30 10 60
Now, let us see “What are the advantages of circular lists?” The various advantages of
circular list are shown below:
Every node is accessible from a given node by traversing successively using the
link field
To delete a node cur, the address of the first node is not necessary. Search for the
predecessor of node cur, can be initiated from cur itself.
9.2 Circular and doubly linked Lists
Certain operations on circular lists such as concatenation and splitting of lists etc.
will be more efficient.
Note: Since link field of last node contains address of first node, there is no special
indication to find which is the last node. So, some care has to be taken during
processing. In circular list, it is very important to detect the end of the list.
Since the list is circular, any node can be considered as first node and its predecessor
is considered as last node. The following two conventions can be used:
Approach 1: A pointer variable first can be used to designate the starting point of
the list. Using this approach, to get the address of the last node, the entire list has
to be traversed from the first node. The pictorial representation of the circular list
using this approach is shown below:
first
20 30 10 60
20 30 10 60
Note: Observe from the above figure that, the variable last contains address of the last
node. Using link field of last node, i.e, last->link, we can get the address of the first
node.
In all our solutions to various problems, let us use the second approach. Using this
approach, now, let us see “How to create an empty list?” An empty list is can be
created by assigning NULL to a self-referential structure variable.
For example, consider the following code:
Data Structures using C - 9.3
struct node
{
int info;
struct node *link
};
if (last == NULL)
{
printf(“List is empty\n”);
return;
}
last
60
Using the above list, “Can you tell when we say there is only one node in circular
list?” Observe that if there is only one node, then link field of last is last. The code to
check for only one node can be written as:
A circular list can be used as a stack or a queue. To implement these data structures,
we require the following functions:
insert_front – To insert an element at the front end of the list
insert_rear – To insert an element at the rear end of the list
delete_front – To delete an element from the front end of the list
delete_rear - To delete an element from the rear end of the list
display – To display the contents of the list
20 30 10 60
Step 1: Obtain a node using getnode() function and copy item into info field as shown
below:
temp = getnode();
1
temp->info = item;
After executing the above statements, assuming the item is 50, the linked list can be
written as shown below:
temp last
1 50 20 30 10 60
Step 2: Copy the address of the first node (i.e., last->link) into link field of temp.
This is possible if list is existing (i.e., last is not NULL). The code for this can be
written as:
if (last != NULL)
temp->link = last->link;
Data Structures using C - 9.5
If last is NULL, make temp itself as the last node. Now, the above code can be
modified as shown below:
if ( last != NULL) // List is existing
2 temp->link = last->link;
else
temp = last;
After executing the above code, the list can be written as shown below:
temp last
50 20 30 10 60
2
Step 3: Make temp as the first node. This is possible by copying temp into link field
of last node as shown below:
3 last->link = temp;
temp last
50 20 30 10 60
Step 4: Finally, we return address of the last node using the statement:
return last;
The list as seen from the calling function is shown below:
last
50 20 30 10 60
9.6 Circular and doubly linked Lists
The C function to insert an item at the front of the circular linked list is shown below:
Example 9.1: Function to insert an item at the front end of the list
Now, let us see “How to insert a node at the rear end of the list?”
Design: To design the function easily, let us consider a list with 4 nodes. Here, the
variable last contains address of the last node of the list.
last
20 30 10 60
Step 1: Obtain a node using getnode() function and copy item into info field as shown
below:
temp = getnode();
1
temp->info = item;
Data Structures using C - 9.7
After executing the above statements, assuming the item is 50, the linked list can be
written as shown below:
last temp
20 30 10 60 50 1
Step 2: Copy the address of the first node (i.e., last->link) into link field of temp.
This is possible if list is existing (i.e., last is not NULL). The code for this can be
written as:
if (last != NULL)
temp->link = last->link;
If last is NULL, make temp itself as the last node. Now, the above code can be
modified as shown below:
After executing the above code, the list can be written as shown below:
last temp
20 30 10 60 50
Step 3: Make temp as the last node. This is possible by copying temp into link field of
last node as shown below:
3 last->link = temp;
last temp
20 30 10 60 50
3
Step 4: Finally, we return address of the last node using the statement:
return temp;
last
20 30 10 60 50
The C function to insert an item at the rear end of the list is shown below:
Example 9.2: Function to insert an item at the rear end of the list
Now, let us see “How to delete a node from the front end of the list?”
Design: When an item is deleted from the list, the various cases to be considered are:
List is empty
List containing one element
List containing more than one element.
Case 1: List is empty: It is not possible to delete an element from the list whenever
list is empty. In such situation, we display the appropriate message as shown below:
Case 2: List with one node: A list with only node can be written as shown below:
last
60 NULL
last
Observe that after deleting the only node in the list, the list will be empty and we have
to return NULL. The code for this can be written as shown below:
Case 3: List with more than one node: Consider the list with more than one node.
last
20 30 10 60
The sequence of steps to be followed to delete an element from the front end of the
list can be written as shown below:
Step 1: Obtain the address of the first node. The code for this can be written as shown
below:
3 first = last->link;
first last
3
20 30 10 60
Step 2: Make second node as first node: This is obtained by copying first->link to
last->link. The code for this case can be written as shown below:
4 last->link = first->link;
Thus, the first node is isolated and second node becomes the first node. The list after
executing above statement is shown below:
first last
20 30 10 60
4
Step 3: Remove the first node: The first node in the list can be removed using free()
function. But, before removing the node, display the appropriate message. The code
for this case can be written as shown below:
Data Structures using C - 9.11
first last
20 30 10 60
5
The final list as seen from the calling function can be written as shown below:
last
30 10 60
Thus, a node at the front end can removed. The complete code to delete an item from
the front end is shown below:
Case 1: List is empty: It is not possible to delete an element from the list whenever
list is empty. In such situation, we display the appropriate message as shown below:
Case 2: List with one node: A list with only node can be written as shown below:
last
60 NULL
last
Observe that after deleting the only node in the list, the list will be empty and we have
to return NULL. The code for this can be written as shown below:
Data Structures using C - 9.13
if (last->link == last) /* There is only one node in the list */
{
printf(“Item deleted = %d\n”, last->info); // Item deleted = 60
2 free(last); // delete the node
return NULL; // return empty list
}
Case 2: List with more than one node: Consider the list with more than one node.
last
20 30 10 60
The sequence of steps to be followed to delete an element from the rear end of the list
can be written as shown below:
Step 1: Obtain the address of the first node. The code for this can be written as shown
below:
3 prev = last->link;
The list after executing above statement is shown below:
prev last
3
20 30 10 60
Step 2: Find the address of the last but one node: This can be done by updating
prev as long as prev->link is not last. The code for this case can be written as shown
below:
while (prev->link != last)
4 {
prev = prev->link;
}
After executing the above loop, the variable prev contains address of the last but one
node as shown below:
9.14 Circular and doubly linked Lists
4 prev last
20 30 10 60
Step3: Make last but one node as the last node: This can be done by copying first
node (that is, last->link) into prev->link. The code for this case can be written as
shown
5 prev->link = last->link;
Thus, the last node is isolated and last but one node becomes the last node. The list
after executing above statement is shown below:
prev last
20 30 10 60
5
Step 4: Remove the last node: The last node in the list can be removed using free()
function. But, before removing the node, display the appropriate message. The code
for this case can be written as shown below:
prev 6 last
20 30 10 60
The new last node prev can be returned using the following statement:
return prev;
last
20 30 10
Thus, a node at the rear end can removed. The complete code to delete an item from
the rear end is shown below:
The C program to implement double ended queue using circular linked list is below:
#include <stdio.h>
#include <stdlib.h>
#incude <alloc.h>
struct node
{
int info;
struct node *link;
};
Data Structures using C - 9.17
typedef struct node* NODE;
/* Include: Example 8.2: Function to get a node from OS */
/* Include: Example 9.1: Function to insert item at front end of the list */
/* Include: Example 9.2: Function to insert item at rear end of the list */
/* Include: Example 9.3: Function to delete an item from the front end */
/* Include: Example 9.4: Function to delete an item from the rear end */
/* Include: Example 9.5: Function to display the contents of the circular queue */
void main()
{
NODE last;
int choice, item;
last = NULL;
for (;;)
{
printf("1:Insert_Front 2:Insert_Rear\n");
printf("3:Delete_Front 4:Delete_Rear\n");
printf("5:Display 6:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
last = insert_front (item, last);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
last = insert_rear (item, last);
break;
case 3:
last = delete_front(last);
break;
case 4:
last = delete_rear(last);
break;
9.18 Circular and doubly linked Lists
case 5:
display(last);
break;
default:
exit(0);
}
}
}
Definition: In a circular list with a header node, the link field of the last node
contains address of the header node and the link field of the header node contains the
address of the first node. Usually the info field of a header node does not contain any
information. Sometimes, some useful information such as number of nodes in the list
can be stored.
So, whenever link field of header node contains address of itself, then we say list is
empty.
Example 2: The non-empty list using header node is represented as shown below:
Head
20 30 10 40 50
Note: In the above list, link field of last node contains the address of header node.
While inserting or deleting elements, we can assume that the list is already present
and we can write the code. The code thus written works for all other cases such as list
empty, a list having only one node etc.
Data Structures using C - 9.19
Now, let us see “What are the various operations that can be performed on circular
singly linked list with header node?”
20 30 10 40
An item 50 can be inserted at the front end of the list using the following sequence of
steps:
Step 1: Create a node: This can be done by using getnode() function and copying item
into the info field as shown below:
temp = getnode()
1
temp->info = item;
The list after executing above statements can be written as shown below:
temp
1
Head 50
20 30 10 40
Step 2: Obtain the address of the first node: The first node can be accessed using
“head->link” and copy this into first as shown below:
2 first = head->link;
Now, the variable first contains address of the first node and the linked list can be
written as shown below:
9.20 Circular and doubly linked Lists
temp
Head 50 first
2
20 30 10 40
Step 3: Make the new node created as the first node: This can be done by inserting
the node temp between head and first. This can be achieved using the following code:
3 head->link = temp;
4 temp->link = first;
The list obtained after executing the above statements can be written as shown below:
temp
Head 50 first
3 4
20 30 10 40
Step 4: Finally return the address of the header node using the following statement:
return head;
The list as seen from the calling function can be written as shown below:
Head
50 20 30 10 40
4 temp->link = first;
return head; /* Return the header node */
}
Now, let us see “How to insert an item at the rear end?” Consider the circular list
with a header node shown in figure below.
Head
20 30 10 40
An item 50 can be inserted at the rear end of the list using the following sequence of
steps:
Step 1: Create a node: This can be done by using getnode() function and copying item
into the info field as shown below:
temp = getnode()
1 temp->info = item;
The list after executing above statements can be written as shown below:
9.22 Circular and doubly linked Lists
Head temp
1
20 30 10 40 50
Step 2: Obtain the address of the first node: The first node can be accessed using
“head->link” and copy this into cur as shown below:
2 cur = head->link;
Now, the variable cur contains address of the first node and the linked list can be
written as shown below:
Head cur temp
2
20 30 10 40 50
Step 3: Obtain the address of the last node: This can be done by updating cur
repeatedly to contain address of the next node till cur->link is not head. This can be
achieved using the following code:
Once control comes out of the loop, the variable cur contains address of the last node.
Now, the linked list obtained can be written as shown below:
20 30 10 40 50
Data Structures using C - 9.23
Step 4: Make the new node created as the last node: This can be done by inserting the
node temp after cur. This can be done by copying temp to link field of cur node as
shown below:
4 cur->link = temp;
The list obtained after executing the above statements can be written as shown below:
Since temp is the last node, the link field of temp should contain address of the header
node head. This can be done using the statement:
5 temp->link = head;
After executing the above instruction, the linked list can be written as shown below:
20 30 10 40 50
Step 5: Finally return the address of the header node using the following statement:
return head;
The list as seen from the calling function can be written as shown below:
Head
20 30 10 40 50
return head;
}
Case 1: List is empty: The pictorial representation of an empty circular list with a
header node and the equivalent code to check for empty list can be written as shown
below:
Head
if (head->link == head)
{
1 printf(“List is empty\n”); 1
return head;
} empty list
Data Structures using C - 9.25
Case 2: List with more than one node: Consider the list with more than one node.
head
10 20 30 40 50
The sequence of steps to be followed to delete an element from the rear end of the list
can be written as shown below:
Step 1: Get the first node and its predecessor. The code for this can be written as
shown below:
cur = head->link; // first node of the list
2
prev = head; // previous to the first node
The list after executing above statements can be written as shown below:
head prev cur
2
10 20 30 40 50
Step 2: Get the address of last node and its previous node. This can be done by
repeatedly updating cur and prev as long as cur->link is not equal to head. The code
for this can be written as shown below:
while (cur->link != head)
{
prev = cur;
3 cur = cur->link;
}
The list obtained after executing the above statements can be written as shown below:
head prev cur
3
10 20 30 40 50
9.26 Circular and doubly linked Lists
Step3: Make last but one node as the last node: This can be done by copying head
into prev->link. The code for this case can be written as shown below:
4 prev->link = head;
Thus, the last node is isolated and last but one node becomes the last node. The list
after executing above statement is shown below:
head prev cur
10 20 30 40 50
Step 4: Remove the last node: The last node in the list can be removed using free()
function. But, before removing the node, display the appropriate message. The code
for this case can be written as shown below:
printf (“The item deleted is %d\n”, cur->info);
5 free(cur);
After executing the above code, the list can be written as shown below:
head prev cur
5
10 20 30 40 50
Step 5: return the header node: This can be done by using the following code:
return head;
The list as seen from the calling function can be written as shown below:
head
10 20 30 40
Data Structures using C - 9.27
Thus, a node at the rear end can be removed. The complete code to delete an item
from the rear end is shown below:
while (cur->link != head) // Obtain last node and its previous node
{
3 prev = cur;
cur = cur->link;
}
return head;
}
Case 1: List is empty: The pictorial representation of an empty circular list with a
header node and the equivalent code to check for empty list can be written as shown
below:
Head
if (head->link == head)
{
1 printf(“List is empty\n”); 1
return head;
} empty list
Case 2: List with more than one node: Consider the list with more than one node.
head
10 20 30 40 50
The sequence of steps to be followed to delete an element from the rear end of the list
can be written as shown below:
Step 1: Get the first node and its successor. The code for this can be written as shown
below:
first = head->link; // first node of the list
2
second = first->link; // second node of the list
The list after executing above statements can be written as shown below:
head first second
2
10 20 30 40 50
Step 2: Make second node as first node: This can be done by copying second into
head->link. The code for this case can be written as shown below:
3 head->link = second;
Thus, the first node is isolated and second node becomes the first node. The list after
executing above statement is shown below:
Data Structures using C - 9.29
10 20 30 40 50
3
Step 4: Remove the first node: The first node in the list can be removed using free()
function. But, before removing the node, display the appropriate message. The code
for this case can be written as shown below:
printf (“The item deleted is %d\n”, first->info);
4 free(first);
After executing the above code, the list can be written as shown below:
head first second
4
10 20 30 40 50
Step 5: return the header node: This can be done by using the following code:
return head;
The list as seen from the calling function can be written as shown below:
head
20 30 40 50
Thus, a node at the front end can be removed. The complete code to delete an item
from the front end is shown below:
return head;
}
Design: The contents of the list can be displayed by considering various cases as
shown below:
Case 1: List is empty: The pictorial representation of an empty circular list with a
header node and the equivalent code to check for empty list can be written as shown
below:
Head
if (head->link == head)
{
1 printf(“List is empty\n”); 1
return;
} empty list
Case 2: List is exiting: Consider a list with three nodes. The variable head always
contains address of the header node. The list starts from head->link onwards. Let
temp is a variable that contains the address of the first node as shown in figure below:
Data Structures using C - 9.31
head temp
20 30 40
Display 20 and update temp using the statement: temp = temp ->link. Now, the list
can be written as shown below:
head temp
20 30 40
Display 30 and update temp using the statement: temp = temp ->link. Now, the list
can be written as shown below:
head temp
20 30 40
Display 40 and update temp using the statement: temp = temp ->link. Now, the list
can be written as shown below:
head
temp
20 30 40
Once temp is equal to head stop displaying and updating temp. It means display info
of temp and update temp as long as temp is not equal to head. Now, the code can be
written as shown below:
9.32 Circular and doubly linked Lists
The program to implement stacks using circular singly linked list is shown below:
Example 9.12: Program for stacks (circular singly linked list with header node)
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *link;
};
Data Structures using C - 9.33
typedef struct node* NODE;
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 9.7: Function to insert at the front end of the list */
/* Include: Example 9.10: Function to delete an item from the front end */
/* Include: Example 9.11: C function to display the contents of linked list */
void main()
{
NODE head;
int choice, item;
head = getnode();
head->link = head;
for (;;)
{
printf("1:Insert_Front 2:Delete_Front\n");
printf("3:Display 4:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
head = insert_front (item, head);
break;
case 2:
head = delete_front(head);
break;
case 3:
display(head);
break;
default:
exit(0);
}
}
}
9.34 Circular and doubly linked Lists
Now, let us see “What are the disadvantages of singly linked lists whether circular or
with or without the header?” Some of the disadvantages of singly linked lists/circular
lists are:
Using singly linked lists and circular lists it is not possible to traverse the list
backwards. Two-way traversing is not possible.
Insertion/Deletion to the left of a designated node x is difficult. This requires
finding the predecessor of x which takes more time.
first
\0 10 20 30 40 \0
Using such lists, it is possible to traverse the list in forward and backward directions.
Such a list where each node has two links is also called a two-way list.
In this section, let us see “How to insert a node at the front end of the list?”
Step 1: Create a node: This can be done using getnode() function and copying item 5
as shown below:
1 temp
temp = getnode()
temp->info = item;
1 \0 5 \0
temp->link = temp->rlink = NULL;
Data Structures using C - 9.35
Step 2: Insert into empty list: If the list is empty, the above created node itself
should be returned as the first node. The code for this case can be written as shown
below:
2 if (first == NULL) return temp; first \0 2 temp
The list as seen from the calling function can be written as: \0 5 \0
Step 2: Insert into existing list: Consider the following list and see how a node temp
can be inserted at the front end:
temp first
\0 5 \0 \0 10 20 30 40 \0
To insert temp at the front end of the list, copy first into rlink of temp and copy temp
into llink of first node using the following code:
temp->rlink = first;
3
first->llink = temp;
The list after executing the above statements can be written as shown below:
temp first
\0 5 10 20 30 40 \0
3
Step 3: Return the first node: After modification, always we have to return the
address of the first node. In the above list, temp is the first node and it can be returned
using the following statement:
return temp;
Now, the list as seen from calling function can be written as shown below:
first
\0 5 10 20 30 40 \0
9.36 Circular and doubly linked Lists
The C function to insert at the front end can be written as shown below:
Example 9.13: C Function to insert an item at the front end of the list
2 if (first == NULL) return temp; /* Insert a node for the first time */
In this section, let us see “How to insert a node at the rear end of the list?”
Step 1: Create a node: This can be done using getnode() function and copying item
50 as shown below:
1 temp
temp = getnode()
1 temp->info = item; \0 50 \0
temp->link = temp->rlink = NULL;
Step 2: Insert into empty list: If the list is empty, the above created node itself
should be returned as the first node. The code for this case can be written as shown
below:
2 if (first == NULL) return temp; first \0 2 temp
The list as seen from the calling function can be written as: \0 50 \0
Data Structures using C - 9.37
Step 2: Find the address of the last node: Consider the following list and see how a
node temp can be inserted at the rear end:
first temp
\0 10 20 30 40 \0 \0 50 \0
Let the variable first always contain address of the first node. Let use another variable
cur to point to the first node. This can be done using the following statement:
3 cur = first;
\0 10 20 30 40 \0 \0 50 \0
Now, keep updating cur to point to the next node as long as rlink field of cur is not
NULL. This can be done using the following code:
Now, after executing the above while loop, the variable cur contains address of the
last node of the list as shown below:
first cur temp
4
\0 10 20 30 40 \0 \0 50 \0
Step 3: Insert node at the end: Using the above list, can you tell me how to insert
the node at the end. It is very clear from above figure that we have to manipulate two
links:
9.38 Circular and doubly linked Lists
rlink of cur should contain address of temp. This can be done using the statement:
5 cur->rlink = temp;
llink of temp should contain address of cur. This can be done using the statement:
6 temp->llink = cur;
After executing the above two statements, the linked list can be written as shown
below:
first cur temp
5
\0 10 20 30 40 50 \0
6
Step 4: Return address of the first node: This can be done using the statement:
return first;
Now, the list as seen from calling function can be written as shown below:
first
\0 10 20 30 40 50 \0
The C function to insert a node at the rear end can be written as shown below:
Example 9.14: C Function to insert an item at the rear end of the list
2 if (first == NULL) return temp; /* Insert a node for the first time */
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return NULL. The
code for this can be written as shown below:
if (first == NULL) first
{
1 printf (“List is empty\n”); \0 1
return NULL; empty list
}
Step 2: Delete if there is only one node: A list having one node can be written as
shown below:
first
\0 10 \0
9.40 Circular and doubly linked Lists
The code to delete the above node can be written as shown below:
if (first->rlink == NULL)
{ first
printf(“Item deleted = %d\n”, first->info); 2
2 free(first);
\0 10 \0
return NULL;
}
When control comes out of the above if-statement, it means that the list has more than
one node and the list can be pictorially represented as shown below:
first
\0 10 20 30 40 50 \0
Now, the first node in the above list can be deleted as shown below:
Step 3: Obtain the address of the second node: The address of the second node can
be obtained using the following statement:
3 second = first->rlink;
first second
3
\0 10 20 30 40 50 \0
Step 4:Make second node as first node: It is achieved by copying NULL to left link
of second node. It can be done using the following code:
4 second->llink = NULL;
Now, first node is isolated and the resulting linked list is shown below:
Data Structures using C - 9.41
first second
\0 10 \0 20 30 40 50 \0
4
Step 5: Delete the front node: It is achieved using free() function. The code can be
written as shown below:
After executing the above code, the list can be written as shown below:
first second
5
\0 10 \0 20 30 40 50 \0
Step 6: Return address of the new first node: Note that the variable second now
contains address of the new first node and it can be returned using the following
statement:
return second;
Now, the linked list as seen from the calling function can be written as shown below:
first
\0 20 30 40 50 \0
Now, the complete C function to delete the first node can be written as shown below:
Example 9.15: C function to delete an item from the front end of the list
NODE delete_front(NODE first)
{
NODE second;
9.42 Circular and doubly linked Lists
return NULL;
}
return second;
}
Now, let us see “How to delete a node from the rear end of the list?”
Design: A node from the rear end of the list can be deleted by considering various
cases as shown below:
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return NULL. The
code for this can be written as shown below:
if (first == NULL) first
{
1 printf (“List is empty\n”); \0 1
return NULL; empty list
}
Data Structures using C - 9.43
Step 2: Delete if there is only one node: A list having one node can be written as
shown below:
first
\0 10 \0
The code to delete the above node can be written as shown below:
if (first->rlink == NULL)
{ first
printf(“Item deleted = %d\n”, first->info); 2
2 free(first);
\0 10 \0
return NULL;
}
When control comes out of the above if-statement, it means that the list has more than
one node and the list can be pictorially represented as shown below:
first
\0 10 20 30 40 \0
To delete the last node, we should know the address of the last node and last but one
node. Let us find the address of last node and last but one node as shown in coming
steps.
Step 3: Obtain the address of first node and its predecessor: This can be done using
two pointer variables: cur and prev. Initially, cur points to the first node and prev
points to \0 (null). This is achieved using the following statements:
prev = NULL;
3 cur = first;
Now, the linked list looks as shown below:
first cur
prev
\0 \0 10 20 30 40 \0
3
9.44 Circular and doubly linked Lists
Step 4: Find the address of last node and last but one node: This can be done by
updating cur and prev till cur contains address of the last node and prev contains
address of the last but one node. This can be achieved by using the following
statements (see section 8.2.4 for detailed explanation).
while( cur->link != NULL )
{
4 prev = cur;
cur = cur->link;
}
After executing the above loop, the variable cur contains address of the last node and
prev contains address of last but one node as shown in figure below:
\0 10 20 30 40 \0
Step 5: The last node pointed to by cur can be deleted as shown below:
Step 4: Once the last node is deleted (the node shown using cross symbol in above
figure), the node pointed to by prev should be the last node. This is achieved by
copying NULL to rlink field of prev as shown below:
first prev
\0 10 20 30 \0 6
Now, the linked list as seen from the calling function is shown below:
first
\0 10 20 30 \0
The complete C function to delete a node from the rear end of the list is shown below:
Example 9.16: Function to delete an item from the rear end of the list
6 prev->rlink = NULL; /* Make last but one node as the last node */
return first; /* return address of the first node */
}
The C program to implement dequeue with the help of above functions can be written
as shown below:
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *llink;
struct node *rlink;
};
typedef struct node* NODE;
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 9.13: Function to insert an item at the front end of the list */
/* Include: Example 9.14: Function to insert an item at the rear end of the list */
/* Include: Example 9.15: Function to delete an item from the front end of the list */
/* Include: Example 9.16: Function to delete an item from the rear end of the list */
/* Include: Example 9.17: Function to display the contents of linked list */
void main()
{
NODE first;
int choice, item;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2: Insert_Rear\n");
printf("3:Delete_Front 4: Delete_Rear\n");
printf(“5:Display 6: Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
9.48 Circular and doubly linked Lists
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_front (item, first);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_rear (item, first);
break;
case 3:
first = delete_front(first);
break;
case 4:
first = delete_rear(first);
break;
case 5:
display(first);
break;
default:
exit(0);
}
}
}
In a doubly linked list (discussed in previous section), observe that the leftlink of the
leftmost node and rightlink of rightmost node points to NULL. The two variations of
doubly linked lists are:
Circular doubly linked list
Circular doubly linked list with a header node
Data Structures using C - 9.49
Let us see “What is circular doubly linked list?”
Definition: A circular doubly linked list is a variation of doubly linked list in which
every node in the list has three fields:
info – This is a field where the information has to be stored
llink – This is a pointer field which contains address of the left node or previous
node in the list
rlink – This is a pointer field which contains address of the right node or next node
in the list
and the llink of the first node contains address of the last node whereas rlink of the
last node contains address of the first node.
The pictorial representation of circular doubly linked list is shown in figure below:
first
20 10 30 5
Using the above list, can you tell “What is the advantage of circular doubly linked
list?” Observe the following points:
As in normal doubly linked list, we can traverse the doubly linked circular list in
both directions.
In normal doubly linked list, given the address of the first node we have to
traverse till the end to get the address of last node. But, in circular doubly linked
list, we can easily find the address of the last node. The left link of the first node
gives the address of the last node.
In this section, let us see “How to insert a node at the front end of the list?”
Step 1: Create a node: This can be done using getnode() function and copying item 5
as shown below:
temp
temp = getnode()
1 temp->info = item;
5
temp->link = temp->rlink = temp;
1
9.50 Circular and doubly linked Lists
Step 2: Insert into empty list: If the list is empty, the above created node itself
should be returned as the first node.
temp
first \0 2
5 return temp;
Initial list
The code for this case can be written as shown below:
2 if (first == NULL) return temp;
If the above condition fails, it means that list is already existing. The new node temp
(created in step 1) which has to be inserted at the front end and the existing list can be
pictorially represented as shown below:
temp first
5 10 20 30 40
Now, the node temp can be inserted at the front end using the following steps:
Step 3: Obtain the address of the last node: The last node of the list can be obtained
using llink of the first node. The code can be written as:
3 last = first->llink; /* Get the address of last node */
Now, the linked list can be written as shown below:
temp first last
3
5 10 20 30 40
Step 4: Link the new node created with first node: This can be done by copying first
into rlink of temp and copying temp into llink of first node as shown below:
temp first last
4
5 10 20 30 40
5
Data Structures using C - 9.51
The code can be written as shown below:
4 temp->rlink = first;
5 first->llink = temp;
Step 4: Make new node created as the first node: This can be done by copying temp to
rlink of last node and copying last node into llink of new first node (i.e., temp->llink)
as shown below:
last
temp first
5 10 20 30 40
7 6
Step 5: Return the first node: After modifying the list, always we have to return the
address of the first node. In the above list, temp is the first node and it can be returned
using the following statement:
return temp;
Now, the list as seen from calling function can be written as shown below:
first
5 10 20 30 40
The C function to insert at the front end can be written as shown below:
Example 9.19: C Function to insert an item at the front end of the list
9.52 Circular and doubly linked Lists
temp = getnode()
1 temp->info = item;
temp->link = temp->rlink = temp;
return temp;
}
In this section, let us see “How to insert a node at the rear end of the list?”
Step 1: Create a node: This can be done using getnode() function and copying item 5
as shown below:
temp
temp = getnode()
temp->info = item; 5
1
temp->link = temp->rlink = temp;
1
Step 2: Insert into empty list: If the list is empty, the above created node itself
should be returned as the first node as shown below:
temp
first \0 2
return temp;
5
Initial list
Data Structures using C - 9.53
The code for this case can be written as shown below:
2 if (first == NULL) return temp;
If the above condition fails, it means that list is already existing. The new node temp
(created in step 1) which has to be inserted at the rear end and the existing list can be
pictorially represented as shown below:
first temp
10 20 30 40 5
Now, the node temp can be inserted at the rear end using the following steps:
Step 3: Obtain the address of the last node: The last node of the list can be obtained
using llink of the first node as shown below:
first
s last temp
3
10 20 30 40 5
Step 4: Link the new node created with last node: This can be done by copying temp
into rlink of last and copying last into llink of temp node as shown below:
4 last->rlink = temp;
5 temp->llink = last;
9.54 Circular and doubly linked Lists
Step 4: Make new node created as the last node: This can be done by copying temp to
llink of first node and copying first node into rlink of temp node (i.e., temp->rlink) as
shown below:
first temp
6 last
10 20 30 40 5
7
first
10 20 30 40 5
The C function to insert at the rear end can be written as shown below:
Example 9.20: C Function to insert an item at the rear end of the list
2 if (first == NULL) return temp; /* Insert the node for the first time */
return temp;
}
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return NULL. The
code for this can be written as shown below:
if (first == NULL) first
{
1 printf (“List is empty\n”); \0 1
return NULL; empty list
}
Step 2: Delete if there is only one node: A list having one node can be written as
shown below:
first
The code to delete the above node can be written as shown below:
9.56 Circular and doubly linked Lists
if (first->rlink == first)
{ first
printf(“Item deleted = %d\n”, first->info);
2 free(first);
5
return NULL; 2
}
When control comes out of the above if-statement, it means that the list has more than
one node and the list can be pictorially represented as shown below:
first
5 10 20 30 40
Now, the first node in the above list can be deleted as shown below:
Step 3: Obtain the address of the second node and last node: The rlink of first
node gives the second node and llink of first node gives the last node as shown below:
last
first 3 second 4
5 10 20 30 40
The corresponding code to find the address of the second node and last node can be
written as shown below:
3 second = first->rlink;
4 last = first->llink;
Step 4:Make second node as first node: It is achieved by copying last to left link of
second node and copying second to rlink of last node (dotted lines) as shown below:
Data Structures using C - 9.57
last
first 5 second
5 10 20 30 40
6
5 second->llink = last;
6 last->rlink = second;
Now, first node is isolated and the resulting linked list is shown below:
last
first second
5 10 20 30 40
Step 5: Delete the front node: Front node can be deleted using free() function. The
code can be written as shown below:
printf(“Item deleted = %d\n”, first->info);
7 free(first);
After executing the above code, the list can be written as shown below:
last
first 7 second
5 10 20 30 40
9.58 Circular and doubly linked Lists
Step 6: Return address of the new first node: Note that the variable second now
contains address of the new first node and it can be returned using the following
statement:
return second;
Now, the linked list as seen from the calling function can be written as shown below:
first
10 20 30 40
Now, the complete C function to delete the first node can be written as shown below:
Example 9.21: C function to delete an item from the front end of the list
NODE delete_front(NODE first)
{
NODE second, last;
6 last->rlink = second;
Data Structures using C - 9.59
/* Delete the old first node */
printf(“Item deleted = %d\n”, first->info);
7 free(first);
Now, let us see “How to delete a node from the rear end of the list?”
Design: A node from the rear end of the list can be deleted by considering various
cases as shown below:
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return NULL. The
code for this can be written as shown below:
if (first == NULL) first
{
1 printf (“List is empty\n”); \0 1
return NULL; empty list
}
Step 2: Delete if there is only one node: A list having one node can be written as
shown below:
first
The code to delete the above node can be written as shown below:
if (first->rlink == first)
{ first
printf(“Item deleted = %d\n”, first->info);
2 free(first);
5
return NULL; 2
}
9.60 Circular and doubly linked Lists
When control comes out of the above if-statement, it means that the list has more than
one node and the list can be pictorially represented as shown below:
first
5 10 20 30 40
Now, the last node in the above list can be deleted as shown below:
Step 3: Obtain the address of the last node and its predessor: The llink of first
node gives the last node and llink of last gives its predessor as shown below:
last
first 4 prev 3
5 10 20 30 40
The corresponding code to find the address of the last node and its predecessor can be
written as shown below:
3 last = first->llink;
4 prev = last->llink;
Step 4:Make last but one node as last node: It is achieved by copying first to right
link of prev and copying prev to llink of first node (dotted lines) as shown below:
6 last
first prev
5 10 20 30 40
5
Data Structures using C - 9.61
It can be done using the following code:
5 prev->rlink = first;
6 first->llink = prev;
Now, last node is isolated and the resulting linked list is shown below:
last
first prev
5 10 20 30 40
Step 5: Delete the last node: Last node can be deleted using free() function. The
code can be written as shown below:
printf(“Item deleted = %d\n”, last->info);
7 free(last);
After executing the above code, the list can be written as shown below:
last
first prev
5 10 20 30 40
Step 6: Return address of the first node: The variable first contains address of the
first node and it can be returned using the following statement:
return first;
Now, the linked list as seen from the calling function can be written as shown below:
first
5 10 20 30
9.62 Circular and doubly linked Lists
Now, the complete C function to delete the last node can be written as shown below:
Example 9.22: Function to delete an item from the rear end of the list
Keep traversing till we get the last node. Once we reach the last node come out of the
loop and display the last node.
Data Structures using C - 9.63
Example 9.23: C function to display the contents of linked list
The C program to implement dequeue with the help of above functions can be written
as shown below:
Example 9.24: Program to implement dequeues using circular doubly linked list
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *llink;
struct node *rlink;
};
typedef struct node* NODE;
9.64 Circular and doubly linked Lists
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 9.19: Function to insert an item at the front end of the list */
/* Include: Example 9.20: Function to insert an item at the rear end of the list */
/* Include: Example 9.21: Function to delete an item from the front end of the list */
/* Include: Example 9.22: Function to delete an item from the rear end of the list */
/* Include: Example 9.23: Function to display the contents of linked list */
void main()
{
NODE first;
int choice, item;
first = NULL;
for (;;)
{
printf("1:Insert_Front 2: Insert_Rear\n");
printf("3:Delete_Front 4: Delete_Rear\n");
printf(“5:Display 6: Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_front (item, first);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
first = insert_rear (item, first);
break;
case 3:
first = delete_front(first);
break;
Data Structures using C - 9.65
case 4:
first = delete_rear(first);
break;
case 5:
display(first);
break;
default:
exit(0);
}
}
}
Now, let us see “What is circular doubly linked list with a header?”
Definition: A circular doubly linked list (can also be called doubly linked circular
list) is a variation of doubly linked with a header node in which:
llink of header contains address of the last node of the list
rlink of header contains address of the first node of the list.
llink of last node contains the address of last but one node of the list
rlink of last node contains the address of the header node of the list
The pictorial representation of circular doubly linked list with a header node is shown
below.
head
10 20 30 40
This list is primarily used in structures that allow access to nodes in both the
directions. An empty circular doubly linked list with a header node can be
represented as shown in figure below where llink and rlink of a header node points to
itself.
head
9.66 Circular and doubly linked Lists
Note: Given any problem let us implement them using doubly linked lists and with a
header node. Using a header node with circular doubly linked lists all the problems
can be solved very easily and efficiently.
Note: Main advantage of using doubly linked circular list with a header node is that
while designing a function we need not consider any extreme cases. Assume that list
is existing and write a function to insert an item at the front end. The function works
for all other cases (i.e., even if list is empty or if list has only one node or more than
one node).
Now, let us see “How to insert an item at the front end of the list” Consider the list
shown in figure below (represented using thick lines).
head
10 20 30 40
Step 1: Create a node and copy the item to be inserted: This can be pictorially
represented as shown below:
head
10 20 30 40
30 1
temp
The code for the above activity can be written as shown below:
temp = getnode();
1
temp->info = item
Data Structures using C - 9.67
Step 2: Get the address of the first node: The right link of header node gives the
address of the first node. Copying rlink of head to first as shown below:
head 2 first
20 30 40
30
temp
Step 3: Insert at the front end: The node temp has to be inserted between head and
first as shown below:
head first
6 40
4 20 30
3 30 5
temp
6 first->llink = temp;
9.68 Circular and doubly linked Lists
Step 3: Return the header node: Using the header node, any node can be accessed. So,
always we return the address of header node. This can be done using the statement:
return head;
Example 9.25: Function to insert a node at the front end of the list
6 first->llink = temp;
Now, let us see “How to insert an item at the rear end of the list” Consider the list
shown in figure below:
head
10 20 30
Step 1: Create a node and copy the item to be inserted: This can be pictorially
represented as shown below:
Data Structures using C - 9.69
temp
head
10 20 30 40
1
The code for the above activity can be written as shown below:
temp = getnode();
1
temp->info = item;
Step 2: Get the address of the last node: The left link of header node gives the address
of the last node. Copying llink of head to last we get the list as shown below:
last temp
head 2
10 20 30 40
The code for the above situation can be written as shown below:
3 last->rlink = temp;
4 temp->llink = last;
Since temp is the last node, rlink of temp should contain address of header node and
llink of header node should contain the address of last node i.e., temp. This can be
pictorially represented as shown below:
9.70 Circular and doubly linked Lists
6
last temp
head
10 20 30 40
5
The code for the above situation can be written as shown below:
5 temp->rlink = head;
6 head->llink = temp;
Step 4: Return the header node: Using the header node, any node can be accessed. So,
always we return the address of header node. This can be done using the statement:
return head;
Example 9.26: Function to insert a node at the rear end of the list
4 temp->llink = last;
5 temp->rlink = head; /* Make temp as the last node */
6 head->llink = temp;
return head;
}
Data Structures using C - 9.71
9.5.3 Delete a node from the front end
Now, let us see “How to delete a node from the front end of the list?”
Design: A node from the front end of the list can be deleted by considering various
cases as shown below:
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return head. The code
for this can be written as shown below:
If the above condition fails, it means that list is existing. Consider the following list
shown below:
head
10 20 30 40
We can delete an element from the front end using the following steps:
Step 2: Get the first node: The right link of head contains address of the first node
and copy it into first as shown in figure below:
head 2 first
10 20 30 40
The code to obtain address of the first node can be written as shown below:
2 first = head->rlink;
9.72 Circular and doubly linked Lists
Step 3: Get the second node: The right link of first contains address of the second
node and copy it into second as shown in figure below:
10 20 30 40
The code to obtain address of the second node can be written as shown below:
3 second = first->rlink;
Step 4: Isolate the first node: This can be done by connecting head and second node
as shown in figure below:
head
4 first second
10 20 30 40
5
Step 5: Remove the first node: This first node can be removed as shown below:
head
first second
10 20 30 40
6
Data Structures using C - 9.73
The corresponding code can be written as shown below:
Step 6: Return the header node: Using the header node, any node can be accessed. So,
always we return the address of header node. This can be done using the statement:
return head;
Now, the complete function to delete an element from the front end can be written as
shown below:
5 second->llink = head;
Now, let us see “How to delete a node from the rear end of the list?”
Design: A node from the rear end of the list can be deleted by considering various
cases as shown below:
Step 1: List is empty: If the list is empty, it is not possible to delete a node from the
list. In such case, we display the message “List is empty” and return head. The code
for this can be written as shown below:
If the above condition fails, it means that list is existing. Consider the following list
shown below:
head
10 20 30 40
We can delete an element from the rear end using the following steps:
Step 2: Get the last node: The llink of head contains address of the last node and
copy it into last as shown in figure below:
` 2
head last
10 20 30 40
The code to obtain address of the last node can be written as shown below:
2 last = head->llink;
Data Structures using C - 9.75
Step 3: Get the last but one node: The left link of last contains address of the last
but one node and copy it into prev as shown in figure below:
10 20 30 40
The code to obtain address of the last but one node can be written as shown below:
3 prev = last->llink;
Step 4: Isolate the last node: This can be done by connecting head and prev node as
shown in figure below:
10 20 30 40
5
Step 5: Remove the last node: This last node can be removed as shown below:
10 20 30 40
6
Step 6: Return the header node: Using the header node, any node can be accessed. So,
always we return the address of header node. This can be done using the statement:
return head;
Now, the complete function to delete an element from the rear end can be written as
shown below:
5 prev->rlink = head;
Start displaying from the first node till we get the header node. The code for this can
be written as shown below:
Data Structures using C - 9.77
Example 9.29: Display the contents of circular doubly linked list with header node
9.5.6 Double ended queue using circular doubly linked lists with header node
The C program to implement dequeue with the help of above functions can be written
as shown below:
Example 9.30: Program to implement dequeues using circular doubly linked list
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
struct node
{
int info;
struct node *llink;
struct node *rlink;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 9.25: Function to insert an item at the front end of the list */
/* Include: Example 9.26: Function to insert an item at the rear end of the list */
/* Include: Example 9.27: Function to delete an item from the front end of the list */
/* Include: Example 9.28: Function to delete an item from the rear end of the list */
/* Include: Example 9.29: Function to display the contents of linked list */
void main()
{
NODE first;
int choice, item;
head = getnode();
head->rlink = head->llink = head;
for (;;)
{
printf("1:Insert_Front 2: Insert_Rear\n");
printf("3:Delete_Front 4: Delete_Rear\n");
printf(“5:Display 6: Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
head = insert_front (item, head);
break;
case 2:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
head = insert_rear (item, head);
break;
case 3:
head = delete_front(head);
break;
case 4:
head = delete_rear(head);
break;
Data Structures using C - 9.79
case 5:
display(head);
break;
default:
exit(0);
}
}
}
Let us “Design, Develop and Implement a menu driven Program in C for the
following operations on Doubly Linked List (DLL) of Employee Data with the
fields: SSN, Name, Dept, Designation, Sal, PhNo”
1. Create a DLL of N Employees Data by using end insertion.
2. Display the status of DLL and count the number of nodes in it
3. Perform Insertion and Deletion at End of DLL
4. Perform Insertion and Deletion at Front of DLL
5. Demonstrate how this DLL can be used as Double Ended Queue
typedef struct
{
int ssn;
char name[20];
char department[20];
char designation[20];
float salary;
char phone[20];
} EMPLOYEE;
The design details are given in section 9.3. Creating a list is nothing but repeated
insertion of items into the list. The insert front function (example 9.13) and insert rear
function (example 9.14) are modified to incorporate the above details.
The C function to insert a node at the front end can be written as shown below:
Example 9.31: C Function to insert an item at the front end of the list
9.80 Circular and doubly linked Lists
if (first == NULL) return temp; /* Insert a node for the first time */
The C function to insert a node at the rear end can be written as shown below:
Example 9.32: C Function to insert various items at the rear end of the list
if (first == NULL) return temp; /* Insert a node for the first time */
Data Structures using C - 9.81
/* Get the address of the first node */
cur = first;
The C function given in example 9.15 to delete an item from the front end of the list
can be modified as shown below:
Example 9.33: C function to delete an item from the front end of the list
NODE delete_front(NODE first)
{
NODE second;
if ( first == NULL ) /* Check for empty list */
{
printf("employee list is empty \n");
return NULL; // We can replace NULL with first also
}
return NULL;
}
return second;
}
The C function given in example 9.16 to delete an item from the front end of the list
can be modified as shown below:
Example 9.34: C function to delete an item from the rear end of the list
prev->rlink = NULL; /* Make last but one node as the last node */
return first; /* return address of the first node */
}
Example 9.36: Program to implement dequeue using doubly linked list (employee)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alloc.h>
9.84 Circular and doubly linked Lists
typedef struct
{
int ssn;
char name[20];
char department[20];
char designation[20];
float salary;
char phone[20];
} EMPLOYEE;
struct node
{
int ssn;
char name[20];
char department[20];
char designation[20];
float salary;
char phone[20];
struct node *llink;
struct node *rlink;
};
/* Include: Example 8.2: Function to get a new node from the operating system */
/* Include: Example 9.31: Function to insert an item at the front end of the list */
/* Include: Example 9.32: Function to insert an item at the rear end of the list */
/* Include: Example 9.33: Function to delete an item from the front end of the list */
/* Include: Example 9.34: Function to delete an item from the rear end of the list */
/* Include: Example 9.35: Function to display the contents of linked list */
void main()
{
NODE first;
int choice;
EMPLOYEE item;
first = NULL;
Data Structures using C - 9.85
for (;;)
{
printf("1:Insert_Front 2: Insert_Rear\n");
printf("3:Delete_Front 4: Delete_Rear\n");
printf(“5:Display 6: Exit\n”);
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("ssn :"); scanf(“%d”,&item.ssn);
printf("name :"); scanf(“%s”,item.name);
printf("department :"); scanf(“%s”,item.department);
printf("designation :"); scanf(“%s”,item.designation);
printf("salary :"); scanf(“%f”,&item.salary);
printf("phone :"); scanf(“%s”,item.phone);
first = insert_front (item, first);
break;
case 2:
printf("ssn :"); scanf(“%d”,&item.ssn);
printf("name :"); scanf(“%s”,item.name);
printf("department :"); scanf(“%s”,item.department);
printf("designation :"); scanf(“%s”,item.designation);
printf("salary :"); scanf(“%f”,&item.salary);
printf("phone :"); scanf(“%s”,item.phone);
first = insert_rear (item, first);
break;
case 3:
first = delete_front(first);
break;
case 4:
first = delete_rear(first);
break;
case 5: display(first);
break;
default: exit(0);
}
}
}
9.86 Circular and doubly linked Lists
struct node
{
int cf, px, py, pz; cf px py pz link
struct node *link;
};
term
typedef struct node* NODE;
Using the above structure declaration, the polynomial:
5x3y2z2 + 3y2 + 4x3z
can be represented using singly linked list as shown below:
Data Structures using C - 9.87
5 3 2 2 3 0 2 0 4 3 0 1 \0
5 x3 y2 z2 + 3 y2 + 4 x3 z
Since there are three terms in the above polynomial, the above polynomial is
represented as a linked list consisting of three nodes. To design the algorithm, for the
sake of simplicity we assume each term in the polynomial is unique i.e., each term has
different powers of x, y and z. Each term in the polynomial can have the same or
different coefficient.
we need to add the node representing the term 6z at the end of the list above list. The
resulting list representing the above polynomial can be written as shown below:
p
5 3 2 2 3 0 2 0 4 3 0 1 6 0 0 1 \0
3 2 2 2 3
5x y z + 3 y + 4 x z + 6 z
The above list can be represented using circular singly linked list with a header node
as shown below:
head
5 3 2 2 3 0 2 0 4 3 0 1 6 0 0 1
5 x3 y2 z2 + 3 y2 + 4 x3 z + 6 z
Thus the polynomial can be represented using circular singly linked list with a header
node by repeatedly inserting each term at the end of the polynomial.
So, let us write the function to insert at the rear end of the list (for the detailed design
see section 9.2.2). The function given in example 9.8 can be modified to incorporate
coefficient, power of x, power of y and power of z as shown below:
9.88 Circular and doubly linked Lists
return head;
}
Now, let us see “How to read a polynomial consisting of n terms?” We can enter n
terms using the scanf() statement. As we read each term representing coefficient,
power of x, power of y and power of z, we insert at the rear end of the list as shown
below:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct node
{
int cf;
int px, py, pz;
struct node *link;
};
Now, let us evaluate the polynomial: 6x2y2z - 4yz5 + 3x3yz + 2xy5z - 2xyz3
where x = 1, y = 2, z = 3
Output
Enter the number of terms in the polynomial: 5
Enter term: 1
Cf px py pz = 6 2 2 1
Enter term: 2
Cf px py pz = -4 0 1 5
Enter term: 3
Cf px py pz = 3 3 1 1
Enter term: 4
Cf px py pz = 2 1 5 1
Enter term: 5
Cf px py pz = -2 1 1 3
9.92 Circular and doubly linked Lists
h1 Poly 1
5x3y2z3 6xy3z2 3xyz4 5
h2 Poly 2
If we want to add two polynomials, first we have to search for power of polynomial 1
in polynomial 2. This can be done using linear search as shown below:
Once we know how to search for a term of polynomial 1 in polynomial 2, the general
procedure to add two polynomials is shown below:
for each term of polynomial 1
Step 1: access each term of poly 1
Step 2: search for power of above term in poly2
Step 3: if found in poly 2
Add the coefficients and add sum to poly 3
else
Add the term of poly1 to poly 3
end for
Add remaining terms of poly2 to poly3
Now, the above algorithm can be written using C statements as shown below:
Example 9.47: Function to add two polynomials
p1 = h1->link;
p2 = h2->link;
while ( p2 != h2 )
{
/* Add remaining terms of poly 2 into poly 3);
if (p2->cf != -999)
{
cf2 = p2->cf, px2 = p2->px, py2 = p2->py, pz2 = p2->pz;
return h3;
}
void main()
{
NODE h1, h2, h3;
h1 = getnode();
h2 = getnode();
h3 = getnode();
Poly 2: 10x3y3z4 + 4x3z3 + 3xy3z2 + 10xy3z + 7. We should get the following result.
----------------------------------------------------------------------------------
Poly 3: 5x3y2z3+ 9xy3z2 + 3xyz4 + 12 + 10x3y3z4 + 4x3z3+ 10xy3z
----------------------------------------------------------------------------------
The first two polynomials should be entered as shown below:
Input
Enter the first polynomial
Enter the number of terms in the polynomial: 4
Data Structures using C - 9.97
Enter term: 1
Coeff = 5 pow x = 3 pow y = 2 pow z = 3
Enter term: 2
Coeff = 6 pow x = 1 pow y = 3 pow z = 2
Enter term: 3
Coeff = 3 pow x = 1 pow y = 1 pow z = 4
Enter term: 4
Coeff = 5 pow x = 0 pow y = 0 pow z = 0
Enter the second polynomial
Enter the number of terms in the polynomial: 5
Enter term: 1
Coeff = 10 pow x = 3 pow y = 3 pow z = 4
Enter term: 2
Coeff = 4 pow x = 3 pow y = 0 pow z = 3
Enter term: 3
Coeff = 3 pow x = 1 pow y = 3 pow z = 2
Enter term: 4
Coeff = 10 pow x = 1 pow y = 3 pow z = 1
Enter term: 5
Coeff = 7 pow x = 0 pow y = 0 pow z = 0
Poly 1: +5 x^3 y^2 z^3 + 6 x^1 y^3 z^2 + 3 x^1 y^1 z^4 + 5
Poly 2: +10x^3 y^3 z^4 + 4 x^3 z^3 + 3 x^1 y^3 z^2 + 10 x^1 y^3 z^1 + 7
---------------------------------------------------------------------------------------------------------------
Poly 3: +5 x^3 y^2 z^3 + 9 x^1 y^3 z^2 + 3 x^1 y^1 z^4 + 12 + 10 x^3 y^3 z^4 + 4
x^3 z^3 + 10 x^1y^3 z^1
---------------------------------------------------------------------------------------------------------------
Definition: A sparse matrix is a matrix that has very few non-zero elements spread
out thinly. In other words, a matrix in which most of the elements are zeroes is called
a sparse matrix.
Ex 1: Consider the following two-dimensional matrices:
row[1] 11 0 31 row[1] 11 0 0 0
Now, the question is, “How can we store a value in a two-dimensional array?” A
value can be stored in the matrix A at specified row and column as shown below:
A[row][col] = value;
So, any element in a matrix A can be accessed using triples <row, col val>
Now, let us see “What is the disadvantage of a sparse matrix?” The sparse matrix
contains many zeroes. If we are manipulating only non-zero values, then we are
wasting the memory space by storing unnecessary zero values.
The above disadvantage can be overcome by storing only non-zero values thus saving
the space as shown in subsequent sections.
Data Structures using C - 9.99
The sparse matrix can be more efficiently represented using linked list. In the linked
representation:
Each column of a sparse matrix is represented as a circularly linked list with a
header node having three fields: down, right and next fields as shown below:
head
next next – is the header of next column
down right
right –not used
down – points to next item in the column
head
next next – not used
right – points to next item in the row
down right down – not used
row [0] 3 0 0 0
row [1] 5 0 0 6
row [2] 0 0 0 0
row [3] 4 0 0 8
row [4] 0 0 9 0
The above matrix can be represented using linked list as shown below:
9.100 Circular and doubly linked Lists
r[0]
0 0 3
r[1]
1 0 5 1 3 6
r[2]
r[3]
3 0 4 3 3 8
r[4]
4 2 9
Note: The first node in the above list identified by the variable head contains the size
of the matrix where 5 represents the number of rows, 4 represent the number of
columns and 6 represent non-zero elements to be manipulated.
Data Structures using C - 9.101
Exercises
7) What is a doubly linked list? What are the advantages of doubly linked lists?
10) What is circular doubly linked list? What is the advantage of circular doubly
linked list?
11) Write C functions to perform the following operations using circular doubly
linked lists
a) Insert front b) Insert rear c) delete front d) delete rear e) display
13) Write C functions to perform the following operations using circular doubly
linked lists with a header node
a) Insert front b) Insert rear c) delete front d) delete rear e) display
14) Write a program to implement dequeue using circular doubly linked list
9.102 Circular and doubly linked Lists
15) Design, Develop and Implement a menu driven Program in C for the following
operations on Doubly Linked List (DLL) of Employee Data with the fields: SSN,
Name, Dept, Designation, Sal, PhNo”
a) Create a DLL of N Employees Data by using end insertion.
b) Display the status of DLL and count the number of nodes in it
c) Perform Insertion and Deletion at End of DLL
d) Perform Insertion and Deletion at Front of DLL
e) Demonstrate how this DLL can be used as Double Ended Queue
20) What is a sparse matrix?” How to represent sparse matrix using linked list?
Data Structures using C - 9.103
Now, let us see “Why very large numbers cannot be added using + operator?”
Some applications may require larger than these numbers. But, all the computers have
a certain maximum number of bits required for representing an integer, float etc. If
the size of the numbers exceeds this limit, ordinary arithmetic operations cannot be
performed. Using linked lists, we can represent these large numbers and any
arithmetic operation can be performed on these large numbers.
Now, let us see “How to write algorithm/function to add two long positive numbers?”
This achieved by splitting the program into various modules based on activity
performed as shown below:
Reading a long positive number
Writing a long positive number
Adding two long positive numbers
Now, let us see “How to read a long positive number using linked list?”
Design: Let us design by taking a small number 6698274. This number can be split
into groups of one digit each as shown below from the most significant digit to least
significant digit.
6, 6, 9, 8, 2, 7, 4
Since there are 7 digits, a singly linked list with seven nodes can be used to store each
digit in each node. Consider the way the numbers are displayed on the display portion
of the calculator. If the number 6698274 is typed, display portion of the calculator
may look like as shown below.
6
66
669
6698
66982
669827
6698274
9.104 Circular and doubly linked Lists
head
6 6 9 8 2 7
The next digit typed i.e., 4 will be the new least significant digit and has to be inserted
immediately after the header node i.e., at the front end of the list. This is pictorially
represented as shown below (see the dotted lines):
head
6 6 9 8 2 7
Now, let us see “How to store the number entered from the keyboard using circular
list with header node?”
Insert the digits at the front end of the list: As the digits are typed, they can be
inserted at the front end of the list. The above while loop can be modified as
shown below:
Now, the function to read a number and store it in circular list can be written as
shown below:
Now, if we call the above function and type the number 6698274 from the keyboard,
we get the following circular list:
head
6 6 9 8 2 7 4
Now, the next question is “How to display the number stored in a linked list?” If we
traverse the list from the first node
the output will be: 4728966.
9.106 Circular and doubly linked Lists
But, we want the output to be: 6698274. To get this output, traverse the list from
front to last and copy each digit into an array. While printing, print the array
elements in reverse order. Now, we get the output: 6698274
cur = head->link, k = 0;
printf("\n");
}
9 9 8 7
h2
8 6
Data Structures using C - 9.107
First, we have to add the digits 7 and 6. Let use c1 to access the digits of first list and
c2 to access the digits of second list. This can be done by pointing c1 to first node of
first list and pointing c2 to first node of the second list as shown below:
h1
c1
9 9 8 7
h2
c2
8 6
Step 3: Insert answer to the end of resulting list: This can be done by inserting ans at
the end of list h3 using the statement:
h3 = insert_rear(ans, h3);
Step 4: Update pointers to point to the next digits: This can be done using the
statements:
c1 = c1->link;
c2 = c2->link;
9.108 Circular and doubly linked Lists
The above steps have to be repeatedly executed as long as one of c1 or c2 reaches the
corresponding header node. The code for this case can be written as shown below:
Once control comes out of the loop, we get the following situation:
h1
c1 1 1
9 9 8 7
h2 c2
8 6
h3
if (c1 != h1)
c = c1, h = h1;
else
c = c2, h = h2;
Data Structures using C - 9.109
Step 6: Add carry to the remaining digits of a number: This can be done using the
following code:
while (c != h)
{
sum = c->info + carry; // add carry to remaining digits
ans = sum % 10; // Separate the answer
carry = sum / 10; // Separate the carry
h3 = insert_rear(ans, h3); // Insert answer at the end
c = c->link; // Point to next two digit
}
Step 7: Add carry to the result: If carry exists after adding all the digits, add that carry
to the result. This can be done using the following code
if (carry == 1) h3 = insert_rear(carry, h3);
Step 8: Return the result: This can be done by executing the following code:
return h3;
The final function to add two long positive integers can be written as shown below:
Now, the two numbers 9987 and 86 are added as shown below:
h1 c1
carry generated 1 1 1 1
9 9 8 7
h2 c2
8 6
h3
1 0 0 7 3
Data Structures using C - 9.111
The complete program to add two long positive numbers is shown below:
Example 9.40: C program to add two long positive numbers
#include <stdio.h>
#include <process.h>
#include <conio.h>
#include <alloc.h>
struct node
{
int info;
struct node *link;
};
typedef struct node* NODE;
/* Include: Example 8.2: Function to get new node from the availability list */
/* Include: Example 9.7: Function to insert at the front end of the list */
/* Include: Example 9.8: Function to insert at the rear end of the list */
/* Include: Example 9.37: Function to read a long positive number */
/* Include: Example 9.38: Function to display a long positive integer */
/* Include: Example 9.39: Function to add two long positive numbers */
void main()
{
NODE h1, h2, h3;
h1 = getnode();
h2 = getnode();
h3 = getnode();
h1->link = h1;
h2->link = h2;
h3->link = h3;
Input
printf("Enter the first number\n"); Enter 1st number
h1 = read_number(h1); 99999
printf("Enter the second number\n"); Enter 2nd number
h2 = read_number(h2); 99999
9.112 Circular and doubly linked Lists
10.1 Introduction
Now, let us see “What is a tree?” A tree is recursively defined as follows.
Definition: A tree is a set of finite set of one or more nodes that shows parent-child
relation such that:
There is a special node called the root node
The remaining nodes are partitioned into disjoint subsets T1, T2, T3 ……Tn, n ≥ 0
where T1, T2, T3 ……Tn which are all children of root node are themselves trees
called subtrees.
Ex1 : Consider the following tree. Let us identify the root node and various subtrees:
root A The tree has 8 nodes : A, B, C, D, E,
F, G and H.
subtrees
The node A is the root of the tree
B C D We normally draw the trees with root
at the top
The node B, C and D are the children
E F G H of node A and hence there are 3
subtrees identified by B, C and D
The node A is the parent of B,C and D whereas D is the parent of G and H
Now, let us see the terminologies used in a tree. Consider the following tree for
explanation:
10.2 Trees
Level 0 Height 1
100
Level 1 Height 2
50 60
Level 2 80 40 Height 3
70
Level 3 Height 4
35 30
Root node: A first node written at the top is root node. The root node does not
have the parent. The node 100 is the root node in above tree.
Child: The node obtained from the parent node is called child node. A parent
node can have zero or more child nodes.
50 and 60 are children of 100
80 and 40 are children of 60
70 is child of 50
35 and 30 are children of 80
Siblings: Two or more nodes having the same parent are called siblings. For
example, for the tree shown in Fig. 10.1
50 and 60 are siblings since they have same parent 100
80 and 40 are siblings since they have same parent 60
35 and 30 are siblings since they have same parent 80
Ancestors: The nodes obtained in the path from the specified node x while
moving upwards towards the root node are called ancestors. For example, for the
tree shown in Fig. 10.1
100 is the ancestor of 50 and 60
60 is the ancestor of 35, 30, 80 and 40
50 and 100 are the ancestors of 70
60 and 100 are the ancestors of 80, 40, 35, 30
80, 60 and 100 are the ancestors of 35 and 30
Data Structures using C - 10.3
Descendants: The nodes in the path below the parent are called descendants. In
other words, the nodes that are all reachable from a node x while moving
downwards are all called descendants of x. For example, for the tree shown in
Fig. 10.1:
All the nodes below 100 are descendants of 100
All the nodes below 50 are descendants of 50 and so on.
Left descendants: The nodes that lie towards left subtree of node x are called left
descendants. For example, for the tree shown in Fig. 10.1
50 and 70 are left descendants of 100
80, 35 and 30 are the left descendants of 60
35 and 80 are left descendants of 60
Right descendants: The nodes that lie towards right subtree of node x are called
right descendants. For example, for the above tree
The right descendants of 100 are 60, 80, 40, 35 and 30
The right descendent of 80 is 30
The right descendent of 60 is 40
Leftsubtree: All the nodes that are all left descendants of a node x form the left
subtree of x. For example, for the above tree
The left subtree of 100 are 50 and 70
The left subtree of 60 are 80, 35 and 30
Right subtree: All the nodes that are all right descendants of a node x form the
right subtree of x. For example, for the above tree
The are right descendants of 100 are 60, 80, 40, 35 and 30
The right descendent of 80 is 30
The right descendent of 60 is 40
Parent: A node having left subtree or right subtree or both is said to be a parent
node for the left subtree and/or right subtree. The word father is also used in place
of parent. [Let us use the word parent, otherwise mothers may feel bad]. For
example, for the above tree
The parent for 50 and 60 is 100
The parent for 70 is 50
The parent for 80 and 40 is 60
the parent for 35 and 30 is 80
Degree: The number of subtrees of a node is called its degree. For example,
The node 100 has two subtrees. So, degree of node 100 is 2
The node 50 has one subtree. So, degree of node 50 is 1
The node 70 has no subtree. So, degree of node 70 is 0
10.4 Trees
Leaf: A node in a tree that has a degree of zero is called a leaf node. In other
words, a node with an empty left child and an empty right child is called a leaf
node. It is also called a terminal node. For example,
70, 35, 30 and 40 are the leaf nodes
Internal nodes: The nodes except leaf nodes in a tree are called internal nodes.
For example,
100, 50, 60 and 80 are the internal nodes
External nodes: The NULL link of any node in a tree is an external node. For
example, rlink of 50, rlink and llink of nodes 70, 35, 30 and 40 are all external
nodes.
Level 0 Height 1
100
Level 1 Height 2
50 60
Level 2 80 40 Height 3
70
Level 3 Height 4
35 30
Level: The distance of a node from the root is called level of the node.
The distance from root to itself is 0. So, level of root node is 0.
The node 50 is at a distance of 1 from root node. So, its level is 1.
The node 70 is at a distance of two nodes from root node. So, its level is 2.
The node 35 and 30 are at a distance of 3 nodes from the root node. So, their
levels are 3.
The level of each node is shown in above tree:
Height (Depth): The height of the tree is defined as the maximum level of any
leaf in the tree. For example,
height of above tree is 4
Data Structures using C - 10.5
10.2 Representation of Trees
Now, let us see “What are the different ways of representing a tree?” A tree can be
represented in three different ways.
List representation
Left child – right sibling representation
Binary tree representation (Degree-2 representation)
10.2.1 List representation
Now, let us see “How a tree is represented using lists?” A tree can be represented
using list as shown below:
The root node comes first.
It is immediately followed by a list of subtrees of that node.
It is recursively repeated for each subtree. For example, consider the tree shown
below:
A
0
B C D
` 0 0 0
E F G H I J
0 0 0 0
K M
L
0
Fig. 10.2: A tree
The above tree can be represented using lists as shown below:
B’s 1st 2nd child C’s 1st child D’s 1st child 2nd child 3rd child
B F 0 C G 0 D I J 0
E’s 1st child 2nd child H’s 1st child
E K L 0 H M 0
10.6 Trees
A binary tree representation is also called left child-right child tree representation or
degree two representation. A binary tree representation can be obtained as shown
below:
Obtain left child-right sibling representation as shown in previous example.
Rotate the horizontal lines clockwise by 45 degrees
For example, consider the left child-right sibling representation discussed in previous
example. It is shown again for quick reference.
A
0
B C D
0 0 0
E F G H I J
0 0 0 0
K L M
0
By rotating horizontal lines 45 degrees clockwise, we get the following binary tree:
10.8 Trees
E C
K F G D
L H
M I
Address of Address of
100
left subtree right subtree
llink info rlink
Data Structures using C - 10.9
Note that a node in a tree consists of three fields namely llink, info and rlink:
llink –contains address of left subtree
info – This field is used to store the actual data or information to be manipulated
rlink –contains address of right subtree.
The pictorial representation of above node can also be written as shown below:
info info
100 100
llink rlink llink rlink
left subtree right subtree left subtree right subtree
Note: In the text book, we show without directions. But, it is implied that directions
are present moving away from the node. The following figure shows some of the
binary trees:
50 10 20
Empty tree Tree with 1 node Tree with 2 nodes Tree with 3 nodes
Note: An empty tree is also a binary tree. Binary here means at most two i.e., zero,
one or two subtrees are possible. But, more than two subtrees are not permitted.
Consider the trees shown below: Are they binary trees? No.
Let us see “Why the above trees are not binary trees?”
10.10 Trees
The tree shown in Fig (a) is not binary tree. This is because, it has more than two
subtrees.
The tree shown in Fig (b) is not a binary tree. There is a cycle from 100 to 200,
200 to 300 and 300 back to 100. The tree should not have cycles.
The node 100 has subtree 10 and 10 has the subtree 100. If 10 is subtree of 100,
then 100 cannot be the subtree of 10. So, it is not a binary tree.
Proof: Consider the following complete binary tree and observe the following factors
from the complete binary tree: level nodes
0
Number of nodes at level 0 = 1 = 2
Number of nodes at level 1 = 2 = 21 0 = 20
Number of nodes at level 2 = 4 = 22 1 = 21
3
Number of nodes at level 3 = 8 = 2
……………………………………. 2 = 22
4 4 4 4
…………………………………….
0 0 0 0
Number of nodes at level i = 2i.
8 4 8 4 8 4 8 4 3 = 23
Total number of nodes in the full binary tree of 0level i = 200 + 21 + 202 + ……20i.
The above series is a geometric progression whose sum is given by
S = a ( rn – 1) / ( r – 1)
where a = 1, n = i+1 and r = 2
So, total number of nodes nt = a ( rn – 1) / ( r – 1)
= 1 (2i+1 – 1)/ ( 2 – 1)
= 2i+1 – 1
Therefore, total number of nodes nt = 2i+1 – 1 Substituting i = 3 (see above tree)
we get nt = 15 which is total number of nodes in a fully binary tree
The depth of the tree k = maximum level + 1.
=i+1
Substituting this value in above equation we get nt = 2k – 1
Data Structures using C - 10.11
Lemma: The number of leaf nodes is equal to number of nodes of degree 2
Now, let us see “What is the relation between the number of leaf nodes and degree-2
nodes?”
Observe from the tree that total number of nodes is equal to the total number of
branches (B) plus one. That is, n = B + 1 -------------------------------(2)
By adding (3) and (4) we get total number of branches B = n1 + 2n2 ------------(5)
The relation between number of leaf nodes and number of nodes of degree-2 can be
obtained by subtracting (6) from (1). That is,
n = n0 + n1 + n2
– n = n1 + 2n2 + 1
0 = n0 – n2 – 1
By re arranging we get, n0 = n2 + 1
10.12 Trees
Definition: A complete binary tree is a binary tree in which every level, except
possibly the last level is completely filled. If the nodes in the last level are not
completely filled, then all the nodes in that level should be filled only from left to
right. For example, all the trees shown below are complete binary trees.
1 1 1
5 6 5 6 5 6
7 7 4 7 4 8
(a) (b) (c)
The tree shown below is not complete binary tree since the nodes in the last level are
not filled from left to right. There is an empty left child for node 5 in figure (d) and
there is an empty right child for node 5 in figure (e). All the nodes should be as left as
possible.
1 1
5 6 5 6
4 8 7 8
(d) (e)
Now, let us see “How binary trees are represented?” The storage representation of
binary trees can be classified as shown below:
Array representation (uses static allocation technique)
Linked representation (uses dynamic allocation technique)
10.14 Trees
struct node
{
int info;
struct node *llink;
struct node *rlink;
};
Address of Address of
100
left subtree right subtree
llink info rlink
The pictorial representation of above node can also be written as shown below:
info info
100 100
llink rlink llink rlink
left subtree right subtree left subtree right subtree
A pointer variable root can be used to point to the root node always. If the tree is
empty, the pointer variable root points to NULL indicating the tree is empty. The
pointer variable root can be declared and initialized as shown below:
Data Structures using C - 10.15
NODE root = NULL;
or
struct node * root = NULL;
Now, let us see “How a tree is represented using static allocation (using arrays)
technique?”
0 A
1 B 2 C 0 1 2 3 4 5 6
A B C D E F G
3 D 4 E 5 F 6 G
(a)
0 A
1 B 2 C 0 1 2 3 4 5 6
A B C D E F
4 D 5 E 6 F
(b)
Given the position of any other node i, 2i+1 gives the position of the left child
and 2i+2 gives the position of the right child.
If i is the position of the left child, i+1 gives the position of the right child
If i is the position of the right child, i-1 gives the position of the left child.
Given the position of any node i, the parent position is given by (i-1) / 2. If i is
odd, it points to the left child otherwise, it points to the right child.
A tree can be represented using arrays in two different methods as shown below:
Method 1: In the first representation shown below, some of the locations may be
used and some may not be used. For this, a flag field namely, used is used just to
indicate whether a memory location is used to represent a node or not. If flag field
used is 0, the corresponding memory location is not used and indicates the absence of
node at that position. So, each node contains two fields:
info where the information is stored
used indicates the presence or the absence of a node
The structure declaration for this is shown below:
#define MAX_SIZE 200
struct node
{
int info;
int used;
};
typedef struct node NODE;
An array a of type NODE can be used to store different items and the declaration
for this is shown below:
NODE a [MAX_SIZE];
A
A
B C
B C
DGH EI F
D E F
G H I BDGH CEIF
DGH EI
` A
BDGH CEIF
ABDGHCEIF
The C function to traverse the tree in preorder can be recursively defined as shown
below:
A
A
B C
B C
D GHD IE F
E F
G H I GHDB IEFC
GHD IE
A
GHDB IEFC
GHDBIEFCA
A
A
B C
B C
GDH EI F
D E F
G H I GDHB EICF
GDH EI
A
GDHB EICF
GDHBAEICF
Now, let us see “How to print the tree in the tree form?” The tree can be printed in the
form of a tree sideways on the screen using converse inorder (RNL) as shown below:
printf(“%d\n”, root->info);
Note: All tree traversal techniques just discussed are recursive algorithms and uses
stack. Now, let us see how to traverse the tree that uses queue data structure.
Definition: The nodes in a tree are numbered starting with the root on level 0,
continuing with the nodes on level 1, level 2 and so on. Nodes on any level are
numbered from left to right. Visiting the nodes using the ordering suggested by the
node numbering is called level order traversing.
Data Structures using C - 10.21
level
0 A 0 Output of level
order traversing:
1 B 2 C 1
A B C D E F G H I
3 D 4 E 5 F 2
6 G H 7 8 I 3
Design: Insert the root node identified by variable root into queue. This is done using
the statement:
front = 0, rear = -1;
q[++rear] = root;
Step 1: Delete a node from the front end and visit that node by displaying using the
statements:
cur = q[front++];
printf(“%d\n”, cur->info);
Step 2: If node visited in step 1 has a left subtree, insert that node into queue. This is
done using the statement:
if (cur->llink != NULL)
q[++rear] = cur->llink;
Step 3: If node visited in step 1 has a right subtree, insert that node into queue. This
is done using the statement:
if (cur->rlink != NULL)
q[++rear] = cur->rlink;
Example 10.5: C function to print the tree using level order traversal
This chapter deals with, mostly of problems involving recursion. In this section we
develop the functions for traversal of trees using iterative technique.
We know that in preorder traversal, node is visited first, then the left subtree is
traversed in preorder and finally right subtree is traversed in preorder. Visiting the
node here is nothing but display the corresponding item in the node. This can be
achieved using the statement
printf(“%d “,cur->info);
only if cur points to root node. So, initially cur points to the root node. Once the node
is visited, next is to traverse the left subtree in preorder. For this descend the tree
towards left. Once all the nodes in the left subtree are displayed, it may be required to
ascend the parts of the tree to display the nodes in the right subtree. To ascend the tree
later, just before descending towards left, push the address of current node cur and
then descend the tree left. This process has to be repeated till end of left subtree is
encountered. The code corresponding to this can be written as
Data Structures using C - 10.23
while ( cur != NULL )
{
printf(“%d “,cur->info); /* Visit the node */
s[++top] = cur; /* push(cur, &top, s) */
cur = cur->llink; /* traverse left */
}
In the tree shown in figure below, after executing these statements 100, 50 and 25 will
be displayed.
100
50 200
25 80
Once traversing the left subtree in preorder is over, traverse the right subtree in
preorder i.e., ascend the tree by popping the address of the node most recently pushed
and descend towards right subtree. This is possible only when the stack is not empty.
If the stack is empty traversing the tree in preorder is complete and the program
terminates. The code corresponding to this is shown below:
After descending the tree towards right, entire right subtree has to be traversed in
preorder. So, the above set of statements has to be repeated. The complete C function
for this is shown below:
if ( root == NULL )
{
printf("Tree is empty\n");
return;
}
cur = root;
for (;;)
{
while ( cur != NULL )
{
printf(“%d “,cur->info); /* Visit the node */
s[++top] = cur; /* push(cur, &top, s) */
cur = cur->llink; /* traverse left */
}
if ( top != -1 ) /* If stack is not empty */
{
cur = s[top--]; /* Obtain the recent node from the stack */
cur = cur->rlink; /* traverse right */
}
else
return;
}
}
Once the left subtree is traversed, visit the node by popping the most recently pushed
node on to the stack and then traverse towards right if the stack is not empty. If the
stack is empty, traversing the tree in inorder is over and the procedure is terminated.
The code to achieve this can be
After traversing towards right, traverse the tree in inorder and repeat the process. This
can be achieved by executing all the above statements. The C function to traverse the
tree in inorder is shown below:
Example 10.7: Function for iterative inorder traversal
/* display the contents of the tree in inorder */
void inorder(NODE root)
{
NODE cur,s[20];
int top = -1;
if ( root == NULL )
{
printf("Tree is empty\n");
return;
}
cur = root;
10.26 Trees
for (;;)
{
while ( cur != NULL )
{
s[++top] = cur; /* push(cur,&top,s); */
cur = cur->llink; /* traverse left*/
}
cur = root;
for (;;)
{
while ( cur != NULL )
{
top++;
s[top].address = cur; /* push(cur,&top,s) */
s[top].flag = 1;
cur = cur->llink; /* traverse left */
}
if ( top == -1 ) return;
}
All these disadvantages can be overcome using threaded binary tree. Now, let us
see “What is threaded binary tree?”
Definition: In a binary tree, more than 50% of link fields have \0 (null) values and
more space is wasted by the presence of \0 (null) values. These link fields which
contains \0 characters can be replaced by address of some nodes in the tree which
facilitate upward movement in the tree. These extra links which contains addresses of
some nodes (pointers) are called threads and the tree is termed as threaded binary tree.
In general, a threaded binary tree is a binary tree which contains threads (i.e.,
addresses of some nodes) which facilitate upward movement in the tree.
Now, let us see “What are the various types of threaded binary trees?” A binary tree is
threaded based on the traversal technique. Since there are three traversal techniques,
threaded binary trees are classified into three types as shown below:
In-threaded binary trees
Types of threaded Pos-threaded binary trees
binary trees
Pre-threaded binary trees
head
B C
D E F
In the above binary tree, if the right link of a node is NULL and if it is replaced by the
address of the inorder successor as shown using dotted lines in fig.10.4, then the tree
is said to be right in-threaded binary tree.
head
B C
rthread
D E F
Now, let us see “How to implement right in-threaded binary tree in C language?”
struct node
{
int info;
struct node *llink; /* Pointer to the left subtree */
struct node *rlink; /* Pointer to the right subtree */
int rthread; /* 1 indicates the presence of a thread*/
/* 0 indicates the absence of a thread */
};
For a binary tree shown in figure 10.3, if the left field of a node is NULL and is
replaced by the inorder predecessor as shown in fig.10.5, then the tree is said to be
left in-threaded binary tree. Here also an extra field lthread is used where 1 indicates
the presence of a thread and 0 indicates ordinary link connecting the left subtree.
head
lthread
A
B C
D E F
head
B C
D E F
G
Figure 10.6 In-threaded binary tree (inorder threading of binary tree)
Thus a node can be defined as
struct node
{
int info;
struct node *llink; /* Pointer to the left subtree */
struct node *rlink; /* Pointer to the right subtree */
int lthread; /* 1 indicates a thread else ordinary link */
};
typedef struct node* NODE;
An inorder threading of a binary tree or in-threaded binary tree is one which is left
in-threaded and right in-threaded and is shown in fig.10.6. Here two extra fields
lthread and rthread as defined earlier are used and the structure of the node can be
defined as shown below:
struct node
{
int info;
struct node *llink; /* Pointer to the left subtree */
struct node *rlink; /* Pointer to the right subtree */
int lthread; /* 1 indicate a thread else ordinary link */
int rthread;
};
10.32 Trees
It is desirable to have a header node so as to solve the problems more easily. In this
case, the header node serves as the predecessor of the first node and successor of the
last node. This imposes a circular structure on the tree. When the tree is empty, the
header node is represented as shown in fig.10.7. The tree is attached always to the
left field of the header node.
head
D
Now let us see, “How to find the inorder successor and predecessor?” and “How to
traverse the tree in inorder?”
Consider the right in-threaded binary tree shown in fig.10.4. Given a node X, if
rthread is 1, which indicates the presence of thread, its rlink gives the address of the
inorder successor. If rthread is 0, rlink contains the address of the right subtree. Left
most node in the right subtree is the inorder successor. The C function is shown
below:
NODE inorder_successor(NODE x)
{
NODE temp;
temp = x->rlink;
if ( x->rthread == 1 ) return temp;
/*Obtain the left most node in the right subtree */
while ( temp->llink != NULL )
temp = temp->llink;
return temp;
}
Data Structures using C - 10.33
The C function to traverse the tree in inorder is straightforward and the reader is
required to trace this program. The C function is shown below:
for (;;)
{
temp = inorder_successor(temp);
printf("%d ",temp->info);
}
}
Definition: In a binary tree, if llink (left link) of any node contains \0 (null) and if it
is replaced by address of the preorder predecessor, then the resulting tree is called left
pre-threaded binary tree.
In a binary tree, if rlink (right link) of a node is NULL and if it is replaced by
address of preorder successor, the resulting tree is called right pre-threaded binary
tree.
A pre-threaded binary tree or preorder threading of a binary tree is the once
which is both left pre-threaded and right pre-threaded.
10.34 Trees
Definition: In a binary tree, if llink (left link) of any node contains \0 (null) and if it
is replaced by address of the postorder predecessor, then the resulting tree is called
left post-threaded binary tree. In a binary tree, if rlink (right link) of a node is NULL
and if it is replaced by address of postorder successor, the resulting tree is called right
post-threaded binary tree. A post-threaded binary tree or postorder threading of a
binary tree is the one which is both left post-threaded and right post-threaded.
Now, let us see “What are the advantages of threaded binary trees?” The various
advantages of the binary tree are shown below:
In a binary tree, more than 50% of link fields have \0 (null) values and more
memory space is wasted by storing \0 (null) values. This wastage of memory
space is avoided by storing addresses of some nodes.
Traversing of a threaded binary tree is very fast. This is because, it does not use
implicit or explicit stack.
Computations of predecessor and successor of given nodes is very easy and
efficient
Any node can be accessed from any other node. Using threads, upward movement
is possible and using links downward movement is possible. Thus, in a threaded
binary tree, we can move in either directions. This is not possible in un-threaded
binary trees.
Even though insertion into a threaded binary tree and deletion from a threaded
binary are time consuming operations, they are very easy to implement.
Now, let us see “What are the disadvantages of threaded binary trees?” The various
disadvantages of threaded binary trees are shown below:
Here extra fields are required to check whether a link is a thread or not and hence
occupy more memory when compared with un-threaded binary trees.
Insertion and deletion of a node consumes more time than its counter part because
many fields have to be modified.
Definition: A binary search tree is a binary tree in which for each node say x in the
tree, elements in the left-subtree are less than info(x) and elements in the right subtree
are greater than info(x). Every node in the tree should satisfy this condition, if left
Data Structures using C - 10.35
subtree or right subtree exists. A binary search tree can be empty. For example, the
figures below shows some of the binary search trees.
100 20 50
50 200 30 40
25 90 150 300 40 30
80 180 50 20
10.10.1 Insertion
Now, let us see “How to insert an element into a binary search tree?”
Design: A binary search tree can be created by repeatedly inserting items into the tree
as shown below:
Step 1: The item read from the keyboard must be stored in a node. This is achieved
using malloc() function with the help of getnode() function defined in example 8.2.
The equivalent code can be written as shown below:
temp = getnode();
temp->info = item;
temp->llink = NULL;
temp->rlink = NULL;
Step 2: If tree does not exist, then return the above node itself as the root node. The
code can be written as shown below:
10.36 Trees
prev = NULL;
cur = root;
Now, as long as the item is less than info(cur), keep updating pointer variable cur
towards left using the statement:
cur = cur->llink;
Otherwise, update cur towards right using the statement:
cur = cur->rlink;
Before updating cur towards left or right, save its address in prev so that the pointer
variable prev always points to the parent node of cur. Now, the code can be written as
shown below:
prev = cur;
if (item < cur->info )
cur = cur->llink;
else
cur = cur->rlink;
Data Structures using C - 10.37
Note that while updating cur towards left or right, it may become NULL. In such
case, we have found the appropriate position to insert and stop updating cur. So, the
above code can be repeatedly executed as long as cur is not NULL. The code for this
can be written as shown below:
Once cur points to NULL, insert the node temp towards left(prev) if item is less than
info(prev), otherwise insert towards right. This can be done using the following code:
Finally, we return the address of the root node using the statement:
return root;
Example 10.11: To insert an item into a binary search tree (duplicate elements)
if ( root == NULL ) return temp; /* Insert a node for the first time */
10.38 Trees
return root;
}
Example 10.12: To insert an item into a binary search tree (No duplicate items)
10.10.2 Searching
Now, let us see “How to search for an element in a binary search tree?”
Design: The following steps are followed to search for an item in binary search tree.
Step 1: Check for empty tree: If tree does not exist, we return NULL indicating
search is unsuccessful. The code for this can be written as:
Step 2: Search for the node in left subtree or right subtree. Same as program
discussed in example in previous section. Only change is that once cur becomes
NULL control comes out of the while-loop and we say key is not present in the tree
and return NULL indicating search is unsuccessful. The complete algorithm is
written in C as shown below:
Example 10.13: Function to search for an item in binary search tree using iteration
cur = root;
while ( cur != NULL ) /* search for the item */
{
if (item == cur->info) return cur; /* If found return the node */
The complete program for creating a tree, traversing and searching can be written as
shown below:
Example 10.15: C program to create, traverse and to search for an item in the tree
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *llink;
struct node *rlink;
};
typedef struct node* NODE;
/* Include: Example 8.2: function to create a node using malloc() */
/* Include: Example 10.1: Display the contents of the tree in preorder */
/* Include: Example 10.2: Display the contents of the tree in postorder */
/* Include: Example 10.3: Display the contents of the tree in inorder */
/* Include: Example 10.4: C function to print the tree in tree form */
/* Include: Example 10.11: Function to insert duplicate items into BST */
or
/* Include: Example 10.12: Function to insert item (no-duplicate) into BST */
/* Include: Example 10.13: Function to search for an item in a tree */
or
/* Include: Example 10.14: Function to search for an item in a tree */
10.42 Trees
void main()
{
NODE root, cur;
int choice, item;
root = NULL;
for (;;)
{
printf("1:Insert 2: Preorder\n");
printf("3:Postorder 4: Inorder\n”);
printf(“5: Search 6:Exit\n");
printf("Enter the choice\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter the item to be inserted\n");
scanf("%d", &item);
root = insert(item, root);
break;
case 2:
if ( root == NULL )
{
printf("Tree is empty\n");
break;
}
printf(“The given tree in tree form is\n”);
display(root, 1);
printf("Preorder traversal is\n");
preorder(root);
printf("\n");
break;
case 3:
if ( root == NULL )
{
printf("Tree is empty\n");
break;
}
Data Structures using C - 10.43
printf(“The given tree in tree form is\n”);
display(root, 1);
printf(“Postorder traversal is\n");
postorder(root);
printf("\n");
break;
case 4:
if ( root == NULL )
{
printf("Tree is empty\n");
break;
}
printf(“The given tree in tree form is\n”);
display(root, 1);
printf("Inorder traversal is\n");
inorder(root);
printf("\n");
break;
case 5:
printf("Enter the item to be searched\n");
scanf("%d",&item);
cur = search(item, root);
if (cur == NULL)
printf(“Item not found\n”);
else
printf(“Item found\n”);
break;
default: exit(0);
}
}
}
10.44 Trees
Let us see “How to find maximum value in a binary search tree?” Given a binary
search tree, a node with maximum value is obtained by traversing and obtaining the
right most node in the tree. If there is no right subtree then return root itself as the
node containing the item with highest value. The corresponding C function is shown
below:
cur = root;
while ( cur->rlink != NULL )
{
cur = cur->rlink; /* obtain right most node in BST */
}
return cur;
}
10.11.2 To find minimum value in a BST
Let us see “How to find minimum value in a binary search tree?” Given a binary
search tree, a node with least value is obtained by traversing and obtaining the left
most node in the tree. If there is no left subtree then return root itself as the node
containing an item with least value. The corresponding C function is shown below:
Data Structures using C - 10.45
Example 10.17: Function to return the address of least item in BST
-1 if root == NULL
Height(root) =
1 + max ( height(root->llink), height(root->rlink ) ) otherwise
The corresponding C function to find the height of the tree is shown below:
Consider the figures shown below where cur denotes the node to be deleted
and in both cases one of the subtrees is empty and the other is non-empty. The node
identified by parent is the parent of node cur. The non-empty subtree can be obtained
and is saved in a variable q. The corresponding code is:
if ( cur->llink == NULL) /* If left subtree is empty */
q = cur->rlink; /* non empty right subtree is saved*/
else if ( cur->rlink == NULL ) /* If right subtree is empty */
q = cur->llink; /* non empty left subtree is saved*/
parent parent
100 100
q
40 q
150
30 55 125 175
Case 2: Non empty left subtree and non-empty right subtree: Consider the figures
shown below where cur denotes the node to be deleted. In both cases both the
subtrees are non-empty. The node identified by parent is the parent of the node cur.
parent parent
100 100
cur cur
50 50 Attaching q to parent
suc suc
20 80 q 20 80 q
85 95 85 95
Figure To delete a node cur from the tree
parent parent
100 100
cur cur
200 50 200
50 Attach q to parent
20 90 20 90 q
3
Obtain right subtree of
80 80 the node to be deleted
suc suc
Obtain inorder successor
70 1 of the node to be deleted
70
2
75 Attach left subtree of 75
node to be deleted to left
of inorder successor
72 76 72 76
Fig. To delete node cur from the tree
Data Structures using C - 10.49
The node can be easily deleted using the following procedure in sequence:
Step 1: Find the inorder successor of the node to be deleted. The corresponding code
is shown below:
suc = cur->rlink; /* Inorder successor always lies towards right */
while ( suc->llink != NULL ) /* and immediately keep traversing left */
{
suc = suc->llink;
}
Step 2: Attach left subtree of the node to be deleted to the left of successor of the
node to be deleted. The corresponding code is:
suc->llink = cur->llink; /* Attach left of node to be deleted to left of
successor of the node to be deleted */
Step 3: Obtain the right subtree of the node to be deleted. The corresponding code is:
q = cur->rlink; /* Right subtree is obtained */
Attach the node q to parent: Now, after case 1 or case 2, it is required to attach the
right subtree of the node to be deleted to the parent of the node to be deleted. If parent
of the node to be deleted does not exist, then return q itself as the root node using the
statement:
if (parent == NULL) return q;
If parent exists for the node to be deleted, attach the subtree pointed to by q, to the
parent of the node to be deleted. In this case, attach q to parent based on the direction.
If cur is the left child, attach q to left(parent) otherwise attach q to right(parent). This
can be achieved by using the following statement
/* connecting parent of the node to be deleted to q */
if ( cur == parent->llink )
parent->llink = q;
else
parent->rlink = q;
Once the node q is attached to the parent, the node pointed to by cur can be deleted
and then return the address of the root node. The corresponding statements are:
free (cur);
return root;
All these statements have been written by assuming the node to be deleted cur and its
parent node is known. So, just before deleting, search for the specified node, obtain
10.50 Trees
its parent and then delete the node. The complete function to delete an item from the
tree is shown below:
Example 10.21: Function to delete an item from the tree
NODE delete_item(int item, NODE root)
{
NODE cur ,parent, suc, q;
if ( root == NULL )
{
printf("Tree is empty! Item not found\n");
return root;
}
parent = NULL;
cur = root; /*obtain node to be deleted, its parent */
parent = cur;
free(cur);
return root;
}
By using the definition of a binary tree and recursive versions of inorder, preorder and
postorder traversals, we can easily create C functions for other binary tree operations.
The various operations considered in this section are:
Copying a tree – copy one binary tree to other tree
Test for equality – check whether two tress are equal or not
The C function to obtain the exact copy of the given tree using recursion is shown
below: It is self-explanatory. In this function address of the root node is given and
after copying, it returns address of the root node of the new tree.
The C function to check whether two trees are equal or not is shown below. It is self-
explanatory.
if (r1 == NULL && r2 != NULL) return 0; // two trees are not equal
if (r1 != NULL && r2 == NULL) return 0; // two trees are not equal
/* Recursively check left subtree of both trees and right subtree of both trees */
return equal(r1->llink, r2->llink) && equal(r1->rlink, r2->rlink);
}
Data Structures using C - 10.53
10.13 Expression trees
Definition: An expression tree is a binary tree that satisfy the following properties:
Any leaf is an operand
The root and internal nodes and operators
The subtrees represent sub expressions with root of the subtree as an operator.
Let us see, “How an infix expression can be written using expression tree?” An infix
expression consisting of operators and operands can be represented using a binary tree
with root as the operator. The left and right subtrees are the left and right operands of
that operator. A node containing an operator is not a leaf where as a node containing
an operand is a leaf. The tree representation for the infix expression
((6+(3-2)*5) ^ 2 + 3)
is shown in figure below: Let us traverse the tree in preorder and postorder as shown:
$ 3 $ 3
+ 2 + 2
6 6 *
*
1 - - 3 5
5
3 2 3 2
2 3 1 2
-3 2 =A 3 2 - =A
10.54 Trees
+ +
3 $ 3
$
+ 2 + 2
6 *
3
6 1 *
A A 5
5
2 3 1 2
*A5 =B A5*=B
+ +
3 $ 3
$
1 + + 3 2
2
6 6 B
B
2 3 1 2
+6B=C 6B+=C
+ +
1 $ $ 3 3
3
C 2 C 2
2 3 1 2
$C2=D C2$ =D
Data Structures using C - 10.55
1 + + 3
3 D 3
D
2 3 1 2
+ D 3 D 3+
+$C23 C2$3+
+$+6B23 6B+2$3+
+$+6*A523 6A5*+2$3+
+$+6*-32523 632–5*+2$3+
( Prefix expression) (Postfix expression)
Let us see “What is the procedure to obtain an expression tree from the postfix
expression?” The procedure to be followed while creating an expression tree using
postfix expression is shown below:
Scan the symbol from left to right.
Create a node for the scanned symbol.
If the symbol is an operand push the corresponding node onto the stack.
If the symbol is an operator, pop one node from the stack and attach to the
right of the corresponding node with an operator. The first popped node
represents the right operand. Pop one more node from the stack and attach to
the left. Push the node corresponding to the operator on to the stack.
Repeat through step 1 for each symbol in the postfix expression. Finally when
scanning of all the symbols from the postfix expression is over, address of the
root node of the expression tree is on top of the stack.
The following C function creates a binary tree for the valid postfix expression.
Example 10.24: Function to create an expression tree for the postfix expression
10.56 Trees
if( isalnum(symbol) )
st[k++] = temp; /* Push the operand node on to the stack */
else
{
temp->rlink = st[--k]; /* Obtain 2nd operand from stack */
temp->llink = st[--k]; /* Obtain 1st operand from stack */
st[k++] = temp; /* Push operator node on to stack */
}
}
Now, let us see “How to evaluate the expression?” In the expression trees, whenever
an operator is encountered, evaluate the expression in the left subtree and evaluate the
expression in the right subtree and perform the operation. The recursive definition to
evaluate the expression represented by an expression tree is shown below:
The C function for the above recurrence relation to evaluate the expression
represented by an expression tree is shown below:
Data Structures using C - 10.57
Example 10.25: Function to evaluate the expression represented as a binary tree
switch(root->info)
{
case '+': return eval(root->llink) + eval(root->rlink);
case '$':
case '^': return pow (eval(root->llink),eval(root->rlink));
default :
if ( isalpha(root->info) )
{
printf("%c = ",root->info);
scanf("%f",&num);
return num;
}
else
return root->info - '0';
}
}
The complete C program to evaluate an expression using expression tree is shown
below:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define STACK_SIZE 20
10.58 Trees
struct node
{
char info;
struct node *llink;
struct node *rlink;
};
typedef struct node* NODE;
/* Function to display in tree form */
void display(NODE root, int level)
{
int i;
printf(“%c\n”, root->info);
/* Include: Example 8.2: C Function to get a new node from availability list */
/* Include: Example 10.25: Function to evaluate the tree */
/* Include: Example 10.24: Function to create expression tree for postfx exprsin */
void main()
{
char postfix[40];
float res;
NODE root = NULL;
Input
printf("Enter the postfix expression\n"); Enter postfix expression
scanf("%s",postfix); 632-5*+1^7+
Data Structures using C - 10.59
root = creat_tree(postfix);
7
printf(“The expression tree is:\n”); +
display(root, 1); 1
^
5
*
2
-
3
+
6
res = eval(root);
Output
printf("Result = %f\n", res); Result = 18.00000
}
Output
Enter the postfix expression:
abc-d*+e^f+
e = 1, a = 6, b = 3, c = 2, d = 5, f = 7
Result = 18.0000
In section 10.3 we have seen as to how a tree can be represented using an array. The
creation of a binary search tree and different traversal techniques using array
representation is discussed in this section. The advantages and disadvantages of
sequential representation over linked representation is also discussed.
The complete program in C to create the binary search tree and to traverse the tree in
inorder, preorder and postorder is shown below:
Example 10.27: C Program to create a tree and traverse the tree array representation
#include <stdio.h>
include <process.h>
#define MAX_SIZE 100
10.60 Trees
i = 0; /* root node */
void main()
{
NODE a[MAX_SIZE];
int item,choice,i;
for(;;)
{ printf("1:Insert 2:Inorder\n");
printf("3:Preorder 4:Postorder\n");
printf("5:Exit\n");
switch(choice)
{ case 1:
printf("Enter the item to be inserted\n");
scanf("%d",&item);
insert(item,a);
break;
case 2:
if ( a[0] == 0 )
printf("Tree is empty\n");
else
{
printf("The inorder traversal is\n");
inorder(a,0);
printf("\n");
}
break;
10.62 Trees
case 3:
if ( a[0] == 0 )
printf("Tree is empty\n");
else
{
printf("The preorder traversal is\n");
preorder(a,0);
printf("\n");
}
break;
case 4:
if ( a[0] == 0 )
printf("Tree is empty\n");
else
{
printf("The postorder traversal is\n");
postorder(a,0);
printf("\n");
}
break;
default: exit(0);
}
}
}
The sequential representation is simple and it saves space if the tree is complete or
almost complete as there is no need to have fields such as left-link, right-link etc. If
the tree is not complete or not almost complete binary tree, too much space may be
wasted. The index i used to move downward while doing some operations should not
exceed the array bound i.e., MAX_SIZE. This is advantageous only when the number
of items in the tree is known in advance.
The linked representation is useful most of the time during repeated deletion or
manipulations, which can be easily done by adjusting the links and where the number
of items in the tree is unpredictable.
Data Structures using C - 10.63
EXERCISES
11.1 Introduction
In this chapter, let us concentrate another important and non-linear data structure called
graph. In this chapter, we discuss basic terminologies and definitions, how to represent
graphs and how graphs can be traversed.
11.2 Graph Theory terminology
First, let us see “What is a vertex?”
Definition: A vertex is a synonym for a node. A vertex is normally represented by a
circle. For example, consider the following figure:
1
3
2 4
0
Fig Vertices
In the above figure, there are four nodes identified by 1, 2, 3, 4. They are also called
vertices and normally denoted by a set V = {1, 2, 3, 4}.
Now, let us see “What is an edge?”
Definition: If u and v are vertices, then an arc or a line joining two vertices u and v is
called an edge.
Example 1: Consider the figure: 6
1
0
11.2 Graphs
G = (V, E)
where V is set of vertices and E is set of edges. For example, consider the graph shown
below:
5
4 E = { <1, 6>, <1, 2>, <2, 3>, <4, 3>, <5, 3>, <5, 6>,
2 0 <6, 4> } is set of directed edges
Note:
3 |V| = |{1, 2, 3, 4, 5, 6}| = 6 represent the number of
vertices in the graph.
Data Structures using C - 11.3
|E| = |{<1, 6>, <1, 2>, <2, 3>, <4, 3>, <5, 3>, <5, 6>, <6, 4> }| = 7 represent the
number of edges in the graph.
Definition: A graph G = (V, E) in which every edge is directed is called a directed graph.
The directed graph is also called digraph. A graph G = (V, E) in which every edge is
undirected is called an undirected graph. Consider the following graphs:
multiple edges
Definition: A graph with multiple occurrence of the 1 4
same edge between any two vertices is called multigraph. 0
Here, there are two edges between the nodes 1 and 4 and
there are three edges between the nodes 4 and 3.
2 3
Now, let us see “What is a complete graph?”
Definition: A graph G = (V, E) is said to be a complete graph, if there exists an edge
between every pair of vertices. The graph (a) below is complete. Observe that in a
complete graph of n vertices, there will be n(n-1)/2 edges. Substituting n = 4, we get 6
edges. Even if one edge is removed as shown in graph (b) below, it is not complete graph.
1 4 1 4
0 0
2 3 2 3
Complete graph Not a complete graph
2 3 2 3
In the graph, the path from vertex 1 to 4 In the graph, the path from vertex 1 to 3 is
is denoted by: 1, 2, 3, 4 which can also be denoted by 1, 4, 2, 3 which can also be
written as (1, 2), (2, 3), (3, 4). written as <1, 4>, <4, 2>, <2, 3>
Data Structures using C - 11.5
Now, let us see “What is simple path?”
Definition: A simple path is a path in which all vertices except possibly the first and last
are distinct. Consider the undirected and directed graph shown below:
1 4 1 4
0 0
2 3 2 3
Ex 1: In the graph, the path 1, 2, 3, 4 is Ex 1: In the graph, the path 1, 4, 2, 3 is
simple path since each node in the simple path since each node in the
sequence is distinct. sequence is distinct.
Ex2: In the graph, the path 1, 2, 3, 2 is not Ex 2: The sequence 1, 4, 3 is not a path
a simple path since the nodes in sequence since there is no edge <4, 3> in the graph.
are not distinct. The node 2 appears twice
in the path
Definition: The length of the path is the number of edges in the path.
Ex 1: In the above undirected graph, the path (1, 2, 3, 4) has length 3 since there are three
edges (1, 2), (2, 3), (3, 4). The path 1, 2, 3 has length 2 since there are two edges (1, 2),
(2, 3).
Ex 2: In the above directed graph, the path <1, 2, 3, 4> has length 3 since there are three
edges <1, 2>, <2, 3>, <3, 4>. The path <1, 4, 2> has length 2 since there are two edges
<1, 4>, <4, 2>.
For example, the path <4, 2, 3, 4> shown in above directed graph is a cycle, since the first
node and last node are same. It can also be represented as <4, 2>, <2, 3>, <3, 4> <4, 2>.
Note: A graph with at least one cycle is called a cyclic graph and a graph with no cycles
is called acyclic graph. A tree is an acyclic graph and hence it has no cycle.
For example, the graphs shown in figure below are connected graphs.
1 4 1 4
0 0
2 3 2 3
Figure Connected graphs
Definition: Let G = (V, E) be a graph. If there exists at least one vertex in a graph that
cannot be reached from other vertices in the graph, then such a graph is called
disconnected graph. For example, the graph shown below is a disconnected graph.
1 4 Since vertex 1 is 1 4
0 not reachable 0
5 from 3, the graph
0 is not connected
2 3 graph. 2 3
Not connected
Adjacency matrix
Representation of graph
Adjacency linked list
Definition: Let G = (V, E) be a graph where V is set of vertices and E is set of edges. Let
N be the number of vertices in graph G. The adjacency matrix A of a graph G is formally
defined as shown below:
Data Structures using C - 11.7
It is clear from the definition that an adjacency matrix of a graph with n vertices is a
Boolean square matrix with n rows and n columns with entries 1’s and 0’s (bit-
matrix)
In an undirected graph, if there exists an edge (i, j) then a[i][j] and a[j][i] is made 1
since (i, j) is same as (j, i)
In a directed graph, if there exists an edge <i, j> then a[i][j] is made 1 and a[j][i] will
be 0.
If there is no edge from vertex i to vertex j, then a[i][j] will be 0.
Note: The above definition is true both for directed and undirected graph. For example,
following figures shows the directed and undirected graphs along with equivalent
adjacency matrices:
0 1 2 3
0 1
0 0 1 1 1
0
1 0 0 1 0
2 0 0 0 1
2 3 3 0 1 0 0
0 1 2 3
0 1
0 0 0 1 1 0
1 1 0 1 1
2 1 1 0 1
2 3 3 0 1 1 0
Note: It is clear from the above definition that if i, j and k are the vertices adjacent to the
vertex u, then i, j and k are stored in a linked list and starting address of linked list is
stored in A[u] as shown below:
A
.
.
u i j k
.
.
For example, figures below shows the directed and undirected graphs along with
equivalent adjacency linked list:
A
0 1 3
0 2 1 nodes adjacent to 0
0
1 2 nodes adjacent to 1
2 3 2 3 nodes adjacent to 2
3 1 nodes adjacent to 3
A
0 1 2 nodes adjacent to 0
0 1
0
1 0 2 3 nodes adjacent to 1
2 3 2 0 1 3 nodes adjacent to 2
3 1 2 nodes adjacent to 3
Note: So, based on the nature of the problem and based on whether the graph is sparse or
dense, one of the two representations can be used.
Let us see “How the weighted graph can be represented?” The weighted graph can be
represented using adjacency matrix as well as adjacency linked list. The adjacency matrix
consisting of costs (weights) is called cost adjacency matrix. The adjacency linked list
consisting of costs (weights) is called cost adjacency linked list. Now, let us see “What is
cost adjacency matrix?”
Definition: Let G = (V, E) be the graph where V is set of vertices and E is set of edges
with n number of vertices. The cost adjacency matrix A of a graph G is formally defined
as shown below:
10 0 1 2 3
0 1
0 0 ∞ 10 20 ∞
15
20 25 1 ∞ ∞ 15 ∞
2 ∞ ∞ ∞ 30
2 3 3 ∞ 25 ∞ ∞
30 Note: Diagonal values
can be replaced by 0’s
(a) Weighted graph (b) Adjacency matrix
For the undirected graph, the elements of the cost adjacency matrix are obtained using the
following definition:
The undirected graph and its equivalent adjacency matrix is shown below:
1 0 1 2 3
0
0 0 ∞ 25 10 ∞
15 1 25 ∞ 15 20
10 20
2 10 15 ∞ 30
2 3 3 ∞ 20 30 ∞
30 Diagonal values can
be replaced by 0’s
(a) (b)
Figure: Weighted undirected graph and the adjacency matrix
Note: The cost adjacency matrix for the undirected graph is symmetric (i.e., a[i, j] is
same as a[j, i]) whereas the cost adjacency matrix for a directed graph may not be
symmetric.
Note: For some of the problems, it is more convenient to store 0’s in the main diagonal of
cost adjacency matrix instead of ∞.
Data Structures using C - 11.11
Now, let us see “What is cost adjacency linked list?”
Definition: Let G = (V, E) be a graph where V is set of vertices and E is set of edges
with n number of vertices. A cost adjacency linked list is an array of n linked lists. For
each vertex u V, A[u] contains the address of a linked list. All the vertices which are
adjacent from vertex u are stored in the form of a linked list (in an arbitrary manner) and
the starting address of first node is stored in A[u]. If i, j and k are the vertices adjacent to
the vertex u, then i, j and k are stored in a linked list along with the weights in A[u] as
shown below:
A
.
.
u k, wk
i, wi j, wj
.
.
For example, the figure below shows the weighted diagraph and undirected graph along
with equivalent adjacency list.
10 0 1,10 2,20
0 1
15 0 1
20 2,15
25
2 3,30
2 3
30
3 1,25
10 0 1,10 2,20
0 1
15 0 1 0,10 2,15 3,25
20 25
2 0,20 1,15 3,30
2 3 1,25 2,30
30
3
Now, the function to read an adjacency matrix can be written as shown below:
Now, we concentrate on a very important topic namely graph traversal techniques and
see “What is graph traversal? Explain different graph traversal techniques”
Definition: The process of visiting each node of a graph systematically in some order
is called graph traversal. The two important graph traversal techniques are:
Definition: The breadth first search is a method of traversing the graph from an
arbitrary vertex say u. First, visit the node u. Then we visit all neighbors of u. Then
we visit the neighbors of neighbors of u and so on. That is, we visit all the
neighboring nodes first before moving to next level neighbors. The search will
terminate when all the vertices have been visited.
Now, let us take an example and see how BFS traversal can be used to see what are
all the nodes which are reachable from a given source vertex.
Example 11.3: Traverse the following graph by breadth-first search and print all the
vertices reachable from start vertex a. Resolve ties by the vertex alphabetical order.
f b c g
d a e
Initialization: Insert source vertex a into queue and add a to S as shown below:
Initialization - - a a
Step 1 a b, c, d, e a, b, c, d, e b, c, d, e
Step 2 b a, d, f a, b, c, d, e, f c, d, e, f
Step 3 c a, g a, b, c, d, e, f, g d, e, f, g
Step 4 d a, b, f a, b, c, d, e, f, g e, f, g
Step 5 e a,g a, b, c, d, e, f, g f, g
Step 6 f b, d a, b, c, d, e, f, g g
Step 7 g c. e a, b, c, d, e, f, g empty
The above activities are shown below in the form of an algorithm along with
pseudocode in C when graph is represented as an adjacency matrix.
Example 11.4: C function to show the nodes visited using BFS traversal
s[u] = 1; // insert u to s
printf(“%d “, u); // print the node visited
while ( f <= r )
{
u = q[f++]; // delete an element from q
for (v = 0; v < n; v++)
{
if (a[u][v] == 1) // If v is adjacent to u
{
if (s[v] == 0) // If v is not in S i.e., v has not been visited
{
printf(“%d “, v); // print the node visited
s[v] = 1; // add v to s, mark it as visited
q[++r] = v; // Insert v into queue
}
}
}
}
printf(“\n”);
}
Now, the C program that prints all the nodes that are reachable from a given source
vertex is shown below:
Example 11.5: Algorithm to traverse the graph using BFS
#include <stdio.h>
/* Insert: Example 11.1: Function to read an adjacency matrix*/
/* Insert: Example 11.4: Function to traverse the graph in BFS */
Data Structures using C - 11.17
void main()
{
int n, a[10][10], source, i, j;
Now, let us see how to obtain the nodes reachable from each node of the following
graph using the above program:
0 1 2 3
0 1 0 0 1 1 0
0
1 0 0 1 1
2 0 0 0 1
2 3 3 0 0 0 0
Output
Enter the number of nodes: 4
Enter the adjacency matrix:
0110
0011
0001
0000
The nodes visited from 0: 0 1 2 3
The nodes visited from 1: 1 2 3
The nodes visited from 2: 2 3
The nodes visited from 3: 3
11.18 Graphs
Example 11.7: C function to show the nodes visited using BFS traversal
s[u] = 1; // insert u to s
printf("%d ", u); // print the node visited
while ( q != NULL ) // as long as queue is not empty
{
u = q->info; // delete a node from queue
q = delete_front(q);
Now, the complete C program to see the nodes reachable from each of the nodes in
the graph can be written as shown below:
Example 11.8: Program to print nodes reachable from a vertex (bfs using adjacency list)
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
read_adjacency_list(a, n);
Input
Enter the number of nodes: 4
Enter the number of nodes adjacent 0: 2
Enter nodes adjacent to 0: 1 2
Output
The nodes visited from 0: 0 1 2 3
The nodes visited from 1: 1 2 3
The nodes visited from 2: 2 3
The nodes visited from 3: 3
Definition: In DFS, a vertex u is picked as source vertex and is visited. The vertex u
at this point is said to be unexplored. The exploration of the vertex u is postponed and
a vertex v adjacent to u is picked and is visited. Now, the search begins at the vertex
11.22 Graphs
v. There may be still some nodes which are adjacent to u but not visited. When the
vertex v is completely examined, then only u is examined. The search will terminate
when all the vertices have been examined.
Note: The search continues deeper and deeper in the graph until no vertex is adjacent
or all the vertices are visited. Hence, the name DFS. Here, the exploration of a node is
postponed as soon as a new unexplored node is reached and the examination of the
new node begins immediately.
Design methodology The iterative procedure to traverse the graph in DFS is shown
below:
Step 1: Select node u as the start vertex (select in alphabetical order), push u onto
stack and mark it as visited. We add u to S for marking
Step 3: Repeat step 1 and step 2 until all the vertices in the graph are considered
Example 11.9: Traverse the following graph using DFS and display the nodes reachable
from a given source vertex
f b c g
d a e
Data Structures using C - 11.23
Solution: Since vertex a is the least in alphabetical order, it is selected as the start
vertex. Follow the same procedure as we did in BFS. But, there are two changes:
Instead of using a queue, we use stack
In BFS, all the nodes adjacent and which are not visited are considered. In DFS,
only one adjacent which is not visited earlier is considered. Rest of the procedure
remains same.
Now, the graph can be traversed using DFS as shown in following table
The recursive function can be written as shown below: (Assuming adjacency matrix a,
number of vertices n and array s as global variables)
11.24 Graphs
Example 11.10: Program to print nodes reachable from a vertex (dfs - adjacency matrix)
void dfs(int u)
{
int v;
s[u] = 1;
printf("%d ", u);
for (v = 0; v < n; v++)
{
if (a[u][v] == 1 && s[v] == 0) dfs(v);
}
}
The complete program that prints the nodes reachable from each of the vertex given in
the graph can be written as shown below:
Example 11.11: Program to print nodes reachable from a vertex (dfs - adjacency matrix)
#include <stdio.h>
Example 11.12: Program to print nodes reachable from a vertex (dfs - adjacency list)
void dfs(int u)
{
int v;
NODE temp;
s[u] = 1;
printf("%d ", u);
for (temp = a[u]; temp != NULL; temp = temp->link)
if (s[temp->info] == 0) dfs(temp->info);
}
11.26 Graphs
The complete program that prints the nodes reachable from each of the vertex given in
the graph using DFS represented using adjacency list can be written as shown below:
Example 11.13: Program to print nodes reachable from a vertex (dfs - adjacency matrix)
#include <stdio.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
NODE a[10];
int s[10], n; // Global variables
Input
Enter the number of nodes: 4
Enter the number of nodes adjacent 0: 2
Enter nodes adjacent to 0: 1 2
Output
The nodes visited from 0: 0 1 2 3
The nodes visited from 1: 1 2 3
The nodes visited from 2: 2 3
The nodes visited from 3: 3
Exercises
d a e
14) Write a C function to show the nodes visited using BFS traversal (adjacency matrix)
15) Write a C function to show the nodes visited using BFS traversal (adjacency list)
16) What is depth first search (DFS)?”
17) Traverse the following graph using DFS and display the nodes reachable from a given
source vertex
f b c g
d a e
18) Write a program to print nodes reachable from a vertex (dfs - adjacency matrix)
19) Write a program to print nodes reachable from a vertex (dfs - adjacency matrix)
Chapter 12: Sorting and Searching
What are we studying in this chapter?
Sorting
Insertion Sort
Radix sort
Address Calculation Sort
Hashing:
Hash Table organizations
Hashing Functions
Static hashing
Dynamic Hashing
12.1 Sorting
We have already seen how to sort elements using bubble sort. In this chapter, we
study three sorting techniques: Insertion sort, Radix sort and address calculation sort.
12.2 Insertion Sort
In this section, let us see “How insertion sort works?”
Procedure: The sorting procedure is similar to the way we play cards. After shuffling
the cards, we pick each card and insert it into the proper place so that cards in hand
are arranged in ascending order. The same technique is being followed while
arranging the elements in ascending order. The given list is divided into two parts:
sorted part and unsorted part as shown below:
n elements
Note that all the elements from 0 to j are sorted and elements from k to n-1 are not
sorted. The kth item can be inserted into any of the positions from 0 to j so that
elements towards left of boundary are sorted. As each item is inserted towards the
sorted left part, the boundary moves to the right decreasing the unsorted list. Finally,
12.2 Sorting and searching
once the boundary moves to the right most position, the elements towards the left of
boundary represent the sorted list.
25 75 40 10 20 75 is inserted after 25
sorted un sorted
25 40 75 10 20 10 is inserted before 25
sorted un sorted
Once control comes out of the above loop, insert the item into a[j+1]. Now, the partial
code can be written as shown below:
item = a[i];
j = i – 1;
while (item < a[j] && j >= 0)
{
These statement should be executed for
a[j+1] = a[j];
each item = a[i], where i = 1 to n-1
j = j – 1;
}
a[j+1] = item;
Example 12.3 : C program to arrange items in ascending order using insertion sort
#include <stdio.h>
/* Include: Example 2.3: Function to read n elements */
/* Include: Example 2.4: Function to display elements */
/* Include: Example 12.2: Function to sort items using insertion sort */
void main()
{
int i, n, j, item, a[10];
printf("Enter the number of elements\n");
scanf("%d",&n);
printf("Enter n elements\n"); create_array(a, n);
insertion_sort(a, n);
printf("The sorted items : "); display_array(a, n);
}
Data Structures using C - 12.5
12.3 Radix sort
Let us arrange numbers in ascending order using radix sort. Since, we are sorting
decimal numbers (having base 10), we assume there are 10 pockets ranging from 0 to
9. Initially all 10 pockets are empty as shown below:
p
0 1 2 3 4 5 6 7 7 9
Now, let us see “How to sort the following elements using Radix sort?” Consider the
following elements:
57, 45, 67, 91, 28, 79, 35, 68, 89, 20, 62, 43, 84, 55, 86, 96, 78, 25
Pass 1: Activities:
Step 1: Scan each item (number) in the list. Obtain least significant digit say d. Insert
item into pocket at position p[d]. If an item is already in p[d] then, insert item above
the last item in p[d]. Each item in the list can be inserted into appropriate pocket as
shown below:
item = 57, least significant digit d = 7. So, insert 57 into p[7] as shown below:
57
p
0 1 2 3 4 5 6 7 7 9
item = 45, least significant digit d = 5. So, insert 45 into p[5] as shown below:
45 57
p
0 1 2 3 4 5 6 7 7 9
item = 67, least significant digit d = 7. So, insert 67 into p[5] as shown below:
67
45 57
p
0 1 2 3 4 5 6 7 7 9
Thus, all other items are scanned one by one and inserted into appropriate pocket as
shown below:
12.6 Sorting and searching
25
55 78
35 96 67 68 89
20 91 62 43 84 45 86 57 28 79
p
0 1 2 3 4 5 6 7 8 9
Step 2: Now, access each pocket and copy the items present in each pocket one after
the other starting from position 0 through 9 into the original array as shown below:
20, 91, 62, 43, 84, 45, 35, 55, 25, 86, 96, 57, 67, 28, 68, 78, 79, 89
Pass 2: Activities: Consider the above items as input to the pass:
Step 1: Scan each item (number) in the above list. Obtain next least significant digit
say d. Insert item into pocket at position p[d] as shown below:
89
28 25 68 79 86 96
20 91 20 35 45 55 67 78 84 91
p
0 1 2 3 4 5 6 7 8 9
Step 2: Now, access the pockets one by one and copy the items present in each
pocket one after the other into the original array as shown below:
20, 28, 91, 20, 25, 35, 45, 55, 67, 68, 78, 79, 84, 86, 89, 91, 96
Design: Now, using the above example, let us think of how to design the algorithm or
the C function. First, let us see how to separate a digit from a given number say: 789.
Pass 1: Given item 789, in pass 1 we have to separate digit 9 i.e., 789/100 % 10 = 9
Pass 2: Given item 789, in pass 2 we have to separate digit 8 i.e., 789 /101 % 10 = 8
Pass 3: Given item 789, in pass 3 we have to separate digit 7 i.e., 789/102 % 10 = 7
So, in jth pass, the digit separated is given by d = item / 10j-1 % 10 j is pass number
So, the function to separate a digit, given item and pass number j can be written as
shown below:
Data Structures using C - 12.7
Example 12.4 : Function to separate a digit of a number given pass number j
int separate_digit ( int item, int j)
{
return item / ( int ) pow (10, j – 1) % 10; /* d = item / 10j-1 % 10 */
}
Another function insert_rear() is used to insert an element at the rear end of the list.
Now, using these functions, perform the following operations for each pass, where
pass number depends on number of digits of a maximum number in the list.
Step 1: Initialization: Each location in the packet can be considered as a linked list
consisting of one or more items. So, the variable p can be considered as an array of 10
linked lists. Initially all 10 pockets contain NULL and it can be represented as shown
below:
p \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0 1 2 3 4 5 6 7 8 9
The corresponding code can be written as shown below:
Step2: Access an item and insert into appropriate pocket. This can be done by
separing jth digit from each number (where j is the pass number) and inserting the
item into pocket p[digit] using the following code:
Step 3: Copy items from each of the pocket into array: This can be done by
accessing each pocket using the following code:
12.8 Sorting and searching
Copying item from each pocket is nothing but traversing the linked list till the end
and copy all items in the linked list into array a. Now, the above code can be modified
as shown below:
k = 0;
for (i = 0; i < 10; i++)
{
temp = p[i];
The code given in above three steps have to be executed for each pass j where
k = 0;
The value of m thus calculated gives total number of passes to made. After executing
the statement m contains the number of passes required. For example, if big is 234,
after the execution of this statement m will be 3 which is the number of digits in the
number. Now, the C function to sort numbers using radix sort can be written as shown
below:
Example 12.5 : Function to sort the numbers in ascending order using radix sort
void radix_sort(int a[], int n)
{
int i, j, k, m, big, digit;
k = 0;
The function largest which returns the largest item in an array consisting of n
elements can be written as shown below:
return a[pos];
}
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
struct node
{
int info;
struct node *link;
};
typedef struct node *NODE;
/* Include: Example 2.3: Function to read n elements into array */
/* Include: Example 2.4: Function to write n elements into array */
/* Include: Example 8.2: C Function to get a new node from the availability list */
/* Include: Example 8.6: Function to insert an item at the rear end of the list */
/* Include: Example 12.4: Function to separate digit */
/* Include: Example 12.5: Function to sort numbers in ascending order */
/* Include: Example 12.6: Function to find the largest element in an array */
void main()
{
int n, i, a[20];
printf("Enter n items\n");
scanf("%d",&n);
printf("Enter the elements to sort\n");
create_array(a, n);
radix_sort(a,n);
printf("The sorted array is\n");
display_array(a, n);
}
12.5 Hashing
We have already seen that the time required to access any element in the array
irrespective of its position is same. The physical location of ith item in the array can be
calculated by multiplying the size of each element of the array with i and adding to
the base address as shown below:
Here, i is the index, w is the size of each element of the array and lb is the lower
bound. Using this formula, the address of any item at any specified position i can be
obtained and from the address obtained, we can access the item. The similar idea is
used in hashing to store and retrieve the data. So, the hashing technique is essentially
independent of n where n is number of elements.
Example 12.8: Create a hash table and using hash function for 5 items:
For example, consider the elements: 11, 12, 13, 14 and 15 and consider the following
function:
H(k) = k % 5 // Hash function is just a function
The value of each function for the items: 10, 11, 12, 13 and 14 can be obtained as
shown below:
H (k ) = k % 5 // Hash function
H (10) = 10 % 5 = 0 Insert 10 into table a[0]
H (11) = 11 % 5 = 1 Insert 11 into table a[1]
H (12) = 12 % 5 = 2 hash values Insert 12 into table a[2]
H (13) = 13 % 5 = 3 Insert 13 into table a[3]
H (14) = 14 % 5 = 4 Insert 14 into table a[4]
Thus, the table obtained after inserting each of the items using hash value as the index
is shown below:
a[0] 10
a[1] 11
a[2] 12 The table thus created using elements and hash values is
a[3] 13 the hash table
a[4] 14
Data Structures using C - 12.13
Now, let us see “What is hash value? What is hash function?”
Definition: A function can be used to provide a mapping between large original data
and the smaller table by transforming a key information into an index to the table. The
index value returned by this function is called hash value. The function that
transforms a data into hash value to a table is called hash function.
Definition: The data can be stored in the form of a table using arrays with the help of
hash function which gives a hash value as an index to access any element in the table.
This table on which insertion, deletion and retrieve operations takes place with the
help of hash value is called hash table.
A hash table can be implemented as an array ht[0..m-1]. The size of the hash
table is limited and so it is necessary to map the given data into this fairly restricted
set of integers. The hash function assigns an integer value from 0 to m-1 to keys and
these values which act as index to the hash table are called hash addresses or hash
values.
Definition: This process of mapping large amounts of data into a smaller table using
hash function, hash value and hash table is called hashing.
Now, let us see “What are the different types of hashing techniques?” The two types
of hashing techniques are:
Static hashing
Dynamic hashing
Definition: This process of mapping large amounts of data into a table whose size is
fixed during compilation time is called static hashing. In static hashing, all the data
items are stored in a fixed size table t called hash table. The hash table is implemented
as an array ht[0..m-1] with 0 as the low index and m – 1 as the high index. Each item
in the table can be stored in ht[0], ht[1],…….ht[m – 1] where m is the size of the table
usually with a prime number such as 5, 7, 11 and so on. The items are inserted into
the table based on the hash value obtained from the hash function.
12.14 Sorting and searching
Now, let us take the following example, where identifiers are inserted into the hash
table.
Solution: We need to store identifiers in a hash table. Let us assume all identifiers
starts with only letters from ‘A’ to ‘Z’ having the range 0 to 25. Since, there are 26
letters, let us have a hash table ht whose size is m = 26. Assume identifiers to be
inserted into hash table ht are: ant, dog, cat, bat, eagle, fish and ape. To insert an
identifier into the hash table, we require hash function. Let us define hash function as:
where 65 is the ASCII value of ‘A’. Now, let us complete hash value for each of the
identifier using the above hash function:
Hash value of identifier “ant” = ‘A’ – 65 = 65 – 65 = 0. So, insert “ant” into ht[0]
Hash value of identifier “dog”= ‘D’ – 65 = 68 – 65 = 3. So, insert “dog” into ht[3]
Hash value of identifier “cat” = ‘C’ – 65 = 67 – 65 = 2. So, insert “cat” into ht[2]
Hash value of identifier “bat” = ‘B’ – 65 = 66 – 65 = 1. So, insert “bat” into ht[1]
Hash value of identifier “eagle” = ‘E’ – 65 = 69 – 65 = 4. So, insert “eagle” into ht[4]
Hash value of identifier “fish” = ‘F’ – 65 = 70 – 65 = 5. So, insert “eagle” into ht[5]
Hash value of identifier “ape” = ‘A’ – 65 = 65 – 65 = 0. So, insert “ape” into ht[0]
Now, the hash table for first six identifiers is shown below:
The seventh identifier “ape” whose hash value is 0 cannot be inserted into ht[0]
because, an identifier is already placed in ht[0]. This condition is called overflow
or collision. How to avoid “overflow” is discussed in the section 12.6.3.1.
When there is no overflow, the time required to insert, delete or search depends
only on the time required to compute the hash function and the time to search on
location in ht[i]. Hence, the insert, delete and search times are independent of n
which is the number of items
Data Structures using C - 12.15
12.6.2 Hash functions
Now, let us see “What are various types of hash functions?” The popular hash
functions are:
Division method
Mid square method
Folding method
Converting keys to integers
12.6.2.1 Division method
In this method, we choose a number m which is prime value and it is larger than the
number of given elements n in array a. Here, the key item k is divided by some
number m and the remainder is used as the hash value. Formally, it is written as:
h(k) = k % m
Because, we are taking modulo m values, the integers we get from the above function
are in the range: 0 to m – 1. Refer example 12.8 to know the details of how to create
hash table and using the hash value, how an item can be accessed.
h(k) = l
where l is obtained by removing digits from both ends of k2. By selecting the middle
portion of squared number, different keys are expected to give different hash values.
Normally, the size of the hash table using this technique will be power of 2.
Now, let us see “How to avoid overflow in hashing?” or “How to avoid collision?”
The various hashing techniques using which collision can be avoided are:
Open addressing
Chaining
In open addressing hashing, the amount of space available for storing various data is
fixed at compile time by declaring a fixed array for the hash table. So, all the keys are
stored in this fixed hash table itself without the use of linked lists. In such a table,
collision can be avoided by finding another, unoccupied location in the array. The
collision can be avoided using linear probing.
Let us create a hash table. To create a hash table, we need the following:
Initial hash table
Select the hashing function
Find the index of each location in the hash table
The empty hash table is indicated by storing 0 values in each location as shown
below:
12.18 Sorting and searching
a[0] a[1] a[2] a[3] a[4] The code: for (i = 0; i < HASH_SIZE; i++)
0 0 0 0 0 {
a[i] = 0;
}
The function to create initial hash table can be written as shown below:
int H(int k)
{
return k % HASH_SIZE;
}
12.7.3 Find the index for hash table given hash value
Now, let us display index of each item. As you know, it is very easy. This can be done
using the following code:
Now, the above code can also be written using % operator as shown below:
Data Structures using C - 12.19
for (i = 0; i < HASH_SIZE; i++)
{
printf(“%d “, i % HASH_SIZE); // 0 1 2 3 4
}
Now, the question is “I want to display all indices starting from specified position
say pos. Hoy we can achieve?” This can be done by modifying the above code by
adding pos to i and then perform mod operation as shown below:
So, index to hash table from pos is given by: index = (pos + i) % HASH_SIZE
So, Index to hash table from h_value is given by: index = (h_value + i) % HASH_SIZE
h_value = H(item);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
a[index];
}
The empty slot can be obtained by comparing a[index] with 0 in the above code. The
modified code can be written as shown below:
12.20 Sorting and searching
h_value = H(item);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
if ( a[index] == 0) break; // empty slot found
}
When control comes out of the loop, “if a[index] is 0” then we can insert the item.
Otherwise, it means that “Hash table is full”. The corresponding code can be written
as shown below:
Example 12.12: Insert an item into the empty slot using linear probing
h_value = H(item);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
if ( a[index] == 0) break; // empty slot found
}
if ( a[index] == 0) // empty slot found
a[index] = item; // insert item into empty slot
else
printf(“Hash table is full\n”); // No empty slot
}
h_value = H(key);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
If all elements have been compared and still item is not present, control comes out of
the loop and key is not present and we display the message “Unsuccessful”. Now the
above code can be written as shown below:
h_value = H(key);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
h_value = H(key);
for (i = 0; i < HASH_SIZE; i++)
{
index = (h_value + i) % HASH_SIZE;
Now, the complete program to create a hash table, insert an item into the table and to
search for a key in the hash table can be written as shown below:
Example 12.14: C program to create hash table and to search for key
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 5
/* Insert: Example 12.11: Compute hash value using the function: H(k) = k % m */
/* Insert: Example 12.12: Insert an item into the empty slot using linear probing */
void main()
{
int a[10], item, key, choice, flag;
Example 12.15: Construct a hash table ht of size 15 (using open addressing method)
to store the following words: like, a, tree, you, first, find, a, place, to, grow, and, then,
branch and out
Solution: Now, we “Find hash address for each word”. Let us find the hash address
by taking the sum of positions of alphabets in the word and taking the remainder by
dividing it by 15. The various hash addresses or hash values are shown below:
12.24 Sorting and searching
ht
The word “first” with hash value 12 is inserted into ht[12] a shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree like first
ht
The next word “find” with hash value 3 should be inserted at ht[3]. But, a word is
already present. So, insert “find” at next free slot i.e., 4 as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find like first
ht
Data Structures using C - 12.25
The next word “a” with hash value 1 is already in ht[1]. So, select the next word
“place” with hash value 7. It is already occupied and hence insert at next empty space
8 as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find like place first
ht
The word “to” with hash value 5 is inserted at position 5 as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to like place first
ht
The word “grow” has the hash value 3 and since the location is full, the next available
location is 6 and so it is inserted at position 6 as show below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to grow like place first
ht
The word “and” has the hash value 4. It is full, next free slot is 9 and hence “and” is
inserted at location 9 as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to grow like place and first
ht
The word “then” has the hash value 2. It is full. It is inserted into next free location 10
as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to grow like place and then first
ht
The word “branch” has the hash value 1. It is full. It is inserted into next free location
11 as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to grow like place and then branch first
ht
The word “out” has the hash value 11. It is full. It is inserted into next free location 13
as shown below:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a you tree find to grow like place and then branch first out
ht
12.26 Sorting and searching
12.8 Chaining
The chaining technique is a very simple method using which collisions can be
avoided. This is achieved using an array of linked lists. If an item has to be inserted,
using a hash function, the hash value is obtained. This hash value is used to find the
list to which the item is to be added and using the appropriate function an item can be
inserted at the end of the list. If more than one key has same hash value, all the keys
hashed into same hash value will be inserted at the end of the list and thus collision is
avoided.
The searching using this technique takes less time. When an item has to be
searched, we find the hash value using hash function. This hash value corresponds to
an index which will be used to obtain the address of the list. If the address of the list is
NULL, the item we search for is not present and report unsuccessful search. If the
address of the list is not NULL, that list is traversed sequentially to search for the
item. If an item is found in the list, report successful search and if end of list is
encountered report unsuccessful search.
Example 12.16: Construct a hash table ht of size 5 (using chaining method to avoid
collision) and store the following words: like, a, tree, you, first, find, a, place, to,
grow, and, then, branch, out
Solution: A hash table of size 5 indicates that it is required to construct a hash table
by using an array of 5 linked lists. The empty hash table can be written as shown
below:
0 \0 The code:
for (i = 0; i < HASH_SIZE; i++) a[i] = NULL;
1 \0
2 \0
3 \0
4 \0
Design of Hashing function: Let us design a simple hashing function which will
accept a string consisting of a key and add the positions of each letter of the string and
compute the remainder by dividing the sum by the size of the table. The size of the
hash table is assumed to be 5 and so while taking the remainder, divide the sum by 5
so that all the hash values of all the words lie within 0 and 4. So, the hash function is
given by:
H(str) = p0 + p1 + p2….pn-1 where each pi is the position of letter in string str.
Data Structures using C - 12.27
The various hash values for all the words in the given list are shown below:
Construction of Hash table: Now, let us see how to construct a hash table. Since the
size of the hash table is 5, we will have an array of 5 rows with each row having a
linked list. Obtain the words one by one, find the hash address and insert at the end of
the list in the appropriate location in the array. The hash table obtained by computing
the hash values is shown below:
0 TO \0
1 A YOU BRANCH OUT \0
2 LIKE FIRST PLACE THEN \0
3 TREE FIND GROW \0
4
AND \0
Observe that hash value of word ‘TO’ is 0 and so, it is inserted into h[0]. The hash
value of the words ‘LIKE’, ‘FIRST’, ‘PLACE’ and ‘THEN’ is 2 and the words are
inserted into h[2] at the end of the list one by one as and when they are scanned. Thus,
a hash table can be created. Now, the equivalent code can be written as shown below:
12.28 Sorting and searching
Example 12.17: Insert an item into hash table (represented as array of linked lists)
h_value = H(item);
Example 12.18: search for a key in hash table (represented as array of linked lists)
Now, the complete program to create a hash table, insert an item into the table and to
search for a key in the hash table can be written as shown below:
Example 12.20: C program to create hash table and to search for key
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 5
struct node
{
int info;
struct node *link;
};
/* Insert: Example 8.6: To insert an item at the rear end of the list */
/* Insert: Example 8.13: Search for a key item in the list */
/* Insert: Example 12.11: Compute hash value using the function: H(k) = k % m */
/* Insert: Example 12.17: Insert item into hash table (as array of linked lists)*/
12.30 Sorting and searching
/* Insert: Example 12.18: Search for an item in the hash table (adjacency list */
/* Insert: Example 12.19: Display the hash table */
void main()
{
NODE a[10];
int item, key, choice, flag, i;
for ( i = 0; i < HASH_SIZE; i++) a[i] = NULL; // Create initial hash table
for (; ;)
{
printf(“1: Insert 2: Search\n”);
printf(“3: Display 4: Exit\n”);
printf(“Enter the choice : “);
scanf(“%d”, &choice);
switch (choice)
{
case 1: printf(“Enter the item : “); scanf(“%d”, &item);
insert_hash_table(item, a);
break;
case 2: printf(“Enter key item : “); scanf(“%d”, &key);
flag = search_hash_table (key, a);
if (flag == 0)
printf(“Key not found\n”);
else
printf(“Key found\n”);
break;
case 3: printf(“Contents of hash table\n”);
display_hash_table(a);
printf(“\n”);
break;
default: exit(0);
}
}
}
Data Structures using C - 12.31
12.9 Address calculation sort
The Address Calculation Sort is also called Hash Sort. In this technique, a particular
kind of hashing is used. The hashing function hash() should have the property that if
x1 x2, then hash(x1) hash(x2). The function which exhibits this property is called
order-preserving or non-decreasing hashing function. If this hashing function is used
to hash an item to which some previous items have already been hashed, then this item
is placed in such a way that all the items hashed to a particular value should be
arranged in ascending or descending order. Thus all the items in one sub-file will be
less than or equal to the elements in the subsequent sub-files. When all the items have
been placed in this order into sub-files, finally concatenate the items in each sub-file
to get the sorted elements. For example, consider the items shown below.
57, 45, 36, 91, 28, 79, 35, 68, 89, 20, 62, 43, 84, 55, 86
Since the digits available are 0 to 9, we use an array of pointers a[10], where each
location in this array points to the first item in the sub-file whose first digit is same.
Hash value for this problem can be obtained by selecting the largest number and
finding at which position the most significant digit is present. For example, for the
item 45 hash value is 10 because the digit 4 is in tens position. For the item 199 hash
value is 3 since the digit 1 is in hundredth position. The hash value of a largest
number can be obtained using the function shown below:
After finding the hash value, divide each item to be sorted using this hash value and
insert the item into the appropriate location. For example, if item is 45 and hash value
is 10, 45/10 is 4. So, insert 45 into the 4th location. If some items are already present
in this location, insert it in the appropriate position such that items in that location are
in order. For this reason, we use an array of ordered linked list. Once all the items are
inserted in this way, obtain the items present in each list in the array one by one and
display. The C function to sort the items using Address Calculation Sort(Hash Sort) is
shown below:
h_value = hash(a,n);
b[digit] = insert(a[i],b[digit]);
}
The C program to sort the items using Address calculation sort (hash sort) is shown
below:
Data Structures using C - 12.33
Example 12.23: C program to sort using Address Calculation Sort (Hash Sort)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct node
{
int info;
struct node *link;
};
void main()
{
int n, a[40];
hash_sort(a,n);
Now, let us create a hash table for storing records of employee details consisting of id
and name of the employee and search for an employee id. The structure declaration
can be written as shown below:
#define HASH_SIZE 5
The program designed in the section 12.7 (see section 12.7 for detailed design) is
slightly modified to incorporate structures. The complete program is shown below:
Example 12.24: Create a hash table for employee record and search using id
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HASH_SIZE 5
Exercises
1) How insertion sort works? Sort the elements 25 75 40 10 20 using insertion sort
2) Write C function to sort items using insertion sort
3) How to sort the following elements using Radix sort?
4) Write a function to sort the numbers in ascending order using radix sort
5) What is hash value? What is hash function? What is a hash table? What is
hashing?
6) What are the different types of hashing techniques?
7) What is static hashing?
8) Construct a hash table ht for storing various identifiers such as “cat”, “rat”, “mat”,
“sat”, “pat”, “bat”, “eat”, “fat”, “hat”, “vat” using linear probing whose hash table
size is 5
9) What are various types of hash functions?
10) When overflow occurs? or What is collision during hashing?
11) How to avoid overflow in hashing? or How to avoid collision?
12) What is linear probing? Write a function to insert an item into the empty slot using
linear probing
13) Write a function to search for an item in the hash table using linear probing
14) Construct a hash table ht of size 15 (using open addressing method) to store the
following words: like, a, tree, you, first, find, a, place, to, grow, and, then, branch
and out
15) Construct a hash table ht of size 5 (using chaining method to avoid collision) and
store the following words: like, a, tree, you, first, find, a, place, to, grow, and,
then, branch, out
16) C function for Address Calculation Sort (Hash Sort)