Data Structures and Algorithms

Download as pdf or txt
Download as pdf or txt
You are on page 1of 34

Appendix C: Questions and Answers

Appendix

398

Questions and Answers


C.1 Set No. 1
1.

(a) What do you mean by Encapsulation and explain in detail.


(b) Explain about friend and inline functions.
(a) Object-oriented programming (OOP) is a programming language model organized around
"objects" rather than "actions", and data rather than logic. A program has been viewed as a logical
procedure that takes input data, processes them, and produces output data.
The programming challenge was seen as how to write the logic, not how to define the data.
Object-oriented programming takes the view that what we really care about are the objects we
want to manipulate rather than the logic required to manipulate them.
In object-oriented programming, encapsulation is the inclusion within a program object of all
the resources needed for the object to function - basically, the methods and the data. The object is
said to "publish its interfaces." Other objects adhere to these interfaces to use the object without
having to be concerned with how the object accomplishes it. The idea is "don't tell me how you do
it; just do it." An object can be thought of as a self-contained atom. The object interface consists of
public methods and instantiated data.
Encapsulation means that the attributes (data) and the behaviours (code) are encapsulated into
a single object. In other models, namely a structured model, code is in files that are separate from
the data. An object, conceptually, combines the code and data into a single entity. To illustrate, let
us begin immediately with a code example.
For this example, we will create a very simple, but functional, checking account object. At
first pass, all we want our checking account object to do is hold the value representing the balance
of the account, set the balance, and return the balance.
class CheckingAccount
{ private:

Appendix C: Questions and Answers

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 );
}

// area of the circle

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

Advanced Data Structures and Algorithms in C++

Rectangle(int a, int b)
// constructor
{ length = a;
width = b;
}
friend int area(Rectangle ob); // friend function
};

/*

area() is not a member function - area() is a friend of Rectangle class,


it can directly access length and width

*/
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)

Friends can be useful when we are overloading certain types of operations.

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.

What is the deal with operator overloading?


What are the benefits of operator overloading?
What are some examples of operator overloading?
What operators can/cannot be overloaded?

(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;

(b) Benefits of operator overloading:


Overloaded operators make the programs easier to write, read, and maintain.
It increases the full integration of new class types into programming.

Appendix C: Questions and Answers

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.

The following program illustrates the + and << operators overloading:

#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

Complex operator+(Complex op2)// Overload + operator


{ Complex tmp;
tmp.real = op2.real + real;
tmp.imag = op2.imag + imag;
return tmp;
}
// Overload << operator
friend ostream &operator << (ostream &s, Complex x)
{ s << "\n complex number: ";
s << x.real << " " << x.imag << endl;
return s;
}
};
int main()
{ Complex c1(2.0, 3.5);
Complex c2(1.5, 4.0);
Complex c3 = c1 + c2;
cout << c3;
return 0;
}

// Overload + operator
// Overload << operator

(d) All C++ operators can be overloaded except the following: .


3.

::

.*

(a) Why should we use iostream instead of the traditional cstdio?


(b) Why does a program go into an infinite loop when someone enters an invalid input
character?
(c) How can we get std::cin to skip invalid input characters?
(a) Consider the following short C++ program shown here.
The traditional cstdio is for the Cs I/O system whereas iostream for C++. Cs I/O system
knows nothing about objects. Therefore, for C++ to provide complete support for object-oriented

402

Advanced Data Structures and Algorithms in C++

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;
}

Input/Output of this program is (bold characters are entered by the user):


Enter a value: 123
You entered: 123
Enter a value: -75
You entered: -75
Enter a value: $
You entered: -75
You entered invalid data...
Enter a value: Y

Appendix C: Questions and Answers

403

You entered: -75


You entered invalid data...
Enter a value: 0
You entered: 0
Normal termination...

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

Circular list with a head node

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

Advanced Data Structures and Algorithms in C++

Node *head;

// head points to first node

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
{

Appendix C: Questions and Answers

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--; }
}
}

The expected running time of this delete method is O(log n).

406
6.

Advanced Data Structures and Algorithms in C++

(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

(b) Explain the advantages of a splay tree in representation of dictionaries.


(a) An m-way search tree of height h (excluding external nodes) may have as few as h elements (one
node per level and one element per node) and as many as (mh-1) nodes. The upper bound can be
calculated for an m-way search tree of height h in which each node at levels 1 through (h-1) has
exactly m children and nodes at level h have no children.
Therefore maximum number of nodes in an m-way search tree
h 1

= (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)

Appendix C: Questions and Answers

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

Lower bound or minimum number of elements

2 * (100) 51 1 = 2 * (10 2 ) 4 1 = 2 *108 1


h 1

Total number of nodes =

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

Maximum number of elements = (m -1)


Therefore n m -1
h

(upper bound of n)

(eq.1)
2

The maximum number of nodes on levels 1, 2, 3, 4, h+1 is 1, 2, 2d, 2d ,.2d


minimum number of external nodes in the B-tree is 2d
one more than the number of elements.

n 2d h1 1
i.e. 2d h 1 1 n

(lower bound of n)

From eq.1 and eq.2 we have

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

. Since the number of external nodes is

(eq.2)

408

Advanced Data Structures and Algorithms in C++

log m (n + 1) h

(eq. 3)

From eq.2 we have

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. ,

From eq.3 and eq.4 we have

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 find(x) is performed by searching for x. If x is found, then we issue splay(x). If x is not


found, then we splay the last non-null node. This is necessary; otherwise an adversary could
defeat splaying by issuing multiple calls to a degenerate tree.

An insert(x) is performed as in BST. We then issue splay(x). This is necessary; otherwise an


adversary could defeat splaying.

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.

(a) Describe about search engine and inverted files.


Note: This question pertains to the syllabus of previous regulations (R05).
(b) Explain the main features of Boyer-Moore algorithm.
(b) The Boyer-Moore algorithm works by searching the pattern, P (target string) from right to left,
while moving it left to right along the text string T. The following example illustrates the general
idea:
P = pill
T = the caterpillar

Appendix C: Questions and Answers

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.)

C.1 Set No. 2


1.

(a)
(b)
(c)
(d)

What do you mean by Stack unwinding?


What is the difference between const char *myPointer and char *const myPointer.
Define precondition and post-condition to a member function.
What are the conditions that have to be met for a condition to be an invariant of the class?

