0

I'm coding a generic AVL tree as both a challenge to myself and, if I can do it properly, a resource for other CS students out there.

As one usually would, I started by implementing a recursive insertion function, which works. However, due to efficiency, I tried to implement the same function, but iteratively. I searched online and found lots of implementations, but the rest of the code was always too different from mine, which led me to continue attempting to create an implementation from scratch.

These are the relevant defined types:

typedef struct info {
    int id;
    char name[200];
} data;

typedef struct avl_node {
    void * data;
    struct avl_node * left;
    struct avl_node * right;
    int height;
} * avl_node_t;

typedef struct avl_tree {
    avl_node_t root;
    int num_nodes;
    int (*less)(const void * first, const void * second);
    int (*key)(const void * data);
} * avl_t;

And as follows is the iterative insertion function (which doesn't work as intended):

avl_node_t
avl_insert(avl_node_t node, void * data)
{
    long key1 = 0, key2 = 0;
    avl_node_t aux = NULL;

    while (1) {
        if (node == NULL) {
            node = avl_node_create(data);
            break;
        }

        key1 = key((void*)&data);
        key2 = key((void*)&(node->data));

        aux = node;

        if (less((void*)&key1, (void*)&key2))
            node = node->left;
        else
            node = node->right;
    }

    if (aux != NULL) {
        if (less((void*)&key1, (void*)&key2))
            aux->right = node;
        else if (less((void*)&key2, (void*)&key1))
            aux->left = node;
    }

    node = avl_balance(node);

    return node;
}

In which the avl_balance function is defined as such:

static short
balance(avl_node_t node)
{
    if (node == NULL)
        return 0;

    return avl_node_height(node->left) - avl_node_height(node->right);
}
static avl_node_t
avl_balance(avl_node_t node)
{
    short balance_factor;

    if (node == NULL)
        return node;

    balance_factor = balance(node);

    if (balance_factor > 1)
        if (balance(node->left) >= 0)
            node = avl_rotRight(node);
        else
            node = avl_rotLeftRight(node);
    else if (balance_factor < -1)
        if (balance(node->right) <= 0)
            node = avl_rotLeft(node);
        else
            node = avl_rotRightLeft(node);
    else
        update_height(node);

    return node;
}

This is the code I'm using to test the AVL tree:

int main()
{
    data d1 = { 1, "we are number one" };
    data d2 = { 2, "boneless pizza" };
    data d3 = { 3, "hehe" };
    data d4 = { 4, "achoo" };
    data d5 = { 5, "I like C" };
    data d6 = { 6, "Assembly is cool too" };
    data d7 = { 7, "SIGSEGV" };

    avl_t tree = avl_create();

    avl_node_t root = tree->root;

    root = avl_insert(root, (void*)&d1);
    traverse(root);
    root = avl_insert(root, (void*)&d2);
    traverse(root);
    root = avl_insert(root, (void*)&d3);
    root = avl_insert(root, (void*)&d4);
    root = avl_insert(root, (void*)&d5);
    root = avl_insert(root, (void*)&d6);
    root = avl_insert(root, (void*)&d7);
    traverse(root);

    free(tree);

    exit(0);
}

In which traverse is defined as such:

void visit(void * d)
{
    data * my_data = (data*)d;
    printf("I am element number %d named %s\n", (*my_data).id, (*my_data).name);
    fflush(stdout);
}
void traverse(avl_node_t node)
{
    if (node == NULL)
        return;

    traverse(node->left);
    traverse(node->right);
    visit(node->data);
}

And finally, this is the output I'm getting from this test:

I am element number 1 named we are number one
I am element number 2 named boneless pizza
I am element number 7 named SIGSEGV

Thank you in advance.

Saucy Goat
  • 1,587
  • 1
  • 11
  • 32
  • 2
    Note what it says in [Is it a good idea to typedef pointers](https://stackoverflow.com/questions/750178/is-it-a-good-idea-to-typedef-pointers) — TL;DR, the answer is generally "No". – Jonathan Leffler Mar 06 '19 at 21:21
  • 1
    You're not using the `less` or `key` members while inserting the data; and you don't pass pointers to initialize them to the `create` function. Inside the `insert()` function, you are doing some funky stuff with `key1` and `key2`, completely subverting the generic code framework you have, and you call `less` and `key` directly — they aren't the function pointer members of the structure. (This is not C++!) – Jonathan Leffler Mar 06 '19 at 21:26
  • 1
    You don't show `traverse()` or `avl_balance()`, so this isn't an MCVE ([MCVE]). And your `free(tree)` is bound to leak a lot of allocated memory if you've inserted any data into the tree. You might well need a function pointer to free the data, too. Or maybe you need an `apply()` function a bit like `traverse()` that can be called to free the data in each node. – Jonathan Leffler Mar 06 '19 at 21:28
  • 1
    Of the various examples, the [Efficient AVL tree - rosettacode.com](https://rosettacode.org/wiki/AVL_tree/C) has a decent iterative insert. – David C. Rankin Mar 06 '19 at 22:01
  • @JonathanLeffler thank you for the reply. ```less``` and ```key``` are both meant to be assigned to the structure, but for testing purposes I simply defined them in the same file and used them. As to key1 and key2: the aforementioned function ```key```, its purpose is so that the user decides by which criteria he wants his data compared. That function should provide an integer by which elements can be compared. – Saucy Goat Mar 06 '19 at 23:08
  • @JonathanLeffler I thought the post would be too long if I added them, but I shall edit them into the answer. Apologies for the inconvenience. As to the freed memory, I have some code that frees everything but deleted it for convenience. If/when this issue is fixed, I will add it so that someone who finds this can effortlessly keep the memory sheet clean :) – Saucy Goat Mar 06 '19 at 23:11
  • @DavidC.Rankin thank you very much, I was baffled by the difficulty I had finding a decent AVL implementation, and the resource you provided is very useful. – Saucy Goat Mar 06 '19 at 23:13
  • Please note that any functions that appear in the code after the latest edit that aren't defined in the post do work properly, as I have tested them when I made the recursive version. – Saucy Goat Mar 06 '19 at 23:22
  • 1
    Please note that we don't have copies of your working functions, so it is hard for us to know how they work. MCVE expands to include **Complete** — which means "can be run". – Jonathan Leffler Mar 10 '19 at 01:45
  • 1
    IMHO you need to reconsider the design requirements. Once you append a new node somewhere in a tree and the particular subtree becomes too high, you need to restore a balance at upper levels. This can be done either on the path back while _returning from recursion_ or by walking through ancestor nodes _iteratively by some `parent` pointers_. You use none of them, so your code is unable to rebalance a tree. – CiaPan May 31 '21 at 13:27

1 Answers1

0

If you do not mind, i implemented it in c++ version.

// In the view of C, just omit the template declaration and replace the class with struct
template<typename T>
class AVL_tree{
    private:
        class AVL_Node{  
            public:
                T val;
                AVL_Node* left;
                AVL_Node* right;
                AVL_Node* parent; 
                int height;
                
                // Node Constructor -
                AVL_Node():left{nullptr}, right{nullptr}, parent{nullptr}, val{}, height{0}{}
                AVL_Node(T val): left{nullptr}, right{nullptr}, parent{nullptr}, height{0}, val{val}{}
        };
        AVL_Node* root;

        short (*cmp_func)(T, T);
        
        // Utility -
        void add_child(AVL_Node* prev_nd, AVL_Node* chd_nd){
            if(prev_nd == nullptr){
                this->root = chd_nd;
                return;
            }
                
            // update parent pointer first.
            switch(cmp_func(chd_nd->val, prev_nd->val)){
                case 1:
                    prev_nd->right = chd_nd;
                    break;
                case 0:
                    prev_nd->left = chd_nd;
                    break;
                case -1:
                    cerr << "Warning : The element should not be duplicate." << endl;
                    return;
                default:
                    cerr << "ERROR_MESSAGE : The self-defin compare func should return triple value (1, 0, -1)." << endl;
                    return;
            }

            // update parent pointer of child node
            chd_nd->parent = prev_nd;
        }

        AVL_Node* find_node(T val){
            AVL_Node* prev_ptr = nullptr;
            for(AVL_Node* tmp_ptr = this->root ; tmp_ptr != nullptr ; ){
                prev_ptr = tmp_ptr;
                switch(cmp_func(val, tmp_ptr->val)){
                    case 1:
                        tmp_ptr = tmp_ptr->right;
                        break;
                    case 0:
                        tmp_ptr = tmp_ptr->left;
                        break;
                    case -1:
                        return prev_ptr;
                }
            }
            // for not find the node, return their parent node.
            return prev_ptr;
        }

        int get_max(int a, int b){ return (a >= b) ? a : b; }

        int get_height(AVL_Node* ptr_nd){
            if(ptr_nd == nullptr)
                return -1;

            return ptr_nd->height;
        }

        int cal_balance(AVL_Node* nd_ptr){ return get_height(nd_ptr->left) - get_height(nd_ptr->right); }

        AVL_Node* Right_rotate(AVL_Node* curr_nd){
            AVL_Node* lft_chd = curr_nd->left;
            AVL_Node* rgt_suc = lft_chd->right;

            // Perform rotation
            lft_chd->right = curr_nd;
            curr_nd->left = rgt_suc;

            // update parent pointer of current pointed node and child node 
            lft_chd->parent = curr_nd->parent;
            curr_nd->parent = lft_chd;
            if(rgt_suc != nullptr)
                rgt_suc->parent = curr_nd;
        
            // Update heights
            lft_chd->height = get_max(get_height(lft_chd->left), get_height(lft_chd->right)) + 1;
            curr_nd->height = get_max(get_height(curr_nd->left), get_height(curr_nd->right)) + 1;

            return lft_chd;
        }

        AVL_Node* Left_rotate(AVL_Node* curr_nd){
            AVL_Node* rgt_chd =  curr_nd->right;
            AVL_Node* lft_suc = rgt_chd->left;
        
            // Perform rotation
            rgt_chd->left = curr_nd;
            curr_nd->right = lft_suc;

            // update parent pointer of current pointed node and child node 
            rgt_chd->parent = curr_nd->parent;
            curr_nd->parent = rgt_chd;
            if(lft_suc != nullptr)
                lft_suc->parent = curr_nd;

            // Update heights
            rgt_chd->height = get_max(get_height(rgt_chd->left), get_height(rgt_chd->right)) + 1;
            curr_nd->height = get_max(get_height(curr_nd->left), get_height(curr_nd->right)) + 1;

            return rgt_chd;
        }

        void splice(AVL_Node* ptr_nd){
            /* remove node confirm that the ptr_nd have successor in single side.
                                                                   Case 1.   ;    Case 2.   */
            AVL_Node* succsor_nd = (ptr_nd->left != nullptr) ? ptr_nd->left : ptr_nd->right;
            
            if(ptr_nd == this->root){  // for remove the root.
                this->root = succsor_nd;
            }else{              
                AVL_Node* par_nd = ptr_nd->parent;
                if(par_nd->left == ptr_nd)
                    par_nd->left = succsor_nd;
                else
                    par_nd->right = succsor_nd;

                if(succsor_nd != nullptr) succsor_nd->parent = par_nd;
            }
        }

    public:
        enum Order{  // for the order traversal.
            pre_order,
            post_order,
            in_order
        };

        // Constructor -
        AVL_tree():root{nullptr}, cmp_func{&defau_cmp<T>}{}
        AVL_tree(short (*def_cmp_func)(T, T)):root{nullptr}, cmp_func{def_cmp_func}{}
        
        // Operation - 
        void insert(T val){
            // BST insertion operation
            AVL_Node* prev_nd = find_node(val);  
            AVL_Node* chd_nd = new AVL_Node(val);
            add_child(prev_nd, chd_nd);
            
            // Balance the tree
            for(AVL_Node* nd_ptr = prev_nd ; nd_ptr != nullptr ; nd_ptr = nd_ptr->parent){
                const int& bf = cal_balance(nd_ptr);
                
                // Left bias unbalance
                if( bf > 1 ){       
                    if(val > nd_ptr->left->val)
                        nd_ptr->left = Left_rotate(nd_ptr->left);

                    // update parent's pointer
                    AVL_Node* par_ptr = nd_ptr->parent;
                    if(par_ptr != nullptr && par_ptr->right == nd_ptr)
                        par_ptr->right = Right_rotate(nd_ptr);
                    else if(par_ptr != nullptr && par_ptr->left == nd_ptr)
                        par_ptr->left = Right_rotate(nd_ptr);
                    else
                        Right_rotate(nd_ptr);
                    
                // Right bias unbalance
                }else if(bf < -1){      
                    if(val < nd_ptr->right->val)
                        nd_ptr->right = Right_rotate(nd_ptr->right);

                    // update parent's pointer
                    AVL_Node* par_ptr = nd_ptr->parent;
                    if(par_ptr != nullptr && par_ptr->right == nd_ptr)
                        par_ptr->right = Left_rotate(nd_ptr);
                    else if(par_ptr != nullptr && par_ptr->left == nd_ptr)
                        par_ptr->left = Left_rotate(nd_ptr);
                    else  // nd_ptr equal root 
                        Left_rotate(nd_ptr);

                // else, the sub-tree is already balanced
                }else{  
                    nd_ptr->height = get_max(get_height(nd_ptr->left), get_height(nd_ptr->right)) + 1;
                } 

                // finally update the new root pointer 
                if(nd_ptr->parent == nullptr)
                    this->root = nd_ptr;
            }
        }

        // remove operation is still working on it though.

        // Smart_queue just like a queue offer general interface, you can use stl-container.
        void BF_print(){
            Smart_queue<AVL_Node*> nd_que(this->root);
            while(!nd_que.is_empty()){
                AVL_Node* tmp_ptr = nd_que.pop();
                if(tmp_ptr == nullptr)
                    continue;

                cout << tmp_ptr->val << " ";
                nd_que.push(tmp_ptr->left);
                nd_que.push(tmp_ptr->right);
            }
        }

};

黃啟恩
  • 21
  • 3