## Table of contents

Binary trees are a fundamental data structure in computer science, often used for searching and sorting algorithms. They are a tree-like data structure where each node can have at most two children, referred to as the left and right children.

Here is a diagram of a binary tree:

```
8
/ \
/ \
/ \
/ \
5 10
/ \ / \
2 6 9 12
```

In the above diagram, the node with the value 8 is the root node of the tree. The nodes with the values 5 and 10 are its children, and so on. Each node in the tree can be thought of as a separate tree in itself, with its own set of children.

The advantage of using a binary tree is that it allows for fast searching, insertion, and deletion. Because each node has at most two children, the height of the tree is logarithmic in the number of nodes, resulting in efficient algorithms.

Here is an implementation of a `Node`

class in Dart:

```
class Node<T> {
T data;
Node<T>? left;
Node<T>? right;
Node({required this.data});
}
```

In the above code, we have defined a generic class `Node`

with three properties: `data`

, `left`

, and `right`

. The `data`

holds the value of the current node, and the `left`

and `right`

properties hold the references to the left and right child nodes respectively.

Here is an implementation of the abstract class `Tree`

```
abstract class Tree<T> {
Node<T> _insert(Node<T>? node, T data);
bool _search(Node<T>? node, T data);
void _inOrderTraversal(Node<T>? node);
void _postOrderTraversal(Node<T>? node);
void _preOrderTraversal(Node<T>? node);
}
```

The above code is an abstract class called `Tree`

that defines a generic type `T`

. This class contains several methods that are related to the operations that can be performed on a tree data structure such as insert, search, inorder traversal, postorder Traversal, and preorder Traversal.

The `_insert`

method takes in two parameters: a node of type `Node<T>`

and data of type `T`

. This method is responsible for inserting a new node into the tree with the given data.

The `_search`

method takes in two parameters: a node of type `Node<T>`

and data of type `T`

. This method is responsible for searching for a specific node in the tree with the given data.

The `_inOrderTraversal`

method takes in a single parameter: a node of type `Node<T>`

. This method is responsible for traversing the tree in an in-order fashion, visiting the left subtree first, then the root, and finally the right subtree.

The `_postOrderTraversal`

method takes in a single parameter: a node of type `Node<T>`

. This method is responsible for traversing the tree in a post-order fashion, visiting the left subtree first, then the right subtree, and finally the root.

The `_preOrderTraversal`

method takes in a single parameter: a node of type `Node<T>`

. This method is responsible for traversing the tree in a pre-order fashion, visiting the root first, then the left subtree, and finally the right subtree.

It is important to notice that all the methods are marked as abstract, this means that these methods are not implemented in this class and must be implemented in a derived class.

Now, let's define the Binary Tree class and some operations that can be performed on a binary tree.

```
class BinaryTree<T extends Comparable> implements Tree<T> {
Node<T>? root;
//some operation..
}
```

The class `BinaryTree<T extends Comparable>`

is a generic class that implements the `Tree<T>`

interface. The generic type `T`

is constrained to types that extend the `Comparable`

class, which means that the type must have a `compareTo`

method defined. This is used in the insert and search methods to compare the data of nodes in the tree.

The class has a nullable property `root`

of type `Node<T>?`

which is the root of the binary tree.

The class `BinaryTree`

implementation of `Tree`

abstract class which means it has to implement all the methods of the tree interface.

### Insert:

```
void insert(T data) {
root = _insert(root, data);
}
@override
Node<T> _insert(Node<T>? node, T data) {
if (node == null) {
return Node(data: data);
}
if (data.compareTo(node.data) <= 0) {
node.left = _insert(node.left, data);
} else {
node.right = _insert(node.right, data);
}
return node;
}
```

The `insert()`

is used to insert new data into the binary tree. This method uses the private helper method `_insert()`

to recursively find the correct location to insert the new data. If the current node is `null`

, a new node with the data is returned. Otherwise, the `compareTo()`

is used to compare the data to the data of the current node. If the data is less than or equal to the current node's data, the `left`

property is updated with the result of calling `_insert()`

on the left child node. Otherwise, the `right`

property is updated with the result of calling `_insert()`

on the right child node.

### Search:

