From 21fbc24e0240471301460c066a0ffb7dbe976ed7 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:31:24 +0100 Subject: [PATCH 1/6] posts: add 'treap-revisited' --- .../posts/2024-07-27-treap-revisited/index.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 content/posts/2024-07-27-treap-revisited/index.md diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md new file mode 100644 index 0000000..c9c01bc --- /dev/null +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -0,0 +1,29 @@ +--- +title: "Treap, revisited" +date: 2024-07-27T14:12:27+01:00 +draft: false # I don't care for draft mode, git has branches for that +description: "An even simpler BST" +tags: + - algorithms + - data structures + - python +categories: + - programming +series: + - Cool algorithms +favorite: false +disable_feed: false +--- + +My [last post]({{< relref "../2024-07-20-treap/index.md" >}}) about the _Treap_ +showed an implementation using tree rotations, as is commonly done with [AVL +Trees][avl] and [Red Black Trees][rb]. + +But the _Treap_ lends itself well to a simple and elegant implementation with no +tree rotations. This makes it especially easy to implement the removal of a key, +rather than the fiddly process of deletion using tree rotations. + +[avl]: https://en.wikipedia.org/wiki/AVL_tree +[rb]: https://en.wikipedia.org/wiki/Red%E2%80%93black_tree + + From 19b535ce49d70e1099a39e764d38ca0f79a0f5dd Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:31:47 +0100 Subject: [PATCH 2/6] posts: treap-revisited: add implementation --- content/posts/2024-07-27-treap-revisited/index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md index c9c01bc..f188568 100644 --- a/content/posts/2024-07-27-treap-revisited/index.md +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -27,3 +27,12 @@ rather than the fiddly process of deletion using tree rotations. [rb]: https://en.wikipedia.org/wiki/Red%E2%80%93black_tree + +## Implementation + +All operations on the tree will be implemented in terms of two fundamental +operations: `split` and `merge`. + +We'll be reusing the same structures as in the last post, so let's skip straight +to implementing those fundaments, and building on them for `insert` and +`delete`. From d33247b786631a0c63bf420d19170342a53737e6 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:32:09 +0100 Subject: [PATCH 3/6] posts: treap-revisited: add split --- .../posts/2024-07-27-treap-revisited/index.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md index f188568..097dfe5 100644 --- a/content/posts/2024-07-27-treap-revisited/index.md +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -36,3 +36,40 @@ operations: `split` and `merge`. We'll be reusing the same structures as in the last post, so let's skip straight to implementing those fundaments, and building on them for `insert` and `delete`. + +### Split + +Splitting a tree means taking a key, and getting the following output: + +* a `left` node, root of the tree of all keys lower than the input. +* an extracted `node` which corresponds to the input `key`. +* a `right` node, root of the tree of all keys higher than the input. + +```python +type OptionalNode[K, V] = Node[K, V] | None + +class SplitResult(NamedTuple): + left: OptionalNode + node: OptionalNode + right: OptionalNode + +def split(root: OptionalNode[K, V], key: K) -> SplitResult: + # Base case, empty tree + if root is None: + return SplitResult(None, None, None) + # If we found the key, simply extract left and right + if root.key == key: + left, right = root.left, root.right + root.left, root.right = None, None + return SplitResult(left, root, right) + # Otherwise, recurse on the corresponding side of the tree + if root.key < key: + left, node, right = split(root.right, key) + root.right = left + return SplitResult(root, node, right) + if key < root.key: + left, node, right = split(root.left, key) + root.left = right + return SplitResult(left, node, root) + raise RuntimeError("Unreachable") +``` From 0798812f86a17f397e12c76dc5ac131ade2d35a0 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:32:28 +0100 Subject: [PATCH 4/6] posts: treap: add merge --- .../posts/2024-07-27-treap-revisited/index.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md index 097dfe5..902ab57 100644 --- a/content/posts/2024-07-27-treap-revisited/index.md +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -73,3 +73,33 @@ def split(root: OptionalNode[K, V], key: K) -> SplitResult: return SplitResult(left, node, root) raise RuntimeError("Unreachable") ``` + +### Merge + +Merging a `left` and `right` tree means (cheaply) building a new tree containing +both of them. A pre-condition for merging is that the `left` tree is composed +entirely of nodes that are lower than any key in `right` (i.e: as in `left` and +`right` after a `split`). + +```python +def merge( + left: OptionalNode[K, V], + right: OptionalNode[K, V], +) -> OptionalNode[K, V]: + # Base cases, left or right being empty + if left is None: + return right + if right is None: + return left + # Left has higher priority, it must become the root node + if left.priority >= right.priority: + # We recursively reconstruct its right sub-tree + left.right = merge(left.right, right) + return left + # Right has higher priority, it must become the root node + if left.priority < right.priority: + # We recursively reconstruct its left sub-tree + right.left = merge(left, right.left) + return right + raise RuntimeError("Unreachable") +``` From 652fe81c418adbd6330769ef1459a012f092b8b5 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:32:47 +0100 Subject: [PATCH 5/6] posts: treap-revisited: add insertion --- .../posts/2024-07-27-treap-revisited/index.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md index 902ab57..068811f 100644 --- a/content/posts/2024-07-27-treap-revisited/index.md +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -103,3 +103,28 @@ def merge( return right raise RuntimeError("Unreachable") ``` + +### Insertion + +Inserting a node into the tree is done in two steps: + +1. `split` the tree to isolate the middle insertion point +2. `merge` it back up to form a full tree with the inserted key + +```python +def insert(self, key: K, value: V) -> bool: + # `left` and `right` come before/after the key + left, node, right = split(self._root, key) + was_updated: bool + # Create the node, or update its value, if the key was already in the tree + if node is None: + node = Node(key, value) + was_updated = False + else: + node.value = value + was_updated = True + # Rebuild the tree with a couple of merge operations + self._root = merge(left, merge(node, right)) + # Signal whether the key was already in the key + return was_updated +``` From 883f0e7e9b08d11303f2a8a678a5929bd4c38be5 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 27 Jul 2024 18:33:05 +0100 Subject: [PATCH 6/6] posts: treap: add removal --- .../posts/2024-07-27-treap-revisited/index.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/content/posts/2024-07-27-treap-revisited/index.md b/content/posts/2024-07-27-treap-revisited/index.md index 068811f..99ae68f 100644 --- a/content/posts/2024-07-27-treap-revisited/index.md +++ b/content/posts/2024-07-27-treap-revisited/index.md @@ -128,3 +128,19 @@ def insert(self, key: K, value: V) -> bool: # Signal whether the key was already in the key return was_updated ``` + +### Removal + +Removing a key from the tree is similar to inserting a new key, and forgetting +to insert it back: simply `split` the tree and `merge` it back without the +extracted middle node. + +```python +def remove(self, key: K) -> bool: + # `node` contains the key, or `None` if the key wasn't in the tree + left, node, right = split(self._root, key) + # Put the tree back together, without the extract node + self._root = merge(left, right) + # Signal whether `key` was mapped in the tree + return node is not None +```