6

I saw some articles about the implementation of AVL's rebalance() function.
After each insertion, we should check the insertion Node's ancestors for balance.
So I think, in order to check ancestors' balance, I got to know the insertion Node's parent.

But, I wonder is there any other way to do that without having to use the parent pointer?
e.g., the node struct:

struct Node {
    int data;
    struct Node *lchild, *rchild; //*parent;
};
Community
  • 1
  • 1
Alcott
  • 17,905
  • 32
  • 116
  • 173

5 Answers5

5

If I remember my data structures homework correctly:

What you do is store the balance factor in the node itself as an int that's either:

  • -1: the node's left subtree is a level higher than the right one (left-heavy)
  • 0 the node is balanced; or
  • 1 the right subtree is higher (right-heavy).

You insert(Node subtree) function returns a boolean, which is true if the insertion made the height of subtree increased. You update the balance factor and rebalance the tree as you return from the recursive insert() calls.

This is probably best explained with a few examples:

If the current node is at balance factor -1, you're inserting into the right subtree, and insert(rchild) returns true, you:

  1. update the balance factor of the current node to 0 - the left subtree was higher before the insertion, and the right subtree's height increased, so they're the same height now; and
  2. return false - the shallower tree's height increased, so the current node's height stays the same

If you're inserting into either subtree, and insert(…) returns false:

  1. the balance factor of the current node is unchanged - the subtree heights are the same as before, and so is the balance
  2. return false - the subtree heights haven't changed, so neither has the current node height

If the current node's balance factor is 0, you're inserting into the left subtree, and insert(lchild) returns true:

  1. the balance factor changes to -1 - the subtrees were the same height before the insertion, and the insertion made the left one higher
  2. return true

(Analogously, if inserting into the right subtree, the balance factor will change to 1.)


If the current node's balance factor is -1, you're inserting into the left subtree, and insert(lchild) returns true:

The balance factor would change to -2, which means you have to rebalance the node by doing the appropriate rotation. I'll admit I'm drawing a blank at what each of the four rotations will do to the balance factor and what insert(current) will return, hopefully the previous examples explain the approach to tracking the nodes' balance sufficiently.

millimoose
  • 39,073
  • 9
  • 82
  • 134
  • I think you're missing the point of the question. The goal is how to implement the rotations without storing parent pointers, not which rotations to apply. – templatetypedef Aug 27 '11 at 01:31
  • @templatetypedef: I understood it as "how to determine whether a node should or should not be rotated", mostly because the Node structure in the question doesn't have a height / balance factor field which is necessary to do this efficiently, and doesn't mention "implementing rotations" at all. – millimoose Aug 27 '11 at 01:42
  • @sii, the way about how to determine which rotation to apply is good, gives me a new idea. BTW, do you mean the Node struct should not contain any height/balance_factor field, just data/lchild/rchild? – Alcott Aug 27 '11 at 03:16
4

You can maintain a stack to the current node while you are traversing the tree

stack<Node*> nodeStack;

When you traverse to a new node, add it to the stack, and then you have your ancestry. When you finish processing a node, pop it from the stack.

** Edit **

Elaboration for the alignment comment:

struct Node {
    int data;
    struct Node *children, *parent
};

when creating children, do it this way:

node.children = new Node[2]; or node.children = malloc(sizeof(Node) * 2);
node.children[0].parent = node;
node.children[1].parent = node;
Mranz
  • 1,260
  • 1
  • 12
  • 20
  • 1
    If you are looking for data compression, then another thing you could do is align the right child to be left child + 1, therefore saving you a pointer space that you can use for the parent. I will edit my answer to elaborate. – Mranz Aug 27 '11 at 00:58
  • 3
    @Alcott: another way is to only use recursion to go down the tree, the stack there is implicit (a stack of function frames) and the `Node` can be kept as part of the function local variable or as the function argument. – Matthieu M. Aug 27 '11 at 10:50
  • 2
    Regarding your optimization with a single new - this is not going to work, cuz now there's no way to correctly free memory. After a rebalance right child can be a left child of another node and calling `delete[] another_node.children` can result in segfault. P.S. I know that this thread is 6 years old :P – Viacheslav Kroilov Mar 14 '17 at 23:57
3

Using double pointers (or references to pointers as you asked for C++) should completely eliminate the need for parent pointers.

typedef struct Node {
    int value;
    int height;
    struct Node *left;
    struct Node *right;
} Node;

int height(Node *node) {
    return (node == NULL) ? -1 : node->height;
}

