Binary search tree: traverse: Difference between revisions
No edit summary |
|||
(17 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
[[Category: | [[Category:Videos]] | ||
[[Category:Algorithms]] | |||
[[Category:Search Algorithms]] | |||
[[Category:Tree Algorithms]] | |||
[[Category:Binary_Search_Tree]] | |||
{{#ev:youtube|https://www.youtube.com/watch?v=PXqM9q57BMk|500|right|Binary search tree: traverse|frame}} | |||
== Genaral Information == | |||
'''Algorithmic problem:''' [[Sorted sequence#Traverse|Sorted sequence: traverse]] | |||
'''Type of algorithm:''' loop | |||
'''Auxiliary data:''' | |||
# A [[Sets and sequences#Stacks and queues|stack]] '''''S''''' whose elements are pairs consisting of | |||
## a natural number '''''seenChildren''''' in the range <math>\{0,1,2\}</math> and | |||
## a binary search tree node '''''node'''''. | |||
# Pointers '''''elem''''' and '''''elem'''''' to stack elements. | |||
== Abstract View == | == Abstract View == | ||
'''Invariant:''' Before and after each iteration: | |||
# <math>S</math> contains all nodes on the path (called the '''current path''') from the root to some binary search tree node (called the '''current node''') in the order from the root to the current node (in particular, the current node is the top element of <math>S</math>). | |||
# Let '''''elem''''' be some element of the stack:] | |||
## If <math>elem.seenChildren = 0</math>, neither <math>elem.node.key</math> nor one of the keys in the [[Directed Tree|subtree]] rooted at <math>elem.node.right</math> has been appended to <math>L</math> so far; possibly, some or all keys in the subtree rooted at <math>elem.node.left</math> have already been appended. | |||
## If <math>elem.seenChildren = 1</math>, all keys in the [[Directed Tree|subtree]] rooted at <math>elem.node.left</math> and, afterwards, <math>elem.node.key</math> have already been appended to <math>L</math>; possibly, some or all keys in the subtree rooted at <math>elem.node.right</math> have been appended as well. | |||
## If <math>elem.seenChildren = 2</math>, all keys in the [[Directed Tree|subtree]] rooted at <math>elem.node</math> have been appended to <math>L</math> | |||
'''Variant:''' Indentify the current content of <math>S</math> with the string of length <math>|S|</math> over the alphabet <math>\{0,1,2\}</math> that is formed by the <math>seenChildren</math> values of the stack elements <in the order from the root to the current node). Then the current string immediately '''after''' the iteration | |||
# is either empty (which, clearly, is tantamount to <math>S</math> being empty), | |||
# or it is lexicographically larger than the string immediately '''before''' the iteration. | |||
'''Break condition:''' <math>S</math> is empty. | |||
'''Remark:''' The number of iterations accomplished so far is the natural induction parameter. For conciseness, the induction parameter is omitted in the following. | |||
== Induction Basis == | == Induction Basis == | ||
'''Abstract view:''' Initialize <math>S</math> with the root. | |||
'''Implementation:''' | |||
# Create a new stack element <math>elem</math>. | |||
# Set <math> elem.node := root</math>. | |||
# Set <math> elem.seenChildren := 0</math>. | |||
# Ensure that <math>S</math> is empty. | |||
# <math>S.push(elem)</math>. | |||
'''Proof:''' All invariants are trivially fulfilled. | |||
== Induction Step == | == Induction Step == | ||
'''Abstract view:''' | |||
# If the left child of the current node has not yet been examined: | |||
## If the left child of the current node exists, proceed to the left child. | |||
#Otherwise, if the right child of the current node has not yet been examined: | |||
## Append the key of the current node to <math>L</math>. | |||
## If the right child of the current node exists, proceed to the right child. | |||
#Otherwise (that is, left and right child examined), proceed to the parent of the current node. | |||
'''Implementation:''' | |||
# Set <math>elem := S.top()</math>. | |||
# Set <math>node := elem.node</math> | |||
# If <math>elem.seenChildren = 0</math>: | |||
## If <math>node.left \neq void</math>: | |||
### Create a new element <math>elem'</math>. | |||
### Set <math>elem'.node := node.left</math>. | |||
### Set <math>elem'.seenChildren := 0</math>. | |||
### <math>S.push(elem')</math>. | |||
## Otherwise: | |||
### Set <math>elem.seenChildren := 1</math>. | |||
### <math>L.append(elem.node.key)</math>. | |||
# Otherwise, if <math>elem.seenChildren = 1</math>: | |||
## If <math>node.right \neq void</math>: | |||
### Create a new elem <math>elem'</math>. | |||
### Set <math>elem'.node := node.right</math>. | |||
### Set <math>elem'.seenChildren := 0</math>. | |||
### <math>S.push(elem')</math>. | |||
## Otherwise, set <math>elem.seenChildren := 2</math>. | |||
# Otherwise, (that is <math>elem.seenChildren = 2</math>). | |||
## <math>S.pop()</math>. | |||
## If <math>S</math> is empty, terminate the algorithm. | |||
## Set <math>elem := S.top()</math> | |||
## Set <math>elem.seenChildren := elem.seenChildren + 1</math>. | |||
## If <math>elem.seenChildren = 1</math> and <math>elem.node.left \neq void</math> then: <math>L.append(elem.node.key)</math>. | |||
'''Correctness:''' Invariant #1 follows immediately from the fact that each push operation simply extends the current path and each pop operation cuts off the end node of the current path. Next consider Invariant #2. | |||
Invariant #2 is trivially maintained both for <math>elem</math> and <math>elem'</math> in Steps 3.1 and 4.1 and <math>elem</math> in Step 3.2 and 4.2 (note the append operation in Step 2.2, which reflects the inclusion of the current node in Invariant 2.2). In Step 5, the algorithm returns from a non-empty subtree, so the increase of <math>seenChildren</math> and the append in case <math>seenChildren = 1</math> are correct. Note that each key is appended only once because Step 3.2.2 applies only if the left child is empty, and Step 5.4, only if the left child is '''not''' empty. | |||
Finally, consider the variant. Whenever a new element is pushed, the new string is an extension of the old string, which is clearly lexicographically larger. On the other hand, whenever an element is removed from <math>S</math>, Step 5.4 ensures that the new string is not just a prefix of the old string but is larger at the last position of the new string (which is the second last position of the old string). | |||
== Complexity == | == Complexity == | ||
'''Statement:''' Linear in the length of the sequence in the worst case. | |||
'''Proof:''' Each iteration of the loop takes constant time. Each node is visited in a most three iterations, viz. once with <math>seenChildren = 0,1,2</math> respectively. This obvservation proves the claim. | |||
== Pseudocode == | == Pseudocode == |
Latest revision as of 13:41, 3 March 2017
Genaral Information
Algorithmic problem: Sorted sequence: traverse
Type of algorithm: loop
Auxiliary data:
- A stack S whose elements are pairs consisting of
- a natural number seenChildren in the range [math]\displaystyle{ \{0,1,2\} }[/math] and
- a binary search tree node node.
- Pointers elem and elem' to stack elements.
Abstract View
Invariant: Before and after each iteration:
- [math]\displaystyle{ S }[/math] contains all nodes on the path (called the current path) from the root to some binary search tree node (called the current node) in the order from the root to the current node (in particular, the current node is the top element of [math]\displaystyle{ S }[/math]).
- Let elem be some element of the stack:]
- If [math]\displaystyle{ elem.seenChildren = 0 }[/math], neither [math]\displaystyle{ elem.node.key }[/math] nor one of the keys in the subtree rooted at [math]\displaystyle{ elem.node.right }[/math] has been appended to [math]\displaystyle{ L }[/math] so far; possibly, some or all keys in the subtree rooted at [math]\displaystyle{ elem.node.left }[/math] have already been appended.
- If [math]\displaystyle{ elem.seenChildren = 1 }[/math], all keys in the subtree rooted at [math]\displaystyle{ elem.node.left }[/math] and, afterwards, [math]\displaystyle{ elem.node.key }[/math] have already been appended to [math]\displaystyle{ L }[/math]; possibly, some or all keys in the subtree rooted at [math]\displaystyle{ elem.node.right }[/math] have been appended as well.
- If [math]\displaystyle{ elem.seenChildren = 2 }[/math], all keys in the subtree rooted at [math]\displaystyle{ elem.node }[/math] have been appended to [math]\displaystyle{ L }[/math]
Variant: Indentify the current content of [math]\displaystyle{ S }[/math] with the string of length [math]\displaystyle{ |S| }[/math] over the alphabet [math]\displaystyle{ \{0,1,2\} }[/math] that is formed by the [math]\displaystyle{ seenChildren }[/math] values of the stack elements <in the order from the root to the current node). Then the current string immediately after the iteration
- is either empty (which, clearly, is tantamount to [math]\displaystyle{ S }[/math] being empty),
- or it is lexicographically larger than the string immediately before the iteration.
Break condition: [math]\displaystyle{ S }[/math] is empty.
Remark: The number of iterations accomplished so far is the natural induction parameter. For conciseness, the induction parameter is omitted in the following.
Induction Basis
Abstract view: Initialize [math]\displaystyle{ S }[/math] with the root.
Implementation:
- Create a new stack element [math]\displaystyle{ elem }[/math].
- Set [math]\displaystyle{ elem.node := root }[/math].
- Set [math]\displaystyle{ elem.seenChildren := 0 }[/math].
- Ensure that [math]\displaystyle{ S }[/math] is empty.
- [math]\displaystyle{ S.push(elem) }[/math].
Proof: All invariants are trivially fulfilled.
Induction Step
Abstract view:
- If the left child of the current node has not yet been examined:
- If the left child of the current node exists, proceed to the left child.
- Otherwise, if the right child of the current node has not yet been examined:
- Append the key of the current node to [math]\displaystyle{ L }[/math].
- If the right child of the current node exists, proceed to the right child.
- Otherwise (that is, left and right child examined), proceed to the parent of the current node.
Implementation:
- Set [math]\displaystyle{ elem := S.top() }[/math].
- Set [math]\displaystyle{ node := elem.node }[/math]
- If [math]\displaystyle{ elem.seenChildren = 0 }[/math]:
- If [math]\displaystyle{ node.left \neq void }[/math]:
- Create a new element [math]\displaystyle{ elem' }[/math].
- Set [math]\displaystyle{ elem'.node := node.left }[/math].
- Set [math]\displaystyle{ elem'.seenChildren := 0 }[/math].
- [math]\displaystyle{ S.push(elem') }[/math].
- Otherwise:
- Set [math]\displaystyle{ elem.seenChildren := 1 }[/math].
- [math]\displaystyle{ L.append(elem.node.key) }[/math].
- If [math]\displaystyle{ node.left \neq void }[/math]:
- Otherwise, if [math]\displaystyle{ elem.seenChildren = 1 }[/math]:
- If [math]\displaystyle{ node.right \neq void }[/math]:
- Create a new elem [math]\displaystyle{ elem' }[/math].
- Set [math]\displaystyle{ elem'.node := node.right }[/math].
- Set [math]\displaystyle{ elem'.seenChildren := 0 }[/math].
- [math]\displaystyle{ S.push(elem') }[/math].
- Otherwise, set [math]\displaystyle{ elem.seenChildren := 2 }[/math].
- If [math]\displaystyle{ node.right \neq void }[/math]:
- Otherwise, (that is [math]\displaystyle{ elem.seenChildren = 2 }[/math]).
- [math]\displaystyle{ S.pop() }[/math].
- If [math]\displaystyle{ S }[/math] is empty, terminate the algorithm.
- Set [math]\displaystyle{ elem := S.top() }[/math]
- Set [math]\displaystyle{ elem.seenChildren := elem.seenChildren + 1 }[/math].
- If [math]\displaystyle{ elem.seenChildren = 1 }[/math] and [math]\displaystyle{ elem.node.left \neq void }[/math] then: [math]\displaystyle{ L.append(elem.node.key) }[/math].
Correctness: Invariant #1 follows immediately from the fact that each push operation simply extends the current path and each pop operation cuts off the end node of the current path. Next consider Invariant #2.
Invariant #2 is trivially maintained both for [math]\displaystyle{ elem }[/math] and [math]\displaystyle{ elem' }[/math] in Steps 3.1 and 4.1 and [math]\displaystyle{ elem }[/math] in Step 3.2 and 4.2 (note the append operation in Step 2.2, which reflects the inclusion of the current node in Invariant 2.2). In Step 5, the algorithm returns from a non-empty subtree, so the increase of [math]\displaystyle{ seenChildren }[/math] and the append in case [math]\displaystyle{ seenChildren = 1 }[/math] are correct. Note that each key is appended only once because Step 3.2.2 applies only if the left child is empty, and Step 5.4, only if the left child is not empty. Finally, consider the variant. Whenever a new element is pushed, the new string is an extension of the old string, which is clearly lexicographically larger. On the other hand, whenever an element is removed from [math]\displaystyle{ S }[/math], Step 5.4 ensures that the new string is not just a prefix of the old string but is larger at the last position of the new string (which is the second last position of the old string).
Complexity
Statement: Linear in the length of the sequence in the worst case.
Proof: Each iteration of the loop takes constant time. Each node is visited in a most three iterations, viz. once with [math]\displaystyle{ seenChildren = 0,1,2 }[/math] respectively. This obvservation proves the claim.
Pseudocode
- INORDER-TREE-WALK(x)
- if x ≠ NULL
- INORDER-TREE-WALK(left[x])
- print key[x]
- INORDER-TREE-WALK(right[x])
- if x ≠ NULL