```
bool search(T data) {
return _search(root, data);
}
@override
bool _search(Node<T>? node, T data) {
if (node == null) {
return false;
}
if (node.data == data) {
return true;
}
if (data.compareTo(node.data) <= 0) {
return _search(node.left, data);
} else {
return _search(node.right, data);
}
}
```

The `search()`

is used to search for a specific piece of data in the binary tree. Like the `insert()`

method, this method uses a private helper method `_search()`

to recursively search the binary tree.

If the current node is `null`

, the data is not in the binary tree and `false`

is returned. If the current node's data is equal to the search data, `true`

is returned.

Otherwise, the `compareTo()`

is used to determine if the search data is less than or greater than the current node's data.

If the search data is less than the current node's data, `_search()`

is called on the left child node. Otherwise, `_search()`

is called on the right child node.

### In-order, Pre-order & Post-order Traversal:

```
void inOrderTraversal() {
_inOrderTraversal(root);
}
void preOrderTraversal() {
_preOrderTraversal(root);
}
void postOrderTraversal() {
_postOrderTraversal(root);
}
@override
void _inOrderTraversal(Node<T>? node) {
if (node != null) {
_inOrderTraversal(node.left);
print(node.data);
_inOrderTraversal(node.right);
}
}
@override
void _postOrderTraversal(Node<T>? node) {
if (node != null) {
print(node.data);
_postOrderTraversal(node.left);
_postOrderTraversal(node.right);
}
}
@override
void _preOrderTraversal(Node<T>? node) {
if (node != null) {
_preOrderTraversal(node.left);
_preOrderTraversal(node.right);
print(node.data);
}
}
```

he `inOrder()`

, `preOrder()`

, and `postOrder()`

methods are used to traverse the binary tree and print out the data in the nodes.

Each of these methods uses a private helper method with the same name but with a leading underscore, such as `_inOrder()`

, to recursively traverse the binary tree.

The `inOrder()`

first recursively calls itself on the left child node, then prints the current node's data, and finally recursively calls itself on the right child node.

The `preOrder()`

method prints the current node's data, recursively calls itself on the left child node, and finally recursively calls itself on the right child node.

The `postOrder()`

method recursively calls itself on the left child node, recursively calls itself on the right child node, and finally prints the current node's data.

here is an implementation full code of Binary tree:

```
class Node<T> {
T data;
Node<T>? left;
Node<T>? right;
Node({required this.data});
}
abstract class Tree<T> {
Node<T> _insert(Node<T>? node, T data);
bool _search(Node<T>? node, T data);
void _inOrderTraversal(Node<T>? node);
void _postOrderTraversal(Node<T>? node);
void _preOrderTraversal(Node<T>? node);
}
class BinaryTree<T extends Comparable> implements Tree<T> {
Node<T>? root;
void insert(T data) {
root = _insert(root, data);
}
@override
Node<T> _insert(Node<T>? node, T data) {
if (node == null) {
return Node(data: data);
}
if (data.compareTo(node.data) <= 0) {
node.left = _insert(node.left, data);
} else {
node.right = _insert(node.right, data);
}
return node;
}
bool search(T data) {
return _search(root, data);
}
@override
bool _search(Node<T>? node, T data) {
if (node == null) {
return false;
}
if (node.data == data) {
return true;
}
if (data.compareTo(node.data) <= 0) {
return _search(node.left, data);
} else {
return _search(node.right, data);
}
}
void inOrderTraversal() {
_inOrderTraversal(root);
}
void preOrderTraversal() {
_preOrderTraversal(root);
}
void postOrderTraversal() {
_postOrderTraversal(root);
}
@override
void _inOrderTraversal(Node<T>? node) {
if (node != null) {
_inOrderTraversal(node.left);
print(node.data);
_inOrderTraversal(node.right);
}
}
@override
void _postOrderTraversal(Node<T>? node) {
if (node != null) {
print(node.data);
_postOrderTraversal(node.left);
_postOrderTraversal(node.right);
}
}
@override
void _preOrderTraversal(Node<T>? node) {
if (node != null) {
_preOrderTraversal(node.left);
_preOrderTraversal(node.right);
print(node.data);
}
}
}
```

In conclusion, binary trees are a powerful and versatile data structure with many uses in computer science. They allow for efficient searching, insertion, and deletion, and their logarithmic height makes them well-suited for algorithms that require fast performance. Understanding binary trees is essential for understanding more complex data structures and algorithms.