Você está na página 1de 33

Appendix C: Questions and Answers

432

C.2 Set No. 1


1.

(a) What is a local class? Why can it be useful?


(b) Can a copy constructor accept an object of the same class as parameter, instead of reference of the
object?
(c) What is a class? What is an object?
(d) What is encapsulation?
(a) A class may be defined within a function. For example, this is a valid C++ program:
#include <iostream>
using namespace std;
void func();
int main()
{ func();
// myclass not known here
return 0;
}
void func()
{ class myclass
{ int a;
public:
void put_a(int n) { a = n; }
int get_a() { return a; }
} ob;
ob.put_a(10);
cout << ob.get_a();
}

When a class is declared within a function, it is known only to that function and unknown outside
of it. Several restrictions apply to local classes. First, all member functions must be defined within
the class declaration. The local class may not use or access local variables of the function in which
it is declared (except that a local class has access to static local variables declared within the
function or those declared as extern). It may access type names and enumerators defined by the
enclosing function, however. No static variables may be declared inside a local class. Because of
these restrictions, local classes are not common in C++ programming.
(b) One of the more important forms of an overloaded constructor is the copy constructor. Defining a
copy constructor can help you prevent problems that might occur when one object is used to
initialize another.
Yes it is possible, as an example we can describe that the copy constructor applies only to
initializations. For example, assuming a class called myclass, and that y is an object of type
myclass, each of the following statements involves initialization.
myclass x = y; // y explicitly initializing x
func(y);
// y passed as a parameter
y = func();
// y receiving a temporary, return object
(c) Classes are created using the keyword class. A class declaration defines a new type that links code
and data. This new type is used to declare objects of that class. Thus, a class is a logical
abstraction, but an object has physical existence. In other words, an object is an instance of a class.
A general form of a class is:

Appendix C: Questions and Answers

class class-name
{
private data and functions
access-specifier:
data and functions
. . .
access-specifier:
data and functions
};

433

// public, private, or protected

By default, functions and data variables declared within a class are private to that class and
may be accessed only by other members of the class. The public access specifier allows functions
or data to be accessible to other parts of the program. The protected specifier is needed when
inheritance is involved. Once an access specifier has been used, it remains in effect until either
another access specifier is encountered or the end of the class declaration is reached. Functions
and variables declared within a class are called members of the class.
(d) Encapsulation is the mechanism that binds together code and the data it manipulates, and keeps
both safe from outside interference and misuse. In an object-oriented language, code and data may
be combined in such a way that a self-contained "black box" is created. When code and data are
linked together in this fashion, an object is created. In other words, an object is the device that
supports encapsulation.
Within an object, code, data, or both may be private to that object or public. Private code or
data is known to and accessible only by another part of the object. That is, private code or data
may not be accessed by a piece of the program that exists outside the object. When code or data is
public, other parts of your program may access it even though it is defined within an object.
Typically, the public parts of an object are used to provide a controlled interface to the private
elements of the object.
2.

(a) When should my destructor be virtual?


(b) What is a virtual constructor?
(c) What is the difference between how virtual and non-virtual member functions are called?
(a) There is something that happens during destruction that you might not immediately expect. If you
are inside an ordinary member function and you call a virtual function, that function is called
using the late-binding mechanism. This is not true with destructors, virtual or not. Inside a
destructor, only the local version of the member function is called; the virtual mechanism is
ignored.
Why is this? Suppose the virtual mechanism were used inside the destructor. Then it would be
possible for the virtual call to resolve to a function that was farther out (more derived) on the
inheritance hierarchy than the current destructor. But destructors are called from the outside in
(from the most-derived destructor down to the base destructor), so the actual function called would
rely on portions of an object that have already been destroyed! Instead, the compiler resolves the
calls at compile-time and calls only the local version of the function.
(b) The hierarchy of constructor calls brings up an interesting dilemma. What happens if you are
inside a constructor and you call a virtual function? Inside an ordinary member function you can
imagine what will happen the virtual call is resolved at runtime because the object cannot know
whether it belongs to the class the member function is in, or some class derived from it. For
consistency, you might think this is what should happen inside constructors.

434

Advanced Data Structures and Algorithms in C++

This is not the case. If you call a virtual function inside a constructor, only the local version of
the function is used. That is, the virtual mechanism does not work within the constructor.
This behavior makes sense for two reasons. Conceptually, the constructors job is to bring the
object into existence. Inside any constructor, the object may only be partially formed you can
only know that the base-class objects have been initialized, but you cannot know which classes are
inherited from you. A virtual function call, however, reaches forward or outward into the
inheritance hierarchy. It calls a function in a derived class. If you could do this inside a constructor,
you would be calling a function that might manipulate members that had not been initialized yet, a
definite way for disaster.
(c) 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. In essence, virtual functions implement the "one
interface, multiple methods" philosophy that underlies polymorphism. The virtual function within
the base class defines the form of the interface to that function. Each redefinition of the virtual
function by a derived class implements its operation as it relates specifically to the derived class.
That is, the redefinition creates a specific method.
When accessed "normally," virtual functions behave just like any other type of class member
function. However, what makes virtual functions important and capable of supporting run-time
polymorphism is how they behave when accessed via a pointer. A base-class pointer can be used
to point to an object of any class derived from that base. When a base pointer points to a derived
object that contains a virtual function, C++ determines which version of that function to call based
upon the type of object pointed to by the pointer. And this determination is made at runtime. Thus,
when different objects are pointed to, different versions of the virtual function are executed. The
same effect applies to base-class references.
3.

(a) Explain about try, catch, throw keywords in C++?


(b) Write a program to illustrate the exception handling mechanism in C++.
(a) 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.
(b) Here is a simple example that illustrates the way C++ exception handling operates.
void main()
{ cout << "Start\n";
try
// start a try block
{ cout << "Inside try block\n"; // S1
throw 100;
//throw an error// S2
cout << "This will not execute"; // S3
}
catch (int i)
// catch an error
{
cout << "Caught an exception -- value is: ";

Appendix C: Questions and Answers

435

cout << i << "\n";


}
cout << "End \n";
}

This program displays the following output:


Start
Inside try block
Caught an exception -- value is: 100
End

In this program, the try block contains three statements: S1, S2, S3 and catch(int i) statement that
processes an integer exception. Within the try block, only S1 and S2 will be executed. Once an
exception has been thrown, control passes to the catch expression and the try block is terminated.
That is catch is not called. Rather, program execution is transferred to it. The S3 following the
throw will never be executed.
4.

Develop a C++ class twoStacks in which a single array is used to represent two stacks. Put the bottom
of one stack at one end of array and bottom of second stack at the other end. The two stacks grow
towards middle of the array. The class should contain the methods to perform all operations of stack
ADT.
StackA
StackB
BottomA

topA

topB

BottomB

