rename tree lectures

This commit is contained in:
Jidong Xiao
2024-03-18 00:44:53 -04:00
parent 72b457e496
commit dc561a1eb0
16 changed files with 0 additions and 0 deletions

View 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
## Todays 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.
- Heres 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.
![alt text](binary_tree.png "binary tree")
- 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 nodes (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 nodes 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.
![alt text](bst.png "binary search tree")
## 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. (Youll 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.
![alt text](ds_set_diagram.png "ds set diagram")
## 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 cant change.
The increment and decrement operators are missing (well 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, lets 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?)

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
lectures/18_trees_I/bst.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View 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

View 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