mirror of
https://github.com/autistic-symposium/master-algorithms-py.git
synced 2025-05-02 06:46:18 -04:00
👾
This commit is contained in:
parent
1d44d182e2
commit
a85ed914d3
320 changed files with 0 additions and 0 deletions
105
trees/BinaryTree.py
Normal file
105
trees/BinaryTree.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.right = None
|
||||
self.left = None
|
||||
|
||||
def add(self, value):
|
||||
new_node = Node(value)
|
||||
if not self.value:
|
||||
self.value = new_node
|
||||
elif not self.left:
|
||||
self.left = new_node
|
||||
elif not self. right:
|
||||
self.right = new_node
|
||||
else:
|
||||
self.left = self.left.add(value)
|
||||
return self
|
||||
|
||||
def search(self, item):
|
||||
if self.value == item:
|
||||
return True
|
||||
found = False
|
||||
if (self.left and self.left.search(item)) or \
|
||||
(self.right and self.right.search(item)):
|
||||
found = True
|
||||
return found
|
||||
|
||||
def preorder(self):
|
||||
yield self.value
|
||||
if self.left:
|
||||
for node in self.left.preorder():
|
||||
yield node
|
||||
if self.right:
|
||||
for node in self.right.preorder():
|
||||
yield node
|
||||
|
||||
def postorder(self):
|
||||
yield self.value
|
||||
if self.left:
|
||||
for node in self.left.postorder():
|
||||
yield node
|
||||
if self.right:
|
||||
for node in self.right.postorder():
|
||||
yield node
|
||||
|
||||
def inorder(self):
|
||||
yield self.value
|
||||
if self.left:
|
||||
for node in self.left.inorder():
|
||||
yield node
|
||||
if self.right:
|
||||
for node in self.right.inorder():
|
||||
yield node
|
||||
|
||||
|
||||
class BinaryTree(object):
|
||||
def __init__(self):
|
||||
self.root = None
|
||||
|
||||
def add(self, value):
|
||||
if not self.root:
|
||||
self.root = Node(value)
|
||||
else:
|
||||
self.root.add(value)
|
||||
|
||||
def search(self, item):
|
||||
if self.root:
|
||||
return self.root.search(item)
|
||||
|
||||
def preorder(self):
|
||||
if self.root:
|
||||
return list(self.root.preorder())
|
||||
|
||||
def inorder(self):
|
||||
if self.root:
|
||||
return list(self.root.inorder())
|
||||
|
||||
def postorder(self):
|
||||
if self.root:
|
||||
return list(self.root.postorder())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
print("\n\n🌳🌳🌳 Testing BinaryTree 🌳🌳🌳")
|
||||
bt = BinaryTree()
|
||||
array1 = [4, 1, 4, 6, 7, 9, 10, 5, 11, 5]
|
||||
print(f'\n🟡 Adding {array1} to the tree...')
|
||||
for i in array1:
|
||||
bt.add(i)
|
||||
print(f"🟢 Print the tree preorder: {bt.preorder()}")
|
||||
print(f"🟢 Print the tree inorder: {bt.inorder()}")
|
||||
print(f"🟢 Print the tree postorder: {bt.postorder()}")
|
||||
|
||||
print(f'\n🟢 Search for node 5: {bt.search(5)}')
|
||||
print(f'❌ Search for node 15: {bt.search(15)}')
|
||||
|
||||
|
276
trees/README.md
Normal file
276
trees/README.md
Normal file
|
@ -0,0 +1,276 @@
|
|||
## trees, heaps, tries, graphs
|
||||
|
||||
<br>
|
||||
|
||||
### trees
|
||||
|
||||
<br>
|
||||
|
||||
* a node is called **leaf** if it has no children.
|
||||
* **binary trees**: each node has up to 2 children.
|
||||
* **binary search tree**: all nodes on the left are smaller than the root, which is smaller than all nodes on the right.
|
||||
* if the bst is **balanced**, it guarantees O(log(n)) for insert and search.
|
||||
* common types of balanced trees: **red-black** and **avl**.
|
||||
* a **complete tree** is a tree on which every level is fully filled (except perhaps for the last).
|
||||
* a **full binary tree** has each node with either zero or two children (and no node has only one child).
|
||||
* a **perfect tree** is both full and complete (it must have exactly 2**k - 1 nodes, where k is the number of levels).
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### breath-first search
|
||||
|
||||
<br>
|
||||
|
||||
- iterative solutions use a queu (level order problem)
|
||||
- one common application of breadth-first search (BFS) is to find the shortest path from the root node to the target node.
|
||||
- in the first round, we process the root node, in the second round, we process the nodes next to the root node, in the third round, we process the nodes which are two steps from the root node, etc. newly-added nodes will not be traversed immediately but will be processed in the next round.
|
||||
- if node X is added to the kth round queue, the shortest path between the root node and X is exactly k.
|
||||
- the processing order of the nodes in the exact same order as how they were added to the queue, which is FIFO.
|
||||
|
||||
<br>
|
||||
|
||||
```python
|
||||
def bfs(root):
|
||||
|
||||
queue = queue()
|
||||
root.marked = True
|
||||
queue.enqueue(root)
|
||||
|
||||
while !queue.is_empty():
|
||||
node = queue.deque()
|
||||
visit(node)
|
||||
for n in node_adj:
|
||||
n.marked = True
|
||||
queue.enque(n)
|
||||
```
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### depth-first search
|
||||
|
||||
<br>
|
||||
|
||||
- similar to BFS, deep-first search (DFS) can also be used to find the path from the root node to the target node.
|
||||
- prefered if you want to visit every node.
|
||||
- overall, we only trace back and try another path after we reach the deepest node. as a result, the first path you find in DFS is not always the shortest path.
|
||||
- we first push the root node to the stack, then we try the first neighbor and push its node to the stack, etc.
|
||||
- when we reach the deepest node, we need to trace back. when we track back, we pop the deepest node from the stack, which is actually the last node pushed to the stack.
|
||||
- the processing order of the nodes is exactly the opposite order of how they are added to the stack.
|
||||
- recursion solutions are easier to implement; however, if the recursion depth is too high, stack overflow might occur. in that case, you might use BFS instead or implement DFS using an explicit stack (i.e., use a while loop and a stack structure to simulate the system call stack).
|
||||
- if the depth of the tree is too large, stack overflow might happen, therefore iterative solutions might be better.
|
||||
- work with stacks.
|
||||
|
||||
|
||||
|
||||
#### in-order
|
||||
|
||||
- left -> node -> right
|
||||
|
||||
```python
|
||||
def inorder(self, root):
|
||||
if root is None:
|
||||
return []
|
||||
return inorder(root.left) + [root.val] + inorder(root.right)
|
||||
````
|
||||
|
||||
<br>
|
||||
|
||||
#### pre-order
|
||||
|
||||
- node -> left -> right
|
||||
|
||||
```python
|
||||
def preorder(self, root):
|
||||
if root is None:
|
||||
return []
|
||||
return [root.val] + preorder(root.left) + preorder(root.right)
|
||||
````
|
||||
|
||||
- top-down (parameters are passed down to children), so deserialize with a queue.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
#### post-order
|
||||
|
||||
- left -> right -> node
|
||||
|
||||
```python
|
||||
def postorder(self, root):
|
||||
if root is None:
|
||||
return []
|
||||
return postorder(root.left) + postorder(root.right) + [root.val]
|
||||
````
|
||||
|
||||
- bottom-up solution (if you know the answer of the children, can you concatenate the answer of the nodes?):
|
||||
- deletion process is always post-order: when you delete a node, you will delete its left child and its right child before you delete the node itself.
|
||||
- also, post-order is used in mathematical expressions as it's easier to write a program to parse a post-order expression. using a stack, each time when you meet a operator, you can just pop 2 elements from the stack, calculate the result and push the result back into the stack.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### heaps
|
||||
|
||||
<br>
|
||||
|
||||
* a heap is a binary tree with two properties: it must have all of its nodes in a specific order and its shape must be complete (all the levels of the tree must be completely filled except maybe for the last one and the last level must have the left-most nodes filled, always).
|
||||
* a heap's root node must have all its children either greater than or equal to its children.
|
||||
* since you always remove the root, insertion and deletion takes O(log(n)).
|
||||
* duplicate values are allowed.
|
||||
* a **min heap** is a complete binary tree where each node is smaller than its children (the root is the min element). two key operations are:
|
||||
- insert: always by the element at the bottom, at the most rightmost post
|
||||
- extract_min: the minimum element is always on top, and removing it is the trickiest part:
|
||||
1. remove and swap it with the last element (the bottom most rightmost)
|
||||
2. the bubble down, swapping it with one of its children until the min-heap is properly restored (there is no order between right and left and it takes O(log n) time.
|
||||
* a heap could also be represented with a queue (array). in this case, the index of the parent node = [(n-1)/2].
|
||||
* a priority queue is a queue of data structures with some additional properties:
|
||||
1. every item has a priority (usually an integer)
|
||||
2. an item with a high priority is dequeued before an item with low priority
|
||||
3. two items with an equal priority are dequeued based on their order in the queue
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
----
|
||||
|
||||
### n-ary tree
|
||||
|
||||
<br>
|
||||
|
||||
* if a tree is a rooted tree in which each node has no more than N children, it's called N-ary tree.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
----
|
||||
|
||||
### tries
|
||||
|
||||
<br>
|
||||
|
||||
* tries, also called prefix tree, are a variant of n-ary tree in which characters are stored in each node.
|
||||
* each trie node represents a string (a prefix) and each path down the tree represents a word. note that not all the strings represented by trie nodes are meaningful.
|
||||
* the root is associated with the empty string.
|
||||
* the * nodes (null nodes) are often used to indicate complete words (usually represented by a special type of child) or a boolean flag that terminates the parent node.
|
||||
* a node can have anywhere from 1 through alphabet_size + 1 child.
|
||||
* can be used to store the entire english language for quick prefix lookup (O(k), where k is the length of the string). they are also widely used on autocompletes, spell checkers, etc.
|
||||
* tries structures can be represented by arrays and maps or trees.
|
||||
|
||||
<br>
|
||||
|
||||
#### insertion
|
||||
|
||||
<br>
|
||||
|
||||
* similar to a bst, when we insert a value to a trie, we need to decide which path to go depending on the target value we insert.
|
||||
* the root node needs to be initialized before you insert strings.
|
||||
|
||||
<br>
|
||||
|
||||
#### search
|
||||
|
||||
<br>
|
||||
|
||||
* all the descendants of a node have a common prefix of the string associated with that node, so it should be easy to search if there are any words in the trie that starts with the given prefix.
|
||||
* we go down the tree depending on the given prefix, once we cannot find the child node, the search fails.
|
||||
* we can also search for a specific word rather than a prefix, treating this word as a prefix and searching in the same way as above.
|
||||
* if the search succeeds, we need to check if the target word is only a prefix of words in the trie or if it's exactly a word (for example, by adding a boolean flag).
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### `Tree.py`
|
||||
|
||||
<br>
|
||||
|
||||
```python
|
||||
> python3 Trees.py
|
||||
|
||||
|
||||
🌴🌴🌴 Testing SimpleTree 🌴🌴🌴
|
||||
a
|
||||
b
|
||||
d
|
||||
e
|
||||
c
|
||||
h
|
||||
g
|
||||
|
||||
|
||||
|
||||
🌳🌳🌳 Testing BinaryTree 🌳🌳🌳
|
||||
|
||||
🟡 Adding [4, 1, 4, 6, 7, 9, 10, 5, 11, 5] to the tree...
|
||||
🟢 Printing the tree in preorder...
|
||||
4
|
||||
1
|
||||
6
|
||||
9
|
||||
5
|
||||
5
|
||||
11
|
||||
10
|
||||
7
|
||||
4
|
||||
|
||||
🟢 Searching for node 5: True
|
||||
❌ Searching for node 15: False
|
||||
❌ Is root a leaf? False
|
||||
🟢 Is root full? True
|
||||
❌ Is the tree balanced? False
|
||||
❌ Is the tree a binary search tree? False
|
||||
|
||||
|
||||
🎄🎄🎄 Testing BinarySearchTree 🎄🎄🎄
|
||||
|
||||
🟡 Adding [4, 1, 4, 6, 7, 9, 10, 5, 11, 5] to the tree...
|
||||
❌ Item 4 not added as BSTs do not support repetition.
|
||||
❌ Item 5 not added as BSTs do not support repetition.
|
||||
🟢 Printing the tree in preorder:
|
||||
4
|
||||
1
|
||||
6
|
||||
5
|
||||
7
|
||||
9
|
||||
10
|
||||
11
|
||||
|
||||
🟢 Searching for node 5: True
|
||||
❌ Searching for node 15: False
|
||||
❌ Is root a leaf? False
|
||||
🟢 Is root full? True
|
||||
🟢 Largest node? 11
|
||||
🟢 Smallest node? 1
|
||||
❌ Is the tree balanced? False
|
||||
🟢 Is the tree a binary search tree? True
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### `BinaryTree.py`
|
||||
|
||||
<br>
|
||||
|
||||
* a clean implementation adapted from the class above.
|
||||
|
||||
```python
|
||||
> python3 BinaryTree.py
|
||||
|
||||
🌳🌳🌳 Testing BinaryTree 🌳🌳🌳
|
||||
|
||||
🟡 Adding [4, 1, 4, 6, 7, 9, 10, 5, 11, 5] to the tree...
|
||||
🟢 Print the tree preorder: [4, 1, 6, 9, 5, 5, 11, 10, 7, 4]
|
||||
🟢 Print the tree inorder: [4, 1, 6, 9, 5, 5, 11, 10, 7, 4]
|
||||
🟢 Print the tree postorder: [4, 1, 6, 9, 5, 5, 11, 10, 7, 4]
|
||||
|
||||
🟢 Search for node 5: True
|
||||
❌ Search for node 15: False
|
||||
```
|
296
trees/SimpleTree.py
Normal file
296
trees/SimpleTree.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
|
||||
class SimpleTree:
|
||||
"""Implementation of a simple tree"""
|
||||
|
||||
def __init__(self, value=None, node=None):
|
||||
self.value = value
|
||||
self.node = node or []
|
||||
|
||||
def __repr__(self, level=0):
|
||||
repr = '\t' * level + self.value + '\n'
|
||||
for n in self.node:
|
||||
repr += n.__repr__(level + 1)
|
||||
return repr
|
||||
|
||||
|
||||
class Node():
|
||||
"""Implementation of a Node for a binary tree"""
|
||||
|
||||
def __init__(self, value=None):
|
||||
self.value = value
|
||||
self.left = None
|
||||
self.right = None
|
||||
|
||||
############################
|
||||
# Private Methods
|
||||
############################
|
||||
def __repr__(self):
|
||||
"""Prints the node"""
|
||||
return f'{self.value}'
|
||||
|
||||
def _repr_preorder(self):
|
||||
"""Prints the tree in preorder traversal (root, left, right)"""
|
||||
|
||||
print(self.value)
|
||||
if self.left:
|
||||
self.left._repr_preorder()
|
||||
if self.right:
|
||||
self.right._repr_preorder()
|
||||
|
||||
def _add_node_binary_tree(self, value):
|
||||
"""Adds a node to a simple binary tree"""
|
||||
|
||||
if self.value is None:
|
||||
self.value = value
|
||||
|
||||
else:
|
||||
if not self.left:
|
||||
self.left = Node(value)
|
||||
elif not self.right:
|
||||
self.right = Node(value)
|
||||
else:
|
||||
self.left = self.left._add_node_binary_tree(value)
|
||||
|
||||
return self
|
||||
|
||||
def _add_node_binary_search_tree(self, value) -> bool:
|
||||
"""Adds a node to a binary search tree"""
|
||||
|
||||
if not self.value:
|
||||
self.value = value
|
||||
|
||||
else:
|
||||
new_node = Node(value)
|
||||
if value < self.value:
|
||||
self.left = self.left and self.left._add_node_binary_search_tree(value) or new_node
|
||||
elif value > self.value:
|
||||
self.right = self.right and self.right._add_node_binary_search_tree(value) or new_node
|
||||
else:
|
||||
print(f'❌ Item {value} not added as BSTs do not support repetition.')
|
||||
|
||||
return self
|
||||
|
||||
def _search_node_preorder(self, query) -> bool:
|
||||
"""Searches through preorder traversal (node, left, right)"""
|
||||
|
||||
if self.value == query:
|
||||
return True
|
||||
|
||||
found = False
|
||||
if self.left:
|
||||
# recursively search left
|
||||
found = self.left._search_node_preorder(query)
|
||||
|
||||
if self.right:
|
||||
# recursively search right
|
||||
found = found or self.right._search_node_preorder(query)
|
||||
|
||||
return found
|
||||
|
||||
def _search_node_binary_search_tree(self, query) -> bool:
|
||||
"""Searches the tree for a value, considering the BST property"""
|
||||
|
||||
this_node_value = self.value
|
||||
|
||||
if this_node_value is not None:
|
||||
if this_node_value == query:
|
||||
return True
|
||||
|
||||
elif this_node_value > query:
|
||||
if self.left is not None:
|
||||
return self.left._search_node_binary_search_tree(query)
|
||||
|
||||
elif this_node_value < query:
|
||||
if self.right is not None:
|
||||
return self.right._search_node_binary_search_tree(query)
|
||||
|
||||
return False
|
||||
|
||||
def _is_leaf(self) -> bool:
|
||||
"""If node has no children, it is a leaf"""
|
||||
|
||||
return bool(not self.right and not self.left)
|
||||
|
||||
def _is_full(self) -> bool:
|
||||
"""If node has two children, it is full"""
|
||||
return bool(self.right and self.left)
|
||||
|
||||
|
||||
class BinaryTreeInterface():
|
||||
|
||||
def __init__(self):
|
||||
self.root = Node()
|
||||
|
||||
############################
|
||||
# Interface Methods
|
||||
############################
|
||||
def add_node(self, value):
|
||||
"""Adds a new node to the tree"""
|
||||
pass
|
||||
|
||||
def search_node(self, value):
|
||||
"""Searches the tree for a value"""
|
||||
pass
|
||||
|
||||
############################
|
||||
# Public Methods
|
||||
############################
|
||||
def is_leaf(self) -> bool:
|
||||
"""Returns True if the node is a leaf"""
|
||||
|
||||
if self.root is not None:
|
||||
return self.root._is_leaf()
|
||||
|
||||
def is_full(self) -> bool:
|
||||
"""Returns True if the node is full"""
|
||||
|
||||
if self.root is not None:
|
||||
return self.root._is_full()
|
||||
|
||||
def print_preorder(self):
|
||||
"""Prints the BST in preorder"""
|
||||
|
||||
if self.root is not None:
|
||||
self.root._repr_preorder()
|
||||
|
||||
############################
|
||||
# Class Methods
|
||||
############################
|
||||
@classmethod
|
||||
def is_balanced(cls, node, left=0, right=0) -> bool:
|
||||
"""Returns True if the tree is balanced"""
|
||||
|
||||
if node is None:
|
||||
return (left - right) < 2
|
||||
|
||||
else:
|
||||
return cls.is_balanced(node.left, left + 1, right) and \
|
||||
cls.is_balanced(node.right, left, right + 1)
|
||||
|
||||
@classmethod
|
||||
def is_binary_search_tree(cls, node, min_node=None, max_node=None) -> bool:
|
||||
"""Returns True if the tree is a BST"""
|
||||
|
||||
min_node = min_node or float('-inf')
|
||||
max_node = max_node or float('inf')
|
||||
|
||||
if not node:
|
||||
return True
|
||||
|
||||
if node.value < min_node or node.value > max_node:
|
||||
return False
|
||||
|
||||
return cls.is_binary_search_tree(node.left, min_node, node.value) and \
|
||||
cls.is_binary_search_tree(node.right, node.value, max_node)
|
||||
|
||||
|
||||
|
||||
class BinaryTree(BinaryTreeInterface):
|
||||
"""Implementation of a binary tree"""
|
||||
|
||||
def add_node(self, value):
|
||||
"""Adds a new node to the tree"""
|
||||
|
||||
if self.root is None:
|
||||
self.root = Node(value)
|
||||
else:
|
||||
self.root._add_node_binary_tree(value)
|
||||
|
||||
def search_node(self, value):
|
||||
"""Searches the tree for a value"""
|
||||
|
||||
if self.root:
|
||||
return self.root._search_node_preorder(value)
|
||||
|
||||
|
||||
class BinarySearchTree(BinaryTreeInterface):
|
||||
|
||||
def add_node(self, value):
|
||||
"""Adds a new node to the tree"""
|
||||
|
||||
if self.root is None:
|
||||
self.root = Node(value)
|
||||
else:
|
||||
self.root._add_node_binary_search_tree(value)
|
||||
|
||||
def search_node(self, value):
|
||||
"""Searches the tree for a value"""
|
||||
|
||||
if self.root.value is not None:
|
||||
return self.root._search_node_binary_search_tree(value)
|
||||
|
||||
@classmethod
|
||||
def largest_node(cls, node):
|
||||
"""Returns the largest node in the tree"""
|
||||
|
||||
if node.right:
|
||||
return cls.largest_node(node.right)
|
||||
else:
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def smallest_node(cls, node):
|
||||
"""Returns the smallest node in the tree"""
|
||||
|
||||
if node.left:
|
||||
return cls.smallest_node(node.left)
|
||||
else:
|
||||
return node
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
############################
|
||||
# Test SimpleTree
|
||||
############################
|
||||
print("\n\n🌴🌴🌴 Testing SimpleTree 🌴🌴🌴")
|
||||
t = SimpleTree('a', [SimpleTree('b', [SimpleTree('d'), SimpleTree('e')]), \
|
||||
SimpleTree('c', [SimpleTree('h'), SimpleTree('g')])])
|
||||
print(t)
|
||||
|
||||
|
||||
############################
|
||||
# Test binary tree
|
||||
############################
|
||||
print("\n\n🌳🌳🌳 Testing BinaryTree 🌳🌳🌳")
|
||||
bt = BinaryTree()
|
||||
array1 = [4, 1, 4, 6, 7, 9, 10, 5, 11, 5]
|
||||
print(f'\n🟡 Adding {array1} to the tree...')
|
||||
for i in array1:
|
||||
bt.add_node(i)
|
||||
print("🟢 Printing the tree in preorder...")
|
||||
bt.print_preorder()
|
||||
print(f'\n🟢 Searching for node 5: {bt.search_node(5)}')
|
||||
print(f'❌ Searching for node 15: {bt.search_node(15)}')
|
||||
print(f'❌ Is root a leaf? {bt.is_leaf()}')
|
||||
print(f'🟢 Is root full? {bt.is_full()}')
|
||||
print(f'❌ Is the tree balanced? {BinaryTree.is_balanced(bt.root)}')
|
||||
print(f'❌ Is the tree a binary search tree? {BinaryTree.is_binary_search_tree(bt.root)}')
|
||||
|
||||
|
||||
|
||||
##############################
|
||||
# Test binary search tree
|
||||
##############################
|
||||
print("\n\n🎄🎄🎄 Testing BinarySearchTree 🎄🎄🎄")
|
||||
bst = BinarySearchTree()
|
||||
array1 = [4, 1, 4, 6, 7, 9, 10, 5, 11, 5]
|
||||
print(f'\n🟡 Adding {array1} to the tree...')
|
||||
for i in array1:
|
||||
bst.add_node(i)
|
||||
print("🟢 Printing the tree in preorder:")
|
||||
bst.print_preorder()
|
||||
print(f'\n🟢 Searching for node 5: {bst.search_node(5)}')
|
||||
print(f'❌ Searching for node 15: {bst.search_node(15)}')
|
||||
print(f'❌ Is root a leaf? {bst.is_leaf()}')
|
||||
print(f'🟢 Is root full? {bst.is_full()}')
|
||||
print(f'🟢 Largest node? {bst.largest_node(bst.root)}')
|
||||
print(f'🟢 Smallest node? {bst.smallest_node(bst.root)}')
|
||||
print(f'❌ Is the tree balanced? {bst.is_balanced(bst.root)}')
|
||||
print(f'🟢 Is the tree a binary search tree? {bst.is_binary_search_tree(bst.root)}')
|
29
trees/construct_tree_inorder_postorder.py
Normal file
29
trees/construct_tree_inorder_postorder.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
# Given two integer arrays inorder and postorder where inorder is the inorder
|
||||
# traversal of a binary tree and postorder is the postorder traversal of the
|
||||
# same tree, construct and return the binary tree.
|
||||
|
||||
|
||||
def fill_tree(i_left, i_right, inorder_map):
|
||||
|
||||
if i_left > i_right:
|
||||
return None
|
||||
|
||||
val = postorder.pop()
|
||||
root = TreeNode(val)
|
||||
|
||||
index_here = inorder_map[val]
|
||||
|
||||
root.right = fill_tree(index_here + 1, i_right, inorder_map)
|
||||
root.left = fill_tree(i_left, index_here - 1, inorder_map)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def build_tree(inorder: list[int], postorder: list[int]) -> Optional[TreeNode]:
|
||||
|
||||
inorder_map = {val: index for index, val in enumerate(inorder)}
|
||||
return fill_tree(0, len(inorder) - 1, inorder_map)
|
||||
|
25
trees/construct_tree_inorder_preorder.py
Normal file
25
trees/construct_tree_inorder_preorder.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
def build_tree(preorder: list[int], inorder: list[int]) -> Optional[TreeNode]:
|
||||
|
||||
def helper(i_left, i_right, index_map):
|
||||
|
||||
if i_left > i_right:
|
||||
return None
|
||||
|
||||
root = TreeNode(preorder.pop(0))
|
||||
index_here = index_map[root.val]
|
||||
|
||||
# this order change from postorder
|
||||
root.left = helper(i_left, index_here - 1, index_map)
|
||||
root.right = helper(index_here + 1, i_right, index_map)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
index_map = {value: index for index, value in enumerate(inorder)}
|
||||
|
||||
return helper(0, len(inorder) - 1, index_map)
|
||||
|
34
trees/count_unival.py
Normal file
34
trees/count_unival.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
'''
|
||||
Given the root of a binary tree, return the number of uni-value subtrees.
|
||||
A uni-value subtree means all nodes of the subtree have the same value.
|
||||
'''
|
||||
|
||||
def count_unival(root: Optional[TreeNode]) -> int:
|
||||
|
||||
global count = 0
|
||||
|
||||
def dfs(node):
|
||||
|
||||
if not node:
|
||||
return True
|
||||
|
||||
is_uni_left = dfs(node.left)
|
||||
is_uni_right = dfs(node.right)
|
||||
|
||||
if is_uni_left and is_uni_right:
|
||||
if node.left and node.left.val != node.val:
|
||||
return False
|
||||
if node.right and node.right.val != node.val:
|
||||
return False
|
||||
|
||||
self.count += 1
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
dfs(root)
|
||||
return count
|
39
trees/find_duplicate_subtrees.py
Normal file
39
trees/find_duplicate_subtrees.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
#
|
||||
# Given the root of a binary tree, return all duplicate subtrees.
|
||||
#
|
||||
# Definition for a binary tree node.
|
||||
# class TreeNode:
|
||||
# def __init__(self, val=0, left=None, right=None):
|
||||
# self.val = val
|
||||
# self.left = left
|
||||
# self.right = right
|
||||
|
||||
|
||||
def find_duplicates(root: Optional[TreeNode]) -> List[Optional[TreeNode]]:
|
||||
|
||||
result = []
|
||||
counter = {}
|
||||
|
||||
def traverse(node):
|
||||
if not node:
|
||||
return ""
|
||||
|
||||
rep = ("(" + traverse(node.left) + ")" + \
|
||||
str(node.val) + "(" + \
|
||||
traverse(node.right) + ")")
|
||||
|
||||
if rep in counter:
|
||||
counter[rep] += 1
|
||||
else:
|
||||
counter[rep] = 1
|
||||
|
||||
if counter[rep] == 2:
|
||||
result.append(node)
|
||||
|
||||
return rep
|
||||
|
||||
traverse(root)
|
||||
return result
|
10
trees/find_max_depth_tree.py
Normal file
10
trees/find_max_depth_tree.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
def max_depth(root: Optional[TreeNode]) -> int:
|
||||
|
||||
if root is None:
|
||||
return 0
|
||||
|
||||
return max(max_depth(root.left) + 1, max_depth(root.right) + 1)
|
25
trees/has_path_sum.py
Normal file
25
trees/has_path_sum.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
def has_path_sum(root: Optional[TreeNode], target_sum: int) -> bool:
|
||||
|
||||
def transverse(node, sum_here=0):
|
||||
|
||||
if not node:
|
||||
return sum_here == target_sum
|
||||
|
||||
sum_here += node.val
|
||||
|
||||
if not node.left:
|
||||
return transverse(node.right, sum_here)
|
||||
if not node.right:
|
||||
return transverse(node.left, sum_here)
|
||||
else:
|
||||
return transverse(node.left, sum_here) or transverse(node.right, sum_here)
|
||||
|
||||
if not root:
|
||||
return False
|
||||
|
||||
return transverse(root)
|
||||
|
42
trees/is_tree_symmetric.py
Normal file
42
trees/is_tree_symmetric.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
def is_symmetrical(root: Optional[TreeNode]) -> bool:
|
||||
|
||||
stack = [(root, root)]
|
||||
|
||||
while stack:
|
||||
|
||||
node1, node2 = stack.pop()
|
||||
|
||||
if (not node1 and node2) or (not node2 and node1):
|
||||
return False
|
||||
|
||||
elif not node1 and not node2:
|
||||
continue
|
||||
|
||||
elif node1 and node2 and node1.val != node2.val:
|
||||
return False
|
||||
|
||||
stack.append([node1.left, node2.right])
|
||||
stack.append([node1.right, node2.left])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_symmetrical_recursive(root: Optional[TreeNode]) -> bool:
|
||||
|
||||
def helper(node1, node2):
|
||||
if (not node1 and node2) or \
|
||||
(not node2 and node1) or \
|
||||
(node1 and node2 and node1.val != node2.val):
|
||||
return False
|
||||
|
||||
if (not node1 and not node2):
|
||||
return True
|
||||
|
||||
return helper(node1.left, node2.right) and helper(node2.left, node1.right)
|
||||
|
||||
return helper(root.left, root.right)
|
||||
|
29
trees/lowest_common_ancestor.py
Normal file
29
trees/lowest_common_ancestor.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
'''
|
||||
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.
|
||||
'''
|
||||
|
||||
class Tree:
|
||||
def lowest_common_ancestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
|
||||
|
||||
def dfs(root, p, q):
|
||||
|
||||
if not root:
|
||||
return False
|
||||
|
||||
left = dfs(root.left, p, q)
|
||||
right = dfs(root.right, p, q)
|
||||
mid = root == p or root == q
|
||||
|
||||
if mid + left + right >= 2:
|
||||
self.answer = root
|
||||
|
||||
return left or right or mid
|
||||
|
||||
dfs(root, p, q)
|
||||
|
||||
return self.answer
|
||||
|
32
trees/preorder_transversal.py
Normal file
32
trees/preorder_transversal.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
# recursive and iterative inorder traversal
|
||||
|
||||
def preorder_recursive(root: Optional[TreeNode]) -> list[int]:
|
||||
|
||||
if root == None:
|
||||
return []
|
||||
|
||||
return [root.val] + preorder_recursive(root.left) + preorder_recursive(root.right)
|
||||
|
||||
|
||||
def preorder_iterative(root: Optional[TreeNode]) -> list[int]:
|
||||
|
||||
result = []
|
||||
stack = [root]
|
||||
|
||||
while stack:
|
||||
|
||||
current = stack.pop()
|
||||
result.append(current.val)
|
||||
|
||||
if current.right:
|
||||
stack.append(current.right)
|
||||
|
||||
if current.left:
|
||||
stack.append(current.left)
|
||||
|
||||
return result
|
||||
|
88
trees/sum_2_numbers_with_bs.py
Normal file
88
trees/sum_2_numbers_with_bs.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
# given a collection of numbers, find the pair
|
||||
# of numbers that sum to a given number
|
||||
|
||||
def bs(array, desired_num):
|
||||
|
||||
start = 0
|
||||
end = len(array)
|
||||
mid = (end - start) // 2
|
||||
|
||||
while len(array) > 0:
|
||||
if array[mid] == desired_num:
|
||||
return True
|
||||
elif array[mid] > desired_num:
|
||||
return bs(array[mid+1:], desired_num)
|
||||
else:
|
||||
return bs(array[:mid], desired_num)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def find_pairs_bs(array, desired_sum):
|
||||
|
||||
for i in range(len(array)):
|
||||
num1 = array[i]
|
||||
desired_num = desired_sum - num1
|
||||
if bs(array[i + 1:], desired_num) == True:
|
||||
return (num1, desired_num)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def find_pairs_max_sum(array, desired_sum):
|
||||
|
||||
i, j = 0, len(array) - 1
|
||||
|
||||
while i < j:
|
||||
if array[i] + array[j] == desired_sum:
|
||||
return array[i], array[j]
|
||||
elif array[i] + array[j] > desired_sum:
|
||||
j = j - 1
|
||||
elif array[i] + array[j] < desired_sum:
|
||||
i = i + 1
|
||||
|
||||
return False
|
||||
|
||||
def find_pairs_not_sorted(array, desired_sum):
|
||||
|
||||
lookup = {}
|
||||
|
||||
for item in array:
|
||||
key = desired_sum - item
|
||||
|
||||
if key in lookup.keys():
|
||||
lookup[key] += 1
|
||||
else:
|
||||
lookup[key] = 1
|
||||
|
||||
for item in array:
|
||||
key = desired_sum - item
|
||||
|
||||
if item in lookup.keys():
|
||||
if lookup[item] == 1:
|
||||
return (item, key)
|
||||
else:
|
||||
lookup[item] -= 1
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
desired_sum = 8
|
||||
array1 = [1, 2, 3, 9]
|
||||
array2 = [1, 2, 4, 5, 4]
|
||||
array3 = [2, 1, 6, 3, 11, 2]
|
||||
|
||||
assert(find_pairs_bs(array1, desired_sum) == False)
|
||||
assert(find_pairs_bs(array2, desired_sum) == (4, 4))
|
||||
assert(find_pairs_max_sum(array1, desired_sum) == False)
|
||||
assert(find_pairs_max_sum(array2, desired_sum) == (4,4))
|
||||
assert(find_pairs_not_sorted(array1, desired_sum) == False)
|
||||
assert(find_pairs_not_sorted(array2, desired_sum) == (4, 4))
|
||||
assert(find_pairs_not_sorted(array3, desired_sum) == (2, 6))
|
34
trees/tree_level_traversal.py
Normal file
34
trees/tree_level_traversal.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
# Given the root of a binary tree, return the level order traversal of its nodes' values.
|
||||
# (i.e., from left to right, level by level).
|
||||
|
||||
|
||||
def level_order(root: Optional[TreeNode]) -> list[list[int]]:
|
||||
|
||||
if root is None:
|
||||
return []
|
||||
|
||||
queue = collections.deque()
|
||||
queue.append(root)
|
||||
result = []
|
||||
|
||||
while queue:
|
||||
|
||||
this_level = []
|
||||
|
||||
for _ in range(len(queue)):
|
||||
|
||||
current = queue.popleft()
|
||||
|
||||
if current:
|
||||
this_level.append(current.val)
|
||||
queue.append(current.left)
|
||||
queue.append(current.right)
|
||||
|
||||
if this_level:
|
||||
result.append(this_level)
|
||||
|
||||
return result
|
47
trees/tree_serialization.py
Normal file
47
trees/tree_serialization.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# author: bt3gl
|
||||
|
||||
'''
|
||||
Serialization is the process of converting a data structure or object into
|
||||
a sequence of bits so that it can be stored in a file or memory buffer, or
|
||||
transmitted across a network connection link to be reconstructed later in
|
||||
the same or another computer environment.
|
||||
|
||||
Design an algorithm to serialize and deserialize a binary tree. There is no
|
||||
restriction on how your serialization/deserialization algorithm should work.
|
||||
You just need to ensure that a binary tree can be serialized to a string and
|
||||
this string can be deserialized to the original tree structure.
|
||||
'''
|
||||
|
||||
|
||||
class Codec:
|
||||
|
||||
def serialize(self, root):
|
||||
|
||||
def helper(root, string):
|
||||
if root is None:
|
||||
string += 'None,'
|
||||
else:
|
||||
string += str(root.val) + ','
|
||||
string = helper(root.left, string)
|
||||
string = helper(root.right, string)
|
||||
return string
|
||||
|
||||
return helper(root, '')
|
||||
|
||||
def deserialize(self, data):
|
||||
|
||||
def helper(data):
|
||||
if data[0] == 'None':
|
||||
data.pop(0)
|
||||
return None
|
||||
root_val = data.pop(0)
|
||||
root = TreeNode(root_val)
|
||||
root.left = helper(data)
|
||||
root.right = helper(data)
|
||||
return root
|
||||
|
||||
root = helper(data.split(','))
|
||||
|
||||
return root
|
Loading…
Add table
Add a link
Reference in a new issue