Project Ideas For Data Structures

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

Project Ideas for Data Structures

You should select one of the following projects as your project. You should team up to

three students for a single project. Each student should have his own unique

contribution for the project. The project should have the following components:

1. The project should have conceptual description first before proceeding to write

the code. This conceptual description will be part of your final report.

2. Each function/method in your implementation should have a pseudo code which

will be part of your report.

3. You may use either Java or C++ to implement your project and the whole source

code should be part of the appendices of you document (report).

4. The project should use codes of each method/function along with the pseudo

code in the final documentation.

5. You should have a main function to show/demonstrate the project. In the main

function, you will instantiate objects and make calls to functions.

6. Your project implementation should be compiled and work during the demo.

7. You should prepare a 6 minute power point presentation for final presentation.

8. You should take 6 minutes to present the project and 2 minutes to show the

demo.

9. Project Submission Deadline: January 8, 2019 at 5:00PM

Have a good work!

1. Implement Cyclic Doubly Linked List

1.1. Requirements

In this sub-project, you will implement two classes:


1
1. Cyclic doubly linked lists: Cyclic_double_list, and
2. Doubly linked nodes: Double_node.

A cyclic doubly linked list with three nodes is shown in Figure 1. The empty cyclic doubly
linked list is shown in Figure 2.

Figure 1. A cyclic doubly linked list and three nodes.

Figure 2. An empty cyclic doubly linked list.

1.2. Class Specification

UML Class Diagram

Cyclic_double_list

- list_head:Double_node

- list_size:Integer

+ create():Cyclic_double_list

+ create( in cdl:Cyclic_double_list ):Cyclic_double_list

+ size():Integer

+ empty():Boolean

+ front():Type

+ back():Type

2
+ head():Double_node

+ count( in obj:Type ):Integer

+ swap( inout list:Cyclic_double_list )

+ =( in rhs:Cyclic_double_list ):Cyclic_double_list

+ push_front( in obj:Type )

+ push_back( in obj:Type )

+ pop_front():Type

+ pop_back():Type

+ erase( in obj:Type ):Integer

+ destroy()

1.3. Description

This class stores a finite list of n (zero or more) elements stored in doubly linked nodes. If there
are zero elements in the list, the list is said to be empty. Each element is stored in an instance of
the Double_node<Type> class. If the list is empty, the head pointer points to nullptr.
Otherwise, the head pointer points to the first node, the next pointer of the ith node (1 ≤ i < n)
points to the (i + 1)st node, the next pointer of the nth node points to the first node, the previous
pointer of the ith node (2 ≤ i ≤ n) points to the (i − 1)st node, the previous pointer of the first
node points to the nth node.

Member Variables

The two member variables are:

 A pointer to a Double_node<Type> referred to as the head pointer, and


 An integer referred to as the list size which equals the number of elements in the list.

Member Functions
Constructors

3
Cyclic_double_list()

This constructor sets all member variables to 0 or nullptr, as appropriate. (O(1))

Destructor

The destructor must delete each of the nodes in the linked list. (O(n))

Copy Constructor

The copy constructor must create a new cyclic doubly linked list with a copy of all of the nodes
within the linked list with the elements stored in the same order. Once a copy is made, any
change to the original linked list must not affect the copy. (O(n))

Accessors

This class has six accessors:

int size() const;

Returns the number of items in the list. (O(1))

bool empty() const;

Returns true if the list is empty, false otherwise. (O(1))

Type front() const;

Retrieves the object stored in the node pointed to by the head pointer. This function
throws a underflow if the list is empty. (O(1))

Type back() const;

Retrieves the object stored in the last node in the list (the node previous to the head).
This function throws a underflow if the list is empty. (O(1))

Double_node<Type> *head() const;

Returns the head pointer. (O(1))

4
int count( Type const & ) const;

Returns the number of nodes in the linked list storing a value equal to the argument.
(O(n))

Mutators

This class has seven mutators:

void swap( Cyclic_double_list & );

The swap function swaps all the member variables of this linked list with those of the
argument. (O(1))

Cyclic_double_list &operator=( Cyclic_double_list & );

The assignment operator makes a copy of the argument and then swaps the member
variables of this cyclic doubly linked list with those of the copy. (O(nlhs + nrhs))

void push_front( Type const & );

Creates a new Double_node<Type> storing the argument, the next pointer of which is
set to the current head pointer, and the previous pointer of which is set to the node
previous to the current head pointer. The previous pointer of what was the first node as
well as the next pointer of the last node in the list are also set to the new node. The
head pointer is set to this new node. If the list was originally empty, both the next and
previous pointers of the new node are set to itself. (O(1))

void push_back( Type const & );

Similar to push_front, this places a new node at the back of the list (it does the same as
push_front but does not update the head pointer). (O(1))

Type pop_front();

Delete the node at the front of the linked list and, as necessary, update the head pointer
and the previous and next pointers of any other node within the list. Return the object

5
stored in the node being popped. Throw an underflow exception if the list is empty.
(O(1))

Type pop_back();

Similar to pop_front, delete the last node in the list. This function throws a underflow
if the list is empty. (O(1))

int erase( Type const & );

Delete the first node (from the front) in the linked list that contains the object equal to
the argument (use == to test for equality with the retrieved element). As necessary,
update the head pointer and the previous and next pointers of any other node within
the list. Return the number of nodes that were deleted. (O(n))

2. A Calculator Program

2.1. Overview of the Assignment


The Assignment:
You will write a program that acts as a simple calculator, reading a file that contains an
arithmetic expression on each line. The program evaluates these expressions and prints
the values to cout. In the ordinary version of this assignment, the expressions are postfix
expressions (as described in class). In a more advanced "honors" version of this
assignment, the expressions are ordinary arithmetic expressions using infix notation.
Purposes:
Ensure that you can use the Stack template class that we have discussed in the class.
Teach you how a program can use its command line parameters.
Teach you to read input from a file (rather than from cin).
Ensure that you understand and can implement the stack-based algorithms for evaluating
an arithmetic expression.
Before Starting:
Read all of Chapter 4 slides and sections from the text book.

6
Due Date:
Files that you must write:

1. calc.cxx: This is a file that contains your main program for this assignment,
along with various other functions that you write for your main program to use.
As described above, the purpose of the main program is to read a file in which
each line contains an arithmetic expression. The program evaluates these
expressions and prints their values to cout.

The details of this main program are given in the sequence of exercises listed
below.

2.2. Discussion of the Assignment

This discussion will lead you through a series of exercises that discuss some of the input issues
that you will face in writing a program to read a file of expressions and print the values of these
expressions to cout. The end of the discussion describes the difference between an ordinary
version of this program (which evaluates postfix expressions) and an honors version (which
evaluates expressions in the usual infix notation).

Command Line Arguments

When you execute a program in most operating systems, you type the name of the
program followed by a list of values called the "command line arguments." For example,
in the Unix or DOS operating system, you might type the following to execute the mkdir
program:

mkdir hw06

The name of the program is "mkdir", and there is one command line argument, "hw06".
Other operating systems allow you to type commands along with command line
arguments in other manners (such as the "Run" dialog box in various Windows operating
systems).

7
When you write and compile a C++ program, the resulting program can also be executed
by typing its name along with command line arguments. Your instructor may have to
show you how to do this on your particular operating system. For example, you can write
a program called calc, compile the program, and execute it with the statement:

calc minimize your therbligs

The running calc program has access to the three strings "minimize", "your", and
"therbligs". In order to obtain this access, you need to make a small change to the
parameter list of the main function in the calc program. This parameter list should be
changed to:

int main(int argc, char *argv[ ])

As you can see, the main program has two parameters that it can access just like any
other parameters. The meaning of these parameters is determined by the command line
arguments in a manner that is described in the next few exercises.

Exercise 1: Using the Argc Parameter to the Main Function

The first parameter, argc, is the count of the number of command line arguments. For
example, if we execute the command:

calc minimize your therbligs

then calc's main program will have argc set to 4 (it counts the name of the program as one
of the four "arguments").

For this exercise, write a complete main program called calc.cxx. The main program
should have the two parameters, as shown above. The body of the main program should
simply print the number of arguments in a message such as "There are 4 calc
arguments!". Compile the main program into an executable file called calc. Run the
resulting calc program with several different choices of command line parameters.

8
Exercise 2: Using the Argv Array

The main program's second argument, argv, has the declaration: char* argv[ ].
What does this mean? It means that argv is an array of pointers to characters. In other
words, argv[0], argv[1], argv[2], ... are each a pointer to a character. In fact, each of these
is a pointer to a dynamic array of characters. For example, suppose we execute our
favorite command:

calc minimize your therbligs

In this example, argv[1] will be a pointer to a dynamic array of characters that contains
the first command line parameter "minimize". As you might guess, argv[2] will be a
pointer to a dynamic array that contains a pointer to a dynamic array of characters that
contains "your". And argv[3] will be a pointer to a dynamic array of characters that
contains "therbligs".

And what about argv[0]? That contains a pointer to a dynamic array of characters that
contains the actual command that was typed. In our example, argv[0] points to a dynamic
array containing "calc".

Each of the dynamic arrays argv[0], argv[1], ... can be treated just like any other string.
For example, the first command line parameter can be printed to the screen by: cout <<
argv[1];

For your next exercise, modify your calc program from Exercise 1 so that it also prints a
copy of the command that was typed along with the command line parameters. For
example, if you execute the command that we have been using in our examples, then the
output of the calc program will now be:

There are 4 calc arguments!


calc minimize your therbligs

Exercise 3: Checking That There is the Right Number of Arguments

9
Most programs require a certain number of command line parameters. If you provide the
wrong number of command line parameters, then the program provides a "usage
message" trying to tell you the correct way to use the program. Let's suppose that your
calc program is supposed to read input from an input file, and that calc is always used
with two command line arguments (the name calc is the first argument, and the name of
the input file is the second argument). If calc is executed with more or less arguments,
then you want a succinct error message to be printed such as "Usage: calc input_file."
This is a succinct message describing how calc is supposed to be used.

For this exercise, add a new function to your calc program, with this prototype

void validate_argc(int argc, int right, const char usage[


]);

The function has three arguments. The first argument, argc, is the value of argc from the
main program. The second argument, right, is the correct number of arguments that this
program is supposed to have. The third argument, usage, is a string which is a suscinct
usage message for the program. Here's what the function does: If argc is equal to right,
then the function does nothing. But if argc differs from right, then the function prints the
message and halts the program. Two points:

1. Print the message to cerr, rather than to cout. (since cerr is the "standard error
output").
2. You can halt the program by calling a function exit(0).

Once the validate_argc function is added to your program, include a call to the function
in your main program. The call should indicate that the right number of arguments is 2,
and provide the usage message: "Usage: calc input_file."

Reading From a File

10
By the way, you are headed toward a program that reads a file of arithmetic expressions
as input. Eventually the program will evaluate all the expressions, and write the resulting
answers to cout. Input from a file is easy in C++. Here are the steps:

1. The program must #include <fstream.h>.


2. The program needs to declare an ifstream variable that will "attach" to the input
file. The data type of this variable is ifstream. For example, you could make the
declaration of an ifstream variable called input, as shown here:

ifstream input; // Will be attached to the input file

3. The ifstream must be attached to the input file by activating the open function.
The argument to the open function is an ordinary C string constant or string
variable. For example, if input is an ifstream, then we can attach input to a file
named foo with the function call:

input.open("foo");

The argument to the open function can also be a string variable-- i.e., an array of
characters with the last character followed by the null character '\0'.

4. You must check that the file was successfully opened. This check can be made by
activating the fail function. This function returns a true/false value to indicate
whether the ifstream has failed to properly attach to the input file (true indicates
failure). I'd suggest something like this:
5. if (input.fail( ))
6. {
7. cerr << "Could not open input file." <<
endl;
8. exit(0);
9. }

11
10. Once the ifstream input has been successfully opened, you may use the name
input in the same way that you use other input devices (such as cin). For
example, suppose that the next item in the input file is an integer, and i is an
integer variable. Then you may execute the statement:

input >> i; // Read an integer from the input file

All the other familiar input functions (such as peek and ignore) can be used with
the ifstream, in the same way that you have previously used these function with
cin.

11. Eventually you will read the end of the input file. At the end of the file there is
usually a special character called EOF (which is not actually part of the data of
the file). You can also use the name of the ifstream (such as input in our
example) as a boolean expression which is true so long as the file has not
contained any bad input (such as an alphabetic character when a digit is
expected). Thus, the complete boolean expression to test whether there is more
input reads like this:

(input && input.peek( ) != EOF)

12. When you are done reading from the input file, then you should activate the
close function, as shown here for our example:

input.close( );

Exercise 4: Three More Functions for the Calc Program

Write the following three functions, and add them to your calc.cxx:

1. A function that opens a file for input and checks that the opening process did not
fail. The specification for this function is:

12
void open_for_read(ifstream& f, const char filename[
]);
// Postcondition: The ifstream f has been opened for
// reading using the given filename. If any errors
// occurred then an error message has been printed
// and the program has been halted.

2. A boolean function that determines whether a specified file still has valid input.
The specification is:

bool is_more(istream& f);


// Postcondition: The return value is true if f still
has
// more valid input to read; otherwise the return
// valid is false.

Notice that the data type of the argument is an "istream" rather than an "ifstream".
An istream is usually an ifstream, although later you will learn of other kinds of
istreams (such as cin). Istream arguments must always be reference parameters.

3. A function named process_line with this prototype:

void process_line(istream& f, bool& okay, double&


answer);

For now, you can implement a simple version of process_line. The simple version
reads one input line, sets okay to true, and sets answer to 42.0. Later you will
implement a more complex version of the function that actually evaluates an
arithmetic expression from the input line. Anyway, put the simple version in your
calc.cxx program and change the body of the main function so that it does this:

o Declares an ifstream variable called input and uses open_for_read to


open input for reading. Use argv[1] as the name of the input file.

13
o Have a loop that continues while there is more input. In the body of the
loop, call process_line. If process_line sets okay to true then print the
value of answer on the next output line; otherwise print the word
INVALID on the next output line.
o Close the input file and print a message saying that the program is done

The Rest of This Programming Assignment

For the rest of this programming assignment, modify the process_line function so that it
reads the next line of input (including the newline character at the end of the line). It
treats this input line as an arithmetic expression. If the expression has an error (such as
division by zero), then the function sets okay to false and leaves answer unchanged.
Otherwise, the function sets okay to true and sets answer to the value of the expression.
Here are a few considerations:

 For the ordinary assignment, the input expressions are postfix expressions. The
possible errors that will cause okay to be set to false: (a) division by zero, (b) an
operation symbol is read but the stack does not have at least two numbers, (c)
the entire expression has been read and evaluated but the numbers stack does
not have exactly one number on it.
 For the extra credit assignment, the input expressions are usual arithmetic
expressions using infix notation. There are two approaches you can use: (1)
Convert the expression to a postfix expression (stored in a string). Then evaluate
the postfix expression. Or (2) Try the better approach which evaluates the infix
expression without first converting it to postfix.
 If an error is detected, be sure to continue to read the rest of the input line (up
to and including the newline character).
 Use input techniques similar to those in Figure 7.5 on pages 319-321.

14
3. PriorityQueue Class Implemented with a Linked List

3.1. Overview of the Assignment


The Assignment:
You will implement and test a PriorityQueue class, where the items of the priority queue
are stored on a linked list. The Node struct for this linked list must be defined in your
header file for the priority queue rather than in a separate header file. All of the
PriorityQueue member functions should be written by you from scratch (not using the
linked list toolkit).
Note: This is not a template class; you will use a typedef statement to define the
underlying data type.
Purposes:
Give you more practice in building and manipulating linked lists.
Introduce you to priority queues which we will use again later in the semester.
Due Date:
________________
Files that you must write:

1. pqueue1.h: The header file for this first version of the PriorityQueue class. If
some of your member functions are implemented as inline functions, then you
may put those implementations in this file too.
2. pqueue1.cxx: The implementation file for the PriorityQueue class. You will
write all of this file, which will have the implementations of all the
PriorityQueue's member functions. Also, remember that the PriorityQueue's
linked list consists of dynamic memory, so you will need to define a copy
constructor, an assignment operator, and a destructor.

3.2. Discussion of the Assignment

You will store the items on a linked list, where each node contains both an item and the item's
priority, as shown here:

15
struct Node
{
// Note: Item is defined with a typedef in the PriorityQueue class
PriorityQueue::Item data;
unsigned int priority;
Node *link;
};

This Node definition appears in the header file pqueue1.h, immediately after the PriorityQueue
class definition. Neither class needs to be a template class (instead, the Item type is defined as an
int by using a typedef within the PriorityQueue class definition).

This approach is different from the approaches discussed in Section 8.4 since all the items are
stored on a single linked list. I suggest that you maintain the linked list in order from highest to
lowest priority. If there are several items with equal priority, then the one that came in first will
be in front of the others. If you choose to follow this suggestion, then make sure that you have a
clearly stated invariant that expresses this way of storing the items.

Because the linked list is kept in order, the get_front operation is simple. It merely gets the head
item of the linked list. On the other hand, the insert operation requires some amount of work,
making sure that the new entry goes at the correct spot in the linked list. A new entry must go
after any existing entries with a higher or equal priority.

4. Implement and Test the Priority Queue using a Heap

4.1. Overview of the Assignment


The Assignment:
Implement the Priority Queue class, using a heap to store the items.
Purposes:
Ensure that you understand and can use heap which is implemented in an array.
Due Date:
________________

16
Files that you must write:

1. pqueue2.h: Header file for this version of the PriorityQueue class. You don't
have to write much of this file. Just copy our version from pqueue2.h and add
your name and other information at the top.
2. pqueue2.cxx: The implementation file for the new PriorityQueue class.

4.2. Discussion of the Assignment

Your PriorityQueue class for this assignment will use a heap that is stored in an array. The
component type of the array is called OneItemInfo, and is defined as part of the PriorityQueue
class in pqueue2.h. In order for you to further learn how to use private member functions, I
would like for you to write and use all the private member functions that are listed in pqueue2.h.

Dynamic Memory

This version of the priority queue does not use dynamic memory. What does that tell you about
the need for a copy constructor, an assignment operator, and a destructor?

Do the Assignment in Two Parts

For easier debugging, carry out this assignment in two parts. Part One should include everything
except the public get_front member function and the private functions that are needed for
get_front (i.e., is_leaf, big_child_index, and big_child_priority). While you are debugging Part
One, you should add a new option to the interactive test program. The new option makes this call
to print the current state of the tree of the test PriorityQueue: test.print_tree("STATE OF THE
TREE:"). (Note that print_tree is a public member function that I wrote for you. You should
remove it before you submit your final work.)

Once Part One is working and tested, you may complete the assignment by implementing the
get_front member function and its associated private functions.

17
Use the interactive test program and the debugger to track down errors in your implementation.
If you have an error, do not start making changes until you have identified the cause of the error.
If you come to me or a TA for help, we will always ask you to do the following:

1. Show us the invariant that describes how your private member variables implement the
List class.
2. Use the debugger to show us the problem!

5. Implementing a the Table class with Chaining

5.1. Overview of the Assignment


The Assignment:
Implement the Table template class using a chained hash table to store the items.
Purposes:
Ensure that you understand and can implement the important data structure of a hash
table.
Due Date:
________________
Files that you must write:

1. table2.h: Header file for this version of the Table class. You don't have to write
much of this file.
2. table2.template: The implementation file for the new Table class. You must
write this yourself. NOTE: Some of you have been forgetting to put your name
and a clearly written invariant at the top of this file. The TAs will start taking off
points for such omissions.
3. makefile: This is a makefile for the assignment. The file should contain targets
for tabtest.o, tabexam.o, tabtest (an executable file) and tabexam (another
executable file). Your makefile should also include "all" and "clean" options.
Include your name in a comment at the top.

18
5.2. Discussion of the Assignment

This version of the Table is a template class with a template parameter called RecordType. In an
actual program (such as tabtest.cxx or tabexam.cxx), the RecordType may be instantiated as any
struct or class type with an integer member variable called key.

Start by understanding the private member variables that have been proposed in table2.h. For
example, where are the head pointers stored for the separate linked lists? Write an invariant that
describes the use of these private member variables, and put the invariant at the top of your
implementation file.

It is good if you also write some private member functions that can help in the implementations
of the public member functions. For example, one of the private member functions that I found
useful was:

Node* find_node(int key);


// Precondition: none.
// Postcondition: If there is a node in the Table with the specified
// key, then the return value is a pointer to this node. Otherwise the
// return value is NULL.

I made use of find_node in my implementations of remove, find and is_present. If you decide to
use something like find_node, then list the prototype in the private section of the Table class.
Put the implementation in the implementation file along with a precondition/postcondition
contract.

Hints for the remove function:

1. Set a pointer (called cursor) to point to the node that you're removing. (If there is no
such node, then return with no work.)
2. Set i equal to the hash value of the key.
3. Copy the data portion of the head node of list i to the cursor node. You have now got rid
of the node with the specified key, but you have two copies of the data from the head
node.

19
4. list_head_remove(array[i]);

NOTE: Since your Table class is using dynamic memory (a bunch of linked lists), the Table
must have a copy constructor, assignment operator, and a destructor! But keep in mind that much
of your work can be carried out by the functions of the linked list toolkit.

Use the interactive test program and the debugger to track down errors in your implementation.
If you have an error, do not start making changes until you have identified the cause of the error.
If you come to me or a TA for help, we will always ask you to do the following:

1. Show us the invariant that describes how your private member variables implement the
Table class.
2. Use the debugger to show us the problem!

6. The Polynomial with a Linked List

6.1. Overview of the Assignment


The Assignment:
Implement a polynomial class that uses a doubly-linked list to store the polynomial's
terms. Each node of the list holds the coefficient and exponent for one term. The terms
are kept in order from smallest to largest exponent. Each polynomial also maintains a
pointer to the most recently accessed node.
Purposes:
Ensure that you can write a class that uses a linked list.
Due Date:
-----------------
Files that you must write:

1. poly2.h: The header file for the new polynomial class.


2. poly2.cxx: The implementation file for the new polynomial class.

6.2. Discussion of the Assignment

As indicated above, you will revise the polynomial class to use a doubly-linked list to store.
You'll need to copy your poly1.cxx to a new file poly2.cxx and make these changes:

1. Change the namespace to main_savitch_5, and change the include statement to


include poly2.h. Also include <cstdlib> (which has NULL).

20
2. Delete the DEFAULT_CAPACITY constant.
3. Look at the new polynode class in poly2.h and look at the new private member
variables for the polynomial class:

private:
polynode *head_ptr; // Head pointer for list of nodes
polynode *tail_ptr; // Tail pointer for list of nodes
mutable polynode *recent_ptr; // Pointer to most recently used node
unsigned int current_degree; // Current degree of the polynomial

The meaning of the mutable keyword will be covered in class. But a brief explanation
now: Our plan is to keep the recent_ptr always pointing to the most recently-used
node. For example, when we call the coefficient member function, we will move the
recent_ptr to point to the node that contains the requested exponent. With a normal
member variable, we could not do this (since the coefficient is a const member
function and it is forbidden from changing normal member variables). So, the meaning
of the mutable keyword is to indicate that changing the member variable does not
change the value of the polynomial in a meaningful way (and therefore, the compiler
will let const member functions change a mutable variable).

4. In your poly2.cxx, write a clear description of how the member variables of a


polynomial are used. The head_ptr and tail_ptr are the head and tail pointers for a
doubly-linked list of nodes that contain the polynomial's terms in order from smallest to
largest exponent. To make certain operations simpler, we will always keep a node for
the zero-order (x^0) term. But other nodes are kept only if the coefficient is non-zero.
We always maintain recent_ptr as a pointer to some node in the list--preferably the
most recently used node. The degree of the polynomial is stored in current_degree
(using zero for the case of all zero coefficients).
5. Delete the reserve member function (and delete any places where you called the
reserve function).
6. The poly2.h header file contains a prototype for a private member function that will
make it easier to implement everything else:

// A private member function to aid the other functions:


void set_recent(unsigned int exponent) const;

The set_recent function will set the recent_ptr to the node that contains the
requested exponent. If there is no such exponent, then recent_ptr should be set to the
last node that is still less than the specified exponent. Note that set_recent is a const
member function, but that it can still change the mutable recent_ptr member variable.
My implementation of set_recent used three cases:

o If the requested exponent is zero, then set recent_ptr to the head of the list.

21
oElse if the exponent is greater than or equal to the current degree, then set
recent_ptr to the tail of the list.
o Else if the exponent is smaller than the exponent in the recent node, then move
the recent_ptr backward as far as needed.
o Else move the recent_ptr forward as far as needed.
7. Implement the new constructors, destructor, and assignment operator.

The default constructor should start by creating a valid empty polynomial (with a head
node that has exponent and coefficient of zero). Then call assign_coef to set the one
term.

The copy constructor should also start by creating a valid empty polynomial. Then have a
loop tht steps through the terms of the source (using source.next_term). Each term of the
source is placed in the polynomial (using assign_coef).

The assignment operator first checks for a self-assignment, then clears out the terms
(using the clear function). Finally, have a loop (similar to the copy constructor) to copy
the terms of the source.

8. Implement the new assign_coef function. After calling set_recent(exponent), my


implementation had these cases:
o If there is a zero coefficient and exponent greater than current degree (return
with no further work).
o Else if there is currently no node for the given exponent (so that recent_ptr-
>exponent( ) is less than the new exponent).
o Else if the coefficient is non-zero or the exponent is zero (in
both these cases, we just change the coefficient of a node).
o Else if the exponent equals the current degree. In this case, the
coefficient is zero (otherwise we would have hit the previous
case), so we remove the tail node, set the recent_ptr to the new
tail, and reduce the current degree.
o Else...in this last case we must have a new zero coefficient for
a node that is neither the head nor the tail. We just remove this
node and set the recent_ptr to the next node in the list. Modify
the other polynomial member functions to use the dynamic array.
9. Rewrite next_term

and previous_term so that they each start with set_recent(exponent).


Hint for next_term: Start by calling set_recent(e). Normally, the right
answer is then in the node after recent_ptr (but there is one exception
that you should handle). Hint for previous_term: Start by checking the
special case where e is zero. Then call set_recent(e-1). Normally, the
right answer is then in the recent_ptr node (but again, there is one
exception that you need to handle--the case where the node has exponent
and coefficient of zero).

10. Revise any other functions that access the member variables directly.
In my implementation, this was only clear and coefficient. I told you

22
that it would save work to avoid accessing those member variables
directly!

7. Dynamic Dual Stack using Array

7.1. Requirements:

In this sub-project, you will implement one class:

1. Dynamic dual stack: Dynamic_dual_stack.

A stack stores objects in an ordered list and allows insertions and deletions at one ends of the list
in Θ(1) time. This class requires you to implement two stacks using a single array (one stack
starting at each end) allowing pushes and pops from each stack. The stacks are identified by the
integers 0 and 1.

The objects in these stacks are stored in a single array. The capacity of the array may be changed
depending on the sum of number of objects stored in the two stacks according to the following
two rules:

 If an object is being pushed onto a stack where the array is already full (the sum of the
number of objects in both stacks equals the array capacity), the capacity of the array is
doubled.
 If, after removing an object from a stack, the sum of the number of objects remaining in
the two stacks is 1/4 the capacity of the array or less, then the capacity of the array is
halved. The capacity of the array may not be reduced below the initially specified
capacity.

7.2. Run Times:

The amortized run time of each member function is specified in parentheses at the end of the
description.

23
7.3. Class Specifications:

Dynamic_dual_stack
Description

A class which implements two stacks using a single array. The capacity of the array may be
changed dynamically after insertions or removals. For run-time requirements, the number of
objects in both stack is n.

Member Variables

The class at least six member variables:

 A pointer to an instance of Type, Type *array, to be used as an array,


 Two size counters int stack_sizes[2],
 The initial capacity of the array, int initial_capacity, and
 The current capacity of the array, int array_capacity.

Member Functions
Constructors

Dynamic_dual_stack( int n = 10 )

The constructor takes as an argument the initial capacity of the array and allocates memory for
that array. If the argument is either 0 or a negative integer, set the initial capacity of the array to
1. The default initial capacity of the array is 10. Other member variables are assigned as
appropriate.

Destructor

~Dynamic_dual_stack()

The destructor deletes the memory allocated for the array.

Copy Constructor

Dynamic_dual_stack( Dynamic_dual_stack const & )

The copy constructor creates a new instance of the stack. (O(n))

Accessors

This class has four accessors:

24
Type top( int m ) const

Return the object at the top of the mth stack. If m is neither 0 nor 1, throw an illegal
argument exception. It may throw a underflow exception. (Θ(1))

int size( int m ) const

Returns the number of objects currently stored in the mth stack. If m is neither 0 nor 1,
throw an illegal argument exception. (Θ(1))

bool empty( int m ) const

Returns true if the mth stack is empty, false otherwise. If m is neither 0 nor 1, throw an
illegal argument exception. (Θ(1))

int capacity() const

Returns the current capacity of the array. (Θ(1))

Mutators

This class has five mutators:

void swap( Dynamic_dual_stack & );

The swap function swaps all the member variables of this stack with those of the
argument. (O(1))
Dynamic_dual_stack &operator=( Dynamic_dual_stack & );

The assignment operator makes a copy of the argument and then swaps the member
variables of this node with those of the copy. (O(nlhs + nrhs))
void push( int m, Type const & )

Insert the new object at the top of the mth stack. If before the object is placed into the
stack, the array is filled (the sum of the number of objects in the stacks equals the array
capacity), the capacity of the array is doubled. If m is neither 0 nor 1, throw an illegal
argument exception. (amortized Θ(1))

Type pop( int m )

Pop the object at the top of the mth stack and return it. If after the object is popped off
the stack, the array is less than or equal to a quarter full, the capacity of the array is
halved, but not below the initial capacity. If m is neither 0 nor 1, throw an illegal
argument exception. It may throw a underflow exception. (amortized Θ(1))

25
void clear()

Empties both stacks. If the array capacity has been increased, restore the capacity to the
original size. (Θ(1))

Friends

The class has no friends.

8. Quad Tree Implementation

8.1. Requirements

In this sub-project, you will implement two classes:

1. Quadtrees: Quadtree, and


2. Quadtree Nodes: Quadtree_node.

This variation of a quadtree is similar to a binary search tree; however, rather than sorting on just
one well-ordered element, it sorts on a pair of well-ordered elements. That is, it stores two-
dimensional vectors which we will denote as (x, y) and each node has up to four children.

8.2. Class Specification

UML Class Diagram


Quadtree

- root:Quadtree_node
- count:Integer