(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

Advanced Data Structures and Algorithms in C++

const char *myPointer

char *const myPointer

char str1[] = "ABCD";


char str2[] = "XYZ";
const char *myPointer;
myPointer = str1;
cout << myPointer << endl;

char str1[] = "ABCD";


char str2[] = "XYZ";
char *const myPointer = str1;
// myPointer = str1;
cout << myPointer << endl;

myPointer = str2;
cout << myPointer << endl;

// myPointer = str2;
// cout << myPointer << endl;
myPointer is a const pointer. It must be

In this case, myPointer is a pointer


variable that stores the address of the
const char object. Here, it permits
assignment of the different objects.
myPointer = str1;
myPointer = str2;

Output of this program code is:


ABCD
XYZ

initialized during declaration itself. We cannot


change its value, because it is a constant.
The second assignment:
myPointer = str2;

gives an error.
Output of this program code is:
ABCD

(c) Precondition - a condition that must hold upon invocation of a function.


Postcondition - a condition that must hold upon exit from a function.
(d) Class invariant - a condition that must always hold for objects of the class, except while a public
member function is executing.
Basically, for a given function, we want to specify a precondition, which needs to hold at the
entry into the function, and a postcondition, that needs to hold at the exit of a function, whether by
explicit return or by stack unwinding from an uncaught exception. Also, we want to be able to
specify a class invariant condition that is checked both at entry and exit from any public member
function.
There are limits to what can be done. In particular, it would not be possible to hinder users
from overriding virtual functions with implementations that do not fulfill the pre- or
postconditions stated in the base function. And the invariant condition cannot be checked
automatically; some kind of trigger must be provided by the programmer.
2.

(a) What are the different types of polymorphism?


(b) What are virtual functions? How do you implement virtual functions in C++?
(a) Object-oriented programming languages support polymorphism, which is characterized by the
phrase "one interface, multiple methods." In simple terms, polymorphism is the attribute that
allows one interface to control access to a general class of actions.
In C++ there are two types of polymorphisms. They are run-time and compile-time
polymorphism. Compile-time polymorphism is achieved by overloading functions and operators.
Run-time polymorphism is accomplished by using inheritance and virtual functions.
For example, we might have a program that defines three different types of stacks. One stack
is used for integer values, one for character values, and one for floating-point values. Because of
polymorphism, we can define one set of names, push() and pop() , that can be used for all three
stacks. In our program we will create three specific versions of these functions, one for each type
of stack, but names of the functions will be the same. The compiler will automatically select the
right function based upon the data being stored.

Appendix C: Questions and Answers

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;

// pointer to base class


// d1: object of derived class 1
// d2: object of derived class 2

ptr = &d1;
ptr->show();

// assign address of d1 to pointer


// function call - execute show()

ptr = &d2;
ptr->show();
return 0;

// assign address of d2 to pointer


// function call - execute show()

The output of this program is: derived1


derived2

3.

(a) Write a program to merge the contents of two given files ?


(b) Write a program to count the number of lines in the given file?
(a) The two data files DATA1.TXT and DATA2.TXT are merged into the third file
DATA3.TXT.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{ char ch;
ifstream in("DATA1.TXT", ios::in);
// Open first file for reading
ifstream in2("DATA2.TXT", ios::in); // Open second file for reading
ofstream out("DATA3.TXT", ios::out); // Open third file for writing
if(!in)
{ cout << "Cannot open file.";
while( in.get(ch) )

return 1; }

// read a char from 1st file and write onto 3rd file

412

Advanced Data Structures and Algorithms in C++

out << ch;


in.close();
if(!in2)
{ cout << "Cannot open file.";

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):

Adds object obj at the rear of a queue.


Input: Object;
Output: None.
obj remove(): Deletes an item from the front of a queue and returns object obj; an error occurs
if the queue is empty.
Input: None;
Output: Object.
obj peek():
Returns the object obj at the front of a queue , without removing it; an error
occurs if the queue is empty.
Input: None;
Output: Object.
bool isEmpty(): Returns a boolean indicating if the queue is empty.
Input: None;
Output: boolean (true or false).

Appendix C: Questions and Answers

int size():

413

Returns the current number of items in the queue.


Input: None;
Output: integer.

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

Advanced Data Structures and Algorithms in C++

ii.

n + 1
log m ( n + 1) h log d
+1
2

(b) Explain the advantages of splay tree in representation of dictionaries.

[10+6]

This question is repeated. Refer set-1 of this section.


8.

(a) Describe about search engine and inverted files.


(b) Explain the main features of Boyer-Moore algorithm.
This question is repeated. Refer set-1 of this section.

C.1 Set No. 3


1.

(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?

(a) Differences between a C++ struct and C++ class:


struct
By default everything in structure is
public.
Structure in C++ does not provide data
hiding.
By default inheritance of structure is
public.
Structures do not support polymorphism.

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.

Appendix C: Questions and Answers

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.

(a) Explain the need for Virtual Destructor.


(b) Can we have Virtual Constructors?
(a) When we manipulate objects through pointers, we often tend to create them dynamically through
the new operator and later on destroy by using the delete operator. A typical situation might be as
shown in the following example:
#include <iostream>
// illustrate why "virtual destructor" is needed
using namespace std;
class Base
{ public:
Base() { cout << "Base: constructor \n"; }
~Base() { cout << "Base: destructor \n"; }
};
class Derived : public Base
{ public:
Derived() { cout << "Derived: constructor \n"; }
~Derived() { cout << "Derived: destructor \n"; }
};
int main()
{ Base *ptr = new Derived;

// use the object


delete ptr;
return 0;

// now delete the object

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

Advanced Data Structures and Algorithms in C++

virtual ~Base() { cout << "Base: destructor \n"; }

By this modification, we get the following output:


Base: constructor
Derived: constructor
Derived: destructor
Base: destructor

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

String and character handling

Mathematical

Time, date, and localization

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).

Appendix C: Questions and Answers

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=

Of the 20 elements in this 4 x 5 matrix, only 6 are nonzero.


Following figure depicts a node structure of an element to represent a sparse matrix, using linear linked
lists.
left
val

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

Advanced Data Structures and Algorithms in C++

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

Linked representation of a sparse matrix, A

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.

Appendix C: Questions and Answers

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

Hashing with separate chaining

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

Advanced Data Structures and Algorithms in C++

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

T1 is an AVL tree and T2 non-AVL tree


The height of a tree is the length of its longest root-to-leaf path. The height of the tree T1 (or
T2) is 3. The depth of a tree is equal to the depth of the deepest leaf; this is always equal to the
height of the tree.
The balance factor of an AVL tree node is the difference of heights between the left and right
subtrees of the tree rooted at that node. The balance factor of node 40 (in tree T2) is 2 and that of
node 25 is -1.
(b) An AVL tree can be built by successive insertions of nodes into the tree. During this operation, the
tree preserves its inorder traversal ordering of all the nodes that is the nodes are in sorted order.
A new node can be inserted into an n-node AVL tree so that the result is an n+1 node AVL tree and
such an insertion can be done in O(log n) time. Because the height of an AVL tree with n nodes is
O(log n).
Since the height of an AVL tree with n nodes is O(log n), the insertion of n nodes is n times
O(log n). Hence, an AVL tree can be used to sort a sequence of n elements in O(n log n) time.
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

(b) Explain the advantages of splay tree in representation of dictionaries.


This question is repeated. Refer set-1 of this section.
8.

(a) Describe about search engine and inverted files.


(b) Explain the main features of Boyer-Moore algorithm.
This question is repeated. Refer set-1 of this section.

C.1 Set No. 4


1.

(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.

Appendix C: Questions and Answers

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)
{
. . .
}

Now, we can call myfunc() in two ways:


myfunc(123.45);
myfunc();

// pass an explicit value


// let function use default value

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

Advanced Data Structures and Algorithms in C++

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()

Appendix C: Questions and Answers

423

cout << m1 << "\t" << m2 << "\t"; }

};
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

Advanced Data Structures and Algorithms in C++

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();

// access member of base


// display member of base
// use member of derived class

3.

(a) Why should we use iostream instead of the traditional cstdio?


(b) Why does a program go into an infinite loop when someone enters an invalid input character?
(c) How can we get std::cin to skip invalid input characters?
This question is repeated. Refer set-1 of this section.

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

Appendix C: Questions and Answers

postfix = postfix + ch;


// append ch to postfix string
if( ch == '(' )
// if(ch is a left-bracket), then
stk.push(ch);
// push onto the stack
/* if(ch is an operator), then repeatedly pop an item from the stack;
If precedence of item >= precedence of ch,
then append item to postfix string
else push item back onto the stack and terminate the while-loop.
Push(ch) onto the stack.
*/
if( isOperator(ch) )
{
while(1)
{ item = stk.top();
stk.pop();
if( precedence(item) >= precedence(ch) )
postfix = postfix + item;
else
{ stk.push(item);
break;
}
}
stk.push(ch);
} // end of if(isOperator(ch))
if( ch == ')' )
{
item = stk.top();
stk.pop();
while( item != '(' )
{
postfix = postfix + item;
item = stk.top();
stk.pop();
}
}
} // end of for-loop
return postfix;
} // end of toPostfix() function
int evalPostfix(string postfix)
{
stack< int, vector<int> > stk;
char ch;
int tmp1, tmp2, result;
for( int i = 0; i < postfix.length(); i++ )
{
ch = postfix.at(i);
if( isOperand(ch) )
stk.push( (int)ch-48); // ASCII char of digit '0' is 48
if( isOperator(ch) )
{
tmp1 = stk.top();
stk.pop();
tmp2 = stk.top();

425

426

Advanced Data Structures and Algorithms in C++

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;

}; // end of InfixToPostfix() class


int main()
{
Postfix obj;
//string infix = "A*(B+C/D)-E";
string infix = "5*(6+2)-8/4";
cout << "infix: " << infix << endl;
string postfix = obj.toPostfix(infix);
cout << "postfix: " << postfix << endl;
int value = obj.evalPostfix(postfix);
cout << "value of postfix: " << value << endl;
return 0;
}

Appendix C: Questions and Answers

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

After LLr color change


G

P
GR

GR

PL

PL
NL

NR

LRr imbalance

NL

NR

After LRr color change

428

Advanced Data Structures and Algorithms in C++

G
P
P
GR

N
PR
NL
NL

NR

LLb imbalance

NR

PR

GR

After LLb rotation

P
GR
N

NL

PL
NR

PL

LRb imbalance

NL

NR

GR

After LRb rotation

(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

Appendix C: Questions and Answers

429

This tree is balanced. Let us insert B.


A
C
B

After performing right rotation on right subtree:


A
B
C

After performing left rotation about B:


B
A

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

Advanced Data Structures and Algorithms in C++

Delete 2

10
3

30

3
40

20

10

10

Before deletion of 2

30

40

20

25

30

25

Rotate left and recolor

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

Rotate right and recolor

30
5

20
25

After deletion of 6

40

Appendix C: Questions and Answers

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.

(a) Describe about search engine and inverted files.


(b) Explain the main features of Boyer-Moore algorithm.
This question is repeated. Refer set-1 of this section.

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy