Project Ideas For Data Structures
Project Ideas For Data Structures
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.
3. You may use either Java or C++ to implement your project and the whole source
4. The project should use codes of each method/function along with the pseudo
5. You should have a main function to show/demonstrate the project. In the main
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.
1.1. Requirements
A cyclic doubly linked list with three nodes is shown in Figure 1. The empty cyclic doubly
linked list is shown in Figure 2.
Cyclic_double_list
- list_head:Double_node
- list_size:Integer
+ create():Cyclic_double_list
+ size():Integer
+ empty():Boolean
+ front():Type
+ back():Type
2
+ head():Double_node
+ =( in rhs:Cyclic_double_list ):Cyclic_double_list
+ push_front( in obj:Type )
+ push_back( in obj:Type )
+ pop_front():Type
+ pop_back():Type
+ 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
Member Functions
Constructors
3
Cyclic_double_list()
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
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))
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))
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
The swap function swaps all the member variables of this linked list with those of the
argument. (O(1))
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))
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))
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))
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
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.
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).
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:
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:
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.
The first parameter, argc, is the count of the number of command line arguments. For
example, if we execute the command:
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:
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:
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
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."
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:
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:
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:
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( );
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:
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.
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:
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
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
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.
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.
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.
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?
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!
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:
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.
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!
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:
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).
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.
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.1. Requirements:
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.
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
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()
Copy Constructor
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))
Returns the number of objects currently stored in the mth stack. If m is neither 0 nor 1,
throw an illegal argument exception. (Θ(1))
Returns true if the mth stack is empty, false otherwise. If m is neither 0 nor 1, throw an
illegal argument exception. (Θ(1))
Mutators
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))
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
8.1. Requirements
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.
- 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
Member Functions
Constructors
Quadtree()
Destructor
The destructor must delete each of the nodes in the quadtree. (O(n))
Accessors
27
Returns true if the quadtree is empty, false otherwise. (O(1))
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))
Returns the address of the root node. If the tree is empty, the root node should be 0.
(O(1))
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
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.
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
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
Member Functions
Constructors
Weighted_graph( int n = 50 )
29
Destructor
Accessors
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 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
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.
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
This class will implement a weighted undirected graph with Prim's algorithm.
10.2.Class Specification
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
Member Functions
Constructors
Weighted_graph( int n = 50 )
31
Construct a weighted undirected graph with n vertices (by default, 50).
Destructor
Accessors
Returns the degree of the vertex n. Throw an illegal argument exception if the argument
does not correspond to an existing vertex. (O(1))
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.
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
32
11. Topological Sort using Graph
11.1.Requirements
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
Copy Constructor
The copy constructor makes a complete copy of the directed acyclic graph passed in the
argument.
Assignment Operator =
33
Accessors
Returns the in degree of the given vertex i. If i is outside the range 0, ..., n − 1, throw an
illegal argument exception.
Returns the out degree of the given vertex i. If i is outside the range 0, ..., n − 1, throw
an illegal argument exception.
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.
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
>>>>>>>>>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
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.
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()
Friends
Testing
#include <iostream>
#include "Directed_acyclic_graph.h"
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 & );
};
36
12. Implement Kruskal's Algorithm
12.1.Requirements
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.
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
Copy Constructor
Assignment Operator =
Accessors
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
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
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()
Friends
Testing
#include <iostream>
#include "Weighted_graph.h"
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 & );
};
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
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}
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