In this situation, one stack, say A, grows from one end of the array and the other stack, say B, stay at
the other end and grows in the opposite direction, That is towards the direction of A. The operations of
this double stack structure are: push1, push2, pop1 and pop2. We declare two stacks of varying length
in a single array to manipulate the two stacks.
#include <iostream>
#define MAX 10
using namespace std;
class DoubleStack
{ private:
char stack[MAX];
int top1, top2, count; // count denotes number of occupied cells
public:
DoubleStack();
// constructor
void push1(char);
void push2(char);
char pop1(void);
char pop2(void);
};
DoubleStack::DoubleStack()
{ top1 = -1; top2 = MAX; count = 0;
// initialize top1, top2, count
for( int i = 0; i < MAX; i++ )
// initialize all cells of the stack
stack[i] = NULL;
}

436

Advanced Data Structures and Algorithms in C++

void DoubleStack::push1(char item)


{ if( count >= MAX )
{ cout<<"\n Stack-1 is Full"; return; }
top1++;
if(stack[top1] == NULL){ stack[top1] = item; count++; }
else top1--;
}
void DoubleStack::push2(char item)
{ if( count >= MAX )
{ cout<<"\n Stack-2 is Full"; return; }
top2--;
if(stack[top2] == NULL) { stack[top2] = item; count++; }
else top2++;
}
char DoubleStack::pop1(void)
{ char item;
if( top1 == -1 )
{ cout<<"\n Stack-1 is Empty"; return(NULL); }
item = stack[top1];
/* assign stack top element to item */
stack[top1] = NULL;
/* set deleted item to NULL - i.e., this cell is empty */
count--;
/* decrement number of occupied cells by 1 */
top1--;
/* reduce top position by 1 */
return(item);
}
char DoubleStack::pop2(void)
{ char item;
if( top2 >= MAX-1 )
{ cout<<"\n Stack-2 is Empty"; return(NULL); }
item = stack[top2];
stack[top2] = NULL;
/* set popped (deleted) item to NULL */
count--;
/* decrement number of occupied cells by 1 */
top2++;
/* reduce top position by 1: i.e., in opposite direction */
return(item);
}
int main()
{ DoubleStack dstk;
dstk.push1('A');
dstk.push1('B');
dstk.push1('C');
dstk.push2('P');
dstk.push2('Q');
dstk.push2('R');
dstk.push2('S');
cout << dstk.pop1()<<
cout << dstk.pop2()<<
dstk.push1('D');
cout << dstk.pop1()<<
cout << dstk.pop2()<<
return 0;
}

endl;
endl;
endl;
endl;

Appendix C: Questions and Answers

5.

437

(a) Explain about the skip list representation of dictionary with an example?
(b) What are the data members of skip list class? Write the constructor for skip list.
(a) Skip list is a data structure that uses a probabilistic algorithm to keep the structure in balance
(invented by William Pugh). The algorithms for skip lists are very simple and elegant. Skip lists
are also very fast. This combination makes them an attractive alternative to other data structures.
Skip lists are made up of a series of nodes connected one after the other. Each node contains a
key-value pair as well as one or more pointers, to nodes further along in the list. The number of
pointers each node contains is determined randomly. This gives skip lists their probabilistic nature,
and the number of pointers a node contains is called its node level.
Each node will have at least one node pointer, and the first pointer will always point to the
next node in the list. In this way, skip lists are very much like linked lists. However, any additional
node pointers can skip one or more intermediate nodes and point to nodes later in the list. This is
where skip lists get their name.
Two nodes that are always present in a skip list are the header node and the NULL node. The
header node marks the beginning of the list and the NULL node marks the end of the list. The
NULL node is unique in that when a skip list is created, it is given a key greater than any legal key
that will be inserted into the skip list. This is important to how skip lists algorithms work.
Skip lists have three more important properties: maximum level, current overall level, and
probability. Maximum level is the highest level a node in a skip list may have. The current overall
level is the value of the node level with the highest level in the skip list. Probability is a value used
in the algorithm for randomly determining the level for each node.
4
3
1
Header

NULL

11

2
3

9
8

13

A Skip List

Level 1 linked list includes all nodes; the level 2 list includes every second node; the level 3
list every fourth node; and so on. In other words, every second node points to positions two nodes
ahead; every fourth node points to positions four nodes ahead; every eighth node points to
positions eight nodes ahead; and so on.
(b) Data members of skip list class:
SkipListNode - contains key value and a forward pointer.
Header pointer of type SkipListNode.
Level of integer type that indicates level of each node.
template <class T>
struct SkipList
{
SkipNode<T> *head;
int level;
SkipList()
// constructor
{
head = new SkipNode<T>(MAX_LEVEL, T());

438

Advanced Data Structures and Algorithms in C++

level = 0;
}
// functions
};

6.

Define a class called binarySearchTree to represent a Binary search tree. Extend this class by adding a
public method outputInRange (Low, High) that outputs, in ascending order of key, all elements in a
binary search tree whose key lies between Low and High. Use recursion and avoid entering subtrees
that cannot possibly contain any elements with keys in desired range.
struct BSTNode
{
int data;
BSTNode *left;
BSTNode *right;
BSTNode( int d )
// constructor
{ data = d;
left = right = NULL;
}
};
class BSTree
{
public:
BSTNode *insertTree(BSTNode *p, int key);
BSTNode *search(BSTNode *root, int key);
BSTNode *deleteTree(BSTNode *root, int key);
void outputInRange(BSTNode *root, int low, int high);
void inorder(BSTNode *p);
void preorder(BSTNode *p);
void postorder(BSTNode *p);
};
void BSTree::outputInRange(BSTNode *p, int low, int high)
{ if( p != NULL )
{ outputInRange(p->left, low, high);
if( p->data >= low && p->data <= high )
cout << p->data << " ";
outputInRange(p->right, low, high);
}
}

7.

(a) Describe the B-trees? Explain the advantages of B-trees.


(b) Prove that let T be a red black tree with n internal nodes then no node has depth greater than 2
log(n+1).
(a) Binary search trees generalize directly to multi-way search trees. For an integer m called the order
of the tree, each node has at most m children. If k m is the number of children, then the node
contains exactly k-1 keys, which partition all the keys in the subtrees into k subsets. If some of
these subsets are empty, then the corresponding children in the tree are empty. Following figure
shows a 4-way search tree.

Appendix C: Questions and Answers

439

46

22

12 15 20

23 27 29

32

52

40

33 36

41 43

47 49 51

56

53 55

57 59

A 4-way search tree

Definition: A B-tree of order m is an m-way tree in which


