In the first part of this post I presented a Binary Search Tree implemented in pure XPath 3.0. Some new nice features of XPath 3.0 were shown in action and other, still missing but very important features were identified and discussed.
The only operation that was missing was the deletion of a node from the tree. I show how to do this now.
This is the function that consumers will use:
(: declare function tree:deleteNode |
It simply delegates to a similar internal function that also requires a third argument – the depth of the (sub)tree on which the delete is performed. Why is the depth necessary? The answer will come later.
(: declare function tree:deleteNode |
We see that the deleteNode() function simply dispatches the operation to the left or right subtree and calls another function – deleteTopNode() – whenever the item to be deleted happens to be the value of the top node.
|
deleteTopNode() does the real deletion work. The operation is straightforward in the case when the tree is empty or either the left subtree or the right subtree is empty.
It becomes interesting when both subtrees are non-empty. In order to understand what the last 20 lines of this code mean, it is best to read a good source on the binary search tree delete operation.
In a few words:
- If the depth is even, then we move to the top the leftmost node (without its right subtree) of the right subtree.
- If the depth is odd, then we move to the top the rightmost node (without its left subtree) of the left subtree.
Why is this different processing done, depending on the oddness of the depth of the subtree? If we always used the leftmost node of the right subtree, then a series of deletions would create a severely imbalanced (to the right) tree. Imbalanced trees lose the advantages of the binary search tree with search and insertions becoming of linear complexity (instead of O(log(N)) ) and sorting becoming O(N^2) instead of O(N*log(N)).
Thus, in order to avoid this deteriorating effect, we need to use with a pseudo-random frequency both the left and the right subtrees. In most cases this can be achieved simply by using the oddness of the depth of the tree, whose top node is to be replaced.
Below is the complete code of the Binary Search tree. Do note that I have added a new “property” to the tree datatype – “size”. Also, note the functions tree:leftmost() and tree:rightmost() – used by tree:deleteTopNode()
Another change is that now the second argument of tree:contains() is of type item()? — that is, it can be empty. By definition, any tree contains the “empty item”.
Finally, there is a new pair of functions: tree:serialize() and tree:deserialize() . The former is a synonym of tree:print() and the latter loads the XML representation created by tree:serialize() into a tree.
|
Now, let’s test just the deleteNode operation:
let $vBigTree := |
And here are the results:
tree:print($vBigTree4): <treeNode> <value>10</value> <treeNode> <value>5</value> <treeNode> <value>3</value> </treeNode> <treeNode> <value>8</value> <treeNode> <value>6</value> <treeNode> <value>7</value> </treeNode> </treeNode> <treeNode> <value>9</value> </treeNode> </treeNode> </treeNode> <treeNode> <value>17</value> <treeNode> <value>14</value> <treeNode> <value>12</value> </treeNode> <treeNode> <value>15</value> </treeNode> </treeNode> <treeNode> <value>20</value> </treeNode> </treeNode> </treeNode> tree:data($vBigTree4): 3 5 6 7 8 9 10 12 14 15 17 20 ==================================== tree:print(tree:deleteNode($vBigTree4, 7)):
<treeNode>
tree:data(tree:deleteNode($vBigTree4, 7)): 3 5 6 8 9 10 12 14 15 17 20 ==================================== tree:print(tree:deleteNode($vBigTree4, 12)): <treeNode> <value>10</value> <treeNode> <value>5</value> <treeNode> <value>3</value> </treeNode> <treeNode> <value>8</value> <treeNode> <value>6</value> <treeNode> <value>7</value> </treeNode> </treeNode> <treeNode> <value>9</value> </treeNode> </treeNode> </treeNode> <treeNode> <value>17</value> <treeNode> <value>14</value> <treeNode> <value>15</value> </treeNode> </treeNode> <treeNode> <value>20</value> </treeNode> </treeNode> </treeNode> tree:data(tree:deleteNode($vBigTree4, 12)): 3 5 6 7 8 9 10 14 15 17 20 ==================================== tree:print(tree:deleteNode($vBigTree4, 6)): <treeNode> <value>10</value> <treeNode> <value>5</value> <treeNode> <value>3</value> </treeNode> <treeNode> <value>8</value> <treeNode> <value>7</value> </treeNode> <treeNode> <value>9</value> </treeNode> </treeNode> </treeNode> <treeNode> <value>17</value> <treeNode> <value>14</value> <treeNode> <value>12</value> </treeNode> <treeNode> <value>15</value> </treeNode> </treeNode> <treeNode> <value>20</value> </treeNode> </treeNode> </treeNode> tree:data(tree:deleteNode($vBigTree4, 6)): 3 5 7 8 9 10 12 14 15 17 20 ==================================== tree:print(tree:deleteNode($vBigTree4, 5)): <treeNode> <value>10</value> <treeNode> <value>6</value> <treeNode> <value>3</value> </treeNode> <treeNode> <value>8</value> <treeNode> <value>7</value> </treeNode> <treeNode> <value>9</value> </treeNode> </treeNode> </treeNode> <treeNode> <value>17</value> <treeNode> <value>14</value> <treeNode> <value>12</value> </treeNode> <treeNode> <value>15</value> </treeNode> </treeNode> <treeNode> <value>20</value> </treeNode> </treeNode> </treeNode> tree:data(tree:deleteNode($vBigTree4, 5)): 3 6 7 8 9 10 12 14 15 17 20 ==================================== tree:print(tree:deleteNode($vBigTree4, 10)): <treeNode> <value>9</value> <treeNode> <value>5</value> <treeNode> <value>3</value> </treeNode> <treeNode> <value>8</value> <treeNode> <value>6</value> <treeNode> <value>7</value> </treeNode> </treeNode> </treeNode> </treeNode> <treeNode> <value>17</value> <treeNode> <value>14</value> <treeNode> <value>12</value> </treeNode> <treeNode> <value>15</value> </treeNode> </treeNode> <treeNode> <value>20</value> </treeNode> </treeNode> </treeNode> tree:data(tree:deleteNode($vBigTree4, 10)): 3 5 6 7 8 9 12 14 15 17 20
|
In my next posts I’ll show some of the most important applications of the Binary Search tree.