Modified Preorder Tree Traversal Algoritmus #1
Modified Preorder Tree Traversal is one of the methods, how to save tree structure of dates in relational databases. Methods, how to save tree structure is of course plenty, but not all are so efficient. We can use for example various recursive methods, which are mostly in the bigger size of trees very slow because of for gaining of the whole tree will be needed minimally as many SQL queries as many levels of branches is. Next chance is use container, which is faster then recursion, but also in the bigger size of tree we will rain down many SQL queries on database.
Method, by which we can use only one or two SQL queries for whatever operation is called modifying preorder tree traversal.
Basic principle of the whole algorithm is very simple. Duty is rate all of the embranchment of trees with ordinal numbers, accord which we walk through the whole tree. For better imagination of rating see this picture:
Ordinal numbers, which we rate the tree begin from lowest number and with each other embranchment this number grows up by value one. The whole tree we walk through from left to right and by this we are gaining his whole structure.
Home (1, 16) -> First (2, 7) -> Thrid (3, 4) -> Fourth(5, 6) -> First(3, 7) -> Second(8, 12) -> Fifth(9, 14) -> Sixth (10, 11) -> Seventh(12, 13) -> Fifth(9, 14) -> Second(8, 12) -> Home(1, 16)
Each embranchment has now two indexes – right one and left one. By rating of embranchment with this method creates effects to us, which will be for our work very important. Embranchment, in which are subembranchments has always lower left index than all of these subembranchments and his right index is bigger than all of his subembranchments. Evidently is it from base of the whole tree, which has the lowest left index and the biggest right index in the whole tree.
Advantage is in it, that it isn’t problem for us gain whatever of this subtrees ordered exactly accord that, how we have them saved in databases, only with one SQL query.
For programming I choose PHP 5 and MySQL version 4.1
Table in database
Before self saving of dates we can create table in database. All, what we need in this table is name of item, right index, left index and moreover we will be saving size of nested item in tree. Eventually we can save prefix for language mutation (in the whole code we will suppose, that prefix of language mutation is saved in array SESSION). Structure of table can then looks for example like this:
1 2 3 4 5 6 7 8 |
CREATE TABLE preorder_tree_traversal ( id_traverz INTEGER NOT NULL AUTO_INCREMENT, traverz_name VARCHAR(100), traverz_parent SMALLINT, traverz_left SMALLINT, traverz_right SMALLINT, lang VARCHAR(6), PRIMARY KEY( id_traverz ); |
Modified Preorder
Tree Traversal class
If we have created table yet, we can create class for servicing of the whole algorithm now. By traversing is service of dates in database little complicated, than it is by other algorithms, but access to these elements (single embranchments) is more efficient.
Problem with editing is exactly in it, with each change it musts be renumbered indexes of embranchments, because of not creating of collisions in tree to us.
All class will be consists of methods, that will serviced traversing. As first we will defined constructor, which includes one parameter – table of table. In this table is our created structure.
1 2 3 4 5 6 |
class traverz extends connect{ function __construct( $table_name ){ $this->table_name = $table_name; parent::__construct(); } } |
For connection to database we will use extends class connect, which we will extends into our class thanks to connects methods.
First we make method, which inserts basic element of tree – base – into table. Base must have, on the supposition that in our tree are no embranchments, indexes 1 and 2. By that we set our index parent to value 0, from which we will starting.
1 2 3 4 5 6 7 8 |
function _insert_first(){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( 'HOME', '0', '1', '2', '" . $_SESSION['lang_prefix'] . "' )"; mysql_query($insert, $this->link); return ( mysql_affected_rows($this->link) == 1 ? 1 : 0); } |
For check, if method can be called, we can use function, which will check, if our table is empty, or doesn’t contain embranchments with this language mutation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function _check_num_of_rows(){ $select = "SELECT COUNT(*) FROM " . $this->table_name . " WHERE lang = '" . $_SESSION['lang_prefix'] . "'"; if( mysql_result( mysql_query( $select, $this->link ), 0, 0) == 0 ){ self::_insert_first(); return 0; } else{ return 0; } } } else{ return 0; } } |
Embranchments adding
One of most important methods in the whole class will be embranchments adding. Important is think about it, that in the each embranchment adding we must renumber all embranchments, which will be after added embranchment. These are all, that have bigger left index than item index, in which we are adding, against value i + 1.
By embranchment saving we have possibility choose, over which item will be new item inserted. For simplicity will class enable only insert item, which will be nesting in our item. We can choose, whether we want its insert to begin or to end thought.
I interpret as yet once on the picture. See picture, which is at the beginning this article. We want insert next embranchment after “First” embranchment. Class enable us insert embranchment from “Thrid”(on beginning) or after “Fourth” (in the end), with it, that inserted item have same nesting index as “Thrid” or “Fourth”.
Function addTree will have three input arguments: new item name, left index, after which we will insert and position, where we inserting ( on beginning or in the end).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function addTree ( $name, $left, $position = 0 ){ if( gettype( $position ) == 'string' ) $position = self::_setup_position( $position ); $position = abs( intval( $position) ); if( $position > 2 ) return 0; $parent = self::select_parent($left, 1); switch( $position ){ case 0 : { if( self::_addTree_h( $name, $left, $parent ) == 1 ) return 1; else return 0; break; } case 1 :{ if( self::_addTree_e( $name, $left, $parent, ( $this->traverz_right - 1 ) ) == 1 ) return 1; else return 0; break; } } return 1; } |
Method addTree use first method setup_position, that is here only by reason of decent input argument for position. This argument value is or number (0 – begin, 1 – end) or char (H – begin, E –end).
1 2 3 4 5 6 7 |
function _setup_position( $position ){ switch( strtolower( $position ) ){ case 'h' : return 0; break; case 'e' : return 1; break; default: return 0; } } |
Next is use method select_parent( $left, 1 ) here, that get nesting value item, after that we will insert:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function select_parent($left, $info = 0){ $select = "SELECT traverz_parent, traverz_right, id_traverz FROM " . $this->table_name . " WHERE traverz_left = '" . $left . "' AND lang = '" . $_SESSION['lang_prefix'] . "' LIMIT 1;"; $data = mysql_query($select, $this->link); if( $info == 1 ){ $this->traverz_right = @mysql_result($data, 0, 1); $this->id_traverz = @mysql_result( $data, 0, 2 ); } return ( @mysql_result($data, 0, 0) + 1 ); } |
Optional argument is $info, If is this parametr on (value 1), this method will saved value of right index ad ID value, to next work.
Now we can insert item. For our insert we must create two methods. One for insert item on begin and second for insert item in the end. Both methods are similar, and isn’t problem merge it’s to a one, but to lucidity we make two methods.
So first method pro insert item to begin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function _addTree_h( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang) VALUES( '" . trim( htmlspecialchars($name) ) . "', '" . intval( $parent ) . "', '" . ( intval( $left ) + 1 ) . "', '" . ( intval( $left ) + 2 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $left) == 1 ? 1 : 0 ); else return 0; } |
In SQL query we see inserted data. Parent value is value, that we stated with select_parent method and send his as argument. As well we handed name value. Left index value is handed value $left + 1, because $left have value of left index item, after which we are inserting. Right index value will be over one larger, than this item haven’t subembranchments.
Important is method _update_tree, to which we will handed two arguments – ID and left index of item, after which we are inserting. This method edits our table so, that it recalculates all of the indexes of embranchments within which we are inserting with exception of embranchment, which we just saved.
After explanation the method shows maybe hard to understand, but is very simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function _update_tree($id, $left){ $sql[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left + 2) WHERE ( traverz_left > '" . $left . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; $sql[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right + 2) WHERE ( traverz_right > '" . ($left - 1) . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($sql as $value){ mysql_query($value, $this->link); if(mysql_affected_rows($this->link) == 0 ) return 0; } } |
After method is called, two SQL queries are made. One edits right index and second edits left index. Now we create only method for inserting of embranchment to the end, which is the same like method for insert to begin, only with simple set-ups.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function _addTree_e( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang) VALUES( '" . trim( htmlspecialchars( $name ) ) . "', '" . intval( $parent ) . "', '" . intval( $this->traverz_right ) . "', '" . ( intval( $this->traverz_right ) + 1 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $this->traverz_right) == 1 ? 1 : 0 ); else return 0; } |
Now we have finished basic of whole class. We can save new embranchments. In the next article we will look to that, how we can delete and move embranchments.
Everything, what is finished now, we can look once more:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
class traverz extends connect{ public $traverz_right = NULL; public $id_traverz = NULL; public $parametrs = array(); public $ids = array(); public $table = NULL; function __construct( $table_name ){ $this->table_name = $table_name; parent::__construct(); self::_check_num_of_rows(); } function _check_num_of_rows(){ $select = "SELECT COUNT(*) FROM " . $this->table_name . " WHERE lang = '" . $_SESSION['lang_prefix'] . "'"; if( mysql_result( mysql_query( $select, $this->link ), 0, 0) == 0 ){ self::_insert_first(); return 0; } else{ return 0; } } //Insert HOME into Traverz_table function _insert_first(){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( 'HOME', '0', '1', '2', '" . $_SESSION['lang_prefix'] . "', 'home' )"; mysql_query($insert, $this->link); return ( mysql_affected_rows($this->link) == 1 ? 1 : 0); } function _update_tree($id, $left){ $sql[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left + 2) WHERE ( traverz_left > '" . $left . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; $sql[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right + 2) WHERE ( traverz_right > '" . ($left - 1) . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($sql as $value){ mysql_query($value, $this->link); if(mysql_affected_rows($this->link) == 0 ) return 0; } } function select_parent($left, $info = 0){ $select = "SELECT traverz_parent, traverz_right, id_traverz FROM " . $this->table_name . " WHERE traverz_left = '" . $left . "' AND lang = '" . $_SESSION['lang_prefix'] . "' LIMIT 1;"; $data = mysql_query($select, $this->link); if( $info == 1 ){ $this->traverz_right = @mysql_result($data, 0, 1); $this->id_traverz = @mysql_result( $data, 0, 2 ); } return ( @mysql_result($data, 0, 0) + 1 ); } function _setup_position( $position ){ switch( strtolower( $position ) ){ case 'h' : return 0; break; case 'e' : return 1; break; default: return 0; } } function addTree ( $name, $left, $position = 0 ){ if( gettype( $position ) == 'string' ) $position = self::_setup_position( $position ); $position = abs( intval( $position) ); if( $position > 2 ) return 0; $parent = self::select_parent($left, 1); switch( $position ){ case 0 : { if( self::_addTree_h( $name, $left, $parent ) == 1 ) return 1; else return 0; break; } case 1 :{ if( self::_addTree_e( $name, $left, $parent, ( $this->traverz_right - 1 ) ) == 1 ) return 1; else return 0; break; } } return 1; } function _addTree_h( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( '" . trim( htmlspecialchars($name) ) . "', '" . intval( $parent ) . "', '" . ( intval( $left ) + 1 ) . "', '" . ( intval( $left ) + 2 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $left) == 1 ? 1 : 0 ); else return 0; } function _addTree_e( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang) VALUES( '" . trim( htmlspecialchars( $name ) ) . "', '" . intval( $parent ) . "', '" . intval( $this->traverz_right ) . "', '" . ( intval( $this->traverz_right ) + 1 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $this->traverz_right) == 1 ? 1 : 0 ); else return 0; } } |