Skip to content
Snippets Groups Projects
Commit 164b7d6c authored by Romain Vuillemot's avatar Romain Vuillemot
Browse files

solution from class

parent eda423c8
No related branches found
No related tags found
No related merge requests found
No preview for this file type
%% Cell type:markdown id:09fc003e tags:
# UE5 Fundamentals of Algorithms
## Lecture 8: Binary trees
### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business
#### Romain Vuillemot
<center><img src="figures/Logo_ECL.png" style="width:300px"></center>
%% Cell type:markdown id:74743087 tags:
---
%% Cell type:markdown id:f3ebe7d2 tags:
## Outline
- Definitions
- Data structures
- Basic operations
- Properties
%% Cell type:markdown id:a4973a08 tags:
## Definitions
> Tree is a hierarchical data structure with nodes connected by edges
- A non-linear data structures (multiple ways to traverse it)
- Nodes are connected by only one path (a series of edges) so trees have no cycle
- Edges are also called links, they can be traversed in both ways (no orientation)
We focus on _binary trees._
> Trees that have at most two children
- Children can be ordered left child and the right child
%% Cell type:markdown id:28bb09dc tags:
## Binary trees representation
Trees are most commonly represented as a node-lin diagram, with the root at the top and the leaves (nodes without children) at the bottom).
%% Cell type:code id:51f0cf57 tags:
``` python
draw_binary_tree(binary_tree)
```
%% Output
%% Cell type:markdown id:55e54f01 tags:
## Binary trees data structures
Binary trees can be stored in multiple ways
- The first element is the value of the node.
- The second element is the left subtree.
- The third element is the right subtree.
Here are examples:
- Adjacency list `T = {'A': ['B', 'C']}`
- Arrays `["A", "B"]`
- Class / Object-oriented programming `Class Node()`
Other are possible: using linked list, modules, etc.
Adjacency lists are the most common ways and can be achieved in multiple fashions.
%% Cell type:markdown id:30faa950 tags:
## Binary trees data structures (dictionnaries and lists)
_Binary trees using dictionnaries where nodes are keys and edges are Lists._
%% Cell type:code id:d495c8a5 tags:
``` python
T = {
'A' : ['B','C'],
'B' : ['D', 'E'],
'C' : [],
'D' : [],
'E' : []
}
```
%% Cell type:markdown id:b2b2c183 tags:
## Using OOP
%% Cell type:code id:5df1c518 tags:
``` python
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def get_value(self):
return self.value
def set_value(self, v = None):
self.value = v
```
%% Cell type:code id:abc855b1 tags:
``` python
root = Node(4)
root.left = Node(2)
root.right = Node(5)
root.left.left = Node(1)
root.left.right = Node(3)
```
%% Cell type:markdown id:8b8ec2a0 tags:
## Definitions on binary trees
`Nodes` - a tree is composed of nodes that contain a `value` and `children`.
`Edges` - are the connections between nodes; nodes may contain a value.
`Root` - the topmost node in a tree; there can only be one root.
`Parent and child` - each node has a single parent and up to two children.
`Leaf` - no node below that node.
`Depth` - the number of edges on the path from the root to that node.
`Height` - maximum depth in a tree.
%% Cell type:markdown id:b0bb3608 tags:
# Basic operations
%% Cell type:markdown id:8726ff36 tags:
### Get the root of a tree
_Return the topmost node in a tree (there can only be one root)._
%% Cell type:code id:1fbb1c2f tags:
``` python
def get_root(T):
if (len(T.keys()) > 0):
return list(T.keys())[0]
else:
return -1
```
%% Cell type:code id:6b4492dc tags:
``` python
get_root(T)
```
%% Output
'A'
%% Cell type:code id:ea01e802 tags:
``` python
assert get_root({}) == -1
assert get_root({"A": []}) == "A"
assert isinstance(get_root({"A": []}), str) # to make sure there is only 1 root (eg not a list)
```
%% Cell type:markdown id:3ffffeda tags:
### Get the list of nodes
_Return all the nodes in the tree (as a list of nodes names)._
%% Cell type:code id:3af082b7 tags:
``` python
def get_nodes(T):
return list(T.keys())
```
%% Cell type:code id:ede5b5f4 tags:
``` python
get_nodes(T)
```
%% Output
['A', 'B', 'C', 'D', 'E']
%% Cell type:code id:2d3305d5 tags:
``` python
assert get_nodes(T) == ['A', 'B', 'C', 'D', 'E']
assert get_nodes({}) == []
```
%% Cell type:markdown id:db9c925d tags:
### Get the list of edges
_Return all the edges as a list of pairs as `Tuple`._
%% Cell type:code id:b50fe9c2 tags:
``` python
def get_edges(graph):
edges = []
for node, neighbors in graph.items():
for neighbor in neighbors:
edges.append((node, neighbor))
return edges
```
%% Cell type:code id:8958bd83 tags:
``` python
get_edges(T)
```
%% Output
[('A', 'B'), ('A', 'C'), ('B', 'D'), ('B', 'E')]
%% Cell type:code id:30dd31d3 tags:
``` python
assert get_edges(T) == [('A', 'B'), ('A', 'C'), ('B', 'D'), ('B', 'E')]
assert get_edges({}) == []
```
%% Cell type:markdown id:95accba5 tags:
### Get the parent of a node
_Return the parent node of a given node (and -1 if the root)._
%% Cell type:code id:37fbe31b tags:
``` python
def get_parent(graph, node_to_find):
for parent, neighbors in graph.items():
if node_to_find in neighbors:
return parent
return None
```
%% Cell type:code id:78e88d23 tags:
``` python
assert get_parent(T, 'D') == 'B'
assert get_parent(T, 'A') is None
assert get_parent({}, '') is None
```
%% Cell type:markdown id:3fb6f347 tags:
### Check if the node is the root
_Return True if the root not, else `None`.
%% Cell type:code id:164e4ef7 tags:
``` python
def is_root(T, node):
return find_parent(T, node) is None
```
%% Cell type:code id:5c053617 tags:
``` python
assert is_root(T, 'A') == True
```
%% Cell type:markdown id:bba64730 tags:
### Get the children of a node
_Given a node, return all its children as a `List`._
%% Cell type:code id:ac145c20 tags:
``` python
def find_children(graph, parent_node):
children = graph.get(parent_node, [])
return children
```
%% Cell type:code id:9444a66f tags:
``` python
assert find_children(T, 'A') == ['B', 'C']
assert find_children(T, 'B') == ['D', 'E']
assert find_children(T, 'C') == []
```
%% Cell type:markdown id:6f600f3d tags:
### Check if the node is a leaf
_Return `True` if the node has no children._
%% Cell type:code id:77f5f17c tags:
``` python
def is_leaf(T, node):
return len(find_children(T, node)) == 0
```
%% Cell type:code id:5f5078d6 tags:
``` python
assert is_leaf(T, 'C')
assert not is_leaf(T, 'A')
```
%% Cell type:markdown id:a41e666e tags:
### Add/Delete a node
_Given a tree as input._
- Add a node to given a current partent
- Remove a given node
%% Cell type:code id:c9312a43 tags:
``` python
def add_node(graph, parent, new_node):
if parent in graph:
graph[parent].append(new_node)
else:
graph[parent] = [new_node]
def delete_node(graph, node_to_delete):
for parent, children in graph.items():
if node_to_delete in children:
children.remove(node_to_delete)
if not children:
del graph[parent]
```
%% Cell type:code id:38181a0f tags:
``` python
U = {"A": []}
add_node(U, "A", 'F')
U
```
%% Cell type:markdown id:4885c320 tags:
### Height of a tree
_Calculate the longest path from the root to leaves. Tip: use a recursive approach_
- if the node is a leaf, return 1
- for a current node, the height is the max height of its children + 1
%% Cell type:code id:ee2973b7 tags:
``` python
T
```
%% Output
{'A': ['B', 'C'], 'B': ['D', 'E'], 'C': [], 'D': [], 'E': []}
%% Cell type:code id:b7ef1fab tags:
``` python
v
```
%% Cell type:code id:6ef9af29 tags:
``` python
def height(T, node):
if node not in T:
return 0 # leaf
children = T[node]
if not children:
return 1 # leaf
list_heights = []
for child in children:
list_heights.append(height(T, child))
return 1 + max(list_heights)
```
%% Cell type:code id:44a54ec8 tags:
``` python
assert height(T, 'A') == 3
assert height(T, 'B') == 2
assert height(T, 'C') == 1
```
%% Cell type:markdown id:e35608be tags:
## Height of a binary tree
<img src="figures/hauteur-arbre.png" style="width: 400px">
$n = 2^{(h+1)} - 1$
$n + 1 = 2^{(h+1)}$
$log(n + 1) = log(2^{(h+1)})$
$log(n + 1) = (h+1) log(2)$
$log(n + 1) / log(2) = h + 1$
so $h = log(n + 1) / log(2) - 1$
$h$ is equivalent to $log(n)$
%% Cell type:markdown id:94f34cf8 tags:
## Binary trees (using Arrays)
<img src="figures/arbre-tableau.png" style="width: 400px">
In a complete or balanced binary tree:
- if the index of a node is equal to $i$, then the position indicating its left child is at $2i$,
- and the position indicating its right child is at $2i + 1$.
%% Cell type:markdown id:8afab007 tags:
## Visualize a tree
%% Cell type:code id:610ad3bb tags:
``` python
from graphviz import Digraph
dot = Digraph()
dot.node_attr['shape'] = 'circle'
dot.node('0', label='0') # Root
dot.node('1')
dot.node('2')
dot.node('3')
dot.node('4')
dot.node('5')
dot.edge('0', '1')
dot.edge('1', '4')
dot.edge('1', '5')
dot.edge('0', '2', color='red')
dot.edge('2', '3', color='red')
dot # Render the graph
```
%% Output
<graphviz.graphs.Digraph at 0x104c32b30>
%% Cell type:markdown id:01880f1d tags:
## Visualize a tree
%% Cell type:code id:3a064bfb tags:
``` python
from graphviz import Digraph
from IPython.display import display
def draw_binary_tree(tree_dict):
# Create a new graph
dot = Digraph(format='png')
# Recursive function to add nodes and edges
def add_nodes_and_edges(node, parent_name=None):
if isinstance(node, dict):
for key, value in node.items():
# Add the node
dot.node(key, key)
# Add the edge to the parent (if it exists)
if parent_name:
dot.edge(parent_name, key)
# Recursively call the function for the children
add_nodes_and_edges(value, key)
# Call the function to build the tree
add_nodes_and_edges(tree_dict)
# Display the graph in the notebook
display(dot)
```
%% Cell type:code id:cb7726ee tags:
``` python
```
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment