Locking and Unlocking of Resources arranged in the form of n-ary Tree

02 Mar, 2022
Given an n-ary tree of resources arranged hierarchically such that height of tree is O(Log N) where N is total number of nodes (or resources). A process needs to lock a resource node in order to use it. But a node cannot be locked if any of its descendant or ancestor is locked. Following operations are required for a given resource node:  
islock()- returns true if a given node is locked and false if it is not. A node is locked if lock() has successfully executed for the node.
Lock()- locks the given node if possible and updates lock information. Lock is possible only if ancestors and descendants of current node are not locked.
unLock()- unlocks the node and updates information.
How design resource nodes and implement above operations such that following time complexities are achieved.  
    islock()  O(1)
    Lock()    O(log N)
    unLock()  O(log N)
We strongly recommend you to minimize your browser and try this yourself first. It is given that resources need to be stored in the form of the n-ary tree. Now the question is, how to augment the tree to achieve the above complexity bounds. 
Some General Questions:
Q1. Why doesn’t setting Lock = true alone solve the purpose?A1. Because for the approaches where we move towards the parent to check if locking is possible if a request comes for any node between a locked node and the root, then there’s no way of telling that a descendant node is locked. Hence, variables like isLockable are used to maintain this info.
Q2. Why not lock all nodes from the current node to the root?A2. Because locking, in general, is a resource-intensive operation and performing locking for all nodes right up to the root would be a waste of resources. Hence, lightweight solutions such as introducing a isLockable variable are used.
Method 1 (Simple)A Simple Solution is to store a boolean variable isLocked with every resource node. The boolean variable isLocked is true if the current node is locked, else false. Let us see how operations work using this Approach.  
isLock(): Check isLocked of the given node.
Lock(): If isLocked is set, then the node cannot be locked. Else check all descendants and ancestors of the node and if any of them is locked, then also this node cannot be locked. If none of the above conditions is true, then lock this node by setting isLocked as true.
unLock(): If isLocked of given node is false, nothing to do. Else set isLocked as false.
Time Complexities: 
isLock()  O(1) 
Lock()    O(N)
unLock()  O(1)

Lock is O(N) because there can be O(N) descendants. 
  Method 2 (Time Complexities according to question) The idea is to augment tree with following three fields:  
A boolean field isLocked (same as above method).Parent-Pointer to access all ancestors in O(Log n) time.Count-of-locked-descendants. Note that a node can be ancestor of many descendants. We can check if any of the descendants is locked using this count.
A boolean field isLocked (same as above method).
Parent-Pointer to access all ancestors in O(Log n) time.
Count-of-locked-descendants. Note that a node can be ancestor of many descendants. We can check if any of the descendants is locked using this count.
Let us see how operations work using this Approach.  
isLock(): Check isLocked of the given node.
Lock(): Traverse all ancestors. If none of the ancestors is locked, Count-of-locked-descendants is 0 and isLocked is false, set isLocked of current node as true. And increment Count-of-locked-descendants for all ancestors. Time complexity is O(Log N) as there can be at most O(Log N) ancestors.
unLock(): Traverse all ancestors and decrease Count-of-locked-descendants for all ancestors. Set isLocked of current node as false. Time complexity is O(Log N)
Thanks to Utkarsh Trivedi for suggesting this approach. 
Method 3 (Time Complexities according to question and better memory usage) The idea is to augment tree with following three fields:
A boolean field isLocked (same as above method).Parent-Pointer to access all ancestors in O(Log n) time.isLockable. We can check if any of the descendants is locked using this variable. isLockable is true if none of the descendants are locked else false.
A boolean field isLocked (same as above method).
Parent-Pointer to access all ancestors in O(Log n) time.
isLockable. We can check if any of the descendants is locked using this variable. isLockable is true if none of the descendants are locked else false.
Let us see how operations work using this Approach.
isLock(): Check isLocked of the given node.
Lock(): Traverse all ancestors. If none of the ancestors is locked, isLockable is true and isLocked is false, set isLocked of current node as true. And mark isLockable false for all ancestors. Time complexity is O(Log N) as there can be at most O(Log N) ancestors.
unLock(): Traverse all ancestors and mark isLockable true for all ancestors. Set isLocked of current node as false. Time complexity is O(Log N)
C++
#include <bits/stdc++.h>using namespace std; class narytree {public:    bool isLock;    bool isLockable;    narytree* parent;    vector<narytree*> children;    narytree()    {        isLock = false;        isLockable = true;        parent = NULL;    }    narytree(narytree* parent)    {        isLock = false;        isLockable = true;        this->parent = parent;    }}; bool isLock(narytree* node) { return node->isLock; } void Lock(narytree* node){    if (node->isLockable == false)        return;     narytree* T = node;    bool flag = false;    while (T != NULL) {        if (T->isLock == true) {            flag = true;            break;        }        T = T->parent;    }    if (flag)        return;    else {        node->isLock = true;        T = node;        // marking isLockable as false for ancestor nodes.        while (T != NULL) {            T->isLockable = false;            T = T->parent;        }    }} void unLock(narytree* node){    if (node->isLock == false)        return;    narytree* T = node;    node->isLock = false;    // marking isLockable as true for ancestor nodes.    while (T != NULL) {        T->isLockable = true;        T = T->parent;    }} int main(){    // Creating N-Array Tree    narytree* root = new narytree();     narytree* t1 = new narytree(root);    narytree* t2 = new narytree(root);    narytree* t3 = new narytree(root);     root->children.push_back(t1);    root->children.push_back(t2);    root->children.push_back(t3);     narytree* t5 = new narytree(root->children[0]);    root->children[0]->children.push_back(t5);    narytree* t4 = new narytree(root->children[1]);    root->children[1]->children.push_back(t4);     // Locking t4 node.    Lock(t4);    //    Checking if the t4 node is locked.    cout << "t4 node is locked:"         << ((isLock(t4) == true) ? "true" : "false")         << "\n";    Lock(t2);    cout << "t2 node is locked:"         << ((isLock(t2) == true) ? "true" : "false")         << "\n";    // Unlocking t4 node.    unLock(t4);    //    Now we should be able to lock the tree.    Lock(t2);    cout << "t2 node is locked:"         << ((isLock(t2) == true) ? "true" : "false");     return 0;}
Thanks to Adarsh Singh for suggesting this approach. 
This article is contributed by Abhay Rathi. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
2001adarshsingh
romilpunetha
kalrap615
singghakshay
simmytarika5
n-ary-tree
Tree
Tree
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Introduction to Data Structures
Introduction to Tree Data Structure
What is Data Structure: Types, Classifications and Applications
Binary Tree | Set 3 (Types of Binary Tree)
Binary Tree | Set 2 (Properties)
Diameter of a Binary Tree
Lowest Common Ancestor in a Binary Tree | Set 1
Decision Tree
Diagonal Traversal of Binary Tree
Insertion in a Binary Tree in level order