1. All leaf nodes are on the same level.
2. All nodes, except the root and the leaves, have between [m/2] and m children.
3. The non leaf nodes store up to m-1 keys to guide the searching; and these keys partition the
keys in the children in the fashion of a search tree.
4. The root is either a leaf or has between two and m children.
5. If a node has d number of children, then it must have d-1 number of keys.
Advantages of B-trees:
1. A B-tree guarantees only a few disk accesses.
2. B-tree is balanced multiway search tree that will minimize file accesses.
3. The use of a high-order B-tree results in a tree index that can be searched making a very small
number of disk accesses, even when the number of entries is very large.
4. They have the advantage of being disk persistent without needing the design and management
overhead associated with databases.
5. By cacheing the B-tree pages, performance can be in the same order as memory-based
collections.
6. Because B-trees always keep their data in order traversal (especially where pages are cached)
is always extremely fast.
7. Because B-trees always maintain their order they don't tend to degrade as badly as databases.
8. B-trees do not need frequent re-balancing as both the upper and lower bounds on the number
of child nodes are typically fixed.
(b) The rank of a node in a red-black tree is the number of black pointers on any path from the node to
any external node in its subtree. So the rank of an external node is zero.
Let the length of a root-to-external-node path be the number of pointers on the path. If P and
Q are two root-to-external-node paths in a red-black tree, the length(P) 2 length(Q).
Let r be the rank of the root in a red-black tree. Each root-to-external-node path has length >
2r, so h 2r (eq.1).
Since the rank of the root is r, there are no external nodes at levels 1 through r, so there are
(2r-1) internal nodes at these levels. So the total number of minimum (at least) internal nodes =
(2r-1)
That is n 2r-1
(n+1) 2r
Taking logs on both sides to the base we have
log2(n+1) log22r
log2(n+1) r

440

Advanced Data Structures and Algorithms in C++

2log2(n+1) 2r
(eq. 2).
That is, 2r 2log2(n+1)
From (eq. 1 and eq. 2) we have
h 2r 2log2(n+1)
That is, h 2log2(n+1) (result equation).
Therefore, the height of a red-black tree is at most 2log2(n+1).
8.

(a) Write an algorithm of suffix trie ? Compute the performance of an algorithm.


(b) What are the properties of compressed trie?
(a) A trie is a type of tree that has N possible branches from each node, where N is the number of
characters in the alphabet. The word 'suffix' is used to refer to the fact that the trie contains all of
the suffixes of a given text. The following figure represents a suffix tree for the string BOOK.
Each suffix ends at a node that consists of one of these three types:
A leaf node: The nodes labeled 1, 2, 4, and 5 are leaf nodes.
An explicit node: The non-leaf nodes that are labeled 0 and 3 in figure are explicit nodes.
They represent a point on the tree where two or more edges part ways.
An implicit node: Prefixes such as BO, BOO, and OO all end in the middle of an edge. These
positions are referred to as implicit nodes. They would represent nodes in the suffix trie, but
path compression eliminated them. As the tree is built, implicit nodes are sometimes
converted to explicit nodes.
0
BOOK
1

3
K
4

OK
5

Every time we add a new suffix to the tree, we are going to automatically extend the edges
leading into every leaf node by a single character. That character will be the last character in the
new suffix. If the edge is split, its starting point may change, but it will still extend all the way to
the end of the input text. This means that we only have to worry about updating explicit and
implicit nodes at the active point, which was the first non-leaf node. Given this, we would have to
progress from the active point to the empty string, testing each node for update eligibility.
The point where you find the first matching descendant is called the end point. The end point
has an additional feature that makes it particularly useful. Since we were adding leaves to every
suffix between the active point and the end point, we now know that every suffix longer than the
end point is a leaf node. This means the end point will turn into the active point on the next pass
over the tree.
By confining our updates to the suffixes between the active point and the end point, we cut
way back on the processing required to update the tree. And by keeping track of the end point, we
automatically know what the active point will be on the next pass. A first pass at the update
algorithm using this information might look something like this (in C-like pseudo code):

Appendix C: Questions and Answers

Note:

441

Before writing an algorithm, this kind of introduction is necessary to properly understand


the algorithm.

Update( new_suffix )
{
current_suffix = active_point
test_char = last_char in new_suffix
done = false;
while ( !done )
{
if current_suffix ends at an explicit node
{
if the node has no descendant edge starting with test_char
create new leaf edge starting at the explicit node
else done = true;
} else
{ if the implicit node's next char isn't test_char
{ split the edge at the implicit node
create new leaf edge starting at the split in the edge
} else done = true;
}
if current_suffix is the empty string
done = true;
else
current_suffix = next_smaller_suffix( current_suffix )
}
active_point = current_suffix
}
Insert is O(dm) where d is the size of the alphabet and m is length of suffix. Constructing an entire
trie is O(dn), where n is the sum of the lengths of all the suffixes in S.
Search is O(dm) where d is the size of the alphabet and m is the length of the suffix. O(d) at each
node and must visit m nodes. Note: It is possible to improve the O(d) by modifying how the
alphabet is stored in the nodehash table O(1)binary search O(log d).
(b) Properties of a compressed trie:
A compressed trie has internal nodes of degree at least two.
The compressed trie is obtained from a standard trie by compressing chains of redundant
nodes.
Compressed tries remove redundant nodes and replaces them with strings instead of
characters. A node is redundant only if it has no children is not the root.
At each node of the trie, we can store the triplet (i, j, k) instead of the string -where i is the
index into the string S, and j and k define the substring within S[i].
Compressed trie can be stored in space O(n), where n is the length of the string by using O(1)
space index ranges at the nodes.
It can be constructed in O(dn) time, where d is an alphabet size.
It supports arbitrary pattern matching in O(dn) time.

442

Advanced Data Structures and Algorithms in C++

C.2 Set No. 2


1.

(a) Explain about the dynamic memory allocation and de-allocation in C++.
(b) Explain about static inner classes with a program.
(a) C++ provides two dynamic allocation operators: new and delete. These are used to allocate and
free memory at run-time. The new operator allocates memory and returns a pointer to the start of it.
The delete operator frees memory previously allocated using new. The general formats of new
and delete are:
ptr_var = new type;

delete ptr_var;

ptr_var is a pointer variable that receives a pointer to memory that is large enough to hold an item
of type, type.
int main()
{ int *p;
p = new int; // allocate space for an int
if( p == NULL )
{ cout << "Insufficient Memory \n"; return 1; }
*p = 100;
cout << "Address of p is " << p << endl;
cout << "value is " << *p << "\n";
delete p;
// free memory space
return 0;
}

This program generated the following output (Note: Address is displayed as an hexadecimal
number).
Address of p is 0x235f0246
value is 100

Operator new automatically allocates enough memory no need to specify required number of
bytes; new automatically returns a pointer of specified type no need to use explicit type cast.
(b) In C++, it is possible to define one class inside another. A class defined inside another one is
called an inner class. C++ provides two kinds of inner classes: static and non-static. Consider the
following program:
#include <iostream>
using namespace std;
class A
// class with static inner class
{ int y;
public:
static class B
{ int x;
public:
B() { x = 25; }
void func()
{ cout << "Inside static inner class\n"; }
int getx()
{ return x; }
};

Appendix C: Questions and Answers

443

};
int main()
{
A::B ob;
ob.func();
cout << ob.getx() << endl;
return 0;
}

