Last active
November 13, 2025 00:39
-
-
Save magicoal-nerb/6c91120e671d557de59e6d87e6617868 to your computer and use it in GitHub Desktop.
avl tree
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --!strict | |
| -- AVL Tree implementations based on the mit ocw lecture videos | |
| -- poopbarrel/magicoal_nerb :^) | |
| local Queue = require("./Queue") | |
| local Avl = {} | |
| Avl.__index = Avl | |
| export type AvlNode<K, V> = { | |
| index: K, | |
| data: V, | |
| height: number, | |
| parent: AvlNode<K, V>?, | |
| right: AvlNode<K, V>?, | |
| left: AvlNode<K, V>?, | |
| } | |
| export type Avl<K, V> = typeof(setmetatable({} :: { | |
| compare: (a: K, b: K) -> boolean, | |
| root: AvlNode<K, V>?, | |
| }, Avl)) | |
| local function height<K, V>(node: AvlNode<K, V>?) | |
| -- Helper that cleans up getting the height | |
| -- of a node | |
| if node then | |
| return node.height | |
| else | |
| return -1 | |
| end | |
| end | |
| function Avl.new<K, V>(compare: (a: K, b: K) -> boolean): Avl<K, V> | |
| return setmetatable({ | |
| compare = compare, | |
| root = nil, | |
| }, Avl) | |
| end | |
| function Avl.get<K, V>(self: Avl<K, V>, key: K): V? | |
| -- Gets a value wow! | |
| local node = self:_query(key) | |
| return node and node.data or nil | |
| end | |
| function Avl.range<K, V>( | |
| self: Avl<K, V>, | |
| callback: (K, V) -> (), | |
| min: K, | |
| max: K | |
| ) | |
| type Node = AvlNode<K, V> | |
| local queue = Queue.new() :: Queue.Queue<Node> | |
| local compare = self.compare | |
| queue:enqueue(self.root :: Node) | |
| -- Do a BFS through the tree and only call the callback | |
| -- if the index is within the range. Moreover, we check | |
| -- within the interval (min, max). min and max are not included. | |
| while not queue:empty() do | |
| local data = queue:dequeue() | |
| local index = data.index | |
| local hasMin = compare(min, index) | |
| local hasMax = compare(index, max) | |
| if data.left and hasMin then | |
| queue:enqueue(data.left) | |
| end | |
| if data.right and hasMax then | |
| queue:enqueue(data.right) | |
| end | |
| if hasMin and hasMax then | |
| callback(index, data.data) | |
| end | |
| end | |
| end | |
| function Avl.delete<K, V>(self: Avl<K, V>, value: K) | |
| local x = self:_query(value) | |
| if not x then | |
| return | |
| end | |
| -- First step, do an ordinary BST deletion | |
| -- It's a branch, replace it with the predecessor | |
| while x.left or x.right do | |
| if x.left then | |
| -- If we have a left child, then we swap with the predecessor | |
| -- somewhere inside our tree | |
| local predecessor = self:predecessor(x) | |
| assert(predecessor) | |
| x.data, predecessor.data = predecessor.data, x.data | |
| x.index, predecessor.index = predecessor.index, x.index | |
| x = predecessor | |
| else | |
| -- If we have a right child, then we swap with the successor | |
| -- somewhere inside our tree | |
| local successor = self:successor(x) | |
| assert(successor) | |
| x.data, successor.data = successor.data, x.data | |
| x.index, successor.index = successor.index, x.index | |
| x = successor | |
| end | |
| end | |
| local parent = x.parent | |
| if parent then | |
| -- Remove and rebalance the tree | |
| if parent.left == x then | |
| parent.left = nil | |
| else | |
| parent.right = nil | |
| end | |
| self:_rebalance(parent) | |
| end | |
| end | |
| function Avl.insert<K, V>(self: Avl<K, V>, index: K, value: V): AvlNode<K, V>? | |
| -- First step, do an ordinary BST insert | |
| local node = self.root | |
| if not node then | |
| -- If no root node exists, then | |
| -- we make our value the root node | |
| self.root = { | |
| data = value, | |
| index = index, | |
| height = 0, | |
| } | |
| return self.root | |
| end | |
| local compare = self.compare | |
| while node do | |
| if node.index == index then | |
| -- Well, we can't do anything | |
| return node | |
| elseif compare(node.index, index) then | |
| -- Go right | |
| if not node.right then | |
| local result = { | |
| height = 0, | |
| index = index, | |
| data = value, | |
| parent = node, | |
| } | |
| -- Second step, rebalance the tree | |
| node.right = result | |
| self:_rebalance(result) | |
| return result | |
| end | |
| node = node.right | |
| else | |
| -- Go left | |
| if not node.left then | |
| local result = { | |
| height = 0, | |
| index = index, | |
| data = value, | |
| parent = node, | |
| } | |
| -- Second step, rebalance the tree | |
| node.left = result | |
| self:_rebalance(result) | |
| return result | |
| end | |
| node = node.left | |
| end | |
| end | |
| return node | |
| end | |
| function Avl.inorder<K, V>(self: Avl<K, V>, callback: (key: K, value: V) -> ()) | |
| -- Inorder traversal goes from left -> root -> right | |
| local stack = {} | |
| local count = 1 | |
| local current: AvlNode<K, V>? = self.root | |
| while current or count > 0 do | |
| while current do | |
| -- Keep on going left | |
| table.insert(stack, current) | |
| count += 1 | |
| current = current.left | |
| end | |
| current = table.remove(stack) | |
| count -= 1 | |
| -- We can proceed to the right if its | |
| -- possible | |
| if current then | |
| callback(current.index, current.data) | |
| current = current.right | |
| end | |
| end | |
| end | |
| function Avl.getHeight<K, V>(self: Avl<K, V>, x: AvlNode<K, V>?): number | |
| if not x then | |
| -- Base case so we get 1 - 1 = 0 for leaf nodes | |
| return -1 | |
| end | |
| return 1 + math.max(self:getHeight(x.left), self:getHeight(x.right)) | |
| end | |
| function Avl.findMin<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V> | |
| -- Find left most node | |
| while x.left do | |
| x = x.left | |
| end | |
| return x | |
| end | |
| function Avl.findMax<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V> | |
| -- Find right most node | |
| while x.right do | |
| x = x.right | |
| end | |
| return x | |
| end | |
| function Avl.getLastInorder<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V> | |
| -- Gets the last inorder element of the given node | |
| while x and x.right do | |
| x = x.right | |
| end | |
| return x | |
| end | |
| function Avl.getFirstInorder<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V> | |
| -- Gets the first inorder element of the given node | |
| while x and x.left do | |
| x = x.left | |
| end | |
| return x | |
| end | |
| function Avl.predecessor<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V>? | |
| if x.left then | |
| -- If a left node exists, then our predecessor is down the tree | |
| return self:getLastInorder(x.left) | |
| end | |
| -- If the current node is to the right of its parent, then that parent | |
| -- must be the predecessor | |
| while x.parent and x.parent.left == x do | |
| x = x.parent | |
| end | |
| return x.parent | |
| end | |
| function Avl.successor<K, V>(self: Avl<K, V>, x: AvlNode<K, V>): AvlNode<K, V>? | |
| if x.right then | |
| -- If a right node exists, then our successor is down the tree | |
| return self:getFirstInorder(x.right) | |
| end | |
| -- If the current node is to the left of its parent, then that parent | |
| -- must be the succesor | |
| while x.parent and x.parent.right == x do | |
| x = x.parent | |
| end | |
| return x.parent | |
| end | |
| function Avl._rotateLeft<K, V>(self: Avl<K, V>, x: AvlNode<K, V>) | |
| -- x y | |
| -- A y => x C | |
| -- B C A B | |
| local y = assert(x.right) | |
| local A = x.left | |
| local B = y.left | |
| local C = y.right | |
| -- Swap | |
| x, y = y, x | |
| x.index, y.index = y.index, x.index | |
| x.data, y.data = y.data, x.data | |
| y.left = x | |
| y.right = C | |
| x.left = A | |
| x.right = B | |
| if A then | |
| A.parent = x | |
| end | |
| if C then | |
| C.parent = y | |
| end | |
| end | |
| function Avl._rotateRight<K, V>(self: Avl<K, V>, x: AvlNode<K, V>) | |
| -- x y | |
| -- y C => A x | |
| -- A B B C | |
| local y = assert(x.left) | |
| local B = y.right | |
| local A = y.left | |
| -- Swap | |
| x, y = y, x | |
| x.index, y.index = y.index, x.index | |
| x.data, y.data = y.data, x.data | |
| y.right = x | |
| y.left = A | |
| x.left = B | |
| if A then | |
| A.parent = y | |
| end | |
| if B then | |
| B.parent = x | |
| end | |
| end | |
| function Avl._rebalance<K, V>(self: Avl<K, V>, a: AvlNode<K, V>?) | |
| -- skew(a) = height(a.right) - height(a.left) | |
| -- height(a) = max(height(a.left), height(a.right)) + 1 | |
| while a do | |
| local skew = height(a.right) - height(a.left) | |
| if skew == 2 then | |
| -- Double right heavy | |
| local right = assert(a.right) | |
| local rightSkew = height(right.right) - height(right.left) | |
| if rightSkew < 0 then | |
| self:_rotateRight(right) | |
| end | |
| self:_rotateLeft(a) | |
| elseif skew == -2 then | |
| -- Double left heavy | |
| local left = assert(a.left) | |
| local leftSkew = height(left.right) - height(left.left) | |
| if leftSkew > 0 then | |
| self:_rotateLeft(left) | |
| end | |
| self:_rotateRight(a) | |
| end | |
| -- Finalize height | |
| a.height = 1 + math.max(height(a.right), height(a.left)) | |
| a = a.parent | |
| end | |
| end | |
| function Avl._query<K, V>(self: Avl<K, V>, index: K): AvlNode<K, V>? | |
| local node = self.root | |
| if not node then | |
| -- The tree is empty | |
| return nil | |
| end | |
| -- Does a standard binary search over our data | |
| local compare = self.compare | |
| while node do | |
| if node.index == index then | |
| break | |
| elseif compare(node.index, index) then | |
| -- Go right | |
| node = node.right | |
| else | |
| -- Go left | |
| node = node.left | |
| end | |
| end | |
| if not node or node.index ~= index then | |
| -- Not the same | |
| return nil | |
| end | |
| return node | |
| end | |
| return Avl |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --!strict | |
| -- yet another queue implementation :P | |
| -- magicoal_nerb | |
| local Queue = {} | |
| Queue.__index = Queue | |
| export type Queue<T> = typeof(setmetatable({} :: { | |
| data: { T }, | |
| size: number, | |
| }, Queue)) | |
| function Queue.new<T>(): Queue<T> | |
| return setmetatable({ | |
| data = {}, | |
| size = 0, | |
| }, Queue) | |
| end | |
| function Queue.empty<T>(self: Queue<T>): boolean | |
| return self.size == 0 | |
| end | |
| function Queue.iterate<T>(self: Queue<T>, callback: (T, ...any) -> (), ...: any) | |
| for i, object in self.data do | |
| callback(object, ...) | |
| end | |
| end | |
| function Queue.enqueue<T>(self: Queue<T>, data: T) | |
| self.data[self.size + 1] = data | |
| self.size += 1 | |
| end | |
| function Queue.dequeue<T>(self: Queue<T>): T | |
| self.size -= 1 | |
| return self.data[self.size + 1] | |
| end | |
| function Queue.clear<T>(self: Queue<T>) | |
| self.size = 0 | |
| table.clear(self.data) | |
| end | |
| return Queue |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment