65.9K
CodeProject 正在变化。 阅读更多。
Home

二叉树介绍

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (14投票s)

2007年3月5日

7分钟阅读

viewsIcon

110157

downloadIcon

3094

二叉树的描述和一维数据中的快速搜索

引言

在本文中,我介绍了二叉树和分层数据结构。在示例项目中,我将二叉树与 qsort 进行了比较。二叉树使用 C++ 模板定义。它可以在任何支持 C++ 的环境中使用,并且适用于支持数据比较运算符 `<` 和 `>` 的任何数据类型。描述易于理解。要使用该模板,您需要在 C++ 项目中包含 *BTree.h*。为了平衡和优化数据插入,我使用了一种简单的重新排序方法,而不是 红黑树AVL 树。优点是数据插入和树创建所需的时间略少。即使不是完美平衡,此方法也能确保数据搜索需要 `log2` 次比较操作。

我主要关注多维数据结构,因此我主要使用易于泛化为 N 维的算法。

背景

二叉树是分层数据结构,允许在一维数据中进行插入和快速的最近邻搜索。它可以用作 qsort 和二分搜索的替代方法,以快速查找数据数组中最接近的点。二叉树的优点是结构简单,可以泛化为多个维度——即所谓的 `KD Tree`。因此,了解它的工作原理以及它如何执行数据搜索很有帮助。我将保持解释清晰易懂。

首先,我们定义一个 `树`。树是由一组节点组成的数据结构。每个节点都有指向其他一些节点的链接,这些节点称为 `子节点` 和 `父节点`。它们之间的区别在于,子节点是根据某种 `准则` 选择的,而父节点链接仅仅保留了我们如何到达给定节点的历史记录。

二叉树有两个子节点——`Left` 和 `Right`,以及一个 `父节点`。父节点链接对于最近邻搜索和树的优化是必需的。

树的每个节点都包含某种类型的数据,其值为 `X`。左子节点的值 `X` 必须小于相应的右值——`X > Left->X` 和 `Right->X > X`。这是二叉树的基本准则。树还有一个 `Root`。根是唯一没有 `Parent` 的节点。当需要将一个值为 `X` 的新节点添加到树中时,我们总是从 `Root` 开始,并沿着树向下,直到到达一个空位置。每次通过一个节点时,我们通过将值 `X` 与节点的值进行比较来决定向左还是向右——如果 `X` 较小,我们向左走;如果 `X` 较大,我们向右走。

在上面的例子中,新值是 `100`。要找到这个值的位置,请按照树中的步骤操作。从根节点开始,我们检查 `100` 是小于还是大于 `127`。在这种情况下,它较小,所以我们向左走到值为 `111` 的节点。现在,`100` 小于 `111`,所以我们向左走到节点 `60`。这次 `100` 较大,但 `60` 没有 `Right` 子节点,它是一个空位置,所以我们将 `100` 附加到这里。

现在让我们看看二叉树如何用于一些有用的事情。让我们看看如何找到限定范围和最近邻。例如,我们选择一个新值 `125`。在两个现有树值之间,我们需要确定哪个是 `125`。同样,我们开始搜索 `125` 的 `Parent` 节点,经过几个步骤,我们到达节点 `115`。

现在很清楚,由于 `125` 是 `115` 的 `Right` 子节点,`125` 是最左侧邻居中最接近的。但搜索右值并不那么简单。在这种情况下,树的 `Root` 是 `127`。二叉树的规则是,限制值是第一个值大于 `125` 的 `Parent` 节点。所以我们必须向上遍历树,直到找到一个值大于 `125` 的 `Parent`。在某些情况下,节点可能只有一个限制邻居。下面给出了二叉树声明、数据插入和最近邻搜索的代码。

代码

二叉树的声明和实现位于 *BTree.h* 中。 `BTNode` 类包含 `insert` 和 `remove` 函数。模板类型是 `X`,带有两个指向 `left` 和 `right` 子节点的指针,以及一个指向 `parent` 的指针。我对标准二叉树只做了两个小的扩展,并添加了一个成员属性——定义为 `bool` 的 `ParentOrientation`,以及一个成员函数——用于优化插入的 `moveup()`。

//Binary Tree Node
template <class>
class BTNode
{
public:
    BTNode();
    BTNode(Xtype x);
    bool Delete_Tree(BTNode<xtype>* Root);
    BTNode(BTNode<xtype>* k1, BTNode<xtype>* k2);
    BTNode<xtype> *Left, *Right, *Parent;
    BTNode<xtype>* place(BTNode<xtype>* T);
    BTNode<xtype>* insert(Xtype x,bool&root_changed,bool optimize=true);
    bool insert(BTNode<xtype>* node,bool&root_changed,bool optimize=true);
    void remove();
    int count_childs();
    bool  moveup();
    Xtype x;
    bool ParentOrientation ; //where is child WRT parent ? 0=left 1=right
};

`ParentOrientation` 成员用于优化插入和最近邻搜索。它标识子节点相对于其 `parent` 的方向;如果为 `0`,则子节点在父节点的左侧;如果为 `1`,则在右侧。当然,我们只需比较子节点和父节点的值即可确定,但由于比较运算符 `<` 和 `>` 可能很慢,我使用了这个标志来减少它们的使用。成员函数 `moveup()` 尝试将节点向上移动到树中。它改善了树的平衡并优化了 `insert` 操作。然后 `Insert` 看起来像这样。