+ create():Quadtree
+ size():Integer
+ empty():Boolean
+ min_x():Type
+ min_y():Type
+ max_x():Type
+ max_y():Type
+ sum_x():Type
+ sum_y():Type

26
+ root():Quadtree_node
+ member( in x:Type, in y:Type ):Boolean
+ insert( in x:Type, in y:Type )
+ clear()
+ destroy()

Description

This class stores a finite collection of n (zero or more) pairs (x,y) stored in nodes. If there are
zero elements in the quadtree, the quadtree is said to be empty. Each pair is stored in an instance
of the Quadtree_node<Type> class. If the quadtree is empty, the root is assigned 0. Otherwise,
the root pointer stores the address of the root node.

Member Variables

The two member variables are:

 A pointer to a quadtree_node<Type> object, referred to as the tree_root, and


 An integer referred to as the count which equals the number of elements in the
quadtree.

Member Functions
Constructors

Quadtree()

This constructor sets all class members to 0. (O(1))

Destructor

The destructor must delete each of the nodes in the quadtree. (O(n))

Accessors

This class has ten accessors:

int size() const;

Returns the number of items in the quadtree. (O(1))


bool empty() const;

27
Returns true if the quadtree is empty, false otherwise. (O(1))

Type min_x() const;


Type max_x() const;
Type min_y() const;
Type max_y() const;

Returns the minimum-or-maximum x-or-y value within the quadtree. Throw an


underflow exception if the tree is empty. (O(n) but O(√n) if balanced)
Type sum_x() const;
Type sum_y() const;

Returns the sum of the x and y values within the quadtree, respectively. The sum of the
nodes of an empty tree is 0. (O(n))

Quadtree_node<Type> *root() const;

Returns the address of the root node. If the tree is empty, the root node should be 0.
(O(1))

bool member( Type const &x, Type const &y ) const;

Returns true if the pair (x,y) is stored in one of the nodes of the quadtree and false
otherwise. (O(n) but O(ln(n)) if balanced)

The first two member functions should be implemented in the Quadtree class; the other member
functions should call the appropriate member functions of the root node.

Mutators

This class has two mutators:

void insert( Type const &x, Type const &y );

Inserts the pair (x, y) into the quadtree. If the root is 0, a new quadtree node is created;
otherwise, the task of insertion is passed to the root node.

(O(n) but O(ln(n)) if balanced)

void clear();

Calls clear on the root if necessary and sets the root and count to 0. (O(n))

28
9. Implement Dijkstra's Algorithm

9.1. Requirements

In this sub-project, you will implement one class:

1. A weighted graph: Weighted_graph.

9.2. Class Specification

UML Class Diagram


Weighted_graph

+ create( in n:Integer = 50 ):Weighted_graph


+ degree( in n:Integer ):Integer
+ edge_count():Integer
+ adjacent( in m:Integer, in n:Integer ):Real
+ distance( in m:Integer, in n:Integer ):Real
+ insert( in m:Integer, in n:Integer, in w:Real )
+ destroy()

Description

This class allows the user to create and destroy an undirected weighted graph. You will be able
to find the shortest distance between two connected vertices. The vertices are numbered 0
through n − 1 where n is the argument to the constructor.

Member Variables

You may define whatever member variables you wish.

Member Functions
Constructors

Weighted_graph( int n = 50 )

Construct an undirected graph with n vertices (by default, 50). If n ≤ 0, use n = 1.

29
Destructor

Clean up any allocated memory.

Accessors

This class has three accessors:

int degree( int n ) const

Returns the degree of the vertex n. Throw an illegal argument exception if the argument
does not correspond to an existing vertex. (O(1))
int edge_count() const

Returns the number of edges in the graph. (O(1))

double adjacent( int m, int n ) const

Returns the weight of the edge connecting vertices m and n. If the vertices are the
same, return 0. If the vertices are not adjacent, return infinity. Throw an illegal
argument exception if the arguments do not correspond to existing vertices.

Mutators

This class has two mutators:

void insert( int m, int n, double w )

If the weight w ≤ 0 if it is infinity, throw an illegal argument exception. If the weight w >
0, add an edge between vertices m and n. If an edge already exists, replace the weight
of the edge with the new weight. If the vertices do not exist or are equal, throw an
illegal argument exception.

double distance( int m, int n )

Return the shortest distance between vertices m and n. Throw an illegal argument
exception if the arguments do not correspond to existing vertices. The distance between
a vertex and itself is 0.0. The distance between vertices that are not connected is
infinity.

30
10. Implement Prim's algorithm

10.1.Requirement

In this sub-project, you will implement one class:

1. A weighted graph: Weighted_graph.

This class will implement a weighted undirected graph with Prim's algorithm.

10.2.Class Specification

UML Class Diagram


Weighted_graph

+ create( in n:Integer = 50 ):Weighted_graph


+ degree( in n:Integer ):Integer
+ edge_count():Integer
+ adjacent( in m:Integer, in n:Integer ):Real
+ minimum_spanning_tree( in m:Integer ):Real
+ is_connected():Boolean
+ insert( in m:Integer, in n:Integer, in w:Real )
+ destroy()

Description

This class allows the user to create and destroy an undirected weighted graph.You will be able to
find the minimum span tree of a graph, using Prim's algorithm. The vertices are numbered 0
through n − 1 where n is the argument to the constructor.

Member Variables

You may define whatever member variables you wish.

Member Functions
Constructors

Weighted_graph( int n = 50 )

31
Construct a weighted undirected graph with n vertices (by default, 50).

Destructor

Clean up any allocated memory.

Accessors

This class has four accessors:

int degree( int n ) const

Returns the degree of the vertex n. Throw an illegal argument exception if the argument
does not correspond to an existing vertex. (O(1))

int edge_count() const

Returns the number of edges in the graph. (O(1))

double adjacent( int m, int n ) const

Returns the weight of the edge connecting vertices m and n. If the vertices are equal,
return 0. If the vertices are not adjacent, return infinity. Throw an illegal argument
exception if the arguments do not correspond to existing vertices.

bool is_connected() const

Determine if the graph is connected.

double minimum_spanning_tree( int m ) const

Return the size of the minimum spanning tree of those nodes which are connected to
vertex m. Throw an illegal argument exception if the arguments do not correspond to
existing vertices.

Mutators

This class has one mutator:

void insert( int m, int n, double w )

If the weight w < 0 or w = ∞, throw an illegal argument exception. If the weight w is 0,


remove any edge between m and n (if any). Otherwise, add an edge between vertices m
and n with weight w. If an edge already exists, replace the weight of the edge with the
new weight. If the vertices do not exist or are equal, throw an illegal argument
exception.

32
11. Topological Sort using Graph

11.1.Requirements

In this sub-project, you will implement one class:

1. A directed acyclic graph: Directed_acyclic_graph

Note that there are no templates used in this project.

11.2.Description

This class stores a number of prioritized vertices and edges which connect those vertices.
Initially the class has no edges and the priority (a double) of each vertex is equal to its numeric
identifier (0 through n − 1). A topological sort is performed in the following manner: at any step
of the topological sort where there are more than one vertices with in-degree zero, that vertex
with highest priority (smallest numeric value) is chosen next.

Member Variables

The design of the class is up to you: you may use any data structure you see fit.

Member Functions
Constructors

The constructor takes an argument n which defines a graph with vertices numbered from 0
through n − 1. The vertex i is given an initial priority of i. The default value of n is 10.

Destructor

The destructor frees up any memory allocated by the constructor.

Copy Constructor

The copy constructor makes a complete copy of the directed acyclic graph passed in the
argument.

Assignment Operator =

The default assignment operator is used.

33
Accessors

This class has six accessors:

int in_degree( int i ) const

Returns the in degree of the given vertex i. If i is outside the range 0, ..., n − 1, throw an
illegal argument exception.

int out_degree( int i ) const

Returns the out degree of the given vertex i. If i is outside the range 0, ..., n − 1, throw
an illegal argument exception.

int edge_count() const

Returns the number of edges in the graph.


bool adjacent( int i, int j ) const

Returns true an edge exists from vertex i to vertex j. If i or j are outside the range 0, ...,
n − 1, throw an illegal argument exception.
bool connected( int i, int j ) const

Returns true there exists a directed path from vertex i to vertex j. Consider using a
queue. Also, ask yourself: is (i) a path? If i or j are outside the range 0, ..., n − 1, throw an
illegal argument exception.

void topological_sort() const

Print a topological sort of the vertices (as described above) in the DAG by printing the
vertices separated by a dash -. The restriction is, if there are multiple possible vertices
which could be included next in the ordering, the one with the highest priority value
must be chosen. Recall that each topological sort must print all n vertices. The output
should not have an end-of-line character at the end. An example of valid output of the
code fragment

cout << ">>>>>>>>>";


graph.topological_sort();
cout << "<<<<<<<<<";
may be

>>>>>>>>>1-3-2-4-5-7-6-9-0-8<<<<<<<<<
Note that the >>>>>>>>> and <<<<<<<<< are a result of the two cout statements, both
before and after.

34
Mutators

This class has four mutators:

bool set_priority( int i, double priority )

If another vertex already exists with that argument priority, return false and do
nothing; otherwise, set the priority of vertex i to priority and return true. If i is outside
the range 0, ..., n − 1, throw an illegal argument exception.

bool insert_edge( int i, int j )

Insert a new edge from vertex i to vertex j so long as the new vertex does not cause a
cycle to appear. Return true if the insertion was successful, and false otherwise. If i
equals j, return false and if an edge from i to j already exists, again, return false. If i or
j are outside the range 0, ..., n − 1, throw an illegal argument exception.

void clear_edges()

Removes all the edges from the graph.


void reset_priorities()

Sets the priority of all of the vertices to their default value.

Friends

The class has no friends.

Testing

Consider testing your code with something like:

#include <iostream>
#include "Directed_acyclic_graph.h"

using namespace std;

int main() {
Directed_acyclic_graph graph(4);

graph.insert_edge( 0, 2 );
graph.insert_edge( 2, 1 );
graph.insert_edge( 1, 0 ); // should return false
graph.insert_edge( 0, 3 );
graph.connected( 0, 1 ); // should return true
graph.connected( 1, 0 ); // should return false
graph.adjacent( 0, 1 ); // should return false
graph.adjacent( 0, 2 ); // should return true

35
graph.adjacent( 1, 0 ); // should return false
graph.adjacent( 2, 0 ); // should return false
graph.topological_sort(); // prints 0-2-1-3
cout << endl;
graph.set_priority( 3, 0.5 );
graph.topological_sort(); // prints 0-3-2-1
cout << endl;

return 0;
}

Common Issues

Note that if there are no edges in the graph, the topological sort will be a printout of the vertices
in order of their priority.

Recall that the order in which member variables are assigned in the copy constructor is the same
order in which the member variables are declared in the class definition. Thus, the following
would fail:

class Faulty {
private:
int *array;
int towers;

public:
Fault( int );
Fault( Fault const & );
};

// 'array' is assigned before 'towers' even though it looks like


// 'towers' is assigned the value of the argument first.
Fault::Fault( int n ):
towers( n ),
array( new int[towers] ) {
// empty
}

// Similarly, 'array' is assigned before 'towers' even though it looks like


// 'towers' is assigned the value of 'faulty.towers' first.
Fault::Fault( int n ):
Fault::Fault( Fault const &faulty ):
towers( faulty.towers ),
array( new int[towers] ) {
for ( int i = 0; i < towers; ++i ) {
array[i] = faulty.array[i];
}
}

36
12. Implement Kruskal's Algorithm

12.1.Requirements

In this sub-project, you will implement one class:

1. A weighted graph: Weighted_graph

This is a graph that has positive real weights for edges (double). Note that there are no templates
used in this project.

12.2.Description

This class allows the user to create and destroy an undirected weighted graph. You will be able
to find a minimum spanning tree using Kruskal's algorithm. The vertices are numbered 0 through
n − 1 where n is the argument to the constructor.

In a nutshell, Kruskal's algorithm runs as follows:

 Start with an empty minimum spanning tree and mark each edge as unvisited,
 While there are edges not yet visited or while the minimum spanning tree does not have
n − 1 edges:
o Find the edge with minimum weight that has not yet been visited.
o Mark that edge as visited.
o If adding that edge does not create a cycle (that is, the two vertices are not
already connected), add that edge to the minimum spanning tree.

Note that there are numerous mechanisms of simplifying this problem. It's up to you to consider
the test case and to determine any optimizations you should make. While the description above is
absolutely correct, there are more efficient mechanisms of, for example, determining whether or
not two vertices are connected in the partially built minimum spanning tree.

Member Variables

The design of the class is up to you: you may use any data structure you see fit.

Member Functions
Constructors

The constructor takes an argument n which defines a graph with vertices numbered from 0
through n − 1. The vertex i is given an initial priority of i. The default value of n is 10.

37
Destructor

The destructor frees up any memory allocated by the constructor.

Copy Constructor

No copy constructor is required.

Assignment Operator =

No assignment operator is required.

Accessors

This class has five accessors:

int degree( int i ) const

Returns the in degree of the given vertex i. If i is outside the range 0, ..., n − 1, throw an
illegal argument exception.
int edge_count() const

Returns the number of edges in the graph.

std::pair<double, int> minimum_spanning_tree() const

Use Kruskal's algorithm to find the minimum spanning tree. You will return the weight
of the minimum spanning tree and the number of edges that were tested for insertion
into the minimum spanning tree.

An example of Kruskal's algorithm is shown in the following figures. To find the minimum
spanning tree on the graph in Figure 1, we begin by examining the edges with least weight: the
edges with weights 1.3 and 1.4 can be safely inserted, but the edge with weight 1.5 cannot, as it
creates a cycle. After this, the edge with weight 1.6 can be inserted. The minimum spanning tree
up to this point is shown in red in Figure 2. We proceed by inserting the edge with weight 1.7,
but we cannot insert the edge with weight 1.8, as this causes a cycle. We conclude by adding the
edge of weight 1.9. The weight of the minimum spanning tree is 7.9 and we visited seven nodes
to find this.

38
Figure 1. A weighted graph.

Figure 2. The partial minimum spanning tree after the first four edges were checked.

Mutators

This class has three mutators:

bool insert_edge( int i, int j, double w )

If i equals j and are in the graph, return false. Otherwise, either insert a new edge from
vertex i to vertex j or, if the edge already exists, update the weight and return true.
Recall that the graph is undirected. If i or j are outside the range 0, ..., n − 1 or if the
weight w is less than or equal to zero, throw an illegal argument exception.

39
bool erase_edge( int i, int j )

If an edge between nodes i and j exists, remove the edge. In this case or if i equals j
return true. Otherwise, if no edge exists, return false. If i or j are outside the range 0, ...,
n − 1, throw an illegal argument exception.
void clear_edges()

Removes all the edges from the graph.

Friends

The class has no friends.

Testing

Consider testing your code with something like:

#include <iostream>
#include "Weighted_graph.h"

using namespace std;

int main() {
Weighted_graph graph(4);

graph.insert_edge( 0, 1, 7.2 );
graph.insert_edge( 0, 2, 7.5 );
graph.insert_edge( 0, 3, 7.3 );
graph.insert_edge( 1, 2, 7.6 );
graph.insert_edge( 1, 3, 7.4 );
graph.insert_edge( 2, 3, 7.7 );
cout << graph.minimum_spanning_tree().first << endl; // returns the
minimum spanning tree weight: 22
cout << graph.minimum_spanning_tree().second << endl; // returns the
number of nodes inspected: 4
graph.insert_edge( 0, 2, 7.5 ); // returns true
graph.insert_edge( 0, 2, 7.8 ); // returns true
cout << graph.minimum_spanning_tree().first << endl; // returns the
minimum spanning tree weight: 22.1
cout << graph.minimum_spanning_tree().second << endl; // returns the
number of nodes inspected: 4

return 0;
}

Common Issues

Recall that the order in which member variables are assigned in the copy constructor is the same
order in which the member variables are declared in the class definition. Thus, the following
would fail:

40
class Faulty {
private:
int *array;
int towers;

public:
Fault( int );
Fault( Fault const & );
};

// 'array' is assigned before 'towers' even though it looks like


// 'towers' is assigned the value of the argument first.
Fault::Fault( int n ):
towers( n ),
array( new int[towers] ) {
// empty
}

// Similarly, 'array' is assigned before 'towers' even though it looks like


// 'towers' is assigned the value of 'faulty.towers' first.
Fault::Fault( int n ):
Fault::Fault( Fault const &faulty ):
towers( faulty.towers ),
array( new int[towers] ) {
for ( int i = 0; i < towers; ++i ) {
array[i] = faulty.array[i];
}
}

Tools you can use

You are welcome to use a sorting algorithm written by someone else, so long as you
acknowledge that author or organization. You can also use the implementation of sort in the
Standard Template Library (STL). You will, however, have to sort a triplet, where two member
variables are the vertices, and one is the weight.

class Edge {
public:
int v1;
int v2;
double weight;
};

or

typedef struct {
int v1;
int v2;
double weight;
} edge_t;

You can then sort a vector of these using the algorithm sort function.

41
You will also require a disjoint set data structure. You are welcome to use Disjoint_set as shown
below. You may use all standard libraries available on ecelinux. This includes the Standard
Template Library (STL), cstring, etc.

Disjoint Sets

An implementation of disjoint sets on the integers 0, ..., n - 1.

The operator << has been overloaded to print the disjoint set data structure as is shown here:

Size: 16
Height: 0
Number of Sets: 16
Parent: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Entry: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Sets: {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12},
{13}, {14}, {15}

The disjoint sets are printed at the bottom.

After sixteen random unions, the output is:

Size: 16
Height: 2
Number of Sets: 8
|
| | | |
Parent: 0 15 12 6 4 5 6 6 8 3 12 12 12 3 14 15
Entry: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Sets: {0}, {4}, {5}, {3,6*,7,9,13}, {8}, {2,10,11,12*}, {14}, {1,15*}

The bars are a visual indication of the height of the tree rooted at that entry; one may note the
path of 9→3→6 which results in a tree of height 2. For any of the disjoint sets printed in the last
line, if it has more than two entries, the root of the tree is starred.

After a sufficient number of unions to result in a single set with the output:

Size: 16
Height: 3
Number of Sets: 1
|
| |
| | | | |
Parent: 8 15 12 6 8 8 8 6 8 3 12 12 8 3 8 8
Entry: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Sets: {0,1,2,3,4,5,6,7,8*,9,10,11,12,13,14,15}

42

You might also like