└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # 100 Core Tree Data Structure Interview Questions in 2025
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | #### You can also find all 100 answers here 👉 [Devinterview.io - Tree Data Structure](https://devinterview.io/questions/data-structures-and-algorithms/tree-data-structure-interview-questions)
11 |
12 |
13 |
14 | ## 1. What is a _Tree Data Structure_?
15 |
16 | A **tree data structure** is a hierarchical collection of nodes, typically visualized with a root at the top. Trees are typically used for representing relationships, hierarchies, and facilitating efficient data operations.
17 |
18 | ### Core Definitions
19 |
20 | - **Node**: The basic unit of a tree that contains data and may link to child nodes.
21 | - **Root**: The tree's topmost node; no nodes point to the root.
22 | - **Parent / Child**: Nodes with a direct connection; a parent points to its children.
23 | - **Leaf**: A node that has no children.
24 | - **Edge**: A link or reference from one node to another.
25 | - **Depth**: The level of a node, or its distance from the root.
26 | - **Height**: Maximum depth of any node in the tree.
27 |
28 | ### Key Characteristics
29 |
30 | - **Hierarchical**: Organized in parent-child relationships.
31 | - **Non-Sequential**: Non-linear data storage ensures flexible and efficient access patterns.
32 | - **Directed**: Nodes are connected unidirectionally.
33 | - **Acyclic**: Trees do not have loops or cycles.
34 | - **Diverse Node Roles**: Such as root and leaf.
35 |
36 | ### Visual Representation
37 |
38 | .png?alt=media&token=d6b820e4-e956-4e5b-8190-2f8a38acc6af&_gl=1*3qk9u9*_ga*OTYzMjY5NTkwLjE2ODg4NDM4Njg.*_ga_CW55HF8NVT*MTY5NzI4NzY1Ny4xNTUuMS4xNjk3Mjg5NDU1LjUzLjAuMA..)
39 |
40 | ### Common Tree Variants
41 |
42 | - **Binary Tree**: Each node has a maximum of two children.
43 | - **Binary Search Tree (BST)**: A binary tree where each node's left subtree has values less than the node and the right subtree has values greater.
44 | - **AVL Tree**: A BST that self-balances to optimize searches.
45 | - **B-Tree**: Commonly used in databases to enable efficient access.
46 | - **Red-Black Tree**: A BST that maintains balance using node coloring.
47 | - **Trie**: Specifically designed for efficient string operations.
48 |
49 | ### Practical Applications
50 |
51 | - **File Systems**: Model directories and files.
52 | - **AI and Decision Making**: Decision trees help in evaluating possible actions.
53 | - **Database Systems**: Many databases use trees to index data efficiently.
54 |
55 | ### Tree Traversals
56 |
57 | #### Depth-First Search
58 |
59 | - **Preorder**: Root, Left, Right.
60 | - **Inorder**: Left, Root, Right (specific to binary trees).
61 | - **Postorder**: Left, Right, Root.
62 |
63 | #### Breadth-First Search
64 |
65 | - **Level Order**: Traverse nodes by depth, moving from left to right.
66 |
67 | ### Code Example: Binary Tree
68 |
69 | Here is the Python code:
70 |
71 | ```python
72 | class Node:
73 | def __init__(self, data):
74 | self.left = None
75 | self.right = None
76 | self.data = data
77 |
78 | # Create a tree structure
79 | root = Node(1)
80 | root.left, root.right = Node(2), Node(3)
81 | root.left.left, root.right.right = Node(4), Node(5)
82 |
83 | # Inorder traversal
84 | def inorder_traversal(node):
85 | if node:
86 | inorder_traversal(node.left)
87 | print(node.data, end=' ')
88 | inorder_traversal(node.right)
89 |
90 | # Expected Output: 4 2 1 3 5
91 | print("Inorder Traversal: ")
92 | inorder_traversal(root)
93 | ```
94 |
95 |
96 | ## 2. What is a _Binary Tree_?
97 |
98 | A **Binary Tree** is a hierarchical structure where each node has up to two children, termed as **left child** and **right child**. Each node holds a data element and pointers to its left and right children.
99 |
100 | ### Binary Tree Types
101 |
102 | - **Full Binary Tree**: Nodes either have two children or none.
103 | - **Complete Binary Tree**: Every level, except possibly the last, is completely filled, with nodes skewed to the left.
104 | - **Perfect Binary Tree**: All internal nodes have two children, and leaves exist on the same level.
105 |
106 | ### Visual Representation
107 |
108 | 
109 |
110 | ### Applications
111 |
112 | - **Binary Search Trees**: Efficient in lookup, addition, and removal operations.
113 | - **Expression Trees**: Evaluate mathematical expressions.
114 | - **Heap**: Backbone of priority queues.
115 | - **Trie**: Optimized for string searches.
116 |
117 | ### Code Example: Binary Tree & In-order Traversal
118 |
119 | Here is the Python code:
120 |
121 | ```python
122 | class Node:
123 | """Binary tree node with left and right child."""
124 | def __init__(self, data):
125 | self.left = None
126 | self.right = None
127 | self.data = data
128 |
129 | def insert(self, data):
130 | """Inserts a node into the tree."""
131 | if data < self.data:
132 | if self.left is None:
133 | self.left = Node(data)
134 | else:
135 | self.left.insert(data)
136 | elif data > self.data:
137 | if self.right is None:
138 | self.right = Node(data)
139 | else:
140 | self.right.insert(data)
141 |
142 | def in_order_traversal(self):
143 | """Performs in-order traversal and returns a list of nodes."""
144 | nodes = []
145 | if self.left:
146 | nodes += self.left.in_order_traversal()
147 | nodes.append(self.data)
148 | if self.right:
149 | nodes += self.right.in_order_traversal()
150 | return nodes
151 |
152 |
153 | # Example usage:
154 | # 1. Instantiate the root of the tree
155 | root = Node(50)
156 |
157 | # 2. Insert nodes (This will implicitly form a Binary Search Tree for simplicity)
158 | values_to_insert = [30, 70, 20, 40, 60, 80]
159 | for val in values_to_insert:
160 | root.insert(val)
161 |
162 | # 3. Perform in-order traversal
163 | print(root.in_order_traversal()) # Expected Output: [20, 30, 40, 50, 60, 70, 80]
164 | ```
165 |
166 |
167 | ## 3. Explain _Height_ and _Depths_ in the context of a _Tree_.
168 |
169 | In tree data structures, the terms **height** and **depth** refer to different attributes of nodes.
170 |
171 | ### Height
172 |
173 | The **height** of a node is the number of edges on the longest downward path between that node and a leaf.
174 |
175 | - **Height of a Node**: Number of edges in the longest path from that node to any leaf.
176 | - **Height of a Tree**: Essentially the height of its root node.
177 |
178 | ### Depth
179 |
180 | The **depth** or **level** of a node represents the number of edges on the path from the root node to that node.
181 |
182 | For instance, in a binary tree, if a node is at depth 2, it means there are two edges between the root and that node.
183 |
184 | ### Visual Representation
185 |
186 | .png?alt=media&token=3c068810-5432-439e-af76-6a8b8dbb746a&_gl=1*1gwqb6o*_ga*OTYzMjY5NTkwLjE2ODg4NDM4Njg.*_ga_CW55HF8NVT*MTY5NzMwNDc2OC4xNTYuMS4xNjk3MzA1OTk1LjUwLjAuMA..)
187 |
188 | ### Code Example: Calculating Height and Depth
189 |
190 | Here is the Python code:
191 |
192 | ```python
193 | class Node:
194 | def __init__(self, data, parent=None):
195 | self.data = data
196 | self.left = None
197 | self.right = None
198 | self.parent = parent
199 |
200 | def height(node):
201 | if node is None:
202 | return -1
203 | left_height = height(node.left)
204 | right_height = height(node.right)
205 | return 1 + max(left_height, right_height)
206 |
207 | def depth(node, root):
208 | if node is None:
209 | return -1
210 | dist = 0
211 | while node != root:
212 | dist += 1
213 | node = node.parent
214 | return dist
215 |
216 | # Create a sample tree
217 | root = Node(1)
218 | root.left = Node(2, root)
219 | root.right = Node(3, root)
220 | root.left.left = Node(4, root.left)
221 | root.left.right = Node(5, root.left)
222 |
223 | # Test height and depth functions
224 | print("Height of tree:", height(root))
225 | print("Depth of node 4:", depth(root.left.left, root))
226 | print("Depth of node 5:", depth(root.left.right, root))
227 | ```
228 |
229 |
230 | ## 4. What is the difference between a _Tree_ and a _Graph_?
231 |
232 | **Graphs** and **trees** are both nonlinear data structures, but there are fundamental distinctions between them.
233 |
234 | ### Key Distinctions
235 |
236 | - **Uniqueness**: Trees have a single root, while graphs may not have such a concept.
237 | - **Topology**: Trees are **hierarchical**, while graphs can exhibit various structures.
238 | - **Focus**: Graphs center on relationships between individual nodes, whereas trees emphasize the relationship between nodes and a common root.
239 |
240 | ### Graphs: Versatile and Unstructured
241 |
242 | - **Elements**: Composed of vertices/nodes (denoted as V) and edges (E) representing relationships. Multiple edges and **loops** are possible.
243 | - **Directionality**: Edges can be directed or undirected.
244 | - **Connectivity**: May be **disconnected**, with sets of vertices that aren't reachable from others.
245 | - **Loops**: Can contain cycles.
246 |
247 | ### Trees: Hierarchical and Organized
248 |
249 | - **Elements**: Consist of nodes with parent-child relationships.
250 | - **Directionality**: Edges are strictly parent-to-child.
251 | - **Connectivity**: Every node is accessible from the unique root node.
252 | - **Loops**: Cycles are not allowed.
253 |
254 | ### Visual Representation
255 |
256 | 
257 |
258 |
259 | ## 5. Define _Leaf_ and _Internal_ nodes in a _Tree_.
260 |
261 | In the context of a **tree data structure**, nodes can take on distinct roles:
262 |
263 | ### Leaf Nodes
264 |
265 | - **Definition**: Nodes without children are leaf nodes. They are the tree endpoints.
266 | - **Properties**:
267 | - In a binary tree, leaf nodes have either one or no leaves.
268 | - They're the only nodes with a depth.
269 | - **Visual Representation**:
270 | - In a traditional tree visualization, leaf nodes are the ones at the "bottom" of the tree.
271 |
272 | ### Internal Nodes
273 |
274 | - **Definition**: Internal nodes, or non-leaf nodes, have at least one child.
275 | - **Properties**:
276 | - They have at least one child.
277 | - They're "in-between" nodes that connect other nodes in the tree.
278 | - **Visual Representation**:
279 | - In a tree diagram, any node that is not a leaf node is an internal node.
280 | - The root, which is often at the "top" in visual representations, is also an internal node.
281 |
282 |
283 | ## 6. What is a _Rooted Tree_, and how does it differ from an _Unrooted Tree_?
284 |
285 | In computer science, a **rooted tree** — often referred to as just a "tree" — is a data structure that consists of nodes connected by edges, typically in a top-down orientation.
286 |
287 | Each tree has exactly one root node, from which all other nodes are reachable. Rooted trees are distinct from **unrooted trees**, as the latter does not have a designated starting point.
288 |
289 | ### Key Concepts
290 |
291 | - **Root Node**: The unique starting node of the tree.
292 | - **Parent and Children**: Nodes are arranged in a hierarchical manner. The root is the parent of all other nodes, and each node can have multiple children but only one parent.
293 | - **Leaf Nodes**: Nodes that have no children.
294 | - **Depth**: The length of the path from a node to the root. The root has a depth of 0.
295 | - **Height**: The length of the longest path from a node to a leaf.
296 |
297 | ### Visual Comparison
298 |
299 | 
300 |
301 | The left side represents a rooted tree with a clear root node. The right side features an unrooted tree, where no such distinction exists.
302 |
303 | ### Code Example: Rooted Tree
304 |
305 | Here is the Python code:
306 |
307 | ```python
308 | class Node:
309 | def __init__(self, data):
310 | self.data = data
311 | self.children = []
312 |
313 | # Example of a rooted tree
314 | root = Node('A')
315 | child1 = Node('B')
316 | child2 = Node('C')
317 |
318 | root.children.append(child1)
319 | root.children.append(child2)
320 |
321 | child3 = Node('D')
322 | child1.children.append(child3)
323 |
324 | # Output: A -> B -> D and A -> C
325 | ```
326 |
327 | ### Code Example: Unrooted Tree
328 |
329 | Here is the Python code:
330 |
331 | ```python
332 | class Node:
333 | def __init__(self, data):
334 | self.data = data
335 | self.neighbours = set()
336 |
337 | # Example of an unrooted tree
338 | node1 = Node('A')
339 | node2 = Node('B')
340 | node3 = Node('C')
341 |
342 | # Connections
343 | node1.neighbours |= {node2, node3}
344 | node2.neighbours.add(node3)
345 |
346 | # Output: A <-> B, A <-> C, B <-> C
347 | ```
348 |
349 | ### Practical Applications
350 |
351 | - **File Systems**: Representing directory structures where each directory is a node of a rooted tree.
352 | - **HTML DOM**: Visualized as a tree with the HTML tag being the root.
353 |
354 | ### Unrooted Trees in Nature
355 |
356 | - **Phylogenetic Trees**: Used to represent the evolutionary relationships among a group of species without a clear ancestor.
357 | - **Stemmatology**: In textual criticism, they're used to describe textual relationships without identifying an original or "root" text.
358 |
359 |
360 | ## 7. What is a _N-ary Tree_, and how does it generalize a binary tree?
361 |
362 | An **N-ary Tree** is a data structure with nodes that can have up to $N$ children, allowing for more than two child nodes. This property provides the tree with a more flexible hierarchical structure compared to the strict two-child policy observed in **binary trees**, permitting either a **binary** or **non-binary** organization of nodes, as per the figure below.
363 |
364 | ### N-ary Tree Representation
365 |
366 | 
367 |
368 | In **N-ary Trees**, nodes can have a dynamic number of child nodes, dictated by the length of a list or array where these nodes are stored. This contrasts with the binary tree, where nodes have a fixed, often predefined, number of children (either 0, 1, or 2).
369 |
370 | Some embodiments conveniently use an array, where each element corresponds to a child. While this allows for $O(1)$ child lookups, **a la binary heap**. It does mean that every internal node has *N* slots, wasting memory on nodes with fewer children.
371 |
372 | ### Code Example: N-ary Tree Node
373 |
374 | Here is the Python code:
375 |
376 | ```python
377 | class Node:
378 | def __init__(self, data, children=None):
379 | self.data = data
380 | self.children = children if children else []
381 |
382 | def add_child(self, child):
383 | self.children.append(child)
384 | ```
385 |
386 | ### Use of N-ary Trees
387 |
388 | 1. **File Systems**: Represent directories and files, where a directory can have multiple subdirectories or files.
389 | 2. **Abstract Syntax Trees (ASTs)**: Used in programming languages to represent the structure of source code. A node in the AST can correspond to various constructs in the code, such as an expression, statement, or declaration.
390 | 3. **Multi-way Trees**: Employed in the management of organized data, particularly in the **index structures** of databases or data warehouses.
391 |
392 | 4. **User Interfaces**: Structures with multiple child components, like list views, trees, or tabbed panels, exemplify the role of n-ary trees in this domain.
393 | 5. **Data Analytics and Machine Learning**: Classification and decision-making processes often entail using multi-way trees, such as **N-ary decision trees**.
394 |
395 |
396 | ## 8. Discuss the properties of a _Full Binary Tree_.
397 |
398 | A **Full Binary Tree**, often referred to as a "strictly binary tree," is a rich mathematical structure that presents a distinctive set of characteristics.
399 |
400 | It is a **tree data structure** in which each node has either zero or two children. Every **leaf node** is found at the same **level**, and the tree is perfectly balanced, reaching its most compact organizational form.
401 |
402 | ### Full Binary Tree Properties
403 |
404 | #### Node Count
405 | - The number of nodes in a Full Binary Tree, $N$, is odd for a finite tree due to the one-root node.
406 | - With $N+1$ nodes, a Full Binary Tree may still be complete (not full).
407 | - The $n$th level holds between $\frac{n}{2}+1$ and $n$ nodes, with all levels either full or skipping just the last node.
408 |
409 | #### Relationship between Node Count and Tree Height
410 | - A Full Binary Tree's height, $h$, can range from $\log_2(N+1) - 1$ (for balanced trees) to $N - 1$ (for degenerate trees).
411 |
412 | #### External Nodes (Leaves)
413 | - The number of leaves, $L$, is:
414 | - even when $N$ is one less than a power of two.
415 | - odd when $N$ is equal to a power of two.
416 | - A Full Binary Tree always has one more non-leaf node than leaf nodes.
417 |
418 | #### Parent-Child Relationship
419 |
420 | - The parent of the $n$th node in a Full Binary Tree is given by:
421 |
422 | $$ \text{parent}(n) = \Bigg \lceil \frac{n}{2} \Bigg \rceil - 1 $$
423 |
424 | - The $n$th node's children are at the $2n+1$ and $2n+2$ positions, respectively.
425 |
426 | - Parent and child relationships are computationally efficient in Full Binary Trees due to direct relationships without needing to search or iterate.
427 |
428 | #### Expression Evaluation & Parentheses Distribution
429 |
430 | - Full Binary Trees excel at two commonly encountered applications:
431 | 1. Efficient expression evaluation, especially arithmetic expressions.
432 | 2. Parentheses management, commonly employed for nested logic or mathematical expressions. These associations are usually implemented using Binary Operators.
433 |
434 | #### Array Representation
435 |
436 | With position $n$ adjusting from 0:
437 | - The root is at index 0.
438 | - The left child of node $n$ is at index $2n+1$.
439 | - The right child of node $n$ is at index $2n+2$.
440 |
441 |
442 | ## 9. What is the significance of the _degree of a node_ in a tree?
443 |
444 | The **degree of a tree** is determined by its most prominent node's degree, which is also the maximum degree of any of its nodes.
445 |
446 | In practical terms, the degree of a tree provides insights into its structure, with numerous applications in computer science, networking, and beyond.
447 |
448 | ### Degree of a Node
449 |
450 | The **degree of a node** in a tree is the count of its children, often referred to simply as "children" or "subtrees". Nodes are categorized based on their degrees:
451 |
452 | - **Leaf nodes** have a degree of zero as they lack children.
453 | - **Non-terminal or internal nodes** (which are not leaves) have a degree greater than zero.
454 |
455 | In tree nomenclature, an internal node with $k$ children is called a node of degree $k$. For example, a node with three children is a node of degree 3.
456 |
457 | ### Code Example: Node Degree
458 |
459 | Here is the Python code:
460 |
461 | ```python
462 | # Node with degree 3
463 | class NodeDegree3:
464 | def __init__(self, children):
465 | self.children = children
466 |
467 | node_degree_3 = NodeDegree3([1, 2, 3]) # Example of a node with degree 3
468 |
469 | # Node with degree 0
470 | class NodeDegree0:
471 | def __init__(self, value):
472 | self.value = value
473 |
474 | node_degree_0 = NodeDegree0(5) # Example of a node with degree 0
475 | ```
476 |
477 | ### Tree Degrees
478 |
479 | The **degree of a tree** is the maximum of the degrees of its nodes. Every individual node’s degree is less than or equal to the tree degree.
480 |
481 | By extension, if a tree's maximum degree is $k$, then:
482 |
483 | - Each level in the tree contains at most $k$ nodes.
484 | - The number of leaves at any level $h$ (with $h < k$) is at most $1 + 1 + 1 + \ldots + 1 = k$.
485 |
486 | The above properties show how the degree of a tree provides a powerful handle on its structure.
487 |
488 | ### Code Example: Tree Degree
489 |
490 | Here is the Python code:
491 |
492 | ```python
493 | # Tree
494 | class Tree:
495 | def __init__(self, root):
496 | self.root = root
497 |
498 | def get_degree(self):
499 | def get_node_degree(node):
500 | if not node:
501 | return 0
502 | return len(node.children)
503 |
504 | max_degree = 0
505 | nodes_to_process = [self.root]
506 |
507 | while nodes_to_process:
508 | current_node = nodes_to_process.pop(0)
509 | if current_node:
510 | current_degree = get_node_degree(current_node)
511 | max_degree = max(max_degree, current_degree)
512 | nodes_to_process.extend(current_node.children)
513 |
514 | return max_degree
515 |
516 | # Define a tree with root and nodes as per requirements and, then you can find the degree of the tree using the get_degree method
517 | # tree = Tree(...)
518 |
519 | # Example: tree.get_degree() will give you the degree of the tree
520 | ```
521 |
522 |
523 | ## 10. Explain the concept of a _Path_ in a tree.
524 |
525 | A **path** in a tree is a sequence of connected nodes representing a traversal from one node to another. The path can be directed – from the root to a specific node – or undirected. It can also be the shortest distance between two nodes, often called a **geodesic path**. Several types of paths exist in trees, such as a **Downward Path**, a **Rooted Tree Path**, and an **Unrooted Tree Path**.
526 |
527 | ### Path Types
528 |
529 | #### Downward Path
530 | This type of path travels from a node to one of its descendants, and each edge in the path is in the same direction.
531 |
532 | #### Upward Path
533 | This is the reversed variant of a Downward Path, which goes from a node to one of its ancestors.
534 |
535 | #### Rooted Tree Paths
536 | These types of paths connect nodes starting from the root. Paths may originate from root and end in any other node. When paths move from the root to a specific node, they're often called **ancestral paths**.
537 |
538 | #### Unrooted Tree Paths
539 | Contrary to Rooted Tree Paths, Unrooted Tree Paths can be considered in rooted trees but not binary trees. They do not necessarily involve the root.
540 |
541 | #### Specific Tree Path Types
542 | - **Siblings**: Connects two sibling nodes or nodes that are children of the same parent.
543 | - **Ancestor-Descendant**: Represents a relationship between an ancestor and a descendant node.
544 | - **Prefix-Suffix**: These paths are specifically defined for binary trees, and they relate nodes in the tree based on their arrangement in terms of children from a particular node or based on their position in the binary tree.
545 |
546 |
547 | ### Code Example: Identifying Path Types
548 |
549 | Here is the Python code:
550 |
551 | ```python
552 | class Node:
553 | def __init__(self, value):
554 | self.value = value
555 | self.children = []
556 |
557 | def path_from_root(node):
558 | path = [node.value]
559 | while node.parent:
560 | node = node.parent
561 | path.append(node.value)
562 | return path[::-1]
563 |
564 | def find_direction(node, child_value):
565 | return "down" if any(c.value == child_value for c in node.children) else "up"
566 |
567 | # Sample usage
568 | root = Node("A")
569 | root.children = [Node("B"), Node("C")]
570 | root.children[0].children = [Node("D"), Node("E")]
571 | root.children[1].children = [Node("F")]
572 |
573 | # Path 'A' -> 'B' -> 'E' is a Downward Path
574 | print([n.value for n in path_from_root(root.children[0].children[1])])
575 | # Output: ['A', 'B', 'E']
576 |
577 | # Path 'C' -> 'F' is a Sibling Path (Downward Path constrained to siblings)
578 | print(find_direction(root, "F"))
579 | # Output: down
580 | ```
581 |
582 |
583 | ## 11. What is a _Binary Search Tree_ (BST)?
584 |
585 | A **Binary Search Tree** (BST) is a binary tree optimized for quick lookup, insertion, and deletion operations. A BST has the distinct property that each node's left subtree contains values smaller than the node, and its right subtree contains values larger.
586 |
587 | ### Key Characteristics
588 |
589 | - **Sorted Elements**: Enables efficient searching and range queries.
590 | - **Recursive Definition**: Each node and its subtrees also form a BST.
591 | - **Unique Elements**: Generally, BSTs do not allow duplicates, although variations exist.
592 |
593 | ### Visual Representation
594 |
595 | 
596 |
597 | ### Formal Properties
598 |
599 | For any node $N$ in the BST:
600 |
601 | $$
602 | $$
603 | \forall L \in \text{Left-Subtree}(N) & : \text{Value}(L) < \text{Value}(N) \\
604 | \forall R \in \text{Right-Subtree}(N) & : \text{Value}(R) > \text{Value}(N)
605 | $$
606 | $$
607 |
608 | ### Practical Applications
609 |
610 | - **Databases**: Used for efficient indexing.
611 | - **File Systems**: Employed in OS for file indexing.
612 | - **Text Editors**: Powers auto-completion and suggestions.
613 |
614 | ### Time Complexity
615 |
616 | - **Search**: $O(\log n)$ in balanced trees; $O(n)$ in skewed trees.
617 | - **Insertion**: Averages $O(\log n)$; worst case is $O(n)$.
618 | - **Deletion**: Averages $O(\log n)$; worst case is $O(n)$.
619 |
620 | ### Code Example: Validating a BST
621 |
622 | Here is the Python code:
623 |
624 | ```python
625 | def is_bst(node, min=float('-inf'), max=float('inf')):
626 | if node is None:
627 | return True
628 | if not min < node.value < max:
629 | return False
630 | return (is_bst(node.left, min, node.value) and
631 | is_bst(node.right, node.value, max))
632 | ```
633 |
634 |
635 | ## 12. Explain the difference between a _Binary Tree_ and a _Binary Search Tree_ (BST).
636 |
637 | While **Binary Trees** and **Binary Search Trees** (BSTs) share a tree-like structure, they are differentiated by key features such as node ordering and operational efficiency.
638 |
639 | ### Key Distinctions
640 |
641 | #### Node Ordering
642 |
643 | - **Binary Tree**: No specific ordering rules between parent and child nodes.
644 | - **BST**: Nodes are ordered—left children are smaller, and right children are larger than the parent node.
645 |
646 | #### Efficiency in Searching
647 |
648 | - **Binary Tree**: $O(n)$ time complexity due to the need for full traversal in the worst case.
649 | - **BST**: Improved efficiency with $O(\log n)$ time complexity in balanced trees.
650 |
651 | #### Node Insertion and Deletion
652 |
653 | - **Binary Tree**: Flexible insertion without constraints.
654 | - **BST**: Ordered insertion and deletion to maintain the tree's structure.
655 |
656 | #### Tree Balancing
657 |
658 | - **Binary Tree**: Generally, balancing is not required.
659 | - **BST**: Balancing is crucial for optimized performance.
660 |
661 | #### Use Cases
662 |
663 | - **Binary Tree**: Often used in heaps, tries, and tree traversal algorithms.
664 | - **BST**: Commonly used in dynamic data handling scenarios like maps or sets in standard libraries.
665 |
666 | ### Visual Comparison
667 |
668 | #### Binary Tree
669 |
670 | In this **Binary Tree**, there's no specific ordering. For instance, 6 is greater than its parent node, 1, but is on the left subtree.
671 |
672 | ```plaintext
673 | 5
674 | / \
675 | 1 8
676 | / \
677 | 6 3
678 | ```
679 |
680 | #### Binary Search Tree
681 |
682 | Here, the **Binary Search Tree** maintains the ordering constraint. All nodes in the left subtree (3, 1) are less than 5, and all nodes in the right subtree (8) are greater than 5.
683 |
684 | ```plaintext
685 | 5
686 | / \
687 | 3 8
688 | / \
689 | 1 4
690 | ```
691 |
692 | ### Key Takeaways
693 |
694 | - **BSTs** offer enhanced efficiency in lookups and insertions.
695 | - **Binary Trees** provide more flexibility but can be less efficient in searches.
696 | - Both trees are comparable in terms of memory usage.
697 |
698 |
699 | ## 13. What is a _Complete Binary Tree_?
700 |
701 | The **Complete Binary Tree** (CBT) strikes a balance between the stringent hierarchy of full binary trees and the relaxed constraints of general trees. In a CBT, all levels, except possibly the last, are completely filled with nodes, which are as far left as possible.
702 |
703 | This structural constraint makes CBTs amenable for array-based representations with efficient storage and speeding up operations like insertions by maintaining the complete level configuration.
704 |
705 | ### Visual Representation
706 |
707 | 
708 |
709 | ### Characteristics
710 |
711 | - A binary tree is "complete" if, for every level $l$ less than the **height $h$** of the tree:
712 | - **All** of its nodes are at the **leftmost side** at level $l$ (When the left side of level $l$ is filled, and the level $l-1$ is complete).
713 | - **None** of these nodes are at a deeper level.
714 |
715 | ### Key Properties
716 |
717 | - A CBT has a minimal **possible height** for a given number of nodes, $n$. This height ranges from $\lfloor \log_2(n) \rfloor$ to $\lceil \log_2(n) \rceil$.
718 | - Behaviorally, except for the last level, a CBT behaves like a _full binary tree_.
719 | - The **number of levels** in a CBT is either the **height** $h$ or $h-1$. The tree is traversed until one of these levels.
720 |
721 | ### Examples
722 |
723 | In both "Before" and "After" trees, the properties of being "complete" and the behavior of the last level are consistently maintained.
724 |
725 | #### Before -> complete
726 | ```plaintext
727 | A **Level 0** (height - 0)
728 | / \
729 | B C **Level 1** (height - 1)
730 | / \ / \
731 | D E f G **Level 2** (height - 2)
732 |
733 | The height of this tree is 2.
734 | ```
735 |
736 | #### After -> complete
737 |
738 | ```plaintext
739 | A **Level 0** (height - 0)
740 | / \
741 | B C **Level 1** (height - 1)
742 | / \ / \
743 | D E F G **Level 2** (height - 2)
744 | / \
745 | H I **level 3** (height 3)
746 |
747 | The height of this tree is 3.
748 | ```
749 |
750 | ### Visual Inspection for Completeness
751 |
752 | Here are some guidelines for identifying whether a given $binary tree$ is "complete":
753 |
754 | - Work from the root, keeping track of the **last encountered node**.
755 | - At each level:
756 | - If a node is **empty**, it and all its children should be the **last nodes** seen
757 | - If a node is **non-empty**, add its children to the **queue of nodes to be inspected.**
758 |
759 | - Continue this process:
760 | - either you'll reach the end of the tree (identified as complete so far)
761 | - or you'll find a level for which "completeness" is violated.
762 |
763 | If the latter is the case, the tree is not "complete."
764 |
765 | #### Code example: Complete Binary Tree Verification
766 |
767 | Here is the Python code:
768 |
769 | ```python
770 | def is_complete(root):
771 | if root is None:
772 | return True
773 |
774 | is_leaf = lambda node: not (node.left or node.right)
775 |
776 | queue = [root]
777 | while queue:
778 | current = queue.pop(0)
779 | if current.left:
780 | if is_leaf(current.left) and current.right:
781 | return False
782 | queue.append(current.left)
783 | if current.right:
784 | if is_leaf(current.right):
785 | return False
786 | queue.append(current.right)
787 | return True
788 | ```
789 |
790 |
791 | ## 14. Define a _Perfect Binary Tree_ and its characteristics.
792 |
793 | A **Perfect Binary Tree**, also known as a **strictly binary tree**, is a type of **binary tree** where each internal node has exactly two children, and all leaf nodes are at the same level.
794 |
795 | The tree is "full" or "complete" at every level, and the number of nodes in the tree is $2^{h+1} - 1$, where $h$ is the height of the tree. Each level $d$ of the tree contains $2^d$ nodes.
796 |
797 | ### Characteristics
798 |
799 | - **Node Count**: $2^{h+1} - 1$ nodes.
800 | - **Level of Nodes**: All levels, apart from the last, are completely filled.
801 | - **Height-Node Relationship**: A perfect binary tree's height $h$ is given by $\log_2 (n+1) - 1$ and vice versa.
802 |
803 | ### Visual Representation
804 |
805 | 
806 |
807 | ### Code Example: Checking for Perfect Binary Tree
808 |
809 | Here is the Python code:
810 |
811 | ```python
812 | # Helper function to calculate the height of the tree
813 | def tree_height(root):
814 | if root is None:
815 | return -1
816 | left_height = tree_height(root.left)
817 | right_height = tree_height(root.right)
818 | return 1 + max(left_height, right_height)
819 |
820 | # Function to check if the tree is perfect
821 | def is_perfect_tree(root):
822 | height = tree_height(root)
823 | node_count = count_nodes(root)
824 | return node_count == 2**(height+1) - 1
825 | ```
826 |
827 |
828 | ## 15. Explain what a _Degenerate (or Pathological) Tree_ is and its impact on operations.
829 |
830 | A **Degenerate Tree** refers to a tree structure where each parent node has only one associated child node. Consequently, the tree effectively becomes a linked list.
831 |
832 | ### Tree Traversal Efficiency
833 |
834 | The nature of degenerate trees directly influences traversal efficiency:
835 |
836 | - **In-Order**: Optimal only for a sorted linked list.
837 | - **Pre-Order and Post-Order**: In these lists, trees are consistently better. Thus, pre-order and post-order strategies remain dependable.
838 | - **Level-Order (BFS)**: This method accurately depicts tree hierarchy, rendering it robust. Nonetheless, it may demand excessive memory for large trees.
839 |
840 | ### Applications
841 |
842 | While degenerate trees might seem limited, they offer utility in various contexts:
843 |
844 | - **Text Parsing**: They are fundamental in efficient string searches and mutable string operations.
845 | - **Arithmetic Expression Trees**: Serve as the basis for implementing mathematical formulae due to their linear property.
846 | - **Database Indexing**: Prerequisite for rapid and indexed I/O operations in databases.
847 |
848 | ### Commonly Used Techniques
849 |
850 | Several strategies mitigate challenges posed by degenerate trees:
851 |
852 | - **Rebalancing**: Techniques such as "AVL Trees" and "Red-Black Trees" facilitate periodic restoration of tree balance.
853 | - **Perfect Balancing**: Schemes like "Full k-Ary Trees" adjust branches or bind multiple nodes to a single parent, restoring balance.
854 | - **Multiway Trees**: Tactics involving trees with multiple children per node (e.g., B-Trees) can offset tree linearization.
855 |
856 | ### Code Example: Degenerate Tree
857 |
858 | Here is the Python code:
859 |
860 | ```python
861 | class Node:
862 | def __init__(self, value):
863 | self.value = value
864 | self.left = None
865 | self.right = None
866 |
867 | # Create a degenerate tree
868 | root = Node(1)
869 | root.left = Node(2)
870 | root.left.left = Node(3)
871 | root.left.left.left = Node(4)
872 | ```
873 |
874 |
875 |
876 |
877 | #### Explore all 100 answers here 👉 [Devinterview.io - Tree Data Structure](https://devinterview.io/questions/data-structures-and-algorithms/tree-data-structure-interview-questions)
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
--------------------------------------------------------------------------------