Finding if a Node X is Present in Subtree of Another Node Y or Vice Versa for Q Queries


For Q queries, do the following to see if node X is in node Y's subtree or vice versa: Starting at node Y, navigate its subtree while keeping an eye out for node X. If discovered, X is in Y's subtree. Start at node X and navigate its subtree to find node Y in the reverse scenario. If Y is found, Y is a member of X's subtree. To efficiently carry out these tests, use tree traversal algorithms like Depth−First Search (DFS) or Breadth−First Search (BFS). The procedure guarantees accurate relationship determination between the nodes in each query.

Methods Used

  • Naive DFS Traversal

  • Using HashSet

Naive DFS Traversal

The Naive DFS Traversal method begins with a Depth−First Search (DFS) traversal from node Y's subtree in order to determine whether a node X is present in the subtree of node Y or vice versa for Q queries. Verify whether node X is encountered throughout the traversal. If it is, it proves that X is a part of Y's subtree. Repeat the procedure by starting the DFS from node X to look for node Y in the case where the scenario is the opposite. This approach may become expensive for large trees owing to repetitive traversals, but it is simple and effective for small trees and a modest number of queries.

Algorithm

  • For each query, decide which node Y (or X, in the case of a Y:X situation) and which node X (or Y, in the case of a Y:X scenario) will be tested.

  • Starting from the subtree anchored at node Y, begin a Depth−First Search (DFS) exploration.

  • Verify if the current node and node X are equal while traversing.

  • Node X certifies that X is present in Y's subtree if node X is encountered. Deliver True.

  • Check if X is present in Y's subtree by going through the process again for each Q query.

  • Start the DFS traversal from the subtree rooted at node X in the opposite direction.

  • During the traverse, see if the current node matches node Y.

  • Node Y confirms that Y is present in X's subtree if node Y is encountered. Deliver True.

  • Check the results of each Q query to see if Y is present in X's subtree.

  • Return False to show that the node is absent if the traversal for a query is finished but the node is not found in the subtree.

  • The process swiftly establishes the link between nodes X and Y in each query using DFS traversal.

Example

#include <iostream>
using namespace std;

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

bool isNodePresent(TreeNode* root, int target) {
    if (root == NULL)
        return false;
    if (root->val == target)
        return true;
    return isNodePresent(root->left, target) || isNodePresent(root->right, target);
}

bool isSubtree(TreeNode* Y, TreeNode* X) {
    if (Y == NULL)
        return false;
    if (Y->val == X->val && isNodePresent(Y, X->val))
        return true;
    return isSubtree(Y->left, X) || isSubtree(Y->right, X);
}

int main() {
    
    TreeNode* rootY = new TreeNode(3);
    rootY->left = new TreeNode(4);
    rootY->right = new TreeNode(5);
    rootY->left->left = new TreeNode(1);
    rootY->left->right = new TreeNode(2);

    TreeNode* rootX = new TreeNode(4);
    rootX->left = new TreeNode(1);
    rootX->right = new TreeNode(2);

    if (isSubtree(rootY, rootX))
        cout << "X is present in the subtree of Y." << endl;
    else
        cout << "X is not present in the subtree of Y." << endl;

    return 0;
}

Output

X is present in the subtree of Y.

Using HashSet

Create a HashSet containing each node in the subtree rooted at Y in order to use HashSet for Q queries. Check to see if each node is present in the HashSet after traversing the subtree with X as the root. It is assumed that X is in Y's subtree if any node from X's subtree is found in the HashSet. Create a HashSet for the subtree rooted at X in a similar manner, and then determine whether any nodes from Y's subtree are present in it. This technique considerably reduces time complexity, making it more advantageous for huge trees and multiple searches when compared to HashSet's quick node lookups as opposed to repeated traversals for each query. By effectively storing and accessing nodes, the HashSet technique optimises the procedure, producing quicker and more accurate results.

Algorithm

  • Make a HashSet that is empty.

  • Perform a Depth−First Search (DFS) traversal of the subtree starting with node Y.

  • Add each node you come across throughout the traversal to the HashSet.

  • Start from node X and traverse its subtree using DFS for each query.

  • Verify each node in the HashSet generated in step 1 is present while traversing the subtree of X.

  • Node X is in the subtree of node Y if any node from X's subtree is found in the HashSet.

  • Repeat steps 2 through 6 to check if any nodes from the subtree of Y are present after creating a HashSet for the subtree of X.

  • For each query, the procedure effectively establishes the connection between nodes X and Y.

Example

#include <iostream>
#include <unordered_set>
using namespace std;

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

void createHashSet(TreeNode* root, unordered_set<int>& nodes) {
    if (!root) return;
    nodes.insert(root->val);
    createHashSet(root->left, nodes);
    createHashSet(root->right, nodes);
}

bool isSubtree(TreeNode* Y, TreeNode* X) {
    unordered_set<int> YNodes;
    createHashSet(Y, YNodes);

    unordered_set<int> XNodes;
    createHashSet(X, XNodes);

    for (const auto& val : YNodes) {
        if (XNodes.count(val))
            return true;
    }
    return false;
}

int main() {
  

    TreeNode* rootY = new TreeNode(3);
    rootY->left = new TreeNode(4);
    rootY->right = new TreeNode(5);
    rootY->left->left = new TreeNode(1);
    rootY->left->right = new TreeNode(2);

    TreeNode* rootX = new TreeNode(4);
    rootX->left = new TreeNode(1);
    rootX->right = new TreeNode(2);

    if (isSubtree(rootY, rootX))
        cout << "X is present in the subtree of Y." << endl;
    else
        cout << "X is not present in the subtree of Y." << endl;

    return 0;
}

Output

X is present in the subtree of Y.

Conclusion

In conclusion, for Q queries, two techniques—Naive DFS Traversal and Using HashSet—can be used to ascertain whether a node X is present in the subtree of a node Y or vice versa. In the Naive DFS Traversal, the subtree rooted at node Y is traversed using Depth−First Search (DFS), and node X's existence is checked. In the reversed scenario, it also conducts a DFS traversal from node X to find node Y. The Using HashSet technique, on the other hand, optimises node lookups by building a HashSet that contains every node in the subtree rooted at Y. It then creates a HashSet for the subtree of X and determines whether any of Y's nodes are present in it. This method considerably reduces time complexity when compared to repetitive traversals, which makes it a preferable choice for larger trees and multiple queries. Both approaches produce accurate results while accommodating different tree sizes and query amounts. Selecting an appropriate method can result in efficient and precise node relationship assessments depending on the particular use case and tree features.

Updated on: 02-Aug-2023

45 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements