rename tree lectures
This commit is contained in:
161
lectures/18_trees_I/README.md
Normal file
161
lectures/18_trees_I/README.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Lecture 17 --- Trees, Part I
|
||||
|
||||
## Review from Lecture 16
|
||||
|
||||
- STL set container class (like STL map, but without the pairs!)
|
||||
- set iterators, insert, erase, find
|
||||
|
||||
## Today’s Lecture
|
||||
|
||||
- Binary trees, binary search trees
|
||||
- Implementation of ds_set class using binary search trees
|
||||
- In-order, pre-order, and post-order traversal
|
||||
|
||||
## Overview: Lists vs Trees vs Graphs
|
||||
- Trees create a hierarchical organization of data, rather than the linear organization in linked lists (and arrays and vectors).
|
||||
- Binary search trees are the mechanism underlying maps & sets (and multimaps & multisets).
|
||||
- Mathematically speaking: A _graph_ is a set of vertices connected by edges. And a tree is a special graph that has no _cycles_. The edges that connect nodes in trees and graphs may be _directed_ or _undirected_.
|
||||
|
||||
## 17.1 Definition: Binary Trees
|
||||
|
||||
- A binary tree (strictly speaking, a “rooted binary
|
||||
tree”) is either empty or is a node that has
|
||||
pointers to two binary trees.
|
||||
- Here’s a picture of a binary tree storing integer
|
||||
values. In this figure, each large box indicates a
|
||||
tree node, with the top rectangle representing the
|
||||
value stored and the two lower boxes representing
|
||||
pointers. Pointers that are null are shown with a
|
||||
slash through the box.
|
||||
|
||||

|
||||
|
||||
- The topmost node in the tree is called the root.
|
||||
- The pointers from each node are called left and
|
||||
right. The nodes they point to are referred to as
|
||||
that node’s (left and right) children.
|
||||
- The (sub)trees pointed to by the left and right
|
||||
pointers at any node are called the left subtree
|
||||
and right subtree of that node.
|
||||
- A node where both children pointers are null is
|
||||
called a leaf node.
|
||||
- A node’s parent is the unique node that points to
|
||||
it. Only the root has no parent.
|
||||
|
||||
## 17.2 Definition: Binary Search Trees
|
||||
|
||||
- A binary search tree (often abbreviated to
|
||||
BST) is a binary tree where at each node
|
||||
of the tree, the value stored at the node is
|
||||
– greater than or equal to all values
|
||||
stored in the left subtree, and
|
||||
|
||||
– less than or equal to all values stored in
|
||||
the right subtree.
|
||||
|
||||
- Here is a picture of a binary search tree
|
||||
storing string values.
|
||||
|
||||

|
||||
|
||||
## 17.3 Definition: Balanced Trees
|
||||
|
||||
- The number of nodes on each subtree of each node in a
|
||||
“balanced” tree is approximately the same. In order to
|
||||
be an exactly balanced binary tree, what must be true
|
||||
about the number of nodes in the tree?
|
||||
- In order to claim the performance advantages of trees, we must assume and ensure that our data structure
|
||||
remains approximately balanced. (You’ll see much more of this in Intro to Algorithms!)
|
||||
|
||||
## 17.4 Exercise
|
||||
|
||||
Consider the following values:
|
||||
4.5, 9.8, 3.5, 13.6, 19.2, 7.4, 11.7
|
||||
|
||||
1. Draw a binary tree with these values that is NOT a binary search tree.
|
||||
|
||||
2. Draw two different binary search trees with these values. Important note: This shows that the binary search
|
||||
tree structure for a given set of values is not unique!
|
||||
|
||||
3. How many exactly balanced binary search trees exist with these numbers? How many exactly balanced
|
||||
binary trees exist with these numbers?
|
||||
|
||||
## 17.5 Beginning our implementation of ds_set: The Tree Node Class
|
||||
|
||||
- Here is the class definition for nodes in the tree. We will use this for the tree manipulation code we write.
|
||||
|
||||
```cpp
|
||||
template <class T> class TreeNode {
|
||||
public:
|
||||
TreeNode() : left(NULL), right(NULL) {}
|
||||
TreeNode(const T& init) : value(init), left(NULL), right(NULL) {}
|
||||
T value;
|
||||
TreeNode* left;
|
||||
TreeNode* right;
|
||||
};
|
||||
```
|
||||
|
||||
- Note: Sometimes a 3rd pointer — to the parent TreeNode — is added.
|
||||
|
||||

|
||||
|
||||
## 17.6 Exercises
|
||||
|
||||
1. Write a templated function to find the smallest value stored in a binary search tree whose root node is pointed
|
||||
to by p.
|
||||
|
||||
2. Write a function to count the number of odd numbers stored in a binary tree (not necessarily a binary search
|
||||
tree) of integers. The function should accept a TreeNode<int> pointer as its sole argument and return an
|
||||
integer. Hint: think recursively!
|
||||
|
||||
## 17.7 ds_set and Binary Search Tree Implementation
|
||||
|
||||
- A partial implementation of a set using a binary search tree is provided in this [ds_set_starter.h](ds_set_starter.h). We will continue to study this implementation in Lab 10 & the next lecture.
|
||||
- The increment and decrement operations for iterators have been omitted from this implementation. Next week
|
||||
in lecture we will discuss a couple strategies for adding these operations.
|
||||
- We will use this as the basis both for understanding an initial selection of tree algorithms and for thinking
|
||||
about how standard library sets really work.
|
||||
|
||||
## 17.8 ds_set: Class Overview
|
||||
|
||||
- There is two auxiliary classes, TreeNode and tree_iterator. All three classes are templated.
|
||||
- The only member variables of the ds_set class are the root and the size (number of tree nodes).
|
||||
- The iterator class is declared internally, and is effectively a wrapper on the TreeNode pointers.
|
||||
– Note that operator* returns a const reference because the keys can’t change.
|
||||
|
||||
– The increment and decrement operators are missing (we’ll fill this in next week in lecture!).
|
||||
|
||||
- The main public member functions just call a private (and often recursive) member function (passing the root
|
||||
node) that does all of the work.
|
||||
- Because the class stores and manages dynamically allocated memory, a copy constructor, operator=, and
|
||||
destructor must be provided.
|
||||
|
||||
## 17.9 Exercises
|
||||
|
||||
1. Provide the implementation of the member function ds_set<T>::begin. This is essentially the problem of
|
||||
finding the node in the tree that stores the smallest value.
|
||||
|
||||
|
||||
|
||||
|
||||
2. Write a recursive version of the function find.
|
||||
|
||||
## 17.10 In-order, Pre-Order, Post-Order Traversal
|
||||
|
||||
- One of the fundamental tree operations is “traversing” the nodes in the tree and doing something at each node.
|
||||
The “doing something”, which is often just printing, is referred to generically as “visiting” the node.
|
||||
- There are three general orders in which binary trees are traversed: pre-order, in-order and post-order.
|
||||
In order to explain these, let’s first draw an “exactly balanced” binary search tree with the elements 1-7:
|
||||
|
||||
– What is the in-order traversal of this tree? Hint: it is monotonically increasing, which is always true for
|
||||
an in-order traversal of a binary search tree!
|
||||
|
||||
|
||||
|
||||
– What is the post-order traversal of this tree? Hint, it ends with “4” and the 3rd element printed is “2”.
|
||||
|
||||
|
||||
|
||||
– What is the pre-order traversal of this tree? Hint, the last element is the same as the last element of the
|
||||
in-order traversal (but that is not true in general! why not?)
|
||||
|
||||
BIN
lectures/18_trees_I/binary_tree.png
Normal file
BIN
lectures/18_trees_I/binary_tree.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
lectures/18_trees_I/bst.png
Normal file
BIN
lectures/18_trees_I/bst.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
lectures/18_trees_I/ds_set_diagram.png
Normal file
BIN
lectures/18_trees_I/ds_set_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
183
lectures/18_trees_I/ds_set_lec17.h
Normal file
183
lectures/18_trees_I/ds_set_lec17.h
Normal file
@@ -0,0 +1,183 @@
|
||||
// Partial implementation of binary-tree based set class similar to std::set.
|
||||
// The iterator increment & decrement operations have been omitted.
|
||||
#ifndef ds_set_h_
|
||||
#define ds_set_h_
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TREE NODE CLASS
|
||||
template <class T>
|
||||
class TreeNode {
|
||||
public:
|
||||
TreeNode() : left(NULL), right(NULL) {}
|
||||
TreeNode(const T& init) : value(init), left(NULL), right(NULL) {}
|
||||
T value;
|
||||
TreeNode* left;
|
||||
TreeNode* right;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TREE NODE ITERATOR CLASS
|
||||
template <class T>
|
||||
class tree_iterator {
|
||||
public:
|
||||
tree_iterator() : ptr_(NULL) {}
|
||||
tree_iterator(TreeNode<T>* p) : ptr_(p) {}
|
||||
tree_iterator(const tree_iterator& old) : ptr_(old.ptr_) {}
|
||||
~tree_iterator() {}
|
||||
tree_iterator& operator=(const tree_iterator& old) { ptr_ = old.ptr_; return *this; }
|
||||
|
||||
// operator* gives constant access to the value at the pointer
|
||||
const T& operator*() const { return ptr_->value; }
|
||||
// comparions operators are straightforward
|
||||
bool operator== (const tree_iterator& rgt) { return ptr_ == rgt.ptr_; }
|
||||
bool operator!= (const tree_iterator& rgt) { return ptr_ != rgt.ptr_; }
|
||||
|
||||
private:
|
||||
// representation
|
||||
TreeNode<T>* ptr_;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// DS_SET CLASS
|
||||
template <class T>
|
||||
class ds_set {
|
||||
public:
|
||||
ds_set() : root_(NULL), size_(0) {}
|
||||
ds_set(const ds_set<T>& old) : size_(old.size_) {
|
||||
root_ = this->copy_tree(old.root_); }
|
||||
~ds_set() {
|
||||
this->destroy_tree(root_);
|
||||
root_ = NULL;
|
||||
}
|
||||
ds_set& operator=(const ds_set<T>& old) {
|
||||
if (&old != this) {
|
||||
this->destroy_tree(root_);
|
||||
root_ = this->copy_tree(old.root_);
|
||||
size_ = old.size_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
typedef tree_iterator<T> iterator;
|
||||
|
||||
int size() const { return size_; }
|
||||
bool operator==(const ds_set<T>& old) const { return (old.root_ == this->root_); }
|
||||
|
||||
// FIND, INSERT & ERASE
|
||||
iterator find(const T& key_value) { return find(key_value, root_); }
|
||||
std::pair< iterator, bool > insert(T const& key_value) { return insert(key_value, root_); }
|
||||
int erase(T const& key_value) { return erase(key_value, root_); }
|
||||
|
||||
// OUTPUT & PRINTING
|
||||
friend std::ostream& operator<< (std::ostream& ostr, const ds_set<T>& s) {
|
||||
s.print_in_order(ostr, s.root_);
|
||||
return ostr;
|
||||
}
|
||||
void print_as_sideways_tree(std::ostream& ostr) const {
|
||||
print_as_sideways_tree(ostr, root_, 0);
|
||||
}
|
||||
|
||||
// ITERATORS
|
||||
iterator begin() const {
|
||||
if (!root_) return iterator(NULL);
|
||||
TreeNode<T>* p = root_;
|
||||
while (p->left) p = p->left;
|
||||
return iterator(p);
|
||||
}
|
||||
iterator end() const { return iterator(NULL); }
|
||||
|
||||
private:
|
||||
// REPRESENTATION
|
||||
TreeNode<T>* root_;
|
||||
int size_;
|
||||
|
||||
// PRIVATE HELPER FUNCTIONS
|
||||
TreeNode<T>* copy_tree(TreeNode<T>* old_root) {
|
||||
if (old_root == NULL)
|
||||
return NULL;
|
||||
TreeNode<T> *answer = new TreeNode<T>();
|
||||
answer->value = old_root->value;
|
||||
answer->left = copy_tree(old_root->left);
|
||||
answer->right = copy_tree(old_root->right);
|
||||
return answer;
|
||||
}
|
||||
|
||||
void destroy_tree(TreeNode<T>* p) {
|
||||
if (!p) return;
|
||||
destroy_tree(p->right);
|
||||
destroy_tree(p->left);
|
||||
delete p;
|
||||
}
|
||||
|
||||
iterator find(const T& key_value, TreeNode<T>* p) {
|
||||
if (!p) return iterator(NULL);
|
||||
if (p->value > key_value)
|
||||
return find(key_value, p->left);
|
||||
else if (p->value < key_value)
|
||||
return find(key_value, p->right);
|
||||
else
|
||||
return iterator(p);
|
||||
}
|
||||
|
||||
std::pair<iterator,bool> insert(const T& key_value, TreeNode<T>*& p) {
|
||||
if (!p) {
|
||||
p = new TreeNode<T>(key_value);
|
||||
this->size_++;
|
||||
return std::pair<iterator,bool>(iterator(p), true);
|
||||
}
|
||||
else if (key_value < p->value)
|
||||
return insert(key_value, p->left);
|
||||
else if (key_value > p->value)
|
||||
return insert(key_value, p->right);
|
||||
else
|
||||
return std::pair<iterator,bool>(iterator(p), false);
|
||||
}
|
||||
|
||||
int erase(T const& key_value, TreeNode<T>* &p) {
|
||||
if (!p) return 0;
|
||||
|
||||
// look left & right
|
||||
if (p->value < key_value)
|
||||
return erase(key_value, p->right);
|
||||
else if (p->value > key_value)
|
||||
return erase(key_value, p->left);
|
||||
|
||||
// Found the node. Let's delete it
|
||||
assert (p->value == key_value);
|
||||
if (!p->left && !p->right) { // leaf
|
||||
delete p; p=NULL;
|
||||
} else if (!p->left) { // no left child
|
||||
TreeNode<T>* q = p; p=p->right; delete q;
|
||||
} else if (!p->right) { // no right child
|
||||
TreeNode<T>* q = p; p=p->left; delete q;
|
||||
} else { // Find rightmost node in left subtree
|
||||
TreeNode<T>* &q = p->left;
|
||||
while (q->right) q = q->right;
|
||||
p->value = q->value;
|
||||
int check = erase(q->value, q);
|
||||
assert (check == 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void print_in_order(std::ostream& ostr, const TreeNode<T>* p) const {
|
||||
if (p) {
|
||||
print_in_order(ostr, p->left);
|
||||
ostr << p->value << "\n";
|
||||
print_in_order(ostr, p->right);
|
||||
}
|
||||
}
|
||||
|
||||
void print_as_sideways_tree(std::ostream& ostr, const TreeNode<T>* p, int depth) const {
|
||||
if (p) {
|
||||
print_as_sideways_tree(ostr, p->right, depth+1);
|
||||
for (int i=0; i<depth; ++i) ostr << " ";
|
||||
ostr << p->value << "\n";
|
||||
print_as_sideways_tree(ostr, p->left, depth+1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
130
lectures/18_trees_I/ds_set_starter.h
Normal file
130
lectures/18_trees_I/ds_set_starter.h
Normal file
@@ -0,0 +1,130 @@
|
||||
// Partial implementation of binary-tree based set class similar to std::set.
|
||||
// The iterator increment & decrement operations have been omitted.
|
||||
#ifndef ds_set_h_
|
||||
#define ds_set_h_
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TREE NODE CLASS
|
||||
template <class T>
|
||||
class TreeNode {
|
||||
public:
|
||||
TreeNode() : left(NULL), right(NULL) {}
|
||||
TreeNode(const T& init) : value(init), left(NULL), right(NULL) {}
|
||||
T value;
|
||||
TreeNode* left;
|
||||
TreeNode* right;
|
||||
};
|
||||
|
||||
template <class T> class ds_set;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TREE NODE ITERATOR CLASS
|
||||
template <class T>
|
||||
class tree_iterator {
|
||||
public:
|
||||
tree_iterator() : ptr_(NULL) {}
|
||||
tree_iterator(TreeNode<T>* p) : ptr_(p) {}
|
||||
tree_iterator(const tree_iterator& old) : ptr_(old.ptr_) {}
|
||||
~tree_iterator() {}
|
||||
tree_iterator& operator=(const tree_iterator& old) { ptr_ = old.ptr_; return *this; }
|
||||
// operator* gives constant access to the value at the pointer
|
||||
const T& operator*() const { return ptr_->value; }
|
||||
// comparison operators are straightforward
|
||||
bool operator==(const tree_iterator& r) { return ptr_ == r.ptr_; }
|
||||
bool operator!=(const tree_iterator& r) { return ptr_ != r.ptr_; }
|
||||
// increment & decrement will be discussed in Lecture 18 and Lab 11
|
||||
|
||||
private:
|
||||
// representation
|
||||
TreeNode<T>* ptr_;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// DS SET CLASS
|
||||
template <class T>
|
||||
class ds_set {
|
||||
public:
|
||||
ds_set() : root_(NULL), size_(0) {}
|
||||
ds_set(const ds_set<T>& old) : size_(old.size_) {
|
||||
root_ = this->copy_tree(old.root_); }
|
||||
~ds_set() { this->destroy_tree(root_); root_ = NULL; }
|
||||
ds_set& operator=(const ds_set<T>& old) {
|
||||
if (&old != this) {
|
||||
this->destroy_tree(root_);
|
||||
root_ = this->copy_tree(old.root_);
|
||||
size_ = old.size_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
typedef tree_iterator<T> iterator;
|
||||
|
||||
int size() const { return size_; }
|
||||
bool operator==(const ds_set<T>& old) const { return (old.root_ == this->root_); }
|
||||
|
||||
// FIND, INSERT & ERASE
|
||||
iterator find(const T& key_value) { return find(key_value, root_); }
|
||||
std::pair< iterator, bool > insert(T const& key_value) { return insert(key_value, root_); }
|
||||
int erase(T const& key_value) { return erase(key_value, root_); }
|
||||
|
||||
// OUTPUT & PRINTING
|
||||
friend std::ostream& operator<< (std::ostream& ostr, const ds_set<T>& s) {
|
||||
s.print_in_order(ostr, s.root_);
|
||||
return ostr;
|
||||
}
|
||||
void print_as_sideways_tree(std::ostream& ostr) const { print_as_sideways_tree(ostr, root_, 0); }
|
||||
|
||||
// ITERATORS
|
||||
iterator begin() const {
|
||||
// Implemented in Lecture 17
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
iterator end() const { return iterator(NULL); }
|
||||
|
||||
private:
|
||||
// REPRESENTATION
|
||||
TreeNode<T>* root_;
|
||||
int size_;
|
||||
|
||||
// PRIVATE HELPER FUNCTIONS
|
||||
TreeNode<T>* copy_tree(TreeNode<T>* old_root) { /* Implemented in Lab 9 */ }
|
||||
void destroy_tree(TreeNode<T>* p) { /* Implemented in Lecture 18 */ }
|
||||
|
||||
iterator find(const T& key_value, TreeNode<T>* p) {
|
||||
// Implemented in Lecture 17
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::pair<iterator,bool> insert(const T& key_value, TreeNode<T>*& p) { /* Discussed in Lecture 18 */ }
|
||||
int erase(T const& key_value, TreeNode<T>* &p) { /* Implemented in Lecture 19 */ }
|
||||
|
||||
void print_in_order(std::ostream& ostr, const TreeNode<T>* p) const {
|
||||
// Discussed in Lecture 18
|
||||
if (p) {
|
||||
print_in_order(ostr, p->left);
|
||||
ostr << p->value << "\n";
|
||||
print_in_order(ostr, p->right);
|
||||
}
|
||||
}
|
||||
|
||||
void print_as_sideways_tree(std::ostream& ostr, const TreeNode<T>* p, int depth) const {
|
||||
/* Discussed in Lecture 17 */ }
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user