Trees are a specific type of graph. Formally, a tree is a
connected graph consisting of N nodes and N-1 edges. A defining
feature of a tree is having no cycles (i.e. acyclic). If an edge is
added to a tree, a cycle will be created, since a graph with as many
edges as nodes must have at least one cycle. In addition, removing
any edge from a tree will divide it into two separate components.
The degree of a node in a graph is defined as the number of
nodes that it is connected to (its neighbors).
The leaves of a tree are the nodes with a degree of 1 (i.e.
only one neighbor).
This counts as a tree because there are 5 nodes, 4 edges, and no
cycles.
The below example is not a tree because it has a cycle with nodes 2,
4, and 5. If the edge from node 4 to node 5 is removed, the graph
would become a tree.
In a rooted tree, the children of a node are its lower neighbors,
and the parent of a node is its upper neighbors. Every node has
exactly one parent besides the root, which doesn’t have a parent.
For example, the parent of node 5 is 2. However, a node can have
multiple children. The children of node 2 are 4 and 5.
It is important to understand that the structure of a tree acts
recursively. In other words, each node of the tree could be viewed
as the root of a subtree that contains all the nodes
underneath that node. For instance, the subtree of node 2 in the
example above would consist of nodes 2, 4, and 5. The subtree of
node 3 would consist only of node 3, and the subtree of node 1 would
consist of nodes 1, 2, 3, 4, and 5.
Trees can be traversed using standard graph traversal techniques
such as DFS (depth-first search) and BFS (breadth-first search). We
will go over the DFS traversal, starting from the root node.
For these examples, we will represent the graph pictured above.
Code:
numberNodes=5
edges = [[False]*(numberNodes+1) for i inrange(numberNodes+1)]
visited = [False] * (numberNodes+1)
# adding edges to graph with an adjacency matrix
edges[1][2] = True
edges[2][1] = True
edges[1][3] = True
edges[3][1] = True
edges[2][4] = True
edges[4][2] = True
edges[2][5] = True
edges[5][2] = Truedefdfs(node):
visited[node]=True;
# printing current nodeprint("Node " + str(node))
for u inrange(numberNodes+1):
if edges[node][u] andnot visited[u]:
# recursively visiting children nodes
dfs(u)
# started at the root node 1
dfs(1)
Output:
> Node 1 > Node 2
> Node 4
> Node 5
> Node 3
Code:
import java.util.ArrayList;
publicclassMain{
staticint numberNodes=5;
staticboolean[] visited = newboolean[numberNodes+1];
static ArrayList> edges = new ArrayList>(numberNodes+1);
publicstaticvoiddfs(int node){
visited[node]=true;
// printing current node
System.out.println("Node "+node);
for (Integer u: edges.get(node)){
if (!visited[u]){
// recursively visiting children nodes
dfs(u);
}
}
}
publicstaticvoidmain(String[] args){
// adding edges to graph with an adjacency listfor (int i=0; i());
}
edges.get(1).add(2);
edges.get(2).add(1);
edges.get(1).add(3);
edges.get(3).add(1);
edges.get(2).add(4);
edges.get(4).add(2);
edges.get(2).add(5);
edges.get(5).add(2);
// starting at the root node 1
dfs(1);
}
}
Output:
> Node 1 > Node 2
> Node 4
> Node 5
> Node 3
Code:
#include<bits/stdc++.h>usingnamespace std;
constint numberNodes=5;
bool visited[numberNodes+1]={0};
vector edges[numberNodes+1];
voiddfs(int node){
visited[node]=true;
// printing current node
cout << "Node " << node << endl;
for (auto u: edges[node]){
if (!visited[u]){
// recursively visiting children nodesdfs(u);
}
}
}
intmain(){
// adding edges to graph with an adjacency list
edges[1].push_back(2);
edges[2].push_back(1);
edges[1].push_back(3);
edges[3].push_back(1);
edges[2].push_back(4);
edges[4].push_back(2);
edges[2].push_back(5);
edges[5].push_back(2);
// starting at the root node 1dfs(1);
}
Output:
> Node 1 > Node 2
> Node 4
> Node 5
> Node 3
Note: the implementation in Python uses an
adjacency matrix to store the edges, while the implementation
in Java and C++ uses an adjacency list to store the edges. An
adjacency matrix is a N by N matrix where each cell is either
true or false depending on if an edge exists between nodes i and
nodes j. An adjacency list is a collection of N lists (one
for each node) that contain the nodes that are adjacent or connected
to a given node.
One well-known tree problem is calculating the size of each node’s
subtree. This can be done by going through the tree with a DFS
traversal, and recursively calculating the subtree size of each
node’s children. Then, we just add the subtree size of each node’s
children to the current node. The key is to initialize each node’s
subtree size as 1 when that node is visited.
Code:
numberNodes=5
edges = [[False]*(numberNodes+1) for i inrange(numberNodes+1)]
visited = [False] * (numberNodes+1)
subtreeSize = [0] * (numberNodes+1)
# adding edges to graph with an adjacency matrix
edges[1][2] = True
edges[2][1] = True
edges[1][3] = True
edges[3][1] = True
edges[2][4] = True
edges[4][2] = True
edges[2][5] = True
edges[5][2] = Truedefdfs(node):
visited[node]=True;
# initializing subtree size to 1
subtreeSize[node]=1for u inrange(numberNodes+1):
if edges[node][u] andnot visited[u]:
# recursively visiting children nodes
dfs(u)
# adding children's subtree size to current node's subtree size
subtreeSize[node]+=subtreeSize[u]
# started at the root node 1
dfs(1)
for i inrange(1, numberNodes+1):
print("Node: "+ str(i) +" Subtree Size: "+ str(subtreeSize[i]));