许多数据结构使用一种叫做"左孩子,右兄弟"的表示法将多叉树存储为二叉树。这意味着什么?为什么要使用它?
A
//|\ \
/ / | \ \
B C D E F
| /|\ / \
G H I J K L
(抱歉,这里的ASCII艺术品质量较低!)
在这个树形结构中,我们可以从树中的任何节点向下导航到其任何子节点。例如,我们可以从A迁移到B、从A迁移到C、从A迁移到D等。
如果我们想要表示树中的一个节点,我们通常会使用一些节点结构/节点类,比如这里的一个(用C++编写):
struct Node {
DataType value;
std::vector<Node*> children;
};
A
/
/
/
B -> C -> D -> E -> F
/ / /
G H->I->J K->L
struct Node {
DataType data;
Node* leftChild;
Node* rightSibling;
};
因此,总空间使用量为
现在,让我们将其与LCRS树进行对比。 LCRS树中的每个节点存储两个指针(2 * sizeof(Node *))和一个数据元素(sizeof(Data)),因此它的总空间为n · (sizeof(Data) + sizeof(Node*) + 2 * sizeof(machine word)) + (n - 1) * sizeof(Node *)
= n * sizeof(Data) + (2n - 1) * sizeof(Node*) + 2n * sizeof(machine word)
由于LCRS表示法涉及时间-空间权衡,通常情况下不使用LCRS表示法,除非满足以下两个条件之一:
如果您需要在主内存中存储一个巨大的多路树,可能会出现情况(1)。例如,如果您需要存储一个包含许多不同物种的系统发育树,并经常更新,则LCRS表示法可能是适当的。
第二种情况出现在专用数据结构中,其中树结构被以非常特定的方式使用。例如,许多使用多路树的堆数据结构可以通过使用LCRS表示进行空间优化。主要原因是在堆数据结构中,最常见的操作往往是:
在LCRS表示中,操作(1)可以非常高效地完成。在LCRS表示中,树的根节点永远没有右子节点(因为它没有兄弟姐妹),因此删除根节点只意味着剥离根节点并进入其左子树。从那里,通过简单地沿着剩余树的右脊走下去并依次处理每个节点即可处理每个子节点。
操作(2)也可以非常高效地完成。回想一下上面提到的,在LCRS表示中,树的根节点从不具有右子节点。因此,可以按照以下方式轻松地将两棵LCRS表示的树连接在一起。从这样的两棵树开始:
R1 R2
/ /
(children 1) (children 2)
R1
/
R2
/ \
(children 2) (children 1)