void insert(Node * & node, int value) {
    if (node == NULL) {
        node = new Node();
        node->value = value;
    } else if (value < node->value) {
        insert(node->left, value);
        if (height(node->left) - height(node->right) == 2) {
            if (value < note->left->value) {
                singleRotateWithLeftChild(node);
            } else {
                doubleRotateWithLeftChild(node);
            }
        }
    } else if (value > node->value) {
        // Symmetric case
    }

    node->height = 1 + max(height(node->left), height(node->right));
}
Can Berk Güder
  • 109,922
  • 25
  • 130
  • 137
  • yes, you're right, but my consideration is, after each insertion, when I rebalance the avl_tree, I have to know the newly inserted node's parent, right? How to do the rebalance() without knowing the parent pointer? – Alcott Aug 27 '11 at 03:11
  • @Alcott: you already know the parent, after the last recursive call returns, node (still) points to the new node's parent. I'll update the code in a minute. – Can Berk Güder Aug 27 '11 at 15:34
  • @CanBerkGüder - do you have the code for your singleRotateWithLeftChild & doubleRotateWithLeftChild - that I could look at please? – Chris Feb 04 '17 at 22:22
2

As there's no complete implementation for this question I've decided to add one. It could be done by using a recursive insert returning a current node. So, here's the code:

typedef struct node
{
    int val;
    struct node* left;
    struct node* right;
    int ht;
} node;

int height(node* current) {
    return current == nullptr ? -1 : current->ht;
}

int balanceFactor(node* root) {
    int leftHeight = height(root->left);
    int rightHeight = height(root->right);

    return leftHeight - rightHeight;
}

int calcHeight(node* n) {
    int leftHeight = height(n->left);
    int rightHeight = height(n->right);

    return max(leftHeight, rightHeight) + 1;
}

node* insert(node * root,int val) {
    /**
    First, recusively insert the item into the tree
    */
    if (root == nullptr) {
        root = new node();
        root->val = val;
    } else if (root->val < val) {
        root->right = insert(root->right, val);
        //the height can increase only because of the right node
        root->ht = std::max(root->ht, root->right->ht + 1);
    } else {
        root->left = insert(root->left, val);
        //the height can increase only because of the left node
        root->ht = std::max(root->ht, root->left->ht + 1);
    }

    //after insertion on this depth is complete check if rebalancing is required

    // the right subtree must be rebalanced
    if (balanceFactor(root) == -2) {
        node* r = root->right;
        node* rl = r->left;
        // it's a right right case
        if (balanceFactor(r) == -1) {
            r->left = root;
            root->right = rl;
            root->ht = calcHeight(root);
            r->ht = calcHeight(r);
            //return new root
            return r;
        } else { // it's a right left case
            node* rlr = rl->right;
            node* rll = rl->left;
            rl->left = root;
            root->right = rll;
            rl->right = r;
            r->left = rlr;

            root->ht = calcHeight(root);
            r->ht = calcHeight(r);
            rl->ht = calcHeight(rl);
            return rl;
        }
    } else if (balanceFactor(root) == 2) {
        node* l = root->left;
        node* lr = l->right;
        // it's a left left case
        if (balanceFactor(l) == 1) {
            l->right = root;
            root->left = lr;
            root->ht = calcHeight(root);
            l->ht = calcHeight(l);

            //return new root
            return l;
        } else { // it's a left right case
            node* lrl = lr->left;
            node* lrr = lr->right;
            lr->right = root;
            lr->left = l;
            root->left = lrr;
            l->right = lrl;

            root->ht = calcHeight(root);
            l->ht = calcHeight(l);
            lr->ht = calcHeight(lr);

            return lr;
        }
    }

    return root;
}
Anatolii
  • 14,139
  • 4
  • 35
  • 65
0

The way I coded it is, as you're searching the tree for the element to delete, temporarily change the child link that you traverse (left or right) to be a link in a stack of traversed nodes (effectively a temporary parent pointer). Then pop each node from this stack, restore the child pointer, and rebalance.

For a C++ encoding, see the remove member function (currently at line 882) in https://github.com/wkaras/C-plus-plus-intrusive-container-templates/blob/master/avl_tree.h .

For a C encoding, see the function whose name is generated by the macro invocation L__(remove) in http://wkaras.webs.com/gen_c/cavl_impl_h.txt .

I don't think having a parent pointer is of any use for inserting.

If you want to delete a node identified by a node pointer rather than a unique key, then it will perhaps be faster I think with parent pointers.

WaltK
  • 724
  • 4
  • 13
  • Hi Walt and welcome to SO, it is customary to not use links (as links do die) and put minimal code example into the answer. Thanks. – Matas Vaitkevicius Oct 24 '16 at 14:17
  • Matas, what is the alternative to links though? I think it's probably safe to link to my own github repos, no? I think in this particular case, the summary is more clear than the pseudo-code would be. If someone would like to see the pseudo code, please request it in a comment, and I will add it. – WaltK Oct 24 '16 at 15:06
  • Putting some of the code on to the page usually the way to do it,... Github is okish, but since you are referring to specific line, I would just copy it over, the other link might die with time, as I can see is quite lengthy so if possible I would just take the relevant bits from it, copying everything over might make answer worse... – Matas Vaitkevicius Oct 24 '16 at 15:11