//insert node to the tree, starts from current node
template <class>
bool
BTNode<xtype>::insert(BTNode<xtype>* node,bool&root_changed, bool optimize)
{
    BTNode<xtype>* pNext = this;
    BTNode<xtype>* pFather;
    while(pNext) //do not insert if already exist
    {
        pFather = pNext;
        if(node->x < pNext->x)
            pNext = pNext->Left;
        else if(node->x > pNext->x)
            pNext = pNext->Right;
        else 
            return false;
    }

    if(!pNext) //empty place, do not insert value twice
    {
        node->Parent = pFather;
        if(node->x < pFather->x)
        {
            pFather->Left = node;
            node->ParentOrientation = 0 ;
        }
        else
        {
            pFather->Right = node;
            node->ParentOrientation = 1;
        }

        if(optimize)
            root_changed = node->moveup();
        else 
            root_changed = false ;

        return true;
    }
    return false;
}

最近邻搜索在上一节中已经解释。以下是代码:

/*   nearest neighbours search
   input:
    Root - the root of the tree
    x - template type with search target value

   output:
    l - the left limiting value or INVALID_VALUE if there 
is no neighbour to left
    r - the right limiting value or INVALID_VALUE if there 
is no neighbour to right
    
   returns:  
      - the closest value to x
      - INVALID_VALUE - empty tree 
*/
template <class>
Xtype FindNearest(BTNode<xtype>* Root, Xtype x, Xtype* l, Xtype* r)
{
    Xtype left,right;
    BTNode<xtype>* pNext = Root;
    BTNode<xtype>* pFather;
    bool ParentOrientation ;
    while(pNext)
    {
        pFather = pNext;
        if(x<pnext->x)
        {
            pNext = pNext->Left;
            ParentOrientation = false; 
        }
        else if(x>pNext->x)
        {
            pNext = pNext->Right;
            ParentOrientation = true; 
        }
        else 
        {
            *l = pNext->x;
            *r = pNext->x;
            return pNext->x;
        }
    }

    if(!ParentOrientation)  //x < pFather->x
    {
        right = pFather->x;
        //go up in the tree    and search for the left limit
        ParentOrientation = pFather->ParentOrientation ;
        pFather = pFather->Parent;
        while(pFather && !ParentOrientation)
    { 
           //search parent to the left
            ParentOrientation = pFather->ParentOrientation ;
            pFather = pFather->Parent;
        }

        if(!pFather)
        {
            *l = INVALID_VALUE;  //no neighbour to the left
            *r = right;
            return right;
        }
        else
        {
            *l = pFather->x;
            *r = right;
            return (x-pFather->x < right-x ? pFather->x:right);
        }
    }

    else //x > pFather->x
    {
        left = pFather->x;
        // go up in the tree and search for the right limit        
        ParentOrientation = pFather->ParentOrientation ;
        pFather = pFather->Parent;
        while(pFather && ParentOrientation)
    { 
           //search parent to the right
            ParentOrientation = pFather->ParentOrientation ;
            pFather = pFather->Parent;
        }

        if(!pFather)
        {
            *l = left;  
            *r = INVALID_VALUE;   //no neighbour to the right
            return left;
        }
        else
        {
            *l = left;
            *r = pFather->x;
            return (x-left < pFather->x-x ? left:pFather->x);;
        }
    }

    return INVALID_VALUE; //empty tree 
}

如上所述,树的优化是通过 `moveup()` 例程实现的。为什么需要优化?嗯,如果树中包含的值以随机(无序)序列出现,树将自然平衡。但是,不能保证值不会以某种非随机模式出现。例如,如果它是 `2,5,8,11`…,那么二叉树将变成一个链表,最近邻搜索将变成顺序搜索,这是无效的。有很多平衡方法——红黑树、AVL… 我更喜欢我自己的局部树优化,它消除了最坏的情况。下面的插图展示了它的工作原理:

这里,`N` 是要插入的节点,`P` 是它的父节点,`G` 是它的祖父节点。如果像上面左侧所示的两种情况,或者它的镜像情况那样发生,则可以将 `P` 或 `N` 向上移动,并将 `G` 向下移动到树中。三个节点——`N`、`P` 和 `G` 按照右侧所示重新排序。通过这种方式,树的高度减小,最坏的情况得到纠正,树的平衡得到改善。从我进行的测试中,我发现 `插入` 操作和树创建的速度慢不到 10%。如果你增加随机选择数据的平均高度,你会发现性能仍然慢 10%,但这个百分比随数据大小而增加,并确保平均高度始终是 log2N,其中 N 是数据大小。这意味着我们在大型数组(100,000)中查找值时将经过的平均路径约为 20,因为 2 的 20 次方是 100,000。项目源代码包含执行这些测试的应用程序。如果你想检查不同点的平均树高,请更改 `POINTS_NUM` 的数字,如果太大,请更改 `generate_random_point()` 例程。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.