In this assignment you’ll implement an AVL tree, using the following avl_tree
class:
#pragma once
/*
* avl_tree.hpp
* Class definition for AVL trees.
*/
#include <cassert>
/* not_implemented
Thrown by functions you haven't written yet, to signal to the test-runner
to not test them.
*/
struct not_implemented {};
/* avl_tree
AVL (height-balanced) tree class.
*/
class avl_tree {
public:
struct node
{
int key;
node* left;
node* right;
node* parent;
int height;
};
/* root()
Helper function, returns the root of the tree.
*/
node* root() { return rt; }
/* height(n)
Returns the height of n. This helper function exists to get the
height of a (possibly nullptr) node. If you just do n->height, then your
program will crash with a segfault if n is ever nullptr, but there are
lots of places where we want to get the height without knowing whether
a node exists or not.
*/
static int height(node* n) { return n == nullptr ? 0 : n->height; }
/* default constructor
The default avl_tree is empty, so this constructor does nothing (it must
be defined, however, because the presence of the copy constructor below
causes the normal compiler-generated constructor to be deleted).
*/
avl_tree() { }
/* destructor
Destroy all the nodes in the tree.
See below for the `destroy()` method which you must implement.
*/
~avl_tree()
{
destroy(rt);
rt = nullptr;
}
/* copy constructor
Construct a copy of the `original` tree.
See below for the `copy()` method which you must implement.
*/
avl_tree(const avl_tree& original)
{
rt = copy(original.rt);
}
/* copy assignment
Copy `original` into the current tree.
*/
avl_tree& operator= (const avl_tree& original)
{
if(rt == original.rt)
return *this;
destroy(rt);
rt = copy(rt);
return *this;
}
/* size()
Returns the size (number of nodes) in the tree.
See below for the version of this function that you need to implement.
*/
int size() const
{
return size(rt);
}
/* ---------------- Complete all the following functions ---------------- */
// Note: There is one function (`rotate()`) in the private section which you
// must also complete.
/* height()
Returns the height of the tree (empty tree has height of 0).
Must run in O(1) time.
*/
int height() const
{
throw not_implemented{};
}
/* empty()
Returns true if the tree is empty.
Must run in O(1) time.
*/
bool empty() const
{
throw not_implemented{};
}
/* find(x)
Returns a pointer to the node containing `x`, or nullptr if `x` is not
found in the tree.
Must run in O(log n) time.
NOTE: If you want to write `find` recursively, you'll need to add a
private helper function
node* find(node* n, int x);
to do the actual recursion.
*/
node* find(int x)
{
throw not_implemented{};
}
/* insert(x)
Insert `x` into the tree. If `x` already exists in the tree, then the
tree should be left unchanged. Otherwise, if `x` is added to the tree,
then the tree should be rebalanced as necessary, so that the AVL height
property still holds.
Must run in O(log n) time.
*/
void insert(int x)
{
throw not_implemented{};
}
/* size(n)
Returns the size of the tree under the node `n`, using recursion. This
is the (recursive) implementation of the `size()` method, above.
Must run in O(n) time. Note that because you are not allowed to add
additional private data members, you cannot use the `int sz;` trick to
make this O(1).
*/
static int size(node* root)
{
throw not_implemented{};
}
/* destroy(root)
Destroy `root` and all its descendants.
Must run in O(n) time, where
n = size(root)
This is called from the destructor to destroy the tree.
*/
static void destroy(node* root)
{
// Your code here.
}
/* copy(root, parent)
Return a copy of the tree under `root`, with root's parent
being `parent`.
Must run in O(n) time.
*/
static node* copy(node* root, node* parent = nullptr)
{
// Your code here.
return nullptr;
}
/* --------------------------- Extra Credit --------------------------- */
// If you complete the functions in this section, then this assignment
// will count as two assignments, for the purposes of computing your grade.
/* merge_with(other)
Merge this tree with `other`. This is similar to `insert`-ing all the
values in `other`, although there are more efficient ways to go about
doing it.
Must run in O(n) time. (Note that `insert`-ing all the values in `other`
would run in O(n log n) time, so this requirement rules out that
approach.) n here is the total size of the final tree (including all the
nodes in this tree, plus all the nodes in the other tree). Must run
in O(n) space. There are special cases where the runtime can be reduced
to O(log n) time.
You may need to write some helper functions to aid with this.
*/
void merge_with(const avl_tree& other)
{
throw not_implemented{};
}
private:
/* ------------------------- Not Extra Credit ------------------------- */
// `rotate` is not an extra-credit function, it is a key part of the AVL
// tree implementation.
/* rotate(child)
Rotate `child` with its parent, updating the heights of both as
necessary.
Must run in O(1) time.
NOTE: `rotate` is *not* static, because it sometimes needs to update
the root `rt` of the tree!
You can assume that `child != nullptr` and `child->parent != nullptr`.
*/
void rotate(node* child)
{
assert(child != nullptr);
assert(child->parent != nullptr);
throw not_implemented{};
}
// Root of the tree (nullptr = empty tree).
node* rt = nullptr;
// Add any other private members/functions you want/need.
// DO NOT add any additional data members; they will break the test code. :(
friend class assign4_test_runner;
};
In each function, remove the throw not_implemented{};
line (which is used to
signal to the test-runner that the function isn’t implemented, and should not
be tested) and replace it with your code.
Optionally, you can implement the extra-credit function at the bottom, merge_with
, in which case this assignment will count double. (If you’re thinking, how hard can one function be, well…)
Like assignment 2, this assignment requires that your code must be memory-safe
to pass; it must not leak memory, access memory that has been freed, etc. The
destructor, copy constructor, and copy assignment operator are provided for
you, but you must implement the destroy
and copy
functions which they rely
on.
Note that the fact that our nodes have parent pointers and store their heights
makes the implementation of the (private
) rotate
function more complex
than what I showed you in class. rotate
must correctly update the parent
pointers of the child node, parent node, and the y
subtree. In addition,
it must also update the grandparent node’s ->left
or ->right
, whichever
previously pointed to the parent node. rotate
must update the heights of
the child and parent nodes. Finally, if a rotate would change the
root of the tree, then rotate
must update the root pointer rt
.
You can download the above header file template, along with the test runner source file for this assignment. On the server,
these can be found in /usr/local/class/src/cs133/
, As with previous
assignments, put your submission into ~/cs133/assign4/
. E.g., to start this
assignment
mkdir -p ~/cs133/assign4/
cp /usr/local/class/src/cs133/assign4_test.cpp ~/cs133/assign4/
cp /usr/local/class/src/cs133/avl_tree.hpp ~/cs133/assign4/
Submission
Save your work in a directory on the server named cs133/assign4/
.
Tree diagrams
If one of the trees built during the testing process is broken in some way, the test runner will print a tree diagram to hopefully help you pinpoint the problem. For example, here’s the diagram for a tree with a bad height:
┌────────────────────────────────┐
│ 7 │
│ ┌──────────┴──────────┐ │
│ 3 11 │
│ ┌──┘ ┌──┘ │
│ 1 1 9 3 │
│ └──┐ └──┐ │
│ 2 10 │
└────────────────────────────────┘
PROBLEMS:
* Node 1 has the wrong height (correct height = 2, height in node = 1)
* Node 9 has the wrong height (correct height = 2, height in node = 3)
The heights of the problematic nodes are printed in red (nodes
with correct heights do not have their heights printed) and the nodes are also listed in the PROBLEMS
section at the bottom.
Tests
List of tests is coming soon, but in general, the class methods are tested in the order:
rotate
(required to testinsert
)insert
(required to test everything else)find
size
height
empty
copy
merge_with
(extra credit)
destroy
is tested by running inside the test-assignment
script, which runs
the test-runner inside Valgrind to check for memory leaks (if you want to do
this manually, run valgrind ./assign4_test
).
A good strategy is to implement rotate
and insert
first, because none of the
other functions will be tested if those are not done.