0% found this document useful (0 votes)
105 views16 pages

Trees: : Some Basics

A tree is a basic container data structure known as "tree" the main property of them is the way they handle the set's elements. C++ has no tree data structure on the well-known STL for c++. If you are a c++ programmer, today is lucky day, i'm going to show you the basic c++ implementation.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
105 views16 pages

Trees: : Some Basics

A tree is a basic container data structure known as "tree" the main property of them is the way they handle the set's elements. C++ has no tree data structure on the well-known STL for c++. If you are a c++ programmer, today is lucky day, i'm going to show you the basic c++ implementation.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 16

Trees: < n-nodes oriented tree> Today we are going to talk about some algorithms and programming.

What I have to share with you today is a basic container data structure known as "Tree". The so called trees allow us to manage a set of objects that are related one to another under some order theory. The main property of them is the way they handle the set's elements. Based on a hierarchical you can access to this elements much faster than you can do it with others containers. What give us green light for this work is the facts that you don't have any tree data structure on the well-known STL for c++. This is because they think about trees as some very basic data structure or a subtype of graph.

--- Pay Attention -If you are a c++ programmer, today is lucky day, I'm going to show you the basic c++ implementation. If you're not a c++ programmer, no need to fear & no need to cry, I will guide the process so you can get the main idea on each algorithm (at least the most important ones) so then you can create your own implementation in your favorite programming language. ----

Some basics:

A tree is a collection of elements known as nodes, one of them (I would say, the most important) is called the root (Yes, just like your File System Structure). There is a family relationship where each node has only one father except for the root node which dont have any father. Any node can have as many children as you want (or as many as your order theory allow them to). Those nodes that have no child, are called leaves. So you can imagine a tree data structure as a real upside down tree.
(If you need some more info I hope this will work for you: http://en.wikipedia.org/wiki/Tree_(data_structure) )

Lets get dirty: For the purpose of this text, we are going to use the c++ n-nodes oriented trees implementation made by scientists at CIMEC which has been tested and use on their lectures at UNL (Universidad Nacional del Litoral). As far as I know the source code is published under GNU license.
(For this and more codes see: http://www.cimec.org.ar/~mstorti/repositorio-cpp/ )

#ifndef AED_TREE_H #define AED_TREE_H #include <iostream> #include <cstddef> #include <cstdlib> #include <cassert> #include <list> using namespace std; namespace aed { // --------------------------------------------------------------template<class T> class tree { public: class iterator; private: class cell { friend class tree; friend class iterator; T t; cell *right, *left_child; cell() : right(NULL), left_child(NULL) {} }; cell *header; iterator tree_copy_aux(iterator nq,

tree<T> &TT,iterator nt) { nq = insert(nq,*nt); iterator ct = nt.lchild(), cq = nq.lchild(); while (ct!=TT.end()) { cq = tree_copy_aux(cq,TT,ct); ct = ct.right(); cq = cq.right(); } return nq; } public: static int cell_count_m; static int cell_count() { return cell_count_m; } class iterator { private: friend class tree; cell *ptr,*prev,*father; iterator(cell *p,cell *prev_a,cell *f_a) : ptr(p), prev(prev_a), father(f_a) { } public: iterator(const iterator &q) { ptr = q.ptr; prev = q.prev; father = q.father; } T &operator*() { return ptr->t; } T *operator->() { return &ptr->t; } bool operator!=(iterator q) { return ptr!=q.ptr; } bool operator==(iterator q) { return ptr==q.ptr; } iterator() : ptr(NULL), prev(NULL), father(NULL) { } iterator lchild() { return iterator(ptr->left_child,NULL,ptr); } iterator right() { return iterator(ptr->right,ptr,father); } // Prefix: iterator operator++() { *this = right(); return *this; } // Postfix: iterator operator++(int) { iterator q = *this; *this = right(); return q; } }; // ------------------------------------------------------------// default construct tree() { header = new cell; cell_count_m++; header->right = NULL; header->left_child = NULL; } // -------------------------------------------------------------

// const by copy tree<T>(const tree<T> &TT) { if (&TT != this) { header = new cell; cell_count_m++; header->right = NULL; header->left_child = NULL; tree<T> &TTT = (tree<T> &) TT; if (TTT.begin()!=TTT.end()) tree_copy_aux(begin(),TTT,TTT.begin()); } } // ------------------------------------------------------------// destructor ~tree() { clear(); delete header; cell_count_m--; } // ------------------------------------------------------------tree<T>& operator=(const tree<T>& Q) { clear(); tree<T> &QQ = (tree<T> &) Q; if (QQ.begin()!=QQ.end()) tree_copy_aux(begin(),QQ,QQ.begin()); } // ------------------------------------------------------------iterator insert(iterator p,T t) { assert(!(p.father==header && p.ptr)); cell *c = new cell; cell_count_m++; c->right = p.ptr; c->t = t; p.ptr = c; if (p.prev) p.prev->right = c; else p.father->left_child = c; return p; } // ------------------------------------------------------------iterator erase(iterator p) { if(p==end()) return p; iterator c = p.lchild(); while (c!=end()) c = erase(c); cell *q = p.ptr; p.ptr = p.ptr->right; if (p.prev) p.prev->right = p.ptr; else p.father->left_child = p.ptr; delete q; cell_count_m--; return p; } // ------------------------------------------------------------iterator splice(iterator to,iterator from) { assert(!(to.father==header && to.ptr)); cell *c = from.ptr; if (from.prev) from.prev->right = c->right; else from.father->left_child = c->right; c->right = to.ptr; to.ptr = c; if (to.prev) to.prev->right = c; else to.father->left_child = c;

return to; } // ------------------------------------------------------------iterator find(T t) { return find(t,begin()); } // ------------------------------------------------------------iterator find(T t,iterator p) { if(p==end() || p.ptr->t == t) return p; iterator q,c = p.lchild(); while (c!=end()) { q = find(t,c); if (q!=end()) return q; else c++; } return iterator(); } // ------------------------------------------------------------void clear() { erase(begin()); } // ------------------------------------------------------------iterator begin() { return iterator(header->left_child,NULL,header); } // ------------------------------------------------------------iterator end() { return iterator(); } // ------------------------------------------------------------void print_prev(iterator p) { if (p==end()) return; cout << "(" << p.ptr << "," << p.ptr->t << ")" << endl; iterator c = p.lchild(); while (c!=end()) print_prev(c++); } // ------------------------------------------------------------void print_prev() { print_prev(begin()); } // ------------------------------------------------------------void print_post(iterator p) { if (p==end()) return; iterator c = p.lchild(); while (c!=end()) print_post(c++); cout << "(" << p.ptr << "," << p.ptr->t << ")" << endl; } // ------------------------------------------------------------void print_post() { print_post(begin()); } // ------------------------------------------------------------void lisp_print(iterator n) { if (n==end()) return; iterator c = n.lchild(); bool is_leaf = c==end(); if (is_leaf) cout << *n; else { cout << "(" << *n; while (c!=end()) { cout << " "; lisp_print(c++); } cout << ")"; } } // ------------------------------------------------------------void lisp_print() { lisp_print(begin()); } //---:---<*>---:---<*>---:---<*>---:---<*>---:---<*>

bool empty() { return begin()==end(); } //---:---<*>---:---<*>---:---<*>---:---<*>---:---<*> int size(iterator n) { int count=1; iterator c = n.lchild(); while (c!=end()) count += size(c++); return count; } //---:---<*>---:---<*>---:---<*>---:---<*>---:---<*> int size() { if (!empty()) return size(begin()); } }; // --------------------------------------------------------------template<class T> int tree<T>::cell_count_m = 0; // ----------------------------------------------------------------} #endif // -----------------------------------------------------------------

Before we start with the code explanation there are some things to clarify. First: Nodes are treated as position. The nodes will contain the data we want to save and we will make the connection with pointers. Second: The Iterator will help us move from one position to another, always under some restrictions, what we talked about in the previous section. Third: Its coded as template because we want it to be general purpose, independent of the elements data type. Fourth: Mostly every algorithm here will be recursive, this is because you can use a recursive definition for a tree. Any node can be thought as a root of one sub-tree, even the leaves which can be seen as the root of an empty sub-tree. Now, lets get back to the code. As you can see, everything here is the class tree. Its composed by two others inner class: cell and iterator. The class cell defines the programming view of the nodes.

class cell { friend class tree; friend class iterator; T t; cell *right, *left_child; cell() : right(NULL), left_child(NULL) {} };

This class is defined by tree things, the first one is the template t which actually will keep the data we want to save in our tree, it is no more than a template data type variable. The others two elements are cells pointers, this is because we need to define some sort of motion between the nodes. At any node we can be on, I can only move to my right, which will give me the brother node (those nodes that are at the same level), and to my left child which we will treat it as it first son (the first element on the sub-tree that my current position node- acts like root). Because of we move from cell to cell, we need it to be a cells pointer. The friends declaration is for allowing the access to the information, we can omit the friend class iterator due to iterator is an inner class of the cell class. Ok, so we can go down and right. Is that all? Now, lets continue with Iterators. What allow motion in the external way, its the iterator. It will give us a way to communicate with our elements or positions (even if in a position we dont have any element, this will be great for inserting elements or finding them).

class iterator { private: friend class tree; cell *ptr,*prev,*father; iterator(cell *p,cell *prev_a,cell *f_a) : ptr(p), prev(prev_a), father(f_a) { }

Once again cells pointers. You can say that with this three new pointers we sort of close de circuit, notice that you can move everywhere.

cell *ptr: saves the memory address of the current cell we are at. cell *prev: saves the memory address to the left brother cell. cell *father: saves the memory address to the father cell.

Something to notice: As we define before, the root of our tree doesnt have any father so that, the father field on the iterator will be NULL. This also happens if we have a pointer to one cell that is a left-child of another cell. In this case, the field that will be NULL is prev.

public: iterator(const iterator &q) { ptr = q.ptr; prev = q.prev; father = q.father; } T &operator*() { return ptr->t; } T *operator->() { return &ptr->t; } bool operator!=(iterator q) { return ptr!=q.ptr; } bool operator==(iterator q) { return ptr==q.ptr; } iterator() : ptr(NULL), prev(NULL), father(NULL) { } iterator lchild() { return iterator(ptr->left_child,NULL,ptr); } iterator right() { return iterator(ptr->right,ptr,father); } // Prefix: iterator operator++() { *this = right(); return *this; } // Postfix: iterator operator++(int) { iterator q = *this; *this = right(); return q; } };

iterator(const iterator &q) its the copy construct, it define an iterator from another iterator that has been already defined. lchild() returns an iterator to the left-child of my current pointed node (cell). right() returns an iterator to the left, it allow us to move from cell in the same level (brothers). Its because of those two functions that we get motion along the tree, so most of the functions will use recursively this two. How does the motion works? We can define our own way of moving between the nodes, but here we are going to use the depth movement (or something like this). And the basic idea is: while ( /* I have a position to jump at */ ){ /* I call this function with my_iterator.lchild() as a parameter*/ /* my_iterator++; //or my_iterator.next(); */ };

Notice that my_iterator++ will be executed once every other lchild() is visited. When we make the function to call itself, it will save it stat and work with the new instance of the same function; once this latest instance finish, it will return to the part where it was called.

The operators overload are made for arithmetic and logical operations over the iterators. The ++ overload is the same as next(), but you have it in two flavors: You got the prefix which will return the result of applying next() to the iterator, and the postfix which gives you the current iterator and apply next() to it, but you keep the previous. Now, there are two more functions to clarify. T &operator*() : let you access the inner data of a cell. T *operator->(): Its use to access the inner data but non direct way, its use for data keep in a structure.

Now that we cover some of the primal concepts of the tree class, lets work on it
template<class T> class tree { public: class iterator; private: class cell {}; cell *header; iterator tree_copy_aux(iterator nq, tree<T> &TT,iterator nt) { nq = insert(nq,*nt); iterator ct = nt.lchild(), cq = nq.lchild(); while (ct!=TT.end()) { cq = tree_copy_aux(cq,TT,ct); ct = ct.right(); cq = cq.right(); } return nq; }

Ok, what do we have here? The first thing we may notice its the template <class T> this is because we want to save any kind of data inside the tree container (Remember that the data type must be the same for those objects that are in the same tree). cell *header its a fictional element that will have no more use than be the father of our root node. I know that we have talk about the root not having a father, but for this implementation we use one so then we can work freely, and it will help us a lot. Although we have a father for our root node, we wont use it from the exterior of the class, it is just made just for inside purposes, it will be the start point of almost everything. You can see it like a sort of a hook where our tree is holding. iterator tree_copy_aux(iterator nq, tree<T> &TT,iterator nt) this function is for inner purpose, we will use it for anything that need a copy. Its very simple you just give it an iterator to the position in your tree (iterator *nq), then you say which tree you want to make copy of (tree<T> &TT) and a iterator to the position on this tree you wanna start coping to your tree (iterator nt).

Something very important about the iterators is that they must be updated once you operate over them. You see it here: nq = insert(nq, *nt);
(we will see how insert() works latter).

In this implementation they declare 2 new iterators, ct and cq which will be define with the appropriate lchild(), what for ? for the recursive!!! And it starts the recursive!!!!

This is made by the while loop. while ( ct != TT.end() ) there are values on the external tree to copy. So, how do we do it? Easy, the first part calls the function itself so this way you do a depth copy. This is, you copy all the left-child stacking the next part of the code, so when you dont have more child you get back to the previous function and do the same for the cell (nodes) at the right() (the brothers). Once this is finished, you end up with a copy of the other tree. It can be an exact copy or it can be a sub-tree of one bigger tree. This will depend on the iterator you pass to the function when you call it. The function end up returning an iterator to the last element inserted to your tree.

public: static int cell_count_m; static int cell_count() { return cell_count_m; } class iterator { }; // ------------------------------------------------------------// Default constructor(loader) tree() { header = new cell; cell_count_m++; header->right = NULL; header->left_child = NULL; }

Here we have the default constructor. This piece of code has a lot of information about how we are doing stuff in here. As we talk about, the header is something (a cell pointer) as a hook where the actual tree is holding on. Truly, it is a pointer where we store the root node memory address. header->right = NULL; : Once again, this pointer isnt part of our tree, it isnt a real node, so it wont have any brother. header->left_child = NULL; : It will have a son, his son will be the root of our tree. But since this is the default constructor we define his left child as NULL, until we save some cell on our tree container.
// ------------------------------------------------------------// construct by copy. tree<T>(const tree<T> &TT) { if (&TT != this) { header = new cell; cell_count_m++; header->right = NULL; header->left_child = NULL; tree<T> &TTT = (tree<T> &) TT;

if (TTT.begin()!=TTT.end()) tree_copy_aux(begin(),TTT,TTT.begin()); } }

Here we have some code that made a tree from another tree. The only difference between this and the default constructor starts with:
tree<T> &TTT = (tree<T> &) TT;

This is just a trick for operating the labeled const tree without any trouble. Since we didnt create a const iterator we should find some way of doing work them, and this is one way of doing that. Once we have everything set, we use the tree_copy_aux function. This what actually makes the job copying everything from the tree starting at begin() in our tree.
// ------------------------------------------------------------iterator insert(iterator p,T t) { assert(!(p.father==header && p.ptr)); cell *c = new cell; cell_count_m++; c->right = p.ptr; c->t = t; p.ptr = c; if (p.prev) p.prev->right = c; else p.father->left_child = c; return p; }

This is the way we put data on our tree. Although it simple, Ill try my best explaining it for those of you that cant get it right away. Motion along the tree is define here by the cell class. You can move to the right or to left child. So for this to work you have to make all the necessary bounds every time you move something, this is telling the father which node will be the new left child (if its necessary) or updating the right cell pointer. The function starts with 2 parameters, an iterator and some T-type data. It will put this T-type data where the iterator is pointing. There are two general option for the iterator: The iterator could be an existing node, or it can be NULL. If it is an existing node, and we want to insert the data there, the function will move everything, from this iterator, to the right and put the data on the position you want.

Once the operation is finished it returns a valid iterator for the new cell position. Since we move the objects, every other iterator will have to be updated (not every iterator, but its nice to see it that way will make your code understandable). Another thing to contemplate is if the iterator is pointing to the root of our tree, since the root cant have any brothers, the insert function must be aborted. The assert function ensures that what is inside the iterator is a valid position node on our tree.
// ------------------------------------------------------------iterator erase(iterator p) { if(p==end()) return p; iterator c = p.lchild(); while (c!=end()) c = erase(c); // this is all the while sentence. cell *q = p.ptr; p.ptr = p.ptr->right; if (p.prev) p.prev->right = p.ptr; else p.father->left_child = p.ptr; delete q; cell_count_m--; return p; }

Here we have another important function, the one that allow the erasing of data. This one is a little tricky. Because we need to erase some data we will erase, in case it has one, the whole sub-tree and that is why you the a recursive method. Think of it as your File System Structure, if you want to erase a folder on your system, and you didnt move what you had inside of it, when you delete it you end up erase all the data you had in that folder. This function does, recursively, the opposite of what insert function does. Every time it delete a node will move to the left all his brothers (making the necessary bounds), if there is one.
// ------------------------------------------------------------iterator find(T t) { return find(t,begin()); } // ------------------------------------------------------------iterator find(T t,iterator p) { if(p==end() || p.ptr->t == t) return p;

iterator q,c = p.lchild(); while (c!=end()) { q = find(t,c); if (q!=end()) return q; else c++; } return iterator(); }

The find function will compare the data you pass through the arguments with every cells data from the iterator p position, looking for what we want. If the function finds what it is called to, will return an iterator to that particular cell; if not, it will return a NULL iterator. Once again, it performs a depth movement along the tree (recursively).
// ------------------------------------------------------------iterator begin() { return iterator(header->left_child,NULL,header); } // ------------------------------------------------------------iterator end() { return iterator(); }

This two are the holy grail of almost all the algorithms. Those two functions works kind of the same way, they both return an iterator. begin() returns an iterator to the root, where our tree starts. end() returns a NULL iterator. This is use to define if it is a non empty node or not, some NULL nodes will have a father, if the empty node is the result of applying right() or lchild() function to leafs nodes.

We are done! I hope Ive covered some of the fundamentals ideas behind the n-nodes oriented trees. If I didnt make it, please let me know. If you have any doubt, please let me know. If you didnt understand anything, please let me know. If you find something wrong, please let me know. You can email me, or just leave a comment below. [email protected] Cheers!

References: C.I.M.E.C. : (http://www.cimec.org.ar) ; Algoritmos y Estructuras de Datos (M. St0orti, J. DEla, R. Paz, L. Dalcn, y M. Pucheta, 2012) Wikipedia.

You might also like