Data Structures and Algorithms
Data Structures and Algorithms
Data Structures and Algorithms
Appendix
398
399
float balance = 0;
public:
void setBalance(float bal)
{ balance = bal;
}
float getBalance()
{ return balance; }
};
(b) There is an important feature in C++, called an inline function, which is commonly used with
classes. We can create short functions that are not actually called; rather, their code is expanded in
line at the point of each invocation (call). We define the inline function using inline keyword.
For example, in the following program, the function area() is expanded in line instead of called:
#include <iostream.h>
inline float area(float r)
{
return( 3.14*r*r );
}
void main()
{
cout << area(1.0);
cout << area(2.0);
}
// prints 3.14
// prints 12.56
As far as the compiler is concerned, this program is equivalent to the following one:
#include <iostream.h>
void main()
{
cout << (3.14* 1.0* 1.0);
cout << (3.14* 2.0* 2.0);
}
// prints 3.14
// prints 12.56
The inline functions allow us to create very efficient code. Each time a function is called, a
significant amount of time and memory are generated by the calling and return mechanism. The
inline functions produce faster run times. It is best to inline only very small functions. The
compiler does not inline the recursive functions. Inline functions may be class member functions.
Friend function
The concept of data hiding and encapsulation dictates that non-member functions should not be
able to access an objects private or protected data. However, there are situations where such rigid
discrimination leads to considerable inconvenience. In order to access the non-public members of
a class, C++ provides the keyword friend. To declare a friend function, its prototype is included
within the class, preceding it with the keyword friend. The following program illustrates the use
of a friend function.
#include <iostream.h>
class Rectangle
{
private:
int length, width;
public:
400
Rectangle(int a, int b)
// constructor
{ length = a;
width = b;
}
friend int area(Rectangle ob); // friend function
};
/*
*/
int area(Rectangle ob)
{
return ob.length * ob.width;
}
void main()
{
Rectangle rect(15, 10);
cout << area(rect);
}
// prints 150
In this example, the area() function is not a member function of Rectangle class. Notice that
area() is called without the use of the dot operator in the main(). Because it is not a member
function, it does not need to be qualified with an objects name.
Although there is nothing gained by making area() a friend rather than a member function, there
are some circumstances in which friend functions are quite valuable.
2.
(a)
(b)
(c)
(d)
The friend functions make the creation of some types of I/O functions easier. The friend
functions may be desirable in some cases, two or more classes may contain members that
are interrelated relative to other parts of the program.
(a) Operator overloading is closely related to function overloading. We can overload most operators
so that they perform special operations relative to classes that we create. For example, a class that
maintains a stack might overload + to perform a push operation and to perform a pop. When an
operator is overloaded, none of its original meanings are lost.
Example: we create two objects of ComplexNumber class and add those objects to get the sum of
the two complex numbers. It is similar to adding two integer numbers. Here, the + operator is
overloaded.
ComplexNumber ob1, ob2, ob3;
ob3 = ob1 + ob2;
401
After overloading the appropriate operators, we can use objects in expressions in just the same
way that we use built-in data types.
We can make input/output (e.g., std::cin / std::cout) operations easy.
We can customize C++ I/O system by creating our own manipulator functions.
(c) Examples:
Concatenate two string objects.
Add, subtract, and multiply matrices.
#include <iostream>
using namespace std;
class Complex
{ private:
double real, imag;
public:
Complex() { }
Complex(double r, double i)
{ real = r; imag = i; }
// default constructor
// constructor
// Overload + operator
// Overload << operator
::
.*
402
programming, it was necessary to create an I/O system that could operate on user-defined objects.
In addition to support for objects, there are several benefits of using C++ I/O system even in
programs that do not make extensive use of user-defined objects. For all new code, we should use
the C++ I/O system. The C I/O is supported by C++ only for compatibility.
(b) int main()
{
int numb = 1;
while(numb)
{ cout << "Enter a value: ";
cin >> numb;
}
cout << "Normal termination..." << endl;
return 0;
We defined an int variable and the while-loop would be checking the variable, numb for true or
false (false = 0 and true = non-zero).
As long as we enter numeric value against the input
statement: cin, the loop will continue for non-zero value. If you enter digit zero, the loop will be
terminated (normal termination). If you enter non-numeric character, the stream encounters an
error. As the stream set "failure flags", the program will go into an infinite loop. Until we clear
"failure flags", the system does not accept any input.
(c) #include <iostream>
#include <limits>
using namespace std;
int main()
{ int numb = 1;
while(numb)
{ cout << "Enter a value: ";
cin >> numb;
cout << "You entered: " << numb << endl;
if( cin.fail() )
{ std::cout<< You entered invalid data... << endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<streamsize>::
max(), '\n');
}
}
cout << "Normal termination..." << endl;
return 0;
}
403
The function clear() resets the stream status flags. For example, when the stream encounters an
error (e.g., it wants to read an int and you enter non-numeric characters), it resets a failure flag.
Even after you skip over the invalid data with ignore(), you cannot continue reading until you
reset the flags with clear(). Notice the input/output.
4.
What is a Circular List? Write a C++ program to search in a circular linked list that has a
header node
The null pointer in the last node of a linked list is replaced with the address of its first node such a
list is called a circularly linked list or simply a circular list. Following figure illustrates the structure of
a circular list.
data
11
22
33
44
A circular list
Circular lists have certain advantages over linear linked lists. From any node in a circular list, it is
possible to reach any other node in the list. If we traverse the list from a given node, we finally reach
the starting point. Deletion of a node from a circular list becomes easy. Certain operations on circular
lists, such as concatenation and splitting become more efficient.
There is a disadvantage in using circular lists. Without some care in processing, it is possible to
get into an infinite loop. In processing a circular list, it is important that we are able to identify the end
of the list by placing a special node called the list head. This technique has an advantage of its own
the list can never be empty. Following figure represents a circular list with a list head. The data field in
the head node is a dummy one (not used).
head
11
22
33
44
C++ program to search in a circular linked list that has a header node: CircularList class is
defined, before writing the search() function.
class CircularList
{ private:
struct Node
{ int data;
Node *next;
};
// data item
// points to next node in the list
404
Node *head;
public:
CircularList();
// constructor
void createList(int[], int);
// create list
void displayList();
bool search(int);
// returns true if search is successful; otherwise false.
};
bool CircularList::search(int key)
{
Node *p = head->next;
if( p == head )
{ cout << "List is empty" << endl;
return NULL;
}
while(
{ if(
p =
}
return
p != head )
p->data == key ) return true;
p->next;
false;
5.
(a) What is the structure to represent a node in a skip list. Write the constructor for skip list.
(b) Write a method in C++ to erase a pair in the dictionary with key theKey in a skip list
representation. What is the complexity of this method?
(a) Each element in the skip list is stored in a node. The level of the node is chosen randomly when
the node is created. Each SkipNode stores an array of forward pointers as well as a value which
represents the element stored in the node. The type of this element is templated.
template <class T>
struct SkipNode
{
T value;
SkipNode<T> **forward; // array of pointers
SkipNode(int level, const T &val) // constructor
{
forward = new SkipNode<T> * [level + 1];
memset(forward, 0, sizeof(SkipNode<T>*)*(level + 1));
value = val;
}
};
A structure that represents a SkipList is defined. It stores a pointer to a header node. The value
stored in the header node is irrelevant and is never accessed. The current level of the list is also
stored as this is needed by the insert, delete and search algorithms (MAX_LEVEL = 6).
template <class T>
struct SkipList
{
405
SkipNode<T> *head;
int level;
SkipList()
// constructor
{
head = new SkipNode<T>(MAX_LEVEL, T());
level = 0;
}
void
bool
void
void
print() const;
contains(const T &) const;
insert(const T &);
delet(const T &);
};
(b) We declare an update array and then search for the node to be deleted. If the node is found we set
the nodes in the update array to point at what |x| is pointing at. This effectively removes x from the
list and so the memory occupied by the node may be freed.
template <class T>
void SkipList<T>::delet(const T &value)
{
SkipNode<T> *x = header;
SkipNode<T> *update[MAX_LEVEL + 1];
// set update pointers to NULL
memset(update, 0, sizeof(SkipNode<T>*)*(MAX_LEVEL + 1));
// find record updates
for (int i = level; i >= 0; i--)
{
while(x->forward[i]!=NULL &&
x->forward[i]->value < value)
{
x = x->forward[i];
}
update[i] = x;
}
x = x->forward[0];
if (x->value == value)
{ for (int i = 0; i <= level; i++)
{
if (update[i]->forward[i] != x)
break;
update[i]->forward[i] = x->forward[i];
}
delete x;
// decrease list level
while (level > 0 && header->forward[level] == NULL)
{
level--; }
}
}
406
6.
(a) State the conditions under which insertion of a vertex in a red-black tree will result
in a
sequence of recoloring steps that terminate with the root changing color.
(b) Will the root of a red-black tree always be black after performing a deletion operation?
Justify with an example?
(a) Before answering this question, it would be better to understand the properties of a red-black tree.
A red-black tree is a binary search tree where each node has a color attribute, the value of
which is either red or black. In addition to the ordinary requirements imposed on binary search
trees, the following additional requirements of any valid red-black tree apply:
Root property: The root is black.
External property: All leaves are black, even when the parent is black (The leaves are the null
children.)
Internal property: Both children of every red node are black.
Depth property: Every simple path from a node to a descendant leaf contains the same
number of black nodes, either counting or not counting the null black nodes.
Condition: (N: new node, P: parent, G: grandparent, and U: uncle)
i. If a new node N is inserted as a root of the tree, then it is repainted black to satisfy root
property.
ii. If both the parent P and the uncle U are red, then both nodes can be repainted black and the
grandparent G becomes red (to maintain depth property).
iii. Parent P is red and uncle U is black - new node N is the right child of P.
iv. Parent P is red and uncle U is black - new node N is the left child of P.
(b) Will the root of a red-black tree always be black after performing a deletion operation? YES.
Example 10.2 (Chap 10: Search Trees) would justify.
7.
(a) Prove that net T be a B-tree of order m and height h. Let d = [m/2] and let n be the
number of elements in T.
i.
2d h 1 1 n m n 1
ii.
n + 1
log m ( n + 1) h log d
+1
2
= (m h 1) /(m 1).
i =0
Since each of these nodes has (m-1) elements, so the maximum number of elements= maximum
number of nodes * number of elements per node
= ( m h 1) /( m 1) * ( m 1)
407
= (m h 1)
For example let m=200. So, the upper bound or maximum number of elements of 200-way search
tree of height 5 is
10
200 5 1 = ( 2 *100) 5 1 = 2 5 *10 5 1 = 32 *10 1
i =0
1
2
3
0
0,1
0,1,2
1
1+ m
0,1,2,3
1+m+ m
+ m
0,1,2,3,4
.
.
0,1,2,3
1+m+ m
.
.
+ m +m
1+m+ m
+ + m
0 , 1 , 2 , 3 , , h-1
1+m+ m
+m
5
.
.
h-1
h
1+m+ m +m
1+m+m
+m
h 1
3
3
h2
+m
h 1
= 1* (m -1)/ (m-1)
h
= (m -1)/ (m-1)
Therefore, maximum number of nodes = 1+m+ m
+m
h 1
= m -1/m-1
(upper bound of n)
(eq.1)
2
n 2d h1 1
i.e. 2d h 1 1 n
(lower bound of n)
2d h 1 1 n m h 1
From eq.1 we have
n mh 1
(n + 1) m h
Taking logs on both sides to the base m
log m (n + 1) log m m h
log m (n + 1) h log m m
h +1
h +1
, so the
(eq.2)
408
log m (n + 1) h
(eq. 3)
n 2d h1 1
(n + 1) 2d h 1
(n + 1) / 2 d h1
Taking log on both sides to the base d we have
n +1
) log d d h 1
2
n +1
log d (
) ( h 1) log d d
2
n +1
log d (
) h 1
2
n +1
log d (
) +1 h
2
n +1
h log d (
) (eq. 4)
2
log d (
i.e. ,
log m ( n + 1) h log d (
n +1
)
2
(b) A splay tree is a self-organizing binary search tree, which uses rotations to move any accessed key
to the root. Frequently used or recently accessed nodes thus sit near the top of the tree, allowing a
fast search - much faster than O(log n) in some cases.
Implementing Dictionary Operations:
8.
A remove(x) is performed by searching for x. Key x is now at the root of the tree with two
subtrees L and R. If L is empty, R becomes the new tree, and vice-versa.
012345678901234
the caterpillar
pill
^
the caterpillar
pill
^
the caterpillar
pill
^
the caterpillar
pill
^
the caterpillar
pill
^
409
Match fails. There is no space (' ') in the search string (P), so
move it right along 4 places.
Match fails. There is no e either in P, so move along 4.
Character 'l' matches, so continue trying to match right to left in
P.
Match fails. But there is an 'i' in pill so move along to
position where the 'i's line up.
Matches, as do all the rest..
This still only requires knowledge of the pattern string (P), but we require an array containing
an indication, for each possible character that may occur, where it occurs in the search text string
(T) and hence how much to move along. So, index['p'] = 0, index['i'] = 1, index['l'] = 3 (index the
rightmost 'l' where repetitions) but index['r'] = -1 (let the value be -1 for all characters not in the
pattern string). When a match fails at a position i in the text string, at a character C we move along
the pattern string to a position where the current character in the text string (T) is above the
index[C]th character in the P (which we know is a C), and start matching again at the right hand
end of the string. (This is only done when this actually results in the string being moved right otherwise the string (P) is just moved up one place, and the search started again from the right
hand end.)
(a)
(b)
(c)
(d)
(a) When an exception is thrown and control passes from a try block to a handler, the C++ run time
calls destructors for all automatic objects constructed since the beginning of the try block. This
process is called stack unwinding.
If an exception is thrown during construction of an object consisting of sub-objects or array
elements, destructors are only called for those sub-objects or array elements successfully
constructed before the exception was thrown. A destructor for a local static object will only be
called if the object was successfully constructed. If during stack unwinding a destructor throws an
exception and that exception is not handled, the terminate() function is called.
(b) Difference between const char *myPointer and char *const myPointer:
410
myPointer = str2;
cout << myPointer << endl;
// myPointer = str2;
// cout << myPointer << endl;
myPointer is a const pointer. It must be
gives an error.
Output of this program code is:
ABCD
411
(b) A virtual function is a member function that is declared within a base class and redefined by a
derived class. To create a virtual function, precede the function's declaration in the base class with
the keyword virtual. When a class containing a virtual function is inherited, the derived class
redefines the virtual function to fit its own needs. Virtual functions support run-time
polymorphism using a pointer. The following program illustrates the concept of a virtual function.
class base
{ public:
virtual void show() { cout << "base \n"; } // virtual function
};
class derived1 : public base
// derived class 1
{ public:
void show() { cout << "derived1 \n"; }
};
class derived2 : public base
// derived class 2
{ public:
void show() { cout << "derived2 \n"; }
};
int main()
{ base *ptr;
derived1 d1;
derived2 d2;
ptr = &d1;
ptr->show();
ptr = &d2;
ptr->show();
return 0;
3.
return 1; }
// read a char from 1st file and write onto 3rd file
412
return 1; }
while( in2.get(ch) )
// read a char from 2nd file and write onto 3rd file
out << ch;
in2.close(); out.close();
return 0;
}
(b) Program to count the number of lines in the given file, DATA.TXT:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{ ifstream in("DATA.TXT", ios::in); // Open file, DATA.TXT for reading
if(!in)
{ cout << "Cannot open file."; return 1; }
int nlines = 0;
char str[255];
while(in)
{ in.getline(str, 255); // delimiter defaults to '\n'
if(in) nlines++;
}
cout << "Number of lines is " << nlines << endl;
in.close();
return 0;
}
4.
Define the Abstract data type for Queue. Write a C ++ program to implement Queue ADT using arrays
The elements in a queue are of generic type Object. The queue elements are linearly ordered from the
front to the rear. Elements are inserted at the rear of the queue (enqueued) and are removed from the
front of the queue (dequeued).
A Queue is an Abstract Data Type (ADT) that supports the following functions:
insert(obj):
int size():
413
Type Object may be any type that can be stored in the queue. The actual type of the object will be
provided by the user.
C ++ program to implement Queue ADT using arrays: Program 6.1: ArrayQueue class.
5. (a) What is a dictionary? Define the abstract data type for it? Write the abstract class for the
dictionary?
(b) Give the applications of dictionary or dictionary with duplicates in which sequential access is
desired.
(a) An abstract data type that supports the operations insert, delete, and search is called a dictionary.
Dictionaries have applications in the design of many algorithms. A dictionary is a collection of
elements. Each element has a field called key (key-value pair). Every key is usually distinct
(unique). A collection of student records (key-value) in a university is an example of a dictionary.
Each record is uniquely identified by roll number (key) and value contains name of the student,
date of birth, class, branch and so on.
Dictionary ADT has the following operations:
Empty(); Determine whether or not the dictionary is empty.
Size(): Determine the dictionary size (i.e., number of pairs).
Insert(): Insert a pair into the dictionary.
Find(): Search the pair with a specified key.
Delete(): Delete the pair with a specified key
(b) Consider the database of books maintained in a library system. When a user wants to check
whether a particular book is available, a search operation is called for. If the book is issued to the
user, a delete operation can be performed to remove this book from the set of books. When the
user returns the book, an insert operation is performed to put back the book into the set.
Any element in the dictionary can be retrieved by simply performing a search on its key
(random access). Elements are retrieved one by one in ascending order of the key field (sequential
access).
Keys are not required to be distinct (dictionary with duplicate keys). Word dictionary is an
example. Pairs are of the form (word: meaning) - may have two or more entries for the same word.
For example, the meanings of the word, method have the following: (method: technique),
(method: procedure), (method: practice), etc.
6. (a) State the conditions under which insertion of a vertex in a red-black tree will result in a sequence
of recoloring steps that terminate with the root changing color.
(b) Will the root of a red-black tree always be black after performing a deletion operation? Justify
with an example?
This question is repeated refer set-1 of this section.
7.
(a) Prove that net T be a B-tree of order m and height h. Let d = [m/2] and let n be the
number of elements in T.
i. 2d h 1 1 n m n 1
414
ii.
n + 1
log m ( n + 1) h log d
+1
2
[10+6]
(a)
(b)
(c)
(d)
What are the differences between a C++ struct and C++ class?
What is the difference between compiling and linking?
In a large program what problems might occur from putting C++ code in headers?
What is an inline function and when would you use it?
class
Everything in class is private, but we can change
it.
Class provides data hiding.
Inheritance of class is private.
Classes support polymorphism.
(b) Compiling: The process involved in converting the source code in which we had written the
program to solve a problem into a formal language called object code is known as compiling.
Since the system cannot understand our source code it is often necessary to convert it into the
system understandable form which is called the object code for this purpose we use compiling
Linking: The process of obtaining the desired output from the object code which we had
generated from the source code by compilation is known as linking. Usually linking is performed
only after the completion of compilation process, without compilation linking will not take place.
(c) Mostly the C++ headers contains the preprocessor directives like iostream with which we will
start our C++ program, the important problem that might occur from putting C++ code in headers
is unable to find iostream preprocessor directive. Apart from syntax errors, logical errors and
runtime errors, this is the most probably existing problem in a C++ program.
(d) In solving the C++ problem of a macro with access to private class members, all the problems
associated with preprocessor macros were eliminated. This was done by bringing the concept of
macros under the control of the compiler where they belong. C++ implements the macro as inline
function, which is a true function in every sense. Any behavior you expect from an ordinary
function, you get from an inline function. The only difference is that an inline function is
expanded in place, like a preprocessor macro, so the overhead of the function call is eliminated.
Thus, we should (almost) never use macros, only inline functions.
415
Any function defined within a class body is automatically inline, but we can also make a nonclass function inline by preceding it with the inline keyword. However, for it to have any effect,
we must include the function body with the declaration, otherwise the compiler will treat it as an
ordinary function declaration.
It is only useful to implement an inline function when the time which is spent during a
function call is long compared to the code in the function. Using the keyword inline is not really a
command for the compiler. Rather, it is a request - the compiler may or may not grant.
2.
This is the case where the main() function creates a copy of a Derived class by using the new
operator and later destroys that instance using delete. When we compile and run this program,
here is what it displays:
Base: constructor
Derived: constructor
Base: destructor
Notice the output Derived: destructor is missing. Why is there not a call to the
destructor of the Derived class?
The reason is that when we call the constructor, we used the Derived class name with the
new operator and, therefore, the compiler correctly created the right type of object. The ptr is
declared to be a pointer to the Base class. When we use the statement: delete ptr; the
compiler cannot tell that we are destroying a Derived class instance. Because delete does its job
by calling the destructor for the class (Base), we can solve the problem by declaring the destructor
of the Base class virtual. That way the destructor will be invoked through the virtual function
table and the correct destructor will get called. We modify the base class destructor as:
416
We notice that the order of calls to the destructors is the reverse of the order in which the
constructors were called. Thus, the virtual destructor ensures that the object is properly destroyed.
How do we decide whether to declare a destructor virtual? If a class has any virtual functions,
then chances are that objects of classes derived from this one will be manipulated through pointers
to get polymorphism. In this case, we should declare a virtual destructor for the class.
(b) Refer to Q # 2(b) of set-1 in section C.2.
3.
What is the difference between the C++ standard library, and the C++ standard template library?
Standard Function Library:
C++ defines two types of libraries. The first is the standard function library. This library consists of
general-purpose, stand-alone functions that are not part of any class. The function library is inherited
from C. The second library is the object-oriented class library.
The standard function library is divided into the following categories:
I/O
Mathematical
Dynamic allocation
Miscellaneous
All compilers supply more functions than are defined by Standard C/C++. These additional functions
typically provide for operating-system interfacing and other environment-dependent operations.
Standard Template Library:
The C++ STL (Standard Template Library) is a generic collection of class templates and algorithms
that allow programmers to easily implement standard data structures like queues, lists, stacks, and so
on.
STL solves many standard data structure and algorithm problems. The STL is the first choice in all
programming. Although you many need to write certain specialized data structures, or build on top of
the STL structures, you should never be writing duplicate code. The STL provides functionality in
several areas: containers, iterators, and algorithms.
A container is a holder object that stores a collection of other objects (its elements). They are
implemented as class templates, which allow a great flexibility in the types supported as elements. The
container manages the storage space for its elements and provides member functions to access them,
either directly or through iterators (an iterator is like a pointer).
417
Iterators provide uniform ways to go over the elements of the STL containers, as well as arrays.
There are iterators for going sequentially forward and/or backward as well as random access iterators.
Iterators are used by the STL algorithms. Iterator syntax uses the ++, --, and * operators which are
familiar to users of pointers.
The STL provides a bunch of useful algorithms - like searching, sorting, and general-purpose
iterating algorithms - that can be used on a variety of data structures. The algorithms are implemented
as function templates and perform various operations on elements of containers.
4. What is a sparse matrix? Explain about the linear list representation of a sparse matrix?
A matrix with a relatively high proportion of zero elements is called a sparse matrix. They are
commonly used in scientific applications and contain hundreds or even thousands of rows and columns.
The representation of such huge matrices is wastage of storage. One may save space by storing only
those entries which may be nonzero. Consider the following matrix, A:
column
row
12
A=
up
row
col
The val, row, and col fields of one node contain the value, row, and column indices of one matrix
element, respectively. The fields left and up are references (pointers) to the next element in a circular
list containing matrix elements for a row or column, respectively. The left refers to the node with the
next smallest column subscript, and up refers to the node with the next smallest row subscript. A
multilinked structure that uses nodes of this type to represent the matrix A is shown in the following
figure.
A circular list represents each row and column. A columns list can share nodes with one or more
of rows lists. Each row and column list has a head node such that more efficient insertion and deletion
algorithms can be implemented. The head node of each row list contains 0 in the col field; similarly,
the head node of each column list has 0 in the row field. The row head nodes are referred to by
respective elements in the array of object references (pointers) arow. Elements of acol refer to the
column head nodes. A row or column without nonzero elements is represented by a head node whose
left or up field refer to itself.
418
acol [0]
acol [1]
arow [0]
acol [2]
X
6 0 2
9 0 4
arow [2]
acol [4]
arow [1]
acol [3]
2 1 0
7 1 3
8 1 4
0
X represents
a null reference
arow [3]
12 3 2
5.
(a) Explain the linear probing method in Hashing. Explain its performance analysis.
(b) What is hashing with Chains? Explain? Compare this with Linear Probing?
(a) Linear probing is a collision-resolution technique: A hash table in which a collision is resolved by
putting the item in the next empty place in the array following the occupied place.
Suppose a small company with 12 employees assigns a 2-digit employee numbers to each
employee. We map these 12 employee record keys into 13 two-digit table indices: 00, 01, 02 11,
12. We apply the division method of hashing for employee numbers. We choose a prime number
M (table size) close to 12, such as M = 13. Now the table index range is 0 to 12 (0 to M-1).
Initially, we insert 10 employees into the hash table. So we take an array size of 13 (a prime
number). In linear probing we search sequentially for vacant cells. For example, the employee
number 17 is occupied in the cell 4. When we try to insert 69 which maps to index 4 of the table,
we go to 5, then 6, and so on, incrementing the index until we find an empty cell. This is called
linear probing because it steps sequentially along the line of cells. As cell 4 is not empty, we place
69 at index 6.
Table index
key
Hash value
0
52
0
1
40
1
2
28
2
3
66
1
4
17
4
5
43
4
6
69
4
8
21
8
9
35
9
10
11
12
12
12
We generate hash values for 10 employee numbers (keys) using division method (hash value or
table index = key mod 13) and insert them into the hash table.
419
Insertion and searching in hash tables can approach O(1) time. If no collision occurs, only a
call to the hash function and a single table reference are necessary to insert a new item or find an
existing item. This is the minimum access time.
If collisions occur, access times become dependent on the resulting probe lengths. Each cell
accessed during a probe adds another time increment to the search for a vacant location (for
insertion) or for an existing cell. During an access, a cell must be checked to see if it is empty, and
- in the case of searching or deletion - if it contains the desired item. Thus an individual search or
insertion time is proportional to the length of the probe. This is in addition to a constant time for
the hash function. The average probe length (and therefore the average access time) is dependent
on the load factor (the ratio of items in the table to the size of the table). As the load factor
increases, probe lengths grow longer.
(b) In open addressing, collisions are resolved by looking for an open cell in the hash table. A
different approach is to install a linked list at each index in the hash table. A data item's key is
hashed to the index in the usual way, and the item is inserted into the linked list at that index.
Other items that hash to the same index are simply added to the linked list; there is no need to
search for empty cells in the primary array. This method is called separate chaining (hashing with
Chains), because items that collide are chained together in separate linked lists. The following
figure shows how separate chaining looks. A portion of the above table is considered to show
separate chaining.
Separate chaining is conceptually somewhat simpler than the linear probing. However, the
code is longer because it must include the mechanism for the linked lists, usually in the form of an
additional class. If the number of items that will be inserted in a hash table is not known when the
table is created, separate chaining is preferable to linear probing. The exception is the situation
where plenty of memory is available and the data would not expand after the table is created; in
this case linear probing is somewhat simpler to implement. When in doubt, use separate chaining.
52
40
28
x
66
3
4
5
17
43
69
6. (a) What is an AVL search tree? How do we define the height of it? Explain about the balance factor
associated with a node of an AVL tree.
(b) Explain how an AVL tree can be used to sort a sequence of n elements in O(n log n) time
(a) An AVL tree is a binary search tree in which the heights of the left and right subtrees of the root
differ at most by 1 and in which the left and right subtrees are again AVL trees. Each node of an
AVL tree is associated with a balance factor that is the left subtree has height greater than, equal to,
or less than that of the right subtree.
420
2
45
40
0
25
0
25
45
0
15
35
10
50
-1
55
20
0
30
40
15
35 1
T1
30
T2
(a) Prove that net T be a B-tree of order m and height h. Let d = [m/2] and let n be the
number of elements in T.
i. 2d h 1 1 n m n 1
ii.
n + 1
log m ( n + 1) h log d
+1
2
(a) Compare various forms of type cast operations (in C and C++ styles). Tell about overloading of
these operations.
(b) How to set default values of function arguments? What are pros and cons of use of this C++
opportunity? Whats its alternative?
(c) When writing catch operator we can write directly type of exception as a type of its argument,
pointer to a type of exception or reference to a type of exception. Compare these approaches.
421
(a) We can force an expression to be of a specific type by using a cast. The general form of a cast is:
(type) expression
where type is a valid data type.
For example, to make sure that the expression a/2 evaluates to type float, we write:
(float) a/ 2
where a is an int.
Casts are technically operators. As an operator, a cast is unary. Although casts are not usually used,
they can be very useful when needed.
C++ defines five casting operators. The first (given above) is the traditional-style cast inherited
from C. The other four are (these operators give us additional control over how casting takes
place):
dynamic_cast: performs a runtime cast that verifies the validity of a cast. The purpose of
the cast is to perform casts on polymorphic types.
const_cast: is used to explicitly override const and/or volatile in a cast. The most
common use of const_cast is to remove const-ness.
static_cast operator performs a non-polymorphic cast. It can be used for any standard
conversion.
reinterpret_cast operator converts one type into a different type. For example, it can
change a pointer into an integer and vice-versa.
(b) C++ allows a function to assign a parameter a default value when no argument corresponding to
that parameter is specified in a call to that function. For example, myfunc() is declared with one
argument with a default value of 0.0;
void myfunc(double x = 0.0)
{
. . .
}
The first call passes the value 123.45 to x. The second call automatically gives x the default value
zero.
We can also use default parameters in an objects constructor. In some situations, default
arguments can be used as a shorthand form of function overloading that default arguments
sometimes provide an alternative function overloading.
Although default arguments can be a very powerful tool when used correctly, they can also be
misused. A function can perform efficiently by using default arguments and also be easy to use. It
allows flexibility. It is an alternative to regular arguments. If a function does not have a parameter,
then there is no need to declare default argument. No default argument should cause a destructive
action.
(c) Exception handling subsystem allows us to manage run-time errors in an orderly fashion. Using
exception handling, the program can automatically invoke an error-handling routine when an error
occurs.
C++ exception handling is built upon three keywords: try, catch and throw. Program
statements that we want to monitor for exceptions (errors) are contained in a try block. If an
exception (i.e., error) occurs within the try block, it is thrown (using throw). The exception is
caught, using catch, and processed.
422
It is important to understand that the code associated with a catch statement will be executed
only if it catches an exception. Otherwise, execution simply bypasses the catch altogether.
There can be more than one catch statement associated with a try. The type of exception
determines which catch statement is used. That is, if the data type specified by a catch matches
that of the exception, then that catch statement is executed and all others are bypassed. When an
exception is caught, argument will receive its value. Any type of data (pointer or reference) may
be caught, including classes that we create. If no exception is thrown (that is, no error occurs
within the try block), then no catch statement is executed.
2. (a) What is Hybrid inheritance? Write a program to illustrate the concept of Hybrid Inheritance.
(b) What is single inheritance? Write a program to illustrate the concept of single Inheritance.
(a) Hybrid inheritance: Consider the following example. We have four classes: student, test, sports,
and results. Here, student and sports are base classes, and test and results are the derived classes.
The test class is derived from base class student, and results is derived from test and sports. The
following figure and program illustrate the hybrid inheritance.
student
test
sports
results
#include <iostream>
using namespace std;
class student
{
private :
int rn;
char name[20];
public:
void getdata()
{ cout << "Enter Name and Roll-No: ";
cin >> name >> rn;
}
void putdata()
{ cout<< endl << name << "\t" << rn << "\t"; }
};
class test : public student
{
protected:
float m1, m2;
public:
void gettest()
{ cout<<"\nEnter your marks in ADS and UNIX: ";
cin >> m1 >> m2;
}
void puttest()
423
};
class sports
{
protected:
float score;
public:
void getscore()
{ cout << endl << "Enter your score: ";
cin >> score;
}
void putscore()
{ cout<< score << "\t"; }
};
class results : public test, public sports
{
private :
float total;
public :
void putresult()
{ total = m1 + m2 + score;
cout << total;
}
};
int main()
{
results s;
s.getdata();
s.gettest();
s.getscore();
s.putdata();
s.puttest();
s.putscore();
s.putresult();
cout<<endl;
return 0;
}
(b) A key feature of C++ classes is inheritance. Inheritance allows us to create classes which are
derived from other classes, so that they automatically include some of its "parent's" members, plus
its own.
When the access specifier for a base class is public, all public members of the base class
become public members of the derived class, and all protected members of the base become
protected members of the derived class. In all cases, the private members of base remain private to
the base and are not accessible by members of the derived class. For example, as illustrated in the
following program, objects of type derived can directly access the public members of base:
424
class base
{ int i, j;
public:
void set(int a, int b) { i = a; j = b; }
void show() { cout << i << " " << j << endl; }
};
class derived : public base
{ int k;
public:
derived(int x) { k = x; }
void showk() { cout << k << endl; }
};
void main()
{
derived ob(3);
ob.set(1, 2);
ob.show();
ob.showk();
3.
4.
Write a C ++ program using stack ADT that reads an infix expression, converts the expression to
postfix form and evaluates the postfix expression.
#include <iostream>
#include <vector>
#include <stack>
#include <string>
using namespace std;
class Postfix
{
stack< char, vector<char> > stk;
public:
string toPostfix(string infix)
{
infix = "(" + infix + ")"; // enclose infix expr within parentheses
string postfix = "";
/* scan the infix char-by-char until end of string is reached */
for( int i=0; i<infix.length(); i++)
{
char ch, item;
ch = infix.at(i);
if( isOperand(ch) )
// if(ch is an operand), then
425
426
stk.pop();
result = evaluate(tmp2, tmp1, ch);
stk.push(result);
}
}
return stk.top();
}
bool isOperand(char c)
{
return (c >= '0' && c <= '9');
}
bool isOperator(char c)
{
return( c=='+' || c=='-' || c=='*' || c=='/' );
}
int precedence(char c)
{
int rank = 2; // rank = 1 for '*' or
if( c == '+' || c == '-' ) rank = 1;
if( c == '(' ) rank = 0;
return rank;
}
int evaluate(int a, int
{ int res = 0;
switch(op)
{
case '+' : res =
case '-' : res =
case '*' : res =
case '/' : res =
}
return res;
}
'/'
b, char op)
(a+b);
(a-b);
(a*b);
(a/b);
break;
break;
break;
break;
427
5. (a) What is the structure to represent node in a skip list. Write the constructor for skip list.
(b) Write a method in C++ to find a pair with key theKey in a dictionary using skip list
representation? What is its complexity?
This question is repeated. Refer set-1 of this section.
6. (a) Explain about the LLr, LRr, LLb, LRb imbalances in a red-black tree with an example?
(b) Draw the sequence of rotations required to perform a single right rotation and a double LR rotation
in an AVL tree?
(a) A red-black tree is a binary search tree in which every node is colored either red or black. Every
null pointer is considered to be an external black node. The additional properties are
P1: The root and all external nodes are colored black.
P2: No root-to-external-node path has two consecutive red nodes.
P3: All root-to-external-node paths have the same number of black nodes.
In inserting new node red causes a violation of property P2. We shall say that the tree has become
imbalance.
N: new node, P: parent of N, G: grandparent of N.
Imbalance type LLb: P is the left child of G, N is the left child of P, and other child of G is black.
Imbalance type LLr: P is the left child of G, N is the left child of P, and the other child of G is red.
Imbalance type LRb: P is the left child of G, N is the right child of P, and the other child of G is
black.
NL: left child of N, NR: right child of N, etc.
G
GR
GR
PR
NL
NR
PR
LLr imbalance
NL
NR
P
GR
GR
PL
PL
NL
NR
LRr imbalance
NL
NR
428
G
P
P
GR
N
PR
NL
NL
NR
LLb imbalance
NR
PR
GR
P
GR
N
NL
PL
NR
PL
LRb imbalance
NL
NR
GR
(b) The sequence of rotations required to perform a single right rotation and a double LR rotation in
an AVL tree:
Right Rotation (RR)
C
To fix this, we will perform a single right rotation, rooted at C. This is done as:
B becomes the new root. C becomes Bs right child. A becomes Bs left child.
Double LR rotation (Left-Right rotation): Sometimes a single left rotation is not sufficient to
balance an unbalanced tree. Take the following tree:
A
C
429
7. (a) What is the maximum number of disk accesses needed to delete an element that is in a non-leaf
node of a B-tree of order m?
(b) Does deleting a leaf node from a red-black tree then reinserting the same key always result in the
original tree? Prove it does or give a counter example where it does not.
(a) Deletion of an element from a B-tree is divided into two cases:
(1) The element is to be deleted from a leaf.
(2) The element is to be deleted from a non leaf.
Case (2) is converted into case (1) by replacing the deleted element with either the largest element
in its left neighboring subtree or the smallest element in its right neighboring subtree.
The worst case for deleting an element from a B-tree of height h occurs when mergers take
place at levels h, h-1, , and 3, and at level 2, we get an element from a nearest sibling.
Therefore, the worst-case disk access count (Maximum number of disk accesses) is 3h; ie, 3h = h
+ (h 1) + (h 2) + 3 ( h reads required to find the leaf with the element to be deleted) + ( h -1
reads are required to get nearest siblings at levels 2 through h) + (h - 2 writes of merged nodes at
levels 3 through h) + ( 3 writes for the modified root and two level 2 nodes).
(b) Case-1: Deletion of a black leaf node which is left child of its parent.
Following figures illustrate the deletion of node 2 and reinsertion of node 2. In this case,
the shape of the original tree is changed. Notice the difference before deletion of 2 and
after reinsertion of 2.
430
Delete 2
10
3
30
3
40
20
10
10
Before deletion of 2
30
40
20
25
30
25
40
20
25
After deletion of 2
Reinsert 2
10
5
30
3
2
40
20
6
4
25
After reinsertion of 2
Case-2: Deletion of a black leaf node which is right child of its parent.
Following figures illustrate the deletion of node 6 and reinsertion of node 6. In this case, the
shape of the original tree is also changed.
10
Delete 6
10
30
3
2
5
40
20
Before deletion of 6
10
3
30
20
5
4
40
25
After reinsertion of 6
30
20
3
2
25
Reinsert 6
10
3
40
25
30
5
20
25
After deletion of 6
40
431
Question: Does deleting a leaf node from a red-black tree then reinserting the same key always
result in the original tree?
No. If the black leaf node (left or right child of its parent) is deleted and the same node is
reinserted, then the original shape of the tree would change.
Case-3: Deletion of a red leaf node which is left child of its parent.
Following figures illustrate the deletion of red node 4 and reinsertion of node 4. It retains its
original shape.
Delete 4
10
3
30
3
40
20
Reinsert 4
10
30
25
Before deletion of 4
3
40
20
10
30
25
After deletion of 4
40
20
25
After reinsertion of 4
Case-4: Deletion of a red leaf node which is right child of its parent.
Following figures illustrate the deletion of red node 25 and reinsertion of node 25. It keeps its
original shape.
10
10
Delete 25
3
2
20
5
4
30
40
25
Before deletion of 25
30
20
5
4
10
Reinsert 25
3
40
After deletion of 25
30
20
5
4
40
25
After reinsertion of 25
If the red leaf node (left or right child of its parent) is deleted and the same node is reinserted, then
the original shape of the tree would not change.
8.