【问题标题】:Implementing an AVL tree using a List使用列表实现 AVL 树
【发布时间】:2013-05-07 08:20:54
【问题描述】:

所以我使用参数化列表实现了二叉搜索树,即

List<Node> tree = new List<>();

树工作正常。节点本身对它的父节点或子节点一无所知。这是因为我根据索引计算位置,例如

                         If i is the index of some None N then:
                                N's left child is in tree[i*2] 
                                N's right child is in tree[(i*2)+1]

这个二叉树工作正常。但现在我想把 AVL 树的特性放到它上面。我被困在这一点上,因为我不知道如何在列表上进行轮换。在旋转时,我如何移动新根的孩子?事实上,他们必须改变索引,不是吗?在列表上执行此操作也会给我一个问题,即每次添加节点时显示树都需要循环遍历列表。这不会在 O(logn) 中发生,因为它会破坏 AVL 树的整个点。

我在 C# 中执行此操作。我只想知道如何使用列表或任何基于数组的可索引数据结构而不是链接列表有效地制作此 AVL 树。这很重要。非常感谢您提供一些代码来说明。

【问题讨论】:

  • list 实现 tree 几乎没有任何意义。 array(它是 List&lt;T&gt; 的底层数据结构)是一种合适的数据结构,仅用于在非常特定的情况下保存树——例如拥有一个静态完美平衡的二叉搜索树,它消耗尽可能少的内存并提供出色的数据局部性。但是,当您想对这样的树进行更改时,我会说,复杂性是不需要的。
  • 所以你建议我使用数组?如果是这样,那么旋转将如何工作
  • 如果您想将树卸载/加载到文件中,使用数组表示树非常好。正如@OndrejTucny 所写, List 在 C# 动态数组中(它基本上是来自 Java 的 ArrayList)。
  • 好的,我对你告诉我的内容做了一些研究。我知道你来自哪里,我完全同意。它仍然给我留下了旋转问题。谁能用数组回答这个问题?
  • 是什么阻止了你把一棵树作为一棵树让你的生活变得更艰难?

标签: c# data-structures avl-tree


【解决方案1】:

在数组/列表中表示树的方式对于堆数据结构很常见,但它实际上不适用于任何其他类型的树。特别是,您不能(有效地)对 AVL 树执行此操作,因为每次旋转都需要过多的复制。

【讨论】:

    【解决方案2】:

    对于没有可用 malloc 的嵌入式应用程序,我需要这个。在我尝试是否可以完成之前,还没有完成任何类型的数据结构算法实现。在编写代码时,我意识到我必须移动很多东西。我寻找补救措施并找到了这篇文章。

    感谢 Chris 的回复,我不会再花时间在上面了。我会找到其他方法来实现我需要的。

    【讨论】:

    • 也许自己实现一个简单的内存分配器。如果您可以进行简化,例如从不删除节点,那可能非常简单。或者只是保持一个排序的数组并一直复制东西......它可能比你想象的要快。
    • 只保留一个排序数组并一直复制内容
    【解决方案3】:

    我相信我找到了答案,诀窍是在列表中上下移动子树,这样您就不会在旋转时覆盖有效节点。

    void shiftUp(int indx, int towards) {
        if (indx >= size || nodes[indx].key == NULL) {
            return;
        }
        nodes[towards] = nodes[indx];
        nodes[indx].key = NULL;
        shiftUp(lChild(indx), lChild(towards));
        shiftUp(rChild(indx), rChild(towards));
    }
    
    void shiftDown(int indx, int towards) {
        if (indx >= size || nodes[indx].key == NULL) {
            return;
        }
    
        // increase size so we can finish shifting down
        while (towards >= size) { // while in the case we don't make it big enough
            enlarge();
        }
    
        shiftDown(lChild(indx), lChild(towards));
        shiftDown(rChild(indx), rChild(towards));
        nodes[towards] = nodes[indx];
        nodes[indx].key = NULL;
    }
    

    如您所见,这是通过递归探索每个子树直到 NULL(在此定义为 -1)节点然后一个一个地向上或向下复制每个元素来完成的。

    通过这个我们可以定义根据这个Wikipedia Tree_Rebalancing.gif命名的4种类型的旋转

    void rotateRight(int rootIndx) {
        int pivotIndx = lChild(rootIndx);
    
        // shift the roots right subtree down to the right
        shiftDown(rChild(rootIndx), rChild(rChild(rootIndx)));
        nodes[rChild(rootIndx)] = nodes[rootIndx]; // move root too
    
        // move the pivots right child to the roots right child's left child
        shiftDown(rChild(pivotIndx), lChild(rChild(rootIndx)));
    
        // move the pivot up to the root
        shiftUp(pivotIndx, rootIndx);
    
        // adjust balances of nodes in their new positions
        nodes[rootIndx].balance--; // old pivot
        nodes[rChild(rootIndx)].balance = (short)(-nodes[rootIndx].balance); // old root
    }
    
    void rotateLeft(int rootIndx) {
        int pivotIndx = rChild(rootIndx);
    
        // Shift the roots left subtree down to the left
        shiftDown(lChild(rootIndx), lChild(lChild(rootIndx)));
        nodes[lChild(rootIndx)] = nodes[rootIndx]; // move root too
    
        // move the pivots left child to the roots left child's right child
        shiftDown(lChild(pivotIndx), rChild(lChild(rootIndx)));
    
        // move the pivot up to the root
        shiftUp(pivotIndx, rootIndx);
    
        // adjust balances of nodes in their new positions
        nodes[rootIndx].balance++; // old pivot
        nodes[lChild(rootIndx)].balance = (short)(-nodes[rootIndx].balance); // old root
    }
    
    
    // Where rootIndx is the highest point in the rotating tree
    // not the root of the first Left rotation
    void rotateLeftRight(int rootIndx) {
        int newRootIndx = rChild(lChild(rootIndx));
    
        // shift the root's right subtree down to the right
        shiftDown(rChild(rootIndx), rChild(rChild(rootIndx)));
        nodes[rChild(rootIndx)] = nodes[rootIndx];
    
        // move the new roots right child to the roots right child's left child
        shiftUp(rChild(newRootIndx), lChild(rChild(rootIndx)));
    
        // move the new root node into the root node
        nodes[rootIndx] = nodes[newRootIndx];
        nodes[newRootIndx].key = NULL;
    
        // shift up to where the new root was, it's left child
        shiftUp(lChild(newRootIndx), newRootIndx);
    
        // adjust balances of nodes in their new positions
        if (nodes[rootIndx].balance == -1) { // new root
            nodes[rChild(rootIndx)].balance = 0; // old root
            nodes[lChild(rootIndx)].balance = 1; // left from old root
        } else if (nodes[rootIndx].balance == 0) {
            nodes[rChild(rootIndx)].balance = 0;
            nodes[lChild(rootIndx)].balance = 0;
        } else {
            nodes[rChild(rootIndx)].balance = -1;
            nodes[lChild(rootIndx)].balance = 0;
        }
    
        nodes[rootIndx].balance = 0;
    }
    
    // Where rootIndx is the highest point in the rotating tree
    // not the root of the first Left rotation
    void rotateRightLeft(int rootIndx) {
        int newRootIndx = lChild(rChild(rootIndx));
    
        // shift the root's left subtree down to the left
        shiftDown(lChild(rootIndx), lChild(lChild(rootIndx)));
        nodes[lChild(rootIndx)] = nodes[rootIndx];
    
        // move the new roots left child to the roots left child's right child
        shiftUp(lChild(newRootIndx), rChild(lChild(rootIndx)));
    
        // move the new root node into the root node
        nodes[rootIndx] = nodes[newRootIndx];
        nodes[newRootIndx].key = NULL;
    
        // shift up to where the new root was it's right child
        shiftUp(rChild(newRootIndx), newRootIndx);
    
        // adjust balances of nodes in their new positions
        if (nodes[rootIndx].balance == 1) { // new root
            nodes[lChild(rootIndx)].balance = 0; // old root
            nodes[rChild(rootIndx)].balance = -1; // right from old root
        } else if (nodes[rootIndx].balance == 0) {
            nodes[lChild(rootIndx)].balance = 0;
            nodes[rChild(rootIndx)].balance = 0;
        } else {
            nodes[lChild(rootIndx)].balance = 1;
            nodes[rChild(rootIndx)].balance = 0;
        }
    
        nodes[rootIndx].balance = 0;
    }
    

    请注意,如果移位会覆盖节点,我们只需复制单个节点

    至于在每个节点中存储余额的效率是必须的,因为在每个节点处获取高度差异会非常昂贵

    int getHeight(int indx) {
        if (indx >= size || nodes[indx].key == NULL) {
            return 0;
        } else {
            return max(getHeight(lChild(indx)) + 1, getHeight(rChild(indx)) + 1);
        }
    }
    

    虽然这样做需要我们在修改列表时更新受影响节点的余额,但通过仅更新严格必要的情况,这可能会有些效率。 删除此调整是

    // requires non null node index and a balance factor baised off whitch child of it's parent it is or 0
    private void deleteNode(int i, short balance) {
        int lChildIndx = lChild(i);
        int rChildIndx = rChild(i);
    
        count--;
        if (nodes[lChildIndx].key == NULL) {
            if (nodes[rChildIndx].key == NULL) {
    
                // root or leaf
                nodes[i].key = NULL;
                if (i != 0) {
                    deleteBalance(parent(i), balance);
                }
            } else {
                shiftUp(rChildIndx, i);
                deleteBalance(i, 0);
            }
        } else if (nodes[rChildIndx].key == NULL) {
            shiftUp(lChildIndx, i);
            deleteBalance(i, 0);
        } else {
            int successorIndx = rChildIndx;
    
            // replace node with smallest child in the right subtree
            if (nodes[lChild(successorIndx)].key == NULL) {
                nodes[successorIndx].balance = nodes[i].balance;
                shiftUp(successorIndx, i);
                deleteBalance(successorIndx, 1);
            } else {
                int tempLeft;
                while ((tempLeft = lChild(successorIndx)) != NULL) {
                    successorIndx = tempLeft;
                }
                nodes[successorIndx].balance = nodes[i].balance;
                nodes[i] = nodes[successorIndx];
                shiftUp(rChild(successorIndx), successorIndx);
                deleteBalance(parent(successorIndx), -1);
            }
        }
    }
    

    类似的插入是

    void insertBalance(int pviotIndx, short balance) {
        while (pviotIndx != NULL) {
            balance = (nodes[pviotIndx].balance += balance);
    
            if (balance == 0) {
                return;
            } else if (balance == 2) {
                if (nodes[lChild(pviotIndx)].balance == 1) {
                    rotateRight(pviotIndx);
                } else {
                    rotateLeftRight(pviotIndx);
                }
                return;
            } else if (balance == -2) {
                if (nodes[rChild(pviotIndx)].balance == -1) {
                    rotateLeft(pviotIndx);
                } else {
                    rotateRightLeft(pviotIndx);
                }
                return;
            }
    
            int p = parent(pviotIndx);
    
            if (p != NULL) {
                balance = lChild(p) == pviotIndx ? (short)1 : (short)-1;
            }
    
            pviotIndx = p;
        }
    }
    

    正如您所看到的,这只是使用“节点”的普通数组,因为我从给定gitHub array-avl-tree 的 c 代码翻译它并从(我将在评论中发布的链接)进行优化和平衡,但在一个列表

    最后,我对 AVL 树或最佳实现知之甚少,因此我不认为这是没有错误或最有效的,但至少出于我的目的已经通过了初步测试

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    • 1970-01-01
    相关资源
    最近更新 更多