Data structures 树的左子级、右同级表示是什么?你为什么要用它?

Data structures 树的左子级、右同级表示是什么?你为什么要用它?,data-structures,tree,binary-tree,multiway-tree,Data Structures,Tree,Binary Tree,Multiway Tree,许多数据结构使用称为表示的表示将多路树存储为二叉树。这是什么意思?为什么要使用它?左子右同级表示法(LCRS)是一种使用编码(每个节点可以有任意数量的子节点的树结构)(每个节点最多可以有两个子节点的树结构)的方法 动机 为了激发这种表示法的工作原理,让我们首先考虑一个简单的多向树,如下所示: A //|\ \ / / | \ \ B C D E F

许多数据结构使用称为表示的表示将多路树存储为二叉树。这是什么意思?为什么要使用它?

左子右同级表示法(LCRS)是一种使用编码(每个节点可以有任意数量的子节点的树结构)(每个节点最多可以有两个子节点的树结构)的方法

动机 为了激发这种表示法的工作原理,让我们首先考虑一个简单的多向树,如下所示:

                   A
                 //|\ \
               / / | \  \
              B C  D  E  F
              |   /|\   / \
              G  H I J K   L
(对低质量的ASCII图形表示歉意!)

在这个树结构中,我们可以从树中的任何节点向下导航到它的任何子节点。例如,我们可以从A迁移到B,从A迁移到C,从A迁移到D,等等

如果我们想在这样的树中表示一个节点,我们通常会在这里使用这样的节点结构/节点类(用C++编写):

请注意,在这个新结构中,仍然可以从父节点导航到其第k个子节点(零索引)。执行此操作的程序如下所示:

  • 下降到当前节点的左侧子节点。(这是其子节点列表中的第一个节点)
  • 跟随那个孩子的右兄弟指针k次。(这会将您带到节点的第k个子节点)
  • 返回该节点
例如,为了找到根节点A的第三个(零索引子节点),我们将下降到最左边的子节点(B),然后跟随三个右链接(访问B、C、D,最后是E)。然后我们到达E的节点,它包含节点A的第三个子节点

以这种方式表示树的主要原因是,即使任何节点都可能有任意数量的子节点,表示也需要每个节点最多两个指针:一个节点存储最左边的子节点,一个指针存储右边的同级节点。因此,我们可以使用更简单的节点结构存储多路树:

struct Node {
    DataType data;
    Node* leftChild;
    Node* rightSibling;
};
此节点结构与二叉树中的节点形状完全相同(数据加上两个指针)。因此,LCRS表示法使每个节点仅使用两个指针就可以表示任意多路树

分析 现在让我们看一下多路树的两种不同表示形式的时间和空间复杂性,以及它的一些基本操作

让我们先看看初始“动态子数组”表示所需的总空间使用情况。n节点树的总内存使用量是多少?那么我们知道以下几点:

  • 每个节点,无论其子节点的数量如何,都会为存储的原始数据(sizeof(data))和动态数组的空间开销贡献空间。动态数组(通常)存储一个指针(指向分配的数组),一个机器字表示动态数组中的元素总数,以及(可选)一个机器字表示数组的分配容量。这意味着每个节点占用sizeof(数据)+sizeof(节点*)+2*sizeof(机器字)空间

  • 在树中的所有动态数组中,将有n-1个指向子节点的指针,因为树中的n个节点中有n-1个具有父节点。这就增加了一个额外的(n-1)*sizeof(Node*)因子

  • 因此,总空间使用率为

    n·(sizeof(数据)+sizeof(节点*)+2*sizeof(机器字))+(n-1)*sizeof(节点*)

    =n*sizeof(数据)+(2n-1)*sizeof(节点*)+2n*sizeof(机器字)

    现在,让我们将其与LCRS树进行对比。LCRS树中的每个节点存储两个指针(2*sizeof(node*))和一个数据元素(sizeof(data)),因此其总空间为

    n*sizeof(数据)+2n*sizeof(节点*)

    这里我们看到了胜利:注意,我们没有存储2n*sizeof(机器字)额外内存来跟踪分配的数组大小。这意味着LCRS表示使用的内存比常规多路树少得多

    但是,LCRS树结构上的基本操作往往比正常多路树上的相应操作花费更长的时间。具体而言,在以标准形式表示的多路树(每个节点存储一个子指针数组)中,从一个节点导航到其第k个子节点所需的时间由跟随单个指针所需的时间给出。另一方面,在LCRS表示中,执行此操作所需的时间由跟随k+1指针(一个左子指针,然后是k个右子指针)所需的时间给出。因此,如果树具有较大的分支因子,则在LCRS树结构中查找比在相应的多路树结构中查找要慢得多

    因此,我们可以将LCRS表示看作是在数据结构、存储空间和访问时间之间提供了一种平衡。LCRS表示比原始多路树具有更少的内存开销,而多路树为其每个子级提供恒定的时间查找

    用例 由于LCRS表示中涉及的时空权衡,通常不使用LCRS表示,除非符合以下两个标准之一:

  • 记忆极度匮乏,或
  • 不需要随机访问节点的子节点
  • 如果您需要在主内存中存储一个惊人巨大的多路树,则可能会出现第(1)种情况。例如,如果您需要存储一个包含许多不同物种且需要频繁更新的,那么LCRS表示可能是合适的

    案例(2)出现在专门的数据结构中,其中树结构以非常特定的方式使用。例如,许多使用多路树的类型可以通过使用LCRS表示进行空间优化。这主要是因为在堆数据结构中,最常见的操作往往是

  • 雷莫
                A
               /
              /
             /
            B -> C -> D -> E -> F
           /         /         /
          G         H->I->J   K->L
    
    struct Node {
        DataType data;
        Node* leftChild;
        Node* rightSibling;
    };
    
        R1             R2
       /               /
     (children 1)    (children 2)
    
                 R1
                /
               R2
              /  \
    (children 2) (children 1)