Data structures 是否要将二叉树保存到磁盘以用于;20个问题“;游戏
简言之,我想学习/开发一种优雅的方法来将二叉树保存到磁盘(通用树,不一定是BST)。以下是我的问题描述: 我正在实施一个“20个问题”的游戏。我写了一个二叉树,它的内部节点是问题,叶子是答案。如果有人对您当前的问题回答“是”,则节点左侧的子节点是您将遵循的路径,而右侧的子节点是“否”的答案。请注意,这不是一个二叉搜索树,只是一个二叉树,其左子级为“是”,右子级为“否” 如果程序遇到一个为空的叶子,程序会向树中添加一个节点,方法是要求用户将其答案与计算机所想的答案区分开来 这很好,因为树在用户播放时会自行构建。不好的是,我没有一个将树保存到磁盘的好方法 我曾考虑过将树保存为数组表示(对于节点I,左边的子节点是2i+1,右边的子节点是2i+2,(I-1)/2表示父节点),但它并不干净,最终会浪费大量空间Data structures 是否要将二叉树保存到磁盘以用于;20个问题“;游戏,data-structures,tree,binary-tree,savestate,Data Structures,Tree,Binary Tree,Savestate,简言之,我想学习/开发一种优雅的方法来将二叉树保存到磁盘(通用树,不一定是BST)。以下是我的问题描述: 我正在实施一个“20个问题”的游戏。我写了一个二叉树,它的内部节点是问题,叶子是答案。如果有人对您当前的问题回答“是”,则节点左侧的子节点是您将遵循的路径,而右侧的子节点是“否”的答案。请注意,这不是一个二叉搜索树,只是一个二叉树,其左子级为“是”,右子级为“否” 如果程序遇到一个为空的叶子,程序会向树中添加一个节点,方法是要求用户将其答案与计算机所想的答案区分开来 这很好,因为树在用户播放
有没有关于将稀疏二叉树保存到磁盘的优雅解决方案的想法?我会做一个级别顺序遍历。也就是说你基本上在做一个算法 你有:
F
F
B
F
B G
F
B G
A
第2步读入:
F
F
B
F
B G
F
B G
A
第三步阅读:
F
F
B
F
B G
F
B G
A
步骤4阅读:
F
F
B
F
B G
F
B G
A
等等
注意:一旦有空节点表示,就不再需要将其子节点列到磁盘上。加载回时,您将知道跳到下一个节点。因此,对于非常深的树,此解决方案仍然是有效的。完成此任务的一个简单方法是在遍历树时输出每个元素。然后要加载回树,只需遍历列表,将每个元素插入回树中。如果您的树不是自平衡的,您可能希望以这样的方式重新排列列表,以便最终的树是合理平衡的。我会像这样存储树:
<node identifier>
node data
[<yes child identfier>
yes child]
[<no child identifier>
no child]
<end of node identifier>
1,2,3,"Does it have wings?"
2,0,0,"a bird"
3,4,0,"Does it purr?"
4,0,0,"a cat"
节点数据
[
是的,孩子]
[
没有孩子]
其中,子节点只是上述的递归实例。[]中的位是可选的,四个标识符只是常量/枚举值。您可以递归存储它:
void encodeState(OutputStream out,Node n) {
if(n==null) {
out.write("[null]");
} else {
out.write("{");
out.write(n.nodeDetails());
encodeState(out, n.yesNode());
encodeState(out, n.noNode());
out.write("}");
}
}
设计你自己的文本输出格式。我确信我不需要描述读取结果输出的方法
这是深度优先遍历。宽度优先也适用。最随意的简单方式只是一种基本格式,可用于表示任何图形
<parent>,<relation>,<child>
这里没有太多的冗余,而且格式大多是人类可读的,唯一的数据重复是它的每个直接子级都必须有一个父级副本
你唯一需要注意的是,你不会意外地产生一个循环;)
除非那是你想要的
这里的问题是重建 之后是一棵树。如果我创建了“does” 它有“翅膀”的对象,在阅读 第一行,我必须找到 这是我后来遇到的线 阅读“它有什么 “翅膀”,“是的”,“它有喙吗?” 这就是为什么我传统上只是在内存中使用图形结构来处理指针无处不在的事情
[0x1111111 "Is It Red" => [ 'yes' => 0xF752347 , 'no' => 0xFF6F664 ],
0xF752347 "does it have wings" => [ 'yes' => 0xFFFFFFF , 'no' => 0x2222222 ],
0xFF6F664 "does it swim" => [ 'yes' => "I Dont KNOW :( " , ... etc etc ]
那么“子/父”连接仅仅是元数据 不确定它是否优雅,但它很简单且可以解释: 为每个节点分配一个唯一的ID,无论是茎还是叶。一个简单的计数整数就可以了 保存到磁盘时,遍历树,存储每个节点ID、“是”链接ID、“否”链接ID以及问题或答案的文本。对于空链接,使用零作为空值。您可以添加一个标志来指示问题或答案,或者更简单地说,检查两个链接是否都为空。你应该得到这样的东西:
<node identifier>
node data
[<yes child identfier>
yes child]
[<no child identifier>
no child]
<end of node identifier>
1,2,3,"Does it have wings?"
2,0,0,"a bird"
3,4,0,"Does it purr?"
4,0,0,"a cat"
请注意,如果使用顺序整数方法,保存节点的ID可能是多余的,如下所示。你可以按ID把它们排好
要从磁盘恢复,请读取一行,然后将其添加到树中。您可能需要一个表或数组来容纳前向引用的节点,例如,在处理节点1时,你需要跟踪2和3直到你可以填充这些值。在java中,< p>如果你要使类成为可序列化的,你可以把类对象写到磁盘上,然后用输入/输出流读取它。 < p>这里是使用预排序DFS:< /P>的C++代码。
void SaveBinaryTreeToStream(TreeNode* root, ostringstream& oss)
{
if (!root)
{
oss << '#';
return;
}
oss << root->data;
SaveBinaryTreeToStream(root->left, oss);
SaveBinaryTreeToStream(root->right, oss);
}
TreeNode* LoadBinaryTreeFromStream(istringstream& iss)
{
if (iss.eof())
return NULL;
char c;
if ('#' == (c = iss.get()))
return NULL;
TreeNode* root = new TreeNode(c, NULL, NULL);
root->left = LoadBinaryTreeFromStream(iss);
root->right = LoadBinaryTreeFromStream(iss);
return root;
}
DFS更容易理解
*********************************************************************************
但是我们可以使用队列来使用级别扫描BFS
ostringstream SaveBinaryTreeToStream_BFS(TreeNode* root)
{
ostringstream oss;
if (!root)
return oss;
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
TreeNode* tn = q.front(); q.pop();
if (tn)
{
q.push(tn->left);
q.push(tn->right);
oss << tn->data;
}
else
{
oss << '#';
}
}
return oss;
}
TreeNode* LoadBinaryTreeFromStream_BFS(istringstream& iss)
{
if (iss.eof())
return NULL;
TreeNode* root = new TreeNode(iss.get(), NULL, NULL);
queue<TreeNode*> q; q.push(root); // The parents from upper level
while (!iss.eof() && !q.empty())
{
TreeNode* tn = q.front(); q.pop();
char c = iss.get();
if ('#' == c)
tn->left = NULL;
else
q.push(tn->left = new TreeNode(c, NULL, NULL));
c = iss.get();
if ('#' == c)
tn->right = NULL;
else
q.push(tn->right = new TreeNode(c, NULL, NULL));
}
return root;
}
这里的问题是以后重建树。如果我在阅读第一行时创建了“it have wings”对象,那么当我后来遇到读“it have wings”、“yes”、“it have have a bike?”的行时,我必须以某种方式找到它。最简单的解决方案通常是最好的:)序列化也可能有助于这个好主意,但如果树不是二叉搜索树,它将无法工作(您如何知道在何处插入下一个元素?)当你构建树时,你知道要插入到哪里,所以读回它也是一样的。我几乎建议了与slim的答案相同的事情,但没有代码。我知道,如果有关于每个级别结束位置的信息,你就会知道。你关于平衡它的评论让我觉得你在说话