问题描述
我是数据结构的初学者。我刚刚开始学习它。我正在尝试使用特里数据结构创建一个家庭结构。我指的是 github 上的文档: https://gist.github.com/tpae/72e1c54471e88b689f85ad2b3940a8f0#file-trie-js-L44
这是一些代码。
function TrieNode(key) {
// the "key" value will be the character in sequence
this.key = key;
// we keep a reference to parent
this.parent = null;
// we have hash of children
this.children = {};
// check to see if the node is at the end
this.end = false;
}
// -----------------------------------------
// we implement Trie with just a simple root with null value.
function Trie() {
this.root = new TrieNode(null);
}
// inserts a word into the trie.
// time complexity: O(k),k = word length
Trie.prototype.insert = function(word) {
var node = this.root; // we start at the root ?
// for every character in the word
for(var i = 0; i < word.length; i++) {
// check to see if character node exists in children.
if (!node.children[word[i]]) {
// if it doesn't exist,we then create it.
node.children[word[i]] = new TrieNode(word[i]);
// we also assign the parent to the child node.
node.children[word[i]].parent = node;
}
// proceed to the next depth in the trie.
node = node.children[word[i]];
// finally,we check to see if it's the last word.
if (i == word.length-1) {
// if it is,we set the end flag to true.
node.end = true;
}
}
};
我的疑问是在插入节点时,我们如何遍历单词并创建一个节点:
node.children[word[i]]
这对我来说是无法理解的。另外如何在函数 TrieNode 中声明键、父、子?它们是否被视为在创建对象时初始化的全局变量?为什么在其他函数中声明了 root 以及它是如何工作的? 附注我必须像这样插入树:
var familyHead = {
name: 'Chit',gender:'Male',grandfather:'',grandmother:'',father:'Shan',mother:'Anga',wife:'Amba',children : [
{
name: 'Chit',children :[]
}]
} ... 继续
解决方法
您可以为 JavaScript 对象提供引用相关对象的属性,以完成您想要形成图形的功能。 (不要使用 trie,这是为了别的。
例如,为每个人创建一个普通的 JavaScript 对象。
// Create an to represent each unique person
const chit = { name: 'Chit',gender: 'male' };
const shan = { name: 'Shan',gender: 'male' };
const anga = { name: 'Anga',gender: 'female' };
const anba = { name: 'Anba',gender: 'female' };
// Give them properties that establish the relationships as required
chit.father = shan;
chit.mother = anga;
chit.wife = amba;
/// ...
到目前为止,这些都是单向链接。如果您还希望父亲拥有他们孩子的数组,那么您必须在构建它时维护这个数据结构。您将需要一些便利的功能来执行此操作。
要在两个人之间建立亲子关系,至少必须发生一件事:
- 子对象应将其
parent
属性设置为父对象。
如果为了方便或效率,您还想维护每个人拥有的孩子列表的缓存,那么进一步:
- 父对象应该将子对象添加到其
children
数组中。
这样做过度指定父子关系,因此数据结构必须保持完整性。这意味着您需要提供函数来在检查不一致的同时对树进行修改。例如,同时尝试为同一个孩子建立两个亲子关系,或者让孩子没有父母(某人需要没有父母,因为你可以' t 存储无限深的世代历史),或者将一个人列为孩子,但不会同时将同一个孩子列为拥有相同的父母。
在这样的函数中,人们现在可能会检查整个树的完整性,但仅检查正在修改的结构部分可能就足够了。
建立新的亲子关系时,检查孩子是否已经有父母。如果这样做,有两种策略可以解决这个问题,这取决于编辑谁是父母的生物学上不可变的属性是否有意义(例如,考虑像收养这样的过程)
- 抛出异常。
- 在更新以设置新的父级之前,通过从父级的子级数组中删除子级来编辑关系。
在这里,我将提供一个实现,允许每个性别都有一个父母。我会将父项的性别映射到 mother
或 father
属性名称,只是为了说明这样做的便利性。例如,如果您发现需要代表有两个母亲的家庭,那么您将需要使用父母数组。 (但是您的示例使用了 mother
和 father
,我也应该如此。)
const parent_property_name = {
male: 'father',female: 'mother'
};
function establishParentChildRelationship(parent,child) {
if (!child.parent) child.parent = {};
if (child[parent_property_name[parent.gender]]) throw new Error('child already has a parent of that gender');
child[parent_property_name[parent.gender]] = parent;
if (!parent.children) parent.children = [];
parent.children.push(child);
}
希望您能看到,由于需要维护,因此通常不会在树中进一步存储明确的祖父和祖母关系。父亲的父亲隐含地是祖父,可以这样确定。类似地,父母的兄弟姐妹是阿姨和叔叔等。表兄弟和二表亲都可以仅根据亲子关系来确定。没有必要再次明确存储它。此外,必须考虑是否需要明确定义mother 和father,因为它们可能来自parent
+ gender
。 (或者不代表女性父亲这样的概念是否可以?这些事情的细节可能会由您工作的法律框架来定义。但世界是一个复杂的地方。)
然而,婚姻关系(法律、宗教或其他)确实需要额外的链接。还必须考虑数据结构是否允许复数婚姻(这可能需要一个数组,就像孩子一样)和同性婚姻('已婚'+'性别'=>'丈夫'或'妻子')。但是对于一夫一妻制的婚姻,人们至少可以想象,只需用 maritalPartner
来表示这一点。
function establishMonogamousMaritalPartnerRelationship(person1,person2) {
if (person1.maritalPartner || person2.maritalPartner) throw new Error('already married!');
person1.maritalPartner = person2;
person2.maritalPartner = person1;
您还可以根据需要使用将性别映射到 husband
或 wife
的技巧。或者,如果您想表示复数婚姻,那么您可以使用一组合作伙伴(就像对待孩子一样。)
可能需要再次修改数据结构,才能表示这些关系随时间变化的复杂性。 (例如离婚或二婚、收养、解放、性别转变或重新分配等)
让我也花点时间澄清一下 trie 的用途。 (它不适用于家谱。)
树将单词表示为一棵树,其中每个节点是一个字母,每个节点的父节点是它之前的字母。一个完整的单词在树中的节点上用一个标志标记,即单词的最后一个字母。
考虑一个存储单词 be
、bee
、bell
和 ball
的树:
.
└── b
├── a
│ └── l
│ └── l*
└── e*
├── e*
└── l
└── l*
标有 *
的节点表示完整的单词(在您的示例中带有 end
标志)。请注意,即使允许更多的字母跟随一个单词以形成不同的作品,它也可能会结束。 (例如:be
是一个单词,即使它后面可能跟着 e
或 l
)。每个带有 end
标志的节点代表一个由其在树中的祖先决定的词。
最后,让我快速回答您关于如何在函数 key
中声明 parent
、children
和 TrieNode
的性质的问题。
您正在查看的是一个 JavaScript 构造函数。我建议你阅读the basics或this answer。
但在这种特定情况下,调用 new TrieNode('X')
将创建一个基本如下所示的 JavaScript 对象:
{ key: 'X',parent: null,children: {},end: false }