mirror of
https://git.adityakumar.xyz/dsa.git
synced 2024-11-21 17:42:53 +00:00
add trees
This commit is contained in:
parent
1bb2a09fb3
commit
8ae181c2c0
2 changed files with 498 additions and 0 deletions
54
content/docs/dsa/trees/_index.md
Normal file
54
content/docs/dsa/trees/_index.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
title: "Trees"
|
||||||
|
weight: 1
|
||||||
|
# bookFlatSection: false
|
||||||
|
# bookToc: true
|
||||||
|
# bookHidden: false
|
||||||
|
bookCollapseSection: true
|
||||||
|
# bookComments: false
|
||||||
|
# bookSearchExclude: false
|
||||||
|
---
|
||||||
|
|
||||||
|
A tree, is a hierarchical way of organizing
|
||||||
|
elements (often referred to as nodes) where each element has zero or more child elements. It is one
|
||||||
|
of the most fundamental and widely used abstract data types (ADT). The structure consists of nodes
|
||||||
|
connected by edges, with distinct properties:
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
- **Root**: A special node at the top of a tree from which all other nodes descend. In some
|
||||||
|
implementations, there might not be a root if it's an empty tree.
|
||||||
|
|
||||||
|
- **Parent and Child Nodes**: Each child node has one parent except for the root node, which doesn't
|
||||||
|
have any parents. There is exactly one edge between each pair of parent and its children (no shared
|
||||||
|
children).
|
||||||
|
|
||||||
|
- **Leaf Nodes**: These are nodes that do not have any children. They represent the "end" points in
|
||||||
|
a tree structure.
|
||||||
|
|
||||||
|
- **Edges/Links**: Connections between nodes, which can be directed or undirected. In binary trees
|
||||||
|
(a special kind of tree), an edge typically represents one possible path from a parent to its
|
||||||
|
child(ren).
|
||||||
|
|
||||||
|
There are several types of trees that have specific properties and uses:
|
||||||
|
|
||||||
|
- **Binary Trees**: Each node has at most two children, which can be named as the left child and
|
||||||
|
right child. Examples include Binary Search Trees (BST), AVL trees, Red-Black trees etc.
|
||||||
|
|
||||||
|
- **Ternary Trees**: Each node may have up to three children. One common example is a Ternary Search
|
||||||
|
Tree used in text indexing.
|
||||||
|
|
||||||
|
- **Balanced Trees**: These are binary trees that maintain their height as balanced with respect to
|
||||||
|
some metric (such as the number of nodes), like AVL and Red-Black trees, which help to ensure
|
||||||
|
operations on them run efficiently.
|
||||||
|
|
||||||
|
- **B-trees and B+ Trees**: Non-binary tree structures used in databases and filesystems due to
|
||||||
|
their ability to handle large amounts of data with good performance for insertions, deletions, and
|
||||||
|
lookups.
|
||||||
|
|
||||||
|
Trees are employed in various applications such as searching (e.g., binary search),
|
||||||
|
sorting (in some cases using a heap structure which is a specific type of tree), managing
|
||||||
|
hierarchical data, parsing expressions, routing protocols like Dijkstra's algorithm for finding the
|
||||||
|
shortest path, and more.
|
||||||
|
|
||||||
|
{{<section summary >}}
|
444
content/docs/dsa/trees/bst.md
Normal file
444
content/docs/dsa/trees/bst.md
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
---
|
||||||
|
title: "Binary Search Tree"
|
||||||
|
weight: 1
|
||||||
|
# bookFlatSection: false
|
||||||
|
# bookToc: true
|
||||||
|
# bookHidden: false
|
||||||
|
# bookCollapseSection: false
|
||||||
|
# bookComments: false
|
||||||
|
# bookSearchExclude: false
|
||||||
|
---
|
||||||
|
|
||||||
|
A Binary Search Tree (BST) is a type of data structure that organizes nodes in a hierarchical
|
||||||
|
manner, where each node has at most two children: left and right. The key characteristic of a BST
|
||||||
|
lies in the way it stores elements based on their values to maintain an ordered sequence that allows
|
||||||
|
for efficient searching, insertion, and deletion operations.
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
The fundamental properties of a binary search tree are as follows:
|
||||||
|
|
||||||
|
1. **Node Structure**: Each node contains data (value), a reference to the left child node, and a
|
||||||
|
reference to the right child node. In addition, it may contain pointers for parent nodes in some
|
||||||
|
implementations but this is not mandatory.
|
||||||
|
|
||||||
|
2. **Ordering Property**: For any given node in the BST, all values in its left subtree are less
|
||||||
|
than or equal to its own value, and all values in its right subtree are greater than its own value.
|
||||||
|
This property must hold for every single node, which is true recursively on each of its children as
|
||||||
|
well.
|
||||||
|
|
||||||
|
3. **Efficiency**: Due to the ordering property, BSTs provide efficient time complexity for
|
||||||
|
operations like search (on average O(log n) in a balanced tree), insertion (O(log n)), and deletion
|
||||||
|
(O(log n)). However, these complexities can degrade to O(n) if the tree is not balanced.
|
||||||
|
|
||||||
|
The efficiency of BSTs makes them useful for various applications that require sorted data storage
|
||||||
|
with quick access times such as database indexing systems, sorting algorithms like heapsort and
|
||||||
|
mergesort (when implemented using a binary heap), and many others in computer science.
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
### Insertion
|
||||||
|
|
||||||
|
1. **Start**: You are given the root of the BST and the integer value 'value' that needs to be
|
||||||
|
inserted into the tree.
|
||||||
|
|
||||||
|
2. **Comparison with Root**: Begin by comparing the 'value' you wish to insert with the current root
|
||||||
|
node's value. If it is equal, skip the following steps as duplicates are not allowed in a BST (this
|
||||||
|
condition can vary based on specific implementation rules).
|
||||||
|
|
||||||
|
3. **Decision for Insertion Location**:
|
||||||
|
|
||||||
|
- If the 'value' is less than the root node's value, move to the left child of the current node
|
||||||
|
and repeat step 2.
|
||||||
|
- If the 'value' is greater than the root node's value, move to the right child of the current
|
||||||
|
node and repeat step 2.
|
||||||
|
|
||||||
|
4. **Find a Spot for New Node**: Continue this process of comparing the 'value' with each node it
|
||||||
|
encounters (left or right children) until an empty spot is found (a NULL pointer), which indicates
|
||||||
|
there is no child in that direction to insert before.
|
||||||
|
|
||||||
|
5. **Insertion**: Once you reach a NULL position, create a new BSTNode object ('new_node') with the
|
||||||
|
'value' as its data and set it as either the left or right child of the last node visited (depending
|
||||||
|
on whether you moved left or right previously). This creates an insertion point in the tree.
|
||||||
|
|
||||||
|
6. **End**: The algorithm ends here, and your BST now includes a new value at the correct position
|
||||||
|
according to its ordering property.
|
||||||
|
|
||||||
|
### Deletion
|
||||||
|
|
||||||
|
1. **Start**: You are given the root of the BST and the integer 'value' that needs to be removed.
|
||||||
|
|
||||||
|
2. **Search for Target Node**: Traverse the tree starting from the root, comparing the target
|
||||||
|
'value' with each node’s value, moving left or right depending on whether it is less than or greater
|
||||||
|
than the current node's.
|
||||||
|
|
||||||
|
3. **Case 1 - Leaf Node**: If the target node has no children (it is a leaf), simply remove it by
|
||||||
|
setting its parent's corresponding link to NULL.
|
||||||
|
|
||||||
|
4. **Case 2 - Single Child**: If the target node has only one child, replace it with this child. For
|
||||||
|
example, if the left child exists, set the left child as root’s new left child and update the parent
|
||||||
|
reference of this child accordingly.
|
||||||
|
|
||||||
|
5. **Case 3 - Both Children**: This is the most complex scenario since simply removing the node
|
||||||
|
might disrupt the BST properties. To maintain the tree structure after removal, you need to find
|
||||||
|
either the maximum value in the target's left subtree (to replace it as root of this subtree) or the
|
||||||
|
minimum value in the right subtree (which will take the place of the removed node). This replacement
|
||||||
|
ensures that the BST properties remain intact.
|
||||||
|
|
||||||
|
6. **End**: The algorithm concludes, and you should now have a tree without the target 'value'.
|
||||||
|
|
||||||
|
### Searching
|
||||||
|
|
||||||
|
1. **Start**: You are provided with the root of the BST and the integer 'value'.
|
||||||
|
|
||||||
|
2. **Initial Comparison**: Begin your search at the root node, comparing it against the target
|
||||||
|
value. If you reach a NULL pointer during this process (which implies that the tree is empty or
|
||||||
|
the element isn't present), stop further search as no match can be found in an empty tree.
|
||||||
|
|
||||||
|
3. **Recursive Searching Process**: Depending on whether 'value' is less than, equal to, or
|
||||||
|
greater than the current node’s value, recursively move left if it's smaller, right if it's
|
||||||
|
larger, and return true (the element was found) if you encounter an exact match.
|
||||||
|
|
||||||
|
4. **End of Search**: If at any point a comparison leads to an immediate equality check between
|
||||||
|
the target 'value' and the current node’s value, stop further search as the BST property
|
||||||
|
guarantees that this will be the only occurrence for duplicates (this step may vary based on
|
||||||
|
specific rules about duplicates in your implementation).
|
||||||
|
|
||||||
|
5. **Outcome**: The algorithm concludes by either returning true if a match is found or false
|
||||||
|
otherwise, indicating whether 'value' exists within the tree or not.
|
||||||
|
|
||||||
|
## Pseudocode
|
||||||
|
|
||||||
|
```
|
||||||
|
add(node, data) {
|
||||||
|
if (node == nullptr)
|
||||||
|
return create_node(data);
|
||||||
|
if (data < node -> data)
|
||||||
|
node -> left = insert_node(node -> left, data);
|
||||||
|
else if (data > node -> data)
|
||||||
|
node -> right = insert_node(node -> right, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_node(data) {
|
||||||
|
if (root == nullptr) return root;
|
||||||
|
if (data < root -> data)
|
||||||
|
root -> left = remove_node(root -> left, data);
|
||||||
|
else if (data > root -> data)
|
||||||
|
root -> right = remove_node(root -> right, data);
|
||||||
|
else {
|
||||||
|
// only child or no child
|
||||||
|
if (root -> left == nullptr) {
|
||||||
|
temp = root -> right;
|
||||||
|
root = nullptr;
|
||||||
|
delete root
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
else if (root -> right == nullptr) {
|
||||||
|
temp = root -> left;
|
||||||
|
root = nullptr;
|
||||||
|
delete root;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
// two children
|
||||||
|
temp = smallest_node(root -> right);
|
||||||
|
root -> data = temp -> data;
|
||||||
|
root -> right = remove_node(root -> right, temp -> data);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
search(root, data) {
|
||||||
|
if (root == null) return nullptr;
|
||||||
|
if (data == root -> data) return root -> data;
|
||||||
|
if (data < root -> data) return search(root -> left, data);
|
||||||
|
if (data > root -> data) return search(root -> right, data);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Code
|
||||||
|
```cpp
|
||||||
|
import <memory>;
|
||||||
|
import <print>;
|
||||||
|
|
||||||
|
struct Node;
|
||||||
|
|
||||||
|
using node_ptr_t = std::shared_ptr<Node>;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
node_ptr_t left{}, right{};
|
||||||
|
|
||||||
|
Node() = default;
|
||||||
|
Node(Node &&) = default;
|
||||||
|
explicit Node(ssize_t data, node_ptr_t left, node_ptr_t right)
|
||||||
|
: data(std::move(data)), left(left), right(right) {}
|
||||||
|
Node &operator=(Node &&) = default;
|
||||||
|
Node(const Node &) = delete;
|
||||||
|
Node &operator=(const Node &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto init_node(const ssize_t &data) -> node_ptr_t {
|
||||||
|
auto temp{std::make_shared<Node>()};
|
||||||
|
temp->data = data;
|
||||||
|
temp->left = nullptr;
|
||||||
|
temp->right = nullptr;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto travel_inorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
travel_inorder(root->left);
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
travel_inorder(root->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto travel_preorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
travel_preorder(root->left);
|
||||||
|
travel_preorder(root->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto travel_postorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
travel_postorder(root->left);
|
||||||
|
travel_postorder(root->right);
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto add_node(const node_ptr_t &node, const ssize_t &data) -> node_ptr_t {
|
||||||
|
if (node == nullptr)
|
||||||
|
return init_node(data);
|
||||||
|
if (data < node->data)
|
||||||
|
node->left = add_node(node->left, data);
|
||||||
|
else
|
||||||
|
node->right = add_node(node->right, data);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto smallest_node(const node_ptr_t &given_node) -> node_ptr_t {
|
||||||
|
auto current_node{given_node};
|
||||||
|
// go to the leftmost node
|
||||||
|
while (current_node && current_node->left != nullptr)
|
||||||
|
current_node = current_node->left;
|
||||||
|
return current_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto remove_node(node_ptr_t root, const ssize_t &data) -> node_ptr_t {
|
||||||
|
if (root == nullptr)
|
||||||
|
return root;
|
||||||
|
if (data < root->data)
|
||||||
|
root->left = remove_node(root->left, data);
|
||||||
|
else if (data > root->data)
|
||||||
|
root->right = remove_node(root->right, data);
|
||||||
|
else {
|
||||||
|
if (root->left == nullptr) {
|
||||||
|
auto temp{root->right};
|
||||||
|
return temp;
|
||||||
|
} else if (root->right == nullptr) {
|
||||||
|
auto temp{root->left};
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto temp{smallest_node(root->right)};
|
||||||
|
root->data = temp->data;
|
||||||
|
root->right = remove_node(root->right, temp->data);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
node_ptr_t root{nullptr};
|
||||||
|
root = add_node(root, 8);
|
||||||
|
root = add_node(root, 5);
|
||||||
|
root = add_node(root, 2);
|
||||||
|
root = add_node(root, 6);
|
||||||
|
root = add_node(root, 7);
|
||||||
|
root = add_node(root, 1);
|
||||||
|
root = add_node(root, 25);
|
||||||
|
root = add_node(root, 54);
|
||||||
|
root = add_node(root, 4);
|
||||||
|
root = add_node(root, 11);
|
||||||
|
root = add_node(root, 9);
|
||||||
|
root = add_node(root, 3);
|
||||||
|
|
||||||
|
travel_inorder(root);
|
||||||
|
std::print("\n");
|
||||||
|
root = remove_node(root, 25);
|
||||||
|
|
||||||
|
travel_inorder(root);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Here I have used smart pointers for automatic memory management.
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
1. **Headers and Type Aliases**
|
||||||
|
```cpp
|
||||||
|
import <memory>;
|
||||||
|
import <print>;
|
||||||
|
```
|
||||||
|
These lines import the necessary standard library components: `memory` for `std::shared_ptr` and `print` for outputting text.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node;
|
||||||
|
|
||||||
|
using node_ptr_t = std::shared_ptr<Node>;
|
||||||
|
```
|
||||||
|
This declares a forward declaration of the `Node` structure and a type alias `node_ptr_t` for a `std::shared_ptr<Node>`.
|
||||||
|
|
||||||
|
2. **Node Structure**
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
node_ptr_t left{}, right{};
|
||||||
|
|
||||||
|
Node() = default;
|
||||||
|
Node(Node &&) = default;
|
||||||
|
explicit Node(ssize_t data, node_ptr_t left, node_ptr_t right)
|
||||||
|
: data(std::move(data)), left(left), right(right) {}
|
||||||
|
Node &operator=(Node &&) = default;
|
||||||
|
Node(const Node &) = delete;
|
||||||
|
Node &operator=(const Node &) = delete;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
The `Node` structure represents a node in the BST. Each node contains:
|
||||||
|
|
||||||
|
- `data`: the value stored in the node.
|
||||||
|
- `left` and `right`: pointers to the left and right children, respectively.
|
||||||
|
|
||||||
|
The constructors and assignment operators are defined as follows:
|
||||||
|
|
||||||
|
- Default constructor: `Node() = default`.
|
||||||
|
- Move constructor and move assignment operator: `Node(Node &&) = default` and `Node &operator=(Node &&) = default`.
|
||||||
|
- Parameterized constructor: initializes `data`, `left`, and `right`.
|
||||||
|
- Copy constructor and copy assignment operator are deleted: `Node(const Node &) = delete` and `Node &operator=(const Node &) = delete` to prevent copying of nodes (only moving is allowed).
|
||||||
|
|
||||||
|
3. **Initialize a Node**
|
||||||
|
```cpp
|
||||||
|
auto init_node(const ssize_t &data) -> node_ptr_t {
|
||||||
|
auto temp{std::make_shared<Node>()};
|
||||||
|
temp->data = data;
|
||||||
|
temp->left = nullptr;
|
||||||
|
temp->right = nullptr;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`init_node` creates and initializes a new node with the given data.
|
||||||
|
|
||||||
|
4. **Tree Traversal Functions**
|
||||||
|
```cpp
|
||||||
|
auto travel_inorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
travel_inorder(root->left);
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
travel_inorder(root->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto travel_preorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
travel_preorder(root->left);
|
||||||
|
travel_preorder(root->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto travel_postorder(const node_ptr_t &root) -> void {
|
||||||
|
if (root != nullptr) {
|
||||||
|
travel_postorder(root->left);
|
||||||
|
travel_postorder(root->right);
|
||||||
|
std::print("{} -> ", root->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
These functions implement in-order, pre-order, and post-order traversal of the BST, respectively, and print the nodes' data during traversal.
|
||||||
|
|
||||||
|
4. **Add Node to Tree**
|
||||||
|
```cpp
|
||||||
|
auto add_node(const node_ptr_t &node, const ssize_t &data) -> node_ptr_t {
|
||||||
|
if (node == nullptr)
|
||||||
|
return init_node(data);
|
||||||
|
if (data < node->data)
|
||||||
|
node->left = add_node(node->left, data);
|
||||||
|
else
|
||||||
|
node->right = add_node(node->right, data);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`add_node` recursively adds a new node with the given data to the BST, maintaining the BST property.
|
||||||
|
|
||||||
|
5. **Find the Smallest Node**
|
||||||
|
```cpp
|
||||||
|
auto smallest_node(const node_ptr_t &given_node) -> node_ptr_t {
|
||||||
|
auto current_node{given_node};
|
||||||
|
while (current_node && current_node->left != nullptr)
|
||||||
|
current_node = current_node->left;
|
||||||
|
return current_node;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`smallest_node` finds and returns the node with the smallest value in the subtree rooted at `given_node`.
|
||||||
|
|
||||||
|
6. **Remove Node from Tree**
|
||||||
|
```cpp
|
||||||
|
auto remove_node(node_ptr_t root, const ssize_t &data) -> node_ptr_t {
|
||||||
|
if (root == nullptr)
|
||||||
|
return root;
|
||||||
|
if (data < root->data)
|
||||||
|
root->left = remove_node(root->left, data);
|
||||||
|
else if (data > root->data)
|
||||||
|
root->right = remove_node(root->right, data);
|
||||||
|
else {
|
||||||
|
if (root->left == nullptr) {
|
||||||
|
auto temp{root->right};
|
||||||
|
return temp;
|
||||||
|
} else if (root->right == nullptr) {
|
||||||
|
auto temp{root->left};
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto temp{smallest_node(root->right)};
|
||||||
|
root->data = temp->data;
|
||||||
|
root->right = remove_node(root->right, temp->data);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`remove_node` removes a node with the specified data from the BST. It handles three cases:
|
||||||
|
|
||||||
|
- Node with only one child or no child.
|
||||||
|
- Node with two children: finds the in-order successor (smallest node in the right subtree), replaces the node's data with the successor's data, and then deletes the successor.
|
||||||
|
|
||||||
|
7. **`main()` Function**
|
||||||
|
```cpp
|
||||||
|
int main() {
|
||||||
|
node_ptr_t root{nullptr};
|
||||||
|
root = add_node(root, 8);
|
||||||
|
root = add_node(root, 5);
|
||||||
|
root = add_node(root, 2);
|
||||||
|
root = add_node(root, 6);
|
||||||
|
root = add_node(root, 7);
|
||||||
|
root = add_node(root, 1);
|
||||||
|
root = add_node(root, 25);
|
||||||
|
root = add_node(root, 54);
|
||||||
|
root = add_node(root, 4);
|
||||||
|
root = add_node(root, 11);
|
||||||
|
root = add_node(root, 9);
|
||||||
|
root = add_node(root, 3);
|
||||||
|
|
||||||
|
travel_inorder(root);
|
||||||
|
std::print("\n");
|
||||||
|
root = remove_node(root, 25);
|
||||||
|
|
||||||
|
travel_inorder(root);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The `main` function demonstrates creating a BST, adding nodes to it, performing an in-order traversal, removing a node, and performing another in-order traversal.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```console
|
||||||
|
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 11 -> 25 -> 54 ->
|
||||||
|
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 11 -> 54 ->
|
||||||
|
```
|
Loading…
Reference in a new issue