想要将二叉树保存到磁盘上“20问题”游戏

时间:2008-12-03 16:53:59

标签: data-structures tree binary-tree savestate

简而言之,我想学习/开发一种优雅的方法来将二叉树保存到磁盘(一般树,不一定是BST)。这是我的问题的描述:

我正在实施一个“20个问题”的游戏。我写了一个二叉树,其内部节点是问题,叶子是答案。如果有人对你当前的问题回答“是”,那么节点的左子节点就是你要遵循的路径,而正确的孩子则是“否”的答案。请注意,这不是二进制搜索树,只是一个二进制树,其左子项为“是”,右侧为“否”。

如果节点通过要求用户将她的答案与计算机所想的答案区分开来而遇到空的叶子,则程序会向树中添加一个节点。

这很简洁,因为树会在用户播放时自行构建。什么不整齐的是我没有把树保存到磁盘的好方法。

我曾考虑将树保存为数组表示形式(对于节点i,左子节点为2i + 1,右侧为2i + 2,父节点为(i-1)/ 2),但它不干净且我结束了很多浪费的空间。

关于将稀疏二叉树保存到磁盘的优雅解决方案的任何想法?

8 个答案:

答案 0 :(得分:10)

我会进行水平顺序遍历。也就是说你基本上在做Breadth-first search算法。

你有:

  1. 创建一个插入根元素的qeueue
  2. 从队列中取出一个元素,称之为E
  3. 将E的左右子项添加到队列中。如果没有左或右,只需放置一个空节点表示。
  4. 将节点E写入磁盘。
  5. 从第2步开始重复。
  6. alt text

    水平顺序遍历序列:F,B,G,A,D,I,C,E,H

    您将存储在磁盘上的内容:F,B,G,A,D,NullNode,I,NullNode,NullNode,C,E,H,NullNode

    从磁盘加载它更容易。只需从左到右阅读存储到磁盘的节点。这将为您提供每个级别的左右节点。即树将从上到下从左到右填充。

    第1步阅读:

    F
    

    第2步阅读:

      F 
    B
    

    第3步阅读:

      F 
     B  G
    

    第4步阅读:

       F 
     B  G
    A
    

    等等......

    注意:一旦有了NULL节点表示,就不再需要将其子节点列入磁盘。加载后,您将知道跳到下一个节点。因此,对于非常深的树,这种解决方案仍然有效。

答案 1 :(得分:9)

您可以递归存储它:

 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("}");
        }
  }

设计自己较少的texty输出格式。我确信我不需要描述读取结果输出的方法。

这是深度优先遍历。广度优先也有效。

答案 2 :(得分:1)

实现此目的的一种简单方法是遍历输出每个元素的树。然后将树加载回来,只需遍历列表,将每个元素插回树中。如果您的树不是自平衡的,您可能希望以最终树合理平衡的方式重新排序列表。

答案 3 :(得分:1)

不确定它是否优雅,但它简单易懂: 为每个节点分配唯一ID,无论是茎还是叶。一个简单的计数整数就可以了。

保存到磁盘时,遍历树,存储每个节点ID,“是”链接ID,“否”链接ID以及问题或答案的文本。对于空链接,使用零作为空值。您可以添加一个标志来指示问题或答案,或者更简单地说,检查两个链接是否都为空。你应该得到这样的东西:

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,直到您可以填写这些值。

答案 4 :(得分:0)

我会像这样存储树:

<node identifier>
node data
[<yes child identfier>
  yes child]
[<no child identifier>
  no child]
<end of node identifier>

其中子节点只是上述的递归实例。 []中的位是可选的,四个标识符只是常量/枚举值。

答案 5 :(得分:0)

最随意的简单方法只是一种可用于表示任何图形的基本格式。

<parent>,<relation>,<child>

即:

"Is it Red", "yes", "does it have wings" 
"Is it Red", "no" , "does it swim"

这里没有很多冗余,而且这些格式大部分是人类可读的,唯一的数据重复是必须有一个父级的副本,用于它拥有的每个直接子级。

你唯一需要注意的是你不会意外地产生一个循环;)

除非那是你想要的。

  

这里的问题是重建   树后来。如果我创造“做   它有翅膀“对象阅读   第一行,我必须以某种方式找到   当我后来遇到这条线的时候   阅读“它有没有   翅膀“,”是“,”它有一个喙?“

这就是为什么我传统上只是在内存中使用图形结构这样的东西,指针到处都是。

[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 ]

然后“子/父”连接仅仅是元数据。

答案 6 :(得分:0)

在java中,如果要使类可序列化,则可以将类对象写入光盘并使用输入/输出流将其读回。

答案 7 :(得分:0)

以下是使用PreOrder DFS的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;
}

main()中,您可以执行以下操作:

ostringstream oss;
root = MakeCharTree();
PrintVTree(root);
SaveBinaryTreeToStream(root, oss);
ClearTree(root);
cout << oss.str() << endl;
istringstream iss(oss.str());
cout << iss.str() << endl;
root = LoadBinaryTreeFromStream(iss);
PrintVTree(root);
ClearTree(root);

/* Output:
               A

       B               C

   D               E       F

     G           H   I
ABD#G###CEH##I##F##
ABD#G###CEH##I##F##
               A

       B               C

   D               E       F

     G           H   I
 */

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;
}

main()中,您可以执行以下操作:

root = MakeCharTree();
PrintVTree(root);
ostringstream oss = SaveBinaryTreeToStream_BFS(root);
ClearTree(root);
cout << oss.str() << endl;
istringstream iss(oss.str());
cout << iss.str() << endl;
root = LoadBinaryTreeFromStream_BFS(iss);
PrintVTree(root);
ClearTree(root);

/* Output:
               A

       B               C

   D               E       F

     G           H   I
ABCD#EF#GHI########
ABCD#EF#GHI########
               A

       B               C

   D               E       F

     G           H   I
 */