3

私は最近、ルート 'ノード' を削除してツリーを破棄するときにスタック オーバーフローを取得することができましたが、ノード デストラクタは次のようになります。

Node::~Node(){
    for(int i=0;i<m_childCount;i++)
        delete m_child[i];
}

私の頭に浮かんだ解決策は、独自のスタックを使用することでした。したがって、この方法でツリーを削除します。

std::stack< Node* > toDelete;

if(m_root)
    toDelete.push(m_root);

while(toDelete.size()){
    Node *node = toDelete.top();
    toDelete.pop();

    for(int i=0;i<node->GetChildCount();i++)
        toDelete.push(node->Child(i));

    delete node;
}

しかし、そこで std::stack::push() が例外をスローすることがあります。例外のないツリー破壊を書くことは可能ですか? どのように?

編集:

誰かが興味を持っている場合は、jpalecek によって指摘されたアルゴリズムに触発された、例外のない非再帰コードを次に示します。

Node *current = m_root;

while(current){
  if(current->IsLeaf()){
    delete current;
    return;
  }

  Node *leftMostBranch = current;// used to attach right subtrees

  // delete all right childs
  for(size_t i=1; i<current->GetChildCount(); i++){
    while(!leftMostBranch->Child(0)->IsLeaf())
      leftMostBranch = leftMostBranch->Child(0);

    delete leftMostBranch->Child(0);
    leftMostBranch->Child(0) = current->Child(i);
  }

  // delete this node and advance to the left child
  Node *tmp = current;
  current = current->Child(0);
  delete tmp;
}

注:Node::IsLeaf()と同等Node::GetChildCount()!=0です。

4

4 に答える 4

4

ちょうどインタビューの質問としてこれを持っていました。

そして、これがその場で解決しなければならなかった最も難しいことの 1 つであることを認めなければなりません。
個人的には、(Knuth を読んだことがあれば) トリックを知っているかもしれないので、それは良い質問ではないと思います。飛ぶ。

これは、ノードが子ポインタを静的構造に格納していると仮定して実行できます。ノードが子ポインタを動的構造に格納する場合、その場でツリーを再形成する必要があるため、機能しません (機能する可能性はありますが、保証はありません)。

驚くべきことに、解決策は O(n) です
(技術的には、すべてのノードがツリーの再スキャンなしで正確に 2 回アクセスされます)。
このソリューションはループを使用し (スタックのメモリを使用しないため)、削除する必要があるノードを保持するために memeroy を動的に割り当てません。なので意外と効率がいいです。

class Node
{
    // Value we do not care about.
    int   childCount;
    Node* children[MAX_CHILDREN];
 };

freeTree(Node* root)
{
    if (root == NULL)
    {    return;
    }
    Node* bottomLeft  = findBottomLeft(root);

    while(root != NULL)
    {
        // We want to use a loop not recursion.
        // Thus we need to make the tree into a list.
        // So as we hit a node move all children to the bottom left.
        for(int loop = 1;loop < root->childCount; ++loop)
        {
            bottomLeft->children[0] = root->children[loop];
            bottomLeft->childCount  = std::max(1, bottomLeft->childCount);
            bottomLeft = findBottomLeft(bottomLeft);
        }

        // Now we have a root with a single child
        // Now we can delete the node and move to the next node.
        Node* bad = root;
        root      = root->children[0];
        delete bad;     // Note the delete should no longer destroy the children.
    }
}

Node* findBottomLeft(Node* node)
{
    while((node->childCount > 0) && node->children[0] != NULL))
    {    node = node->children[0];
    }
    return node;
}

上記のメソッドは、(NULL であっても) 常に children[0] ノードである限り機能します。children[0] を保持するためにスペースを動的に割り当てる必要がない限り。または、ノード オブジェクトにもう 1 つのポインターを追加して、削除リストを保持し、これを使用してツリーをリストに変換します。

于 2010-08-20T16:25:07.560 に答える
1

これは、すべてのガベージ コレクターが苦労していることです。ただし、できる最善のこと (私見) は、スタックに十分なメモリを確保することです。そうすれば、99.99999% の確率で祈りが聞こえます。それが起こらなければ、ただabort().

ところで、興味がある場合は、多くのメモリを割り当てずに長い (そして深い) ツリーをトラバースするソリューションがあります。

于 2010-08-20T11:47:25.340 に答える
0

元のコードが例外をスローするのはなぜですか?ツリーの複数の場所で同じノードオブジェクトを使用するようなことをしていると思います。スタックオーバーフローは、通常の予想される状況によって引き起こされることはめったにありません。スタックオーバーフローは問題ではなく、問題の兆候です。

コードを別の方法で書き直しても、それは修正されません。エラーを調査して修正する必要があります。

于 2010-08-20T11:33:23.477 に答える
0

例外のないツリー破壊を書くことは可能ですか? どのように?

おそらくこれ(テストされていないコード):

void destroy(Node* parent)
{
  while (parent)
  {
    //search down to find a leaf node, which has no children
    Node* leaf = parent;

    while (leaf->children.count != 0)
      leaf = leaf->chilren[0];

    //remember the leaf's parent
    parent = leaf->parent;

    //delete the leaf
    if (parent)
    {
      parent->children.remove(leaf);
    }
    delete leaf; 

  } //while (parent)
}
于 2010-08-20T13:10:05.897 に答える