This program defines the class A which contains a static inner class B. A static inner class behaves
like any outer class. It may contain functions and fields, and it may be instantiated like this:
A::B ob;

This statement creates a new instance of the inner class B. Given such an instance, we can invoke
the func() in the usual way:
ob.func();

Note: it is not necessarily the case that an instance of the outer class A exists even when we have
created an instance of the inner class. Similarly, instantiating the outer class A does not create any
instances of the inner class B.
2.

(a) Explain about the virtual functions in C++?


(b) Explain about the abstract classes in C++?
(a) 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;

// assign address of d2 to pointer

444

Advanced Data Structures and Algorithms in C++

ptr->show();
return 0;

// function call - execute show()

The output of this program is: derived1


derived2

(b) Abstract classes

Abstract class: A class that contains at least one or more pure virtual functions a pure
virtual function has no definition (or body). Example of pure virtual function is: virtual
void func() = 0;

Objects may not be created from the abstract classes.


An abstract class represents an incomplete type that is used as a foundation for derived classes.
Although we cannot create objects of an abstract class, we can create pointers and references
to an abstract class. This allows abstract classes to support run-time polymorphism, which
relies upon base-class pointers and references to select the proper virtual function.
#include <iostream>
// demonstration of abstract class
using namespace std;
class Test
{ public:
virtual void first() =0;
// pure virtual function
void second()
{ cout << "second Method\n"; }
};
class child1 : public Test
{
public:
void first()
{ cout << "first in child-1\n"; }
};
class child2 : public Test
{
public:
void first()
{ cout << "first in child-2\n"; }
};
int main ()
{ Test *ptr;
child1 tst1;
child2 tst2;
ptr = &tst1;
ptr->first();
ptr->second();
ptr = &tst2;
ptr->first();
ptr->second();
return 0;

Appendix C: Questions and Answers

445

Output:
first in child-1
second Method
first in child-2
second Method

3.

(a) How can we write/read objects of myclass to/from a data file?


(b) How can we send objects of myclass to another computer (e.g., via a socket, TCP/IP, FTP, email,
a wireless link, etc.)?
(c) Why cant we open a file in a different directory such as ..\test.dat?
(d) How can we tell {if a key, which key} was pressed before the user presses the ENTER key?
(a) Write the objects of the myclass onto a data file and read this data file.
i.

Let myclass contain some data items. Create the object of myclass.
myclass ob;

ii. Open the data file: test.dat in output mode (include header file: <fstream>).
ofstream outfile("test.dat", ios::out);

iii. Make the myclass object by reading data items, and write the object.
outfile.write((char*) &ob, sizeof(ob));

This step is repeated for the required number of records.


iv. Close the data file:
outfile.close();

v.

Open the data file: test.dat in input mode.


ifstream infile("test.dat", ios::in);

vi. Read the data file until end of file.


while(!infile.eof())
{ infile.read((char*) &ob, sizeof(ob));
. . .
}

vii. Close the data file.


infile.close();

(b) An object or group of objects are taken, put on a disk or sent through a wire or wireless transport
mechanism. Later on another computer, reverses the processes, and saves the original object(s).
The basic mechanism is to flatten object(s) into a one dimensional stream of bits, and to turn that
stream of bits back in the original object(s). Objects of classes are sent through human-readable
(text) and human-non readable (binary) formats. This process is known as serialization.
We probably want to use iostream's >> and << operators rather than its read() and
write() methods. The >> and << operators are better for text mode, whereas read() and
write() are better for binary mode.
(c) We can open a file in a different directory such as ..\test.dat.
ofstream outfile("C:\myDir\test.dat", ios::out);

The data file: test.dat is opened in the directory: C:\myDir.

446

Advanced Data Structures and Algorithms in C++

(d) How can we tell {if a key, which key} was pressed before the user presses the ENTER key?
Use the header file: <conio.h> and its input character function, getch().
Before pressing the key, the key would display.
#include <iostream>
#include <conio.h>
using namespace std;
int main()
{
char ch;
cout << "Enter a key: ";
ch = getch();
cout << ch << endl;
return 0;
}

4.

What is a sparse matrix? Explain about the linear list representation of a sparse matrix?
This question is repeated. Refer set-3 of section C.1.

5.

Use linear probing, a hash table with b=17 buckets, and the hash function f(k) = k% b; Start with an
empty hash table and insert pairs whose keys are 7, 42, 25, 70, 14, 38, 8, 21, 34, 11. The pairs are
inserted in this order.
(a) Draw the hash table for each insertion?
(b) What is the loading factor after the last insertion?
(c) What is the maximum number of buckets examined in an unsuccessful search of your table?
(d) What is the maximum number of buckets examined in a successful search?
(a) Following table shows keys and their hash values generated by f(k) = k % 17.
key, k
hash value, k%17

7
7

42
8

25
8

70
2

14
14

38
4

8
8

21
4

34
0

11
11

Hash tables are shown for each insertion of the keys (in the given order):
index:
key:

7
7

index:
key:

7
7

8
42

index:
key:

7
7

8
42

9
25

index:
key:

2
70

7
7

8
42

10

11

10

9
25

12

11

10

10

13

12

11

11

14

13

12

12

15

14

13

13

16

15

14

14

16

15

15

16

16

Appendix C: Questions and Answers

index:
key:

2
70

7
7

index:
key:

2
70

4
38

7
7

8
42

9
25

10

11

12

13

14
14

15

16

index:
key:

2
70

4
38

7
7

8
42

9
25

10
8

11

12

13

14
14

15

16

index:
key:

2
70

4
38

5
21

index:
key:

0
34

2
70

4
38

5
21

7
7

8
42

9
25

10
8

11

12

13

14
14

15

16

index:
key:

0
34

2
70

4
38

5
21

7
7

8
42

9
25

10
8

11
11

12

13

14
14

15

16

8
42

7
7

9
25

8
42

10

9
25

11

10
8

12

11

13

12

14
14

13

15

14
14

447

16

15

16

(b) Load factor = (number of occupied cells) / (table size) = 10 / 17 = 0.588


(c) Maximum number of buckets examined in an unsuccessful search = 6.
The following table gives the number of unsuccessful searches for a set of keys which are not in
the above set: {7, 42, 25, 70, 14, 38, 8, 21, 34, 11};
Key:
hash value, k%17
Unsuccessful searches:

143
7
6

13
13
1

123
4
3

1234
10
3

12345
3
1

(d) Maximum number of buckets examined in a successful search = 3


key, k
hash value, k%17
Successful searches:
6.

7
7
1

42
8
1

25
8
2

70
2
1

14
14
1

38
4
1

8
8
3

21
4
2

34
0
1

11
11
1

Define a red-black tree? Write the procedures to perform insertion, deletion in a red-black tree?
13
8

13

Black node

17

Red node

17

11

25

15

N
6

22

27

An example of a red-black tree

Null black node

448

Advanced Data Structures and Algorithms in C++

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. (Counting or not counting the
null black nodes does not affect the structure as long as the choice is used consistently.)
Red-Black Tree Insertion
When a node is inserted in the tree it is given the color red. This does not affect the black node count
on any path to a leaf. But it could lead to a single pair of consecutive red nodes in the tree. If the new
node becomes a child of a black node there is no problem. But it may become a child of a red node.
The double red violation will begin at a leaf. The steps below are designed to move the double
violation up toward the root without affecting any path's black node count until it can be eliminated by
bringing down a black from above or it reaches the root where it can be eliminated since the root can
be coloued black without consequence.
Let current refer to the red node that has a red child thereby identifying the location of the violation.
The parent of current will always be black.
1. If current's sibling is red, color black current and its sibling and color parent red. Set current to the
parent of the parent and go to step 4.
2. If current is the right child of its parent, rotate clockwise around current. Otherwise rotate counterclockwise around current. Set current to the new parent of current (the pre-rotation red child of
current). Go to step 3.
3. Current's sibling is black; current's red child is on the opposite side as parent, rotate in the
direction that causes current to become the parent of its pre-rotation parent.
Hence, exchange colous between current and its pre-rotation parent. The algorithm terminates here.
4. If current is the root or the root sentinel, set the root to black and exit; otherwise, repeat.
Red-Black Tree Deletion
Initially current is the node selected for deletion. Suppose it has two children. Then another node is
selected for deletion instead: namely, either a successor of current (a leaf or a node with only a right
child of next greater value), or a predecessor of current (a leaf or node with only a left child of next
smaller value). Either is easy to find. The deleted successor or predecessor is saved until the run
terminates and then it replaces the node originally selected to be deleted. If a successor or predecessor
is necessary, it becomes current when it is found. Due to the use of a successor, only nodes with at
most one child need to be considered for deletion. Assume current initially has at most one child.
1.
2.

3.

If current is a red leaf, delete the leaf; If a successor or predecessor is used, replace the node
selected for deletion with the leaf.
If current is red with one child; this is impossible since the child must be black to avoid two reds
in a row on a path but then the number of blacks on paths through the left side of the node is
different from the number of blacks on paths through the right side.
If current is black with one black; this is impossible since the number of blacks on paths through
the left side of the node is different from the number of blacks on paths through the right side.

Appendix C: Questions and Answers

4.

5.

7.

449

If current is black with one red child (child must be a leaf); detach current from the tree and make
current's child a child of current's former parent. If current is a successor or predecessor, it
replaces the node selected for deletion. Otherwise current is deleted.
If current is black and has no children, this step may be reached immediately or through Step
[5.1.2] or [5.2.1]. If immediately, current is either the node selected for deletion, or the successor
node, or the predecessor node. In all three cases disconnect it from the tree (that is, replace it in the
tree with a sentinel), but retain the notion of parent, sibling and such as though it is still in the tree
because it may be needed in one or more steps below. If current is the root, just delete it and
terminate.
(5.1.1) If current's sibling is red, exchange the colors of parent and sibling.
(5.1.2) If the sibling is to the left of the node, rotate clockwise around the parent, otherwise
rotate counter-clockwise around the parent.
(5.2.1) If current's sibling is black with two black children, make the sibling red.
(5.2.2) Otherwise, current is red. Make it black.

(a) Describe the B-trees? Explain the advantages of B-trees.


(b) Prove that let T be a red black tree with n internal nodes then no node has depth greater than 2
log(n+1
This question is repeated. Refer set-1 of this section.

8.

(a) Explain the Boyer-Moore algorithm with an example.


(b) What are the advantages and disadvantages of tries with respect to binary search tree?
(a) This question is repeated. Refer set-1 of section C1: JNTU November 2007
(b) The following are the main advantages of tries over binary search trees (BSTs):
Looking up keys is faster. Looking up a key of length m takes worst case O(m) time. A BST takes
O(log n) time, where n is the number of elements in the tree, because lookups depend on the depth
of the tree, which is logarithmic in the number of keys if the tree is balanced. But in the worst case,
both are as bad as O(log n), because, for tries, log(n) will approach m in the worst case. Also, the
simple operations tries use during lookup, such as array indexing using a character, are fast on real
machines.
Tries can require less space when they contain a large number of short strings, because the keys
are not stored explicitly and nodes are shared between keys with common initial subsequences.
Tries help with longest-prefix matching, where we wish to find the key sharing the longest
possible prefix of characters all unique.

C.2 Set No. 3


1.

(a) What is Object Oriented paradigm?


(b) Explain about OOPs principles in detail?
(a) Object-Oriented paradigm: An approach to software design and programming in which software is
primarily thought of as a collection of classes that each have responsibilities for various operations,
and which are instantiated at run time to create objects. It
has feature polymorphism
has fundamental units objects
helps to ensure communicational cohesion

450

Advanced Data Structures and Algorithms in C++

is a kind of paradigm
organizes code into classes that each contain procedures for manipulating instances of that
class alone
The first step in OOP is to identify all the objects you want to manipulate and how they relate
to each other, an exercise often known as data modeling. Once we have identified an object, we
generalize it as a class of objects and define the kind of data it contains and any logic sequences
that can manipulate it. Each distinct logic sequence is known as a method. A real instance of a
class is called an "object" or, in some environments, an "instance of a class." The object or class
instance is what we run in the computer. Its methods provide computer instructions and the class
object characteristics provide relevant data. We communicate with objects - and they
communicate with each other - with well-defined interfaces called messages.
An object is an abstract definition of something, identified by two elements: attributes (data)
and behaviour. First, an object has attributes (sometimes called states or properties), which
describe the object. Second, an object has behaviour, called methods or functions which describe
what the object can do. For example, a car is an object of class, motor-vehicle. The car has
possible attributes such as make, model, year, color, and speed with possible behaviours:
accelerate, brake, honk and so on.
(b) To be truly considered "object oriented", a programming language should support at a minimum
four characteristics.
Encapsulation aids the software designer by enforcing information hiding. Objects encapsulate
data and the procedures for manipulating those data. In a sense, the object hides the details of the
implementation from the user of that object. Encapsulation enables us to create objects that are
self-contained. When combined with polymorphism, encapsulation enables us to create objects
that are self-sufficient, and, therefore easy to reuse. Encapsulation enables us to create black box
functionality (input and output are known, but the process is not known).
Polymorphism: Poly means many and morph means form. A polymorphic program is one that can
take many forms the program is able to adapt automatically. Polymorphism refers to the fact that
a single operation can have different behaviour in different objects. In other words, different
objects react differently to the same message. For example, we can use the expression x + y to
denote the sum of x and y, for many different types of x and y: integers, floating-point numbers,
and complex numbers. We can even define the + operation for two strings to mean concatenation
of the strings.
Inheritance: In object-oriented programming, inheritance is the concept that when a class of
objects is defined, any subclass that is defined can inherit the definitions of one or more general
classes. This means for the programmer that an object in a subclass need not carry its own
definition of data and methods that are generic to the class (or classes) of which it is a part. This
not only speeds up program development; it also ensures an inherent validity to the defined
subclass object (what works and is consistent about the class will also work for the subclass). We
study in detail about inheritance later.
Dynamic binding: Objects could come from anywhere, possibly across the network. You need to
be able to send messages to objects without having to know their specific type at the time we write
our code. Dynamic binding provides maximum flexibility while a program is executing.
2.

What is operator overloading? Explain how to overload the operators in C++ by taking any five
operators.

Appendix C: Questions and Answers

451

You can redefine or overload the function of most built-in operators in C++. These operators can be
overloaded globally or on a class-by-class basis. Overloaded operators are implemented as functions
and can be member functions or global functions.
An overloaded operator is called an operator function. You declare an operator function with the
keyword operator preceding the operator. Overloaded operators are distinct from overloaded functions,
but like overloaded functions, they are distinguished by the number and types of operands used with
the operator.
Creating a Member Operator Function
A member operator function takes the following general format:
return-type class-member::operator#(arg-list)
{
...
}
return-type: any valid type
#: placeholder

For example, if we are overloading the + operator, use operator+.


When we are overloading a unary operator, arg-list will be empty.
When we are overloading binary operators, arg-list will contain one parameter.

The following program creates a class point, which stores x and y coordinate values. It overloads the +
operator relative to this class.
#include <iostream>
Using namespace std;
class point
{ int x, y;
public:
point() {}
// Constructors
point(int a, int b) { x = a; y = b;

void show()
{ cout << x << ", " << y << "\n"; }
point operator+(point op2);
// operator function
};
point point::operator+(point op2) // Overload + for point
{
point tmp;
tmp.x = x + op2.x;
tmp.y = y + op2.y;
return tmp;
}
void main()
{
point ob1(10, 20), ob2( 5, 30);
ob1.show();
// displays 10, 20
ob2.show();
// displays 5, 30
ob1 = ob1 + ob2; // alternatively (ob1+ob2).show();
ob1.show();
// displays 15, 50
}

452

Advanced Data Structures and Algorithms in C++

The operator+() has only one parameter even though it overloads the binary + operator. We might
expect two parameters corresponding to the two operands of a binary operator +. The operator+()
takes only one parameter, the operand on the left side of the + (op2.x) is passed implicitly to the
function through the this pointer. The operand on the right is passed in the parameter op2. When
binary operators are overloaded, it is the object on the left that generates the call to the operator
function.
point point::operator-(point op2)
// Overload - for point
point point::operator=(point op2) // Overload = for point
point point::operator++()
// Overload prefix ++ for point
{ x++;
y++;
return *this;
}

3.

(a)
(b)
(a)
(b)

Write a program to change the case of each word in a file to initial capitals.
Write a program to concatenate the two given strings?
This question relates to FILE I/O not included in the current syllabus.
#include <iostream>
// Concatenation of two strings
using namespace std;
int main()
{
char str1[80] = "Annamacharya Institute of ";
char str2[50] = "Technology & Sciences";
int i, j, n1, n2;
n1 = strlen(str1);
n2 = strlen(str2);
for( i = n1, j=0; j < n2; i++, j++ )
str1[i] = str2[j];
cout << str1 << endl;
return 0;
}

4.

Define the Abstract data type for Stack. Write a C ++ program to implement stack ADT using arrays.
A Stack is an Abstract Data Type (ADT) that supports the following functions:
push(obj):

Add object obj at the top of the stack.


Input: Object;
Output: None.
obj pop(): Delete an item from the top of the stack and returns object obj; an error occurs if the stack
is empty.
Input: None; Output: Object.
obj peek(): Returns the top object obj on the stack, without removing it; an error occurs if the stack is
empty.
Input: None; Output: Object.
boolean isEmpty(): Returns a boolean indicating if the stack is empty.
Input: None; Output: boolean (true or false).

Appendix C: Questions and Answers

453

int size(): Returns the number of items on the stack.


Input: None; Output: integer.
C ++ program to implement stack ADT using arrays: Program 5.1: An ArrayStack class
5. Develop a class for hash table using linear probing and neverUsed concept to handle an erase operation.
Write complete C++ code for all the methods. Include a method to reorganize the table when (say)
60% of the empty buckets have never used equal to false. The reorganization should move pairs
around as necessary and leave a properly configured hash table in which neverUsed is true for every
empty bucket.
#include <iostream>
#include <string>
using namespace std;
#define null -1
struct EmpRecord
{ int empCode;
// key
// other data may be included here
};
class HashTable
{ private:
EmpRecord *hashArray; // array holds hash table
bool *neverUsed;
// array holds true/false
int arraySize;
int count;
// number of empty cells (never used = false)
int unused;
EmpRecord *remain;
// array holds the remaining items after deletions
public:
HashTable(int size);
// constructor
int hashFunc(int key);
void insert(EmpRecord item);
EmpRecord erase(int key);
void reorganize();
EmpRecord find(int key);
void displayTable();
};
HashTable::HashTable(int size)
// constructor
{ hashArray = new EmpRecord[size];
neverUsed = new bool[size];
remain = new EmpRecord[size];
arraySize = size;
count = size;
// total number of empty cells = arraySize
unused = 0;
for( int i=0; i<arraySize; i++)
{ hashArray[i].empCode = null;
remain[i].empCode = null;
neverUsed[i] = true;
}
}

// initialize hashArray with null = -1

454

Advanced Data Structures and Algorithms in C++

int HashTable::hashFunc(int key)


{ return key % arraySize; }

// hash function (Division method)

void HashTable::insert(EmpRecord item)


{ int key = item.empCode;
int hashVal = hashFunc(key);

// insert a record
// extract the key
// hash the key

// until empty cell,


while(hashArray[hashVal].empCode != null)
{
++hashVal;
// go to next cell
hashVal %= arraySize;
// wraparound if necessary
}
hashArray[hashVal] = item;
// insert item (record)
remain[hashVal] = item;
neverUsed[hashVal] = false;
}
EmpRecord HashTable::erase(int key) // delete a record with the key
{ int hashVal = hashFunc(key); // hash the key
double empty;
EmpRecord dummy; dummy.empCode=null;
while(hashArray[hashVal].empCode != null) // until empty cell,
{
if(hashArray[hashVal].empCode == key)
// found the key?
{ EmpRecord tmp = hashArray[hashVal]; // save item
hashArray[hashVal].empCode = null;
// delete item
remain[hashVal].empCode = null;
count--;
unused++;
empty = (double)unused/count;
if( empty >= 0.60) reorganize();
return tmp;
}
++hashVal;
hashVal %= arraySize;

// return item
// go to next cell
// wraparound if necessary

}
return dummy; // cannot find item
}
void HashTable::reorganize()
{
for(int i=0; i<arraySize; i++)
{ hashArray[i].empCode = null;
neverUsed[i] = true;
}
for(i=0; i<arraySize; i++)
{ if( remain[i].empCode != null )
insert(remain[i]);
}
cout << "\n Reorganized Table: \n";
displayTable();

Appendix C: Questions and Answers

}
EmpRecord HashTable::find(int key)
// find item with key
{ EmpRecord dummy; dummy.empCode=null;
int hashVal = hashFunc(key); // hash the key
while(hashArray[hashVal].empCode != null) // until empty cell,
{
if(hashArray[hashVal].empCode == key)
// found the key?
return hashArray[hashVal];
// yes, return item
++hashVal;
// go to next cell
hashVal %= arraySize;
// wraparound if necessary
}
return dummy;
// cannot find item
}
void HashTable::displayTable()
{
string trueFalse;
cout << "<< Table >>" << endl;
for(int i=0; i<arraySize; i++)
{ if( neverUsed[i] ) trueFalse = "true";
else trueFalse = "false";
cout<<i<<": "<<hashArray[i].empCode<<" "<< trueFalse<<endl;
}
}
int main()
{ EmpRecord item;
int aKey;
int recKey[] = {12,17,21,28,35,40,43,52,66,69};
HashTable h(13);
// create table with size = 13
for(int i=0; i < 10; i++)
// insert 10 records
{
item.empCode = recKey[i];
h.insert(item);
}
h.displayTable();
// searching a record
aKey = 65;
item = h.find(aKey);
if(item.empCode != null) cout << aKey << " found"<<endl;
else cout << aKey << " not found"<<endl;
// delete 5 records
h.erase(43);
h.erase(21);
h.erase(69);
h.erase(28);
h.erase(12);
h.erase(35);
return 0;
}

Output of this hash table program:

455

456

Advanced Data Structures and Algorithms in C++

<< Table >>


0: 52 false
1: 40 false
2: 28 false
3: 66 false
4: 17 false
5: 43 false
6: 69 false
7: -1 true
8: 21 false
9: 35 false
10: -1 true
11: -1 true
12: 12 false
65 not found
Reorganized Table:
<< Table >>
0: 52 false
1: 40 false
2: 66 false
3: -1 true
4: 17 false
5: 69 false
6: -1 true
7: -1 true
8: -1 true
9: -1 true
10: -1 true
11: -1 true
12: -1 true

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.
This question is repeated. Refer set-3 of section C.1.
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.

log m ( n + 1) h log d (

n +1
) +1
2

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


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

(a) Explain the brute force algorithm with an example


(b) Draw the compact representation of the suffix tire for the string minimize minima.
(a) A simple method to string matching is starting the comparison of P (string pattern) and T (text
string) from the first character of T and the first character of P. If there is a mismatch, the
comparison starts from the second character of T, and so on.

Appendix C: Questions and Answers

457

Boolean BruteForce( T: text, P: pattern )


n: length of T,
i: index of T
m: length of P, j: index of P
1. i = 0
2. Repeat steps 3 and 4, while( i < nm)
3. (a) j = 0
(b) Repeat steps (i) to (iii), while( Ti = Pj ) // repeat to match all chars in P
(i) Increment i
(ii) Increment j
(iii) If( j = m ) return true .
// success if the end of P is reached
4. i = i - j + 1 // after mismatch, start comparison from the next character of T
5. return false // at the end, if pattern matching fails, return false.
Example: Figure 11.2
(b) The suffix trie of a string S is the compressed trie of all the suffixes of S. The compact
representation of the suffix trie for the string minimize minima is shown in the following
figure. The * indicates the space between the two words. We store pointers (indices) rather than
words in the leaves. And we replaced every string by a pair of indices, (i, j), where i is the index of
the beginning of the string and j the index of the end of the string. That is, we write
(8, 14) for the suffix *minima
(14, 14) for the suffix a
The following table shows all the suffixes of the string: minimize minima.
0
m

10

11

12

13

14

a
a

458

Advanced Data Structures and Algorithms in C++

root

14,14

7, 7

3, 3

0, 0

8, 14

4, 4

2, 4

6, 14

5, 14

14,14

5, 14

14,14

1, 1
2, 4
5, 14

14,14

2, 4
5, 14

6, 14

8, 14

10, 10

6, 14

10,10

C.2 Set No. 4


1.

(a) What is a class? How do you define class in C++?


(b) Explain about the data members and member functions of a class?
(c) Explain about access control mechanism in C++?
(a) Classes are created using the keyword class. A class declaration defines a new type that links code
and data. This new type is then used to declare objects of that class. Thus, a class is a logical
abstraction, but an object has physical existence. In other words, an object is an instance of a class.
A class declaration is similar syntactically to a structure. Here is the entire general form of a class
declaration that does not inherit any other class.
class class-name
{
private data and functions
access-specifier:
data and functions
access-specifier:
data and functions
// ...
access-specifier:
data and functions
} object-list;
The object-list is optional. If present, it declares objects of the class. Here, access-specifier is one
of these three C++ keywords:
public
private
protected

Appendix C: Questions and Answers

Example:
class myclass
{ int a, b;
public:
myclass(int i, int j)
{a=i; b=j;}

// by default, a & b are private


// constructor

void show()
// function
{cout << a << " " << b;}
};

(b) Consider the following class, Stack:


#include <iostream>
using namespace std;
#define N 10
class Stack
{ private:
char a[N];
int top;
// stack top
public:
Stack()
// constructor
{ top = -1; }
void push(char item)
// add an item on top of stack
{
if(top == N-1)
{ cout << "Stack is full" << endl;
return;
}
top++;
// increment top
a[top] = item;
// insert an item
}
char pop()
// remove an item from top of stack
{
if( isEmpty() )
{ cout << "Stack is empty" << endl;
return NULL;
}
char item = a[top];
// access top item
top--;
// decrement top
return item;
}
bool isEmpty()
// true if stack is empty
{ return (top == -1); }
};

459

460

Advanced Data Structures and Algorithms in C++

The functions isEmpty(), push(), and pop() are called member functions because they are part of
the class Stack. The variables a and top are called member variables (or data members). An
object forms a bond between code and data. Only member functions have access to the private
members of their class. Thus, only isEmpty(), push(), and pop() may access a and top. Once we
have defined a class, we can create an object of that type by using the class name. In essence, the
class name becomes a new data type specifier. For example, this creates an object called mystack
of type Stack:
Stack mystack;

When we declare an object of a class, we are creating an instance of that class. In this case,
mystack is an instance of Stack class.
(c) By default, functions and data declared within a class are private to that class and may be
accessed only by other members of the class. The public access specifier allows functions or data
to be accessible to other parts of a program. The protected access specifier is needed only when
inheritance is involved. Once an access specifier has been used, it remains in effect until either
another access specifier is encountered or the end of the class declaration is reached. We may
change access specifications as often as we like within a class declaration. For example, we may
switch to public for some declarations and then switch back to private again.
Consider the class Stack. Within the class, the functions isEmpty(), push(), and pop() can
access the private data members (a and top) of the class. They are not accessible to main()
function where we create the mystack object. We can access the functions as they are public. For
example, the statement in the main() function
cout <<

mystack.top;

gives an error (top is private)

2. (a) What is multiple inheritance? Write a program to illustrate the concept of multiple inheritance.
(b) What is hybrid inheritance? Write a program to illustrate the concept of hybrid inheritance.
(a) Multiple inheritance: A derived class may inherit two or more base classes. The following
example illustrates how derived inherits both base1 and base2.

base1

base2

derived
#include <iostream>
Using namespace std;
class base1
{ protected: int x;
public:
void showx() { cout << x << endl; }
};
class base2

Appendix C: Questions and Answers

461

protected: int y;
public:
void showy() {cout << y << endl;}

};

// Inherit multiple base classes.


class derived : public base1, public base2
{ public:
void set(int i, int j) { x = i; y = j; }
};
void main()
{ derived ob;
ob.set(10, 20); // provided by derived
ob.showx();
// from base1
// from base2
ob.showy();
}

(b) This question is repeated. Refer set-4 of section: C.1.


3.

(a) Write a program that reverses the order of the characters in a string.
(b) A palindrome is a word or group of words that read the same forward and backward. For example
madam or wow. Write a program that takes a string argument from the command line and
prints whether the string was a palindrome or not.
(a) #include <iostream>

// reverse the order of chars in the string

using namespace std;


int main()
{
char str[] = "ABCDEFG";
int i, n = 0; char tmp;
cout << "String: " << str << endl;

// count number of chars in the given string (= n)


for( i=0; str[i] != NULL; i++ ) n++;

// reverse the chars in the given string in place.


// swap first and last chars, second and last but one chars, and so on.
for( i = 0; i < n/2; i++ )
{ tmp = str[i];
str[i] = str[n-i-1];
str[n-i-1] = tmp;
}
cout << "Reversed String: " << str << endl;
return 0;
}

462

Advanced Data Structures and Algorithms in C++

(b) #include <iostream>

// Program file: Palindrome.cpp

using namespace std;


int main(int argc, char *argv[])
{
char str[20];

// argv[0]: program-name,

argv[1]: string

strcpy(str, argv[1]);
int n = strlen(str);
bool isPalindrome = true;
for(int i=0; i<n/2; i++)
if( str[i] != str[n-i-1] )
{ isPalindrome = false;
break;
}
if(isPalindrome)
cout<< "string: "<< str << " is a palindrome" << endl;
else
cout<< "string: "<<str<<" is not a palindrome"<< endl;
return 0;
}

Note: Execute this program on command prompt.


C:\...\myDir\Debug\Palindrome

madam

Output: string: madam is a palindrome


4.

Write a method in C++ to join two doubly linked lists into a single doubly linked list. In a join the
elements of the second list are appended to the end of first list.
Pointer variables head and tail denote left-most and right-most node addresses of the doubly linked list,
respectively. The left and right links of a node are denoted by pointer variables prev and next,
respectively. The following figure illustrates the concatenation of two lists. The formal procedure is as
follows:
1.
2.
3.

Join tail of list-1 to head2 of list-2.


Connect previous link of head2 to tail of list-1.
Assign tail2 of list-2 to tail of list-1.
tail

head

Doubly Linked List-1

x A

tail2

head2

Doubly Linked List-2

D x

Appendix C: Questions and Answers

463

void concatenate(list1, list2)


{
// step (1)
tail->next = head2;
head2->prev = tail;
// step (2)
tail = tail2;
// step (3)
}

5.

Use linear probing, a hash table with b=17 buckets, and the hash function f(k) = k% b; Start with an
empty hash table and insert pairs whose keys are 7, 42, 25, 70, 14, 38, 8, 21, 34, 11. The pairs are
inserted in this order.
(a) Draw the hash table for each insertion?
(b) What is the loading factor after the last insertion?
(c) What is the maximum number of buckets examined in an unsuccessful search of your table?
(d) What is the maximum number of buckets examined in a successful search?
This question is repeated. Refer set-2 of this section.

6.

Define a class called binarySearchTree to represent a binary search tree. Extend this class by adding a
public method outputInRange (Low,high) that outputs, in ascending order of key, all elements in a
binary search tree whose key lies between Low and High. Use recursion and avoid entering sub trees
that cannot possibly contain any elements with keys in desired range.
This question is repeated. Refer set-1 of this section C.2.

7.

(a) Write an algorithm of red-black tree insertion.


(b) Explain the operation of splay trees with an example.
(a) Refer Q # 6, set-2 of this section.
(b) Splay tree operations:
All normal operations on a binary search tree are combined with one basic operation, called
splaying. When a node x is accessed, a splay operation is performed on x to move it to the root.
To perform a splay operation we carry out a sequence of splay steps, each of which moves x closer
to the root. By performing a splay operation on the node of interest after every access, the recently
accessed nodes are kept near the root and the tree remains roughly balanced, so that we achieve
the desired amortized time bounds.
Each split, join, delete and insert operations can be reduced to splay operations and
modifications of the tree at the root which take only constant time. Thus, the run time for each
operation is essentially the same as for a splay operation.
The splay operation is done using rotations on x and parent p and grandparent g. There are
two kinds of double rotations and one single rotation. Due to symmetry, we need mirror-image
versions of each rotation. The three types of splay steps are:
Type 1 (Zig Step): This step is done when p is the root (splay node x is a child of the root). We
need a single rotation.

464

Advanced Data Structures and Algorithms in C++

Zig rotation

Zag rotation

Type 2 (Zig-zig Step): This step is done when p is not the root and x and p are either both right
children or are both left children.

p
A

C
A

B
C

Type 3 (Zig-zag Step): This step is done when p is not the root and x is a left child and p is a right
child or vice versa.

8.

A
B

(a) Explain the construction of the kmp flowchart with an example.


(b) Explain the search engines.
Note: Topics of this question are not included in the current syllabus, R07.

Você também pode gostar