Welcome to Day 19 of my 100 Days of DSA challenge! Today, I tackled five problems focused on advanced binary tree concepts. From structural checks to transformations and various traversal techniques, these problems deepened my understanding of binary trees and how to manipulate them efficiently.
Check out my GitHub repository for all the solutions and progress updates at: 100 Days of DSA
Let’s dive in and continue growing! 🚀
1. Check if a Binary Tree is Balanced
This program defines a binary tree and checks if it is balanced. It uses a recursive function to calculate the height of each subtree while simultaneously checking if the height difference between the left and right subtrees of each node is more than 1. If any subtree is unbalanced, the tree is marked as unbalanced.
Code:
#include <iostream>
#include <algorithm>
using namespace std;
// Node structure for the binary tree
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// Function to check the height of the tree and check if it's balanced
int check_balance(Node* root, bool& isBalanced) {
if (root == nullptr) {
return 0; // Base case: height of an empty tree is 0
}
// Recursively get the height of left and right subtrees
int leftHeight = check_balance(root->left, isBalanced);
int rightHeight = check_balance(root->right, isBalanced);
// If the left or right subtree is unbalanced, propagate false
if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1) {
isBalanced = false;
return -1; // Return -1 to indicate an unbalanced tree
}
// Return the height of the current tree
return 1 + max(leftHeight, rightHeight);
}
// Function to check if the tree is balanced
bool is_balanced_tree(Node* root) {
bool isBalanced = true;
check_balance(root, isBalanced);
return isBalanced;
}
// Function to insert a node into the binary tree
Node* insert(Node* root, int value) {
if (root == nullptr) {
return new Node(value);
}
if (value < root->data) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
int main() {
Node* root = nullptr;
root = insert(root, 10);
insert(root, 5);
insert(root, 15);
insert(root, 3);
insert(root, 7);
insert(root, 12);
insert(root, 17);
if (is_balanced_tree(root)) {
cout << "The binary tree is balanced." << endl;
} else {
cout << "The binary tree is not balanced." << endl;
}
return 0;
}
Output:
2. Lowest Common Ancestor of Two Nodes in a Binary Tree
The program defines a binary tree and implements a function to find the lowest common ancestor (LCA) of two nodes. It starts by recursively checking if the current node is either of the target nodes. If not, it searches both the left and right subtrees. If both subtrees return non-null values, the current node is the LCA. Otherwise, it returns the node found in the non-null subtree.
Code:
#include <iostream>
using namespace std;
// Node structure for the binary tree
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// Function to find the lowest common ancestor of two nodes
Node* find_lca(Node* root, int n1, int n2) {
// Base case: if the tree is empty or if we reach a leaf node
if (root == nullptr) {
return nullptr;
}
// If either n1 or n2 matches the root's data, return root
if (root->data == n1 || root->data == n2) {
return root;
}
// Recur for left and right subtrees
Node* left_lca = find_lca(root->left, n1, n2);
Node* right_lca = find_lca(root->right, n1, n2);
// If both left and right calls return non-null, the current node is the LCA
if (left_lca && right_lca) {
return root;
}
// Otherwise, return the non-null child (either left or right)
return (left_lca != nullptr) ? left_lca : right_lca;
}
// Function to insert a node into the binary tree
Node* insert(Node* root, int value) {
if (root == nullptr) {
return new Node(value);
}
if (value < root->data) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
// Function to print the result
void print_lca(Node* lca) {
if (lca != nullptr) {
cout << "Lowest Common Ancestor: " << lca->data << endl;
} else {
cout << "Lowest Common Ancestor not found." << endl;
}
}
int main() {
// Create a binary tree
Node* root = nullptr;
root = insert(root, 20);
insert(root, 10);
insert(root, 30);
insert(root, 5);
insert(root, 15);
insert(root, 25);
insert(root, 35);
int n1 = 5, n2 = 15; // Nodes to find the LCA for
Node* lca = find_lca(root, n1, n2);
print_lca(lca);
return 0;
}
Output:
3. Convert a Binary Tree to its Mirror
This program defines a binary tree and implements a function to convert it into its mirror image. The mirror
function recursively swaps the left and right children of each node in the tree. The insert
function is used to build the binary tree by inserting nodes, while the inorder
function prints the inorder traversal of the tree.
Code:
#include <iostream>
using namespace std;
// Structure for a node in the binary tree
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// Function to convert a binary tree to its mirror
void mirror(Node* root) {
if (root == nullptr) {
return;
}
// Swap the left and right child
swap(root->left, root->right);
// Recursively mirror the left and right subtrees
mirror(root->left);
mirror(root->right);
}
// Function to print inorder traversal of the tree
void inorder(Node* root) {
if (root == nullptr) {
return;
}
inorder(root->left);
cout << root->data << " ";
inorder(root->right);
}
// Function to insert a node in the binary tree (basic BST insertion for simplicity)
Node* insert(Node* root, int value) {
if (root == nullptr) {
return new Node(value);
}
if (value < root->data) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
int main() {
Node* root = nullptr;
root = insert(root, 20);
insert(root, 10);
insert(root, 30);
insert(root, 5);
insert(root, 15);
insert(root, 25);
insert(root, 35);
cout << "Inorder traversal of original tree: ";
inorder(root);
cout << endl;
mirror(root);
cout << "Inorder traversal of mirrored tree: ";
inorder(root);
cout << endl;
return 0;
}
Output:
4. Zig-Zag Level Order Traversal of a Binary Tree
This program implements a zigzag level order traversal of a binary tree. It uses a queue for level-order traversal and a stack to reverse the order of nodes at alternate levels. The direction of traversal alternates after each level: left to right for even levels and right to left for odd levels. At each level, the nodes are processed, and if the traversal is right to left, their values are pushed onto a stack. After processing a level, if the traversal was right to left, the stack is emptied to print the values in reverse order.
Code:
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
// Definition for a binary tree node.
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// Function to perform zigzag level order traversal
void zigzag_level_order(Node* root) {
if (!root) return;
queue<Node*> q; // Queue for level order traversal
stack<int> levelStack; // Stack to reverse the order on alternate levels
bool leftToRight = true; // Flag to check the direction of traversal
q.push(root); // Start with the root node
while (!q.empty()) {
int levelSize = q.size(); // Number of nodes at current level
for (int i = 0; i < levelSize; ++i) {
Node* currentNode = q.front();
q.pop();
// If leftToRight is false, push the node data to stack to reverse order
if (leftToRight) {
cout << currentNode->data << " ";
} else {
levelStack.push(currentNode->data);
}
// Add left and right children to the queue for next level
if (currentNode->left) q.push(currentNode->left);
if (currentNode->right) q.push(currentNode->right);
}
// If leftToRight is false, print the elements from the stack
if (!leftToRight) {
while (!levelStack.empty()) {
cout << levelStack.top() << " ";
levelStack.pop();
}
}
// Alternate the direction for the next level
leftToRight = !leftToRight;
cout << endl;
}
}
// Function to insert nodes in the binary tree (for testing purposes)
Node* insert(Node* root, int value) {
if (!root) return new Node(value);
if (value < root->data) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
int main() {
Node* root = nullptr;
root = insert(root, 1);
insert(root, 2);
insert(root, 3);
insert(root, 4);
insert(root, 5);
insert(root, 6);
insert(root, 7);
cout << "Zigzag level order traversal: " << endl;
zigzag_level_order(root);
return 0;
}
Output:
5. Flatten a Binary Tree to a Linked List
This program flattens a binary tree into a linked list in-place by performing a preorder traversal. It recursively flattens the left and right subtrees, then moves the left child to the right, and connects the original right subtree to the end of the new right subtree. This transformation results in a linked list where each node’s right pointer points to the next node in the preorder traversal, and all left pointers are set to nullptr
.
Code:
#include <iostream>
using namespace std;
// Definition for a binary tree node.
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// Function to flatten the binary tree
void flatten(Node* root) {
if (!root) return;
// Recursively flatten the left and right subtrees
if (root->left) {
flatten(root->left);
// Save the right child before overwriting
Node* temp = root->right;
// Move the left child to the right
root->right = root->left;
root->left = nullptr;
// Move to the last node in the new right subtree (which was the left subtree)
Node* current = root->right;
while (current->right) {
current = current->right;
}
// Connect the original right subtree to the last node of the new right subtree
current->right = temp;
}
// Flatten the right subtree
flatten(root->right);
}
// Function to print the flattened binary tree (linked list)
void print_flattened(Node* root) {
Node* current = root;
while (current) {
cout << current->data << " ";
current = current->right;
}
cout << endl;
}
// Function to insert nodes in the binary tree (for testing purposes)
Node* insert(Node* root, int value) {
if (!root) return new Node(value);
if (value < root->data) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
int main() {
Node* root = nullptr;
root = insert(root, 1);
insert(root, 2);
insert(root, 3);
insert(root, 4);
insert(root, 5);
insert(root, 6);
flatten(root);
cout << "Flattened binary tree to linked list: ";
print_flattened(root);
return 0;
}
Output:
Day 19 expanded my understanding of binary trees by diving into more complex operations and traversal strategies. These problems highlighted the practical applications of binary trees and reinforced how recursion can be effectively used to solve intricate tasks. Excited to continue building on this knowledge in the upcoming days! 🚀