'Updating a binary tree using a pointer
I'm trying to update a self-balancing binary tree. Normally, you can update it by 1) searching a node, 2) deleting it, 3) and inserting the tree with a new node. But, I want to see if this is possible simply by retaining a pointer to a node from the first step and updating it so that I can bypass the deletion and insertion and improve the time complexity, especially when it comes to a large number of nodes.
The tree itself is standard binary search tree.
public class TreeNode<T: Comparable>: Equatable {
public typealias Node = TreeNode<T>
var key: T?
var leftChild: Node?
var rightChild: Node?
fileprivate weak var parent: Node?
var isNullLeaf: Bool {
return key == nil && isLeaf
}
var isLeaf: Bool {
return rightChild == nil && leftChild == nil
}
public init(key: T?, leftChild: Node?, rightChild: Node?, parent: Node?) {
self.key = key
self.leftChild = leftChild
self.rightChild = rightChild
self.parent = parent
self.leftChild?.parent = self
self.rightChild?.parent = self
}
/// Null leaf
public convenience init() {
self.init(key: nil, leftChild: nil, rightChild: nil, parent: nil)
}
static public func == <T>(lhs: TreeNode<T>, rhs: TreeNode<T>) -> Bool {
return lhs.key == rhs.key
}
}
public final class Tree<T: Comparable> {
public typealias Node = TreeNode<T>
fileprivate(set) var root: Node
fileprivate let nullLeaf = Node()
public init() {
root = nullLeaf
}
func search(key: T, f: (inout Node) -> Void) {
search(key: key, node: &root, f: f)
}
fileprivate func search(key: T, node: inout Node, f: (inout Node) -> Void) {
if !node.isNullLeaf {
if let nodeKey = node.key {
/// When a node is found, pass by reference as an argument to a closure so that it retains the connection to the node when it's being update.
if key == nodeKey {
f(&node)
} else if key < nodeKey {
guard node.leftChild != nil else {
return
}
search(key: key, node: &node.leftChild!, f: f)
} else {
guard node.rightChild != nil else {
return
}
search(key: key, node: &node.rightChild!, f: f)
}
}
}
}
public func insert(key: T) {
/// insertion logic
}
/// Other operations
}
My idea was to search the tree through recursion and when a node is found, pass it as an argument to a closure function, which will ultimately be called to update the node. Also, the found node would be pass by reference.
class Test<T: Comparable> {
private(set) var tree = Tree<T>()
func insert(key: T) {
tree.insert(key: key)
}
func update(for node: T, with newNode: T) {
tree.search(key: node) { foundNode in
foundNode.key = newNode
}
}
}
let test = Test<MyNode>()
let node = MyNode()
let anotherNode = MyNode()
test.insert(key: node)
test.update(for: node, with: anotherNode)
The problem is the update doesn't happen. If I search for newly updated node in the tree, it doesn't exist.
Update
Above code is a modified version of a Red-Black tree, specifically modifying the search method to use a pointer instead.
I've tried my idea on a simplified version of a binary search tree below and it seems to be updating the value of a specified node.
public final class BinaryTree<T: Comparable> {
public final class Node<T> {
public var value: T
public var leftChild: Node<T>?
public var rightChild: Node<T>?
public init(value: T, leftChild: Node<T>? = nil, rightChild: Node<T>? = nil) {
self.value = value
self.leftChild = leftChild
self.rightChild = rightChild
}
}
public var rootNode: Node<T>
public init(rootNode: Node<T>) {
self.rootNode = rootNode
}
public func addNodes(to parent: Node<T>, leftChild: Node<T>?, rightChild: Node<T>?) {
parent.leftChild = leftChild
parent.rightChild = rightChild
}
public func searchTree(_ value: T, node: inout Node<T>?, f: (inout Node<T>?) -> Void) {
if node == nil || value == node?.value {
f(&node)
} else if value < node!.value {
searchTree(value, node: &node!.leftChild, f: f)
} else {
searchTree(value, node: &node!.rightChild, f: f)
}
}
}
Tested here.
var rootNode: BinaryTree<Int>.Node<Int>? = BinaryTree<Int>.Node(value: 100, leftChild: nil, rightChild: nil)
let tree = BinaryTree(rootNode: rootNode!)
/// add new nodes. This is not a self-balancing tree so the left child's value has to be smaller than the parent and the right child's value greater than the parent.
let leftChild = BinaryTree<Int>.Node(value: 0, leftChild: nil, rightChild: nil)
let rightChild = BinaryTree<Int>.Node(value: 200, leftChild: nil, rightChild: nil)
tree.addNodes(to: rootNode!, leftChild: leftChild, rightChild: rightChild)
/// the node argument is the starting point of the search so let's start from the root node.
/// the found node will be updated with a new node with a value 50
tree.searchTree(0, node: &rootNode) { foundNode in
let newNode = BinaryTree<Int>.Node(value: 50, leftChild: nil, rightChild: nil)
foundNode = newNode
}
/// The node with a value as 0 is now gone.
tree.searchTree(0, node: &rootNode) { foundNode in
print(foundNode) /// nil
}
/// The node has bee properly updated.
tree.searchTree(50, node: &rootNode) { foundNode in
print(foundNode) /// node with 50 as the value found
}
But can't seem to figure out why the original code isn't updating a node by pointer.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
