0

我一直在使用代码来实现插入到 AVL 平衡二叉树中,该二叉树将用于大型项目。我选择了 AVL 而不是红黑和其他平衡方法,因为它简单,因为我需要确信它是正确的。我的测试代码工作正常 - 但是我被引导相信可以使用可变余额限制/容差。我还没有看到这是作为 AVL 的一部分实现的,是的,我真的不需要它,但作为调试的一部分,我测试过它失败了。看起来更高的平衡限制可能需要在经过一些旋转后向上传播树以降低树的高度。

有没有其他人成功尝试过可变余额限制?注意:如果可以的话,我不想走树来重新计算平衡因子。在这个阶段,除非有人有一些有用的想法,否则我可能只会坚持 1 的余额限制。

在下面的测试代码中,如果 BALANCELIMIT 为 1,则所有似乎都有效 - 但如果更高则无效

#include <stdio.h>

#define BALANCELIMIT 1      // 1 is good - anything else seems bad
#define MINI(a, b) (a<=b ? a : b)
#define MAXI(a, b) (a>=b ? a : b)

struct TREE { // Standard binary tree structure with a SIGNED balance factor (normally -1, 0, or 1)
    struct TREE *left;
    struct TREE *right;
    signed char balance;
    int key;
} *root = NULL;
int count = 0; // Keep a count of created nodes for debugging purposes

struct TREE *rotateLeft(struct TREE *r) { // Rotate left routine to replace node with node->left
    struct TREE *n = r->left;
    r->left = n->right;
    n->right = r;
    r->balance += 1 - MINI(0, n->balance); // Balance calculation sourced from:-
    n->balance += 1 + MAXI(0, r->balance); // http://oopweb.com/Algorithms/Documents/AvlTrees/Volume/AvlTrees.htm
    return n;
}

struct TREE *rotateRight(struct TREE *r) { // Rotate right routine to replace node with node->right
    struct TREE *n = r->right;
    r->right = n->left;
    n->left = r;
    r->balance += -1 - MAXI(0, n->balance); // Balance calculation sourced from:-
    n->balance += -1 + MINI(0, r->balance); // http://oopweb.com/Algorithms/Documents/AvlTrees/Volume/AvlTrees.htm
    return n;
}

int insert(struct TREE **pnode, int key) {
    struct TREE *node = *pnode;
    if (node == NULL) { // If no node then create one and initialise it to contain new value
        node = malloc(sizeof(struct TREE));
        if (node == NULL) exit(0);
        node->left = NULL;
        node->right = NULL;
        node->balance = 0;
        node->key = key;
        *pnode = node;
        count++;
        return 1;   // Recursion exit - signal tree height change (for new node)
    }

    int cmp = key - node->key;
    if (cmp == 0) return 0;
    if (cmp < 0) {  // Decide if we need to recurse to the left or to the right
        if (insert(&node->left, key)) { // For smaller item recurse left - finish unless a height change was signalled
            if (--node->balance < 0) {  // Adjust node balance - if it got worse then we need to do something...
                if (node->balance >= -BALANCELIMIT) return 1; // If height change is within limit just propagate it upward
                if (node->left->balance > 0) {              // If left path heavy to right then do a double rotation
                    printf("rotateRightLeft node %d to replace %d around %d\n", node->left->right->key, node->key, node->left->key);
                    node->left = rotateRight(node->left);   // Double rotate by first rotating left right node up one level
                    *pnode = rotateLeft(node);              // replacing left - and then rotate it again to replace current node
                } else {
                    printf("rotateLeft node %d to replace %d\n", node->left->key, node->key);
                    *pnode = rotateLeft(node);              // Left path heavy to left so just rotate left node up one level
                }
            }
        }
    } else {
        if (insert(&node->right, key)) { // For larger item recurse right - finish unless a height change was signalled
            if (++node->balance > 0) {  // Adjust node balance - if it got worse then we need to do something...
                if (node->balance <= BALANCELIMIT) return 1; // If height change is within limit just propagate it upward
                if (node->right->balance < 0) {             // If right path heavy to left then do a double rotation
                    printf("rotateLeftRight node %d to replace %d around %d\n", node->right->left->key, node->key, node->right->key);
                    node->right = rotateLeft(node->right);  // Double rotate by first rotating right left node up one level
                    *pnode = rotateRight(node);             // replacing right - and then rotate it again to replace current node
                } else {
                    printf("rotateRight node %d to replace %d\n", node->right->key, node->key);
                    *pnode = rotateRight(node);             // Right path heavy to right so just rotate right node up one level
                }
            }
        }
    }
    return 0;   // Recursion exit - signal no further action required
}

