How it works
1. LCRS
LCRS, 即left-child, right-sibling representation is a way of encoding a multi-way tree using a binary tree
- 树的本质在于用继承性的关系描述数据结构,只需理解继承性即可理解树
- Binary Tree:意味着每个节点最多有两个孩子
- Multi-way tree: 意味着每个节点有任意数目的孩子
- LCRS仅仅是一种编码形式
2. 为什么要用到LCRS Tree
传统的Mutil-way Tree
A
//|\ \
/ / | \ \
B C D E F
| /|\ / \
G H I J K L
数据结构表达
struct Node {
DataType value;
std::vector children; /*用到多个孩子指针*/
};
每个节点存储一个是leftmost child,另外一个指针指向其右兄弟节点,形式变化如下:
A
/
/
/
B -> C -> D -> E -> F
/ / /
G H->I->J K->L
层次和结构不变,变的是存储方式。可以理解为double chained tree:
- 每个节点的leftmost child都链接在单链表里, 用child域存储下一个child,在tree的层次逐层增加;
- 每个节点的兄弟节点都链接在另外一个单链表里,用sibling域存储该节点的兄弟节点,在tree中的层次是一样的
遍历
From a parent node to its kth child (zero-indexed):
Descend至当前节点的leftmost child,即child向右遍历该节点的兄弟节点k次返回节点,
数据结构描述相应变为:
struct Node {
DataType data;
Node* leftChild;/**/
Node* rightSibling; /*存储着*/
};
Multi-way tree VS LCSR tree
- 内存空间:后者比前者节省大约40%
- 时间:如果分叉因子很多,即每层的节点很多,前者查询节点只需找到对应的子节点指针即可,后者则需进行单链表遍历(树退化为链表),导致时间变长
3. LCRS适用场景
- Memory is extremely scarce: 如果在主存中存储一颗巨大多路树,LCRS会合适
- Random access of a node’s children is not required
在构建某些特定的数据结构是,比如Heap data structures ,采样LCRS可以优化空间,而最常见操作集中如下:删除tree的根节点和处理一个孩子;合并两颗树,而这二者对于LCRS易于完成,对于certificate存储系统,该操作常见。
LCRS在证书系统中的应用
1. 数据结构
#define CTREE_CLASS_CERT. 0
#define CTREE_C_SCLASS_PUBLIC. 0x0001.
#define CTREE_C_SCLASS_PRIVATE. 0x0002.
#define CTREE_C_SCLASS_BOTH. 0x0003.
#define CTREE_CLASS_ROOT. 1
#define CTREE_CLASS_FOLDER. 2.
#define CTREE_F_SCLASS_COMMON. 0x0020.
#define CTREE_F_SCLASS_UNKNOWN. 0x0021
struct tree {
struct tree *parent;
struct tree *child; /*leftmost child, as the sibling’s list head*/
struct tree *sibling; /*link to child in single list structure*/
uchar. name[64]; /*file name, eg certificate name*/
uint . id; /* Must be > 0 */
ushort. type, subtype; /* both for reserved */
ushort .iclass, subclass; /*cert/root/folder; private key/public key*/
uchar. num_child, level, unused[2]; /*tree level*/
uint . capability; /*reserved*/
pBmp. EBmp, CBmp; /*Icon*/
void. *iprivate; /*for unkown CA folder and private info*/
};
2. API
Insert node
remove node
STATIC void
ctree_remove_node(struct tree *troot, uchar *filename)
{
struct tree *delte, *parent, *child, *lost, *lastchild;
delte = tree_find_node_by_name(troot, filename, 1);/*获取要删除的节点*/
if (delte == 0)
return;
parent = delte->parent; /*取得被删节点的父指针*/
child = parent->child; /*取得leftmost child指针*/
if (parent->child == delte) /*Case1: 被删节点就是leftmost child*/
parent->child = delte->sibling; /* 修改leftmost child指向被删节点的兄弟*/
else { /*Case1: 被删节点是sibling节点*/
child = parent->child; /*以leftmost child为单链表头遍历,获取要删除的sibling节点 */
while (child->sibling != delte)
child = child->sibling;
ASSERT(child->sibling == delte);
if (child->sibling != delte)
return;
child->sibling = delte->sibling;
}
delte->parent = 0;
delte->sibling = 0;
if (delte->child == 0) /*如果被删节点没有孩子,则直接返回*/
return;
lost = (struct tree *) troot->iprivate; /*重新连接整个子树至unkown CA*/
//re-link child
child = delte->child;
do { /*将该层所有的孩子节点的父指针修改为lost,即unkown CA*/
child->parent = lost;
lastchild = child; /*记录该层最后一个孩子节点*/
child = child->sibling;/*单链表遍历*/
} while (child);
lastchild->sibling = lost->child; /*最后一个孩子节点的next指针(sibling)指向lost的leftmost child*/
lost->child = delte->child; /*修改lost的leftmost child为被删节点的leftmost child*/
delte->child = 0;
ctree_free_node(delte);
tree_update_order(lost); /*更新以lost为root的子树*/
}
update subtree
void tree_update_order(struct tree *parent)
{
struct tree *child;
uint. previd = 0;.
PRINTF("TW: parent update [%s] (lv %d, id %x)\n", parent->name, parent->level, parent->id);
parent->num_child = 0;
while (child = tree_next_child(parent, previd)) { /*获取该层的所有孩子*/
child->level = parent->level + 1; /*孩子的层是父亲层加1*/
child->id = tree_calc_orderid(parent);
PRINTF("TW: child [%s] lv %d id %x\n", child->name, child->level, child->id);
tree_update_order(child); /*child作为子树root递归调用*/
previd = child->id; /*更新前一个id*/
child = child->sibling; /*单链表遍历*/
}
PRINTF("TW: [%s] return \n", parent->name);
}