void tree_print(struct TREE *node, int depth) { // Recursive tree print routine
    if (node != NULL) {
        tree_print(node->left, depth+1);
        printf("%*.s%d (%d)\n", depth * 5, "", node->key, node->balance);
        tree_print(node->right, depth+1);
    }
}

int check_count; // node count while checking tree
int check(struct TREE *node) { // Recursive tree check routine - check count, balance factor and key order
    int lh = 0, rh = 0;
    if (node != NULL) {
        check_count++;
        if (node->left != NULL) {
            if (node->key < node->left->key) {
                printf("ERROR node key %d smaller than left node\n", node->key);
                exit(0);
            }
            lh = check(node->left); // check left subtree and get its height
        }
        if (node->right != NULL) {
            if (node->key > node->right->key) {
                printf("ERROR node key %d bigger than right node\n", node->key);
                exit(0);
            }
            rh = check(node->right); // check right subtree and get its height
        }
        if (node->balance != rh - lh) {
            printf("ERROR balance %d for %d should be %d\n", node->balance, node->key, rh-lh);
            exit(0);
        }
    }
    return MAXI(lh, rh) + 1; // Return tree height
}

void tree_check(struct TREE *r) { // wrapper function for tree check routine
    check_count = 0;
    check(r);
    if (check_count != count) {
        printf("ERROR Check count is %d not %d \n", check_count, count);
        exit(0);
    }
}

main() {
    int i;
    for (i=0; i<10; i++) insert(&root, rand() % 30000);
    for (i=0; i<10; i++) {
        int r = rand() % 30000;
        insert(&root, r);
        insert(&root, r+1);
        insert(&root, r+2);
        insert(&root, r-1);
        insert(&root, r-2);
    }
    tree_print(root, 0);
    tree_check(root);
    printf("-- Done ------- %d\n\n\n", count);
}
4

1 回答 1

0

是的,我已经成功地尝试了 AVL 树的可变余额限制。我的确实在插入后走到了树上,调整了平衡因素。我也一直在跟踪每个节点的高度(更多的是出于懒惰)。

虽然您使用的公式(用于在轮换后调整降级和升级节点上的平衡因子)是正确的,但它们还不够!(当 BALANCELIMIT 大于 1 时)。旋转(应用于子树的顶部)只有在使子树更加平衡时才会降低子树的高度。

在双重旋转的情况下,当 BALANCELIMIT 为 1 时,第一次旋转总是应用于平衡因子为 +/- 1 的子树的顶部,并且它会切换不平衡的符号(它不会改变高度子树,因此它不会使子树上方节点的平衡因子无效)。

但是当 BALANCELIMIT 为 2 或更大时,第一次旋转(在双旋转场景中)实际上可能会降低它所应用的子树的高度。这里显示了最简单的示例。

-               first rotate       second rotate
- input         output             output
-  1               1                    3
-   \               \                  / \
-    4               3                1   4  
-   /               / \                \
-  3               2   4                2
- /
-2

该问题是由于第一次旋转(在 2、3、4 子树上)而发生的。它不仅改变了编号为 3 和 4 的节点的平衡因子。因为它使 (2,3,4) 子树达到更好的平衡,它提高了节点 1 的平衡因子(从 +3 到 +2)。您的代码不允许这样做。这就是它破裂的原因。为了演示,从一棵空树开始,然后按顺序将输入 1、4、3、2 插入到您的树中。第一次旋转应该改变节点 1 的平衡因子,但它没有。在第二次旋转之后,节点 1 的平衡因子应该为 1,但平衡因子会不正确,为 2。

当旋转“意外”提高子树高度(如上所示,当 BALANCELIMIT 大于 1 时可能发生)时添加代码来纠正平衡因子可能是可能的,但我怀疑它会比跟踪和调整高度复杂得多,并在每次旋转后重新计算平衡因子。

于 2014-05-10T09:14:34.483 回答