下の画像のように配置されたノードのセットがあります。左の列は親、右の列は子です。線は祖先/子孫を示します。
ノードを選択するときに、直接の家族を検索し、次に子の子供と親の親を検索します。しかし、親子や親子ではありません。
したがって、この例では、#7
を選択しました。 #2
と#4
が親であることがわかるので、それらを選択します。 #8
は親の子であるため、選択されていません。別の方向に目を向けると、子の下にハイライトが表示されますが、#13
と#9
の関係は無視されています。これは、親子関係だからです。
関連するノードが誰であるかを知る必要があるだけです。それらがどのように関連しているか、グラフのどこにあるかは関係ありません。 これを行うための最良の方法は何ですか?これらの種類のアルゴリズムについては何も知らないので、これは重複している可能性があります。それを理解してください。もしそれが問題なら、私は最終的にJSでこれを実装するでしょう。
これは、2つの非常に単純な再帰関数で実装できます。1つはすべての祖先を検索し(左にのみ移動できます)、もう1つはすべての子孫を検索します(右にのみ移動できます)。
擬似コードの例:
get_family(node a)
{
list family = get_ancestors(a) + get_children(a)
return family
}
get_ancestors(node a)
{
list ancestors
foreach anc in a.ancestors
{
ancestors = ancestors + anc + get_ancestors(anc)
}
return ancestors
}
get_children(node a)
{
list children
foreach child in a.children
{
children = children + child + get_children(child)
}
return children
}
pdate: Derek Elkinsが指摘したように、分岐した2つのパスが再び結合する可能性がある場合、ノードが複数回アクセスされるのを防ぐ方法が必要です。各ノードを訪問済みとしてマークするなど。
これが手続き型ソリューションです。私はC#で記述しましたが、C#に精通していなくても、コメントによって何が起こっているのかが明確になることを願っています。
このアルゴリズムの背後にある考え方は、ルートノードを確認し、さらに各ノードの子を確認することで、すべての子孫を見つけることができるということです。まだ確認する必要のあるノードの「やること」リストを保持しているので、道に迷うことはありません。
(このアルゴリズムにはおそらく名前がありますが、それが何かはわかりません。)
// This method defines how to find the descendants of a node "rootNode".
// It returns a set of nodes.
static HashSet<Node> FindDescendants(Node rootNode)
{
// Let "descendants" and "descendantsToCheck" initially be
// the set containing only rootNode.
HashSet<Node> descendants = new HashSet<Node> { rootNode };
HashSet<Node> descendantsToCheck = new HashSet<Node> { rootNode };
// While there are still descendants left to check...
while (descendantsToCheck.Count > 0)
{
// Let thisDescendant be an arbitrary descendant
// which still needs to be checked. We will check this descendant.
Node thisDescendant = descendantsToCheck.First();
// For each child of thisDescendant...
foreach (Node child in thisDescendant.Children)
{
// We have found a descendant. If it is already in our "descendants"
// set, then we don't need to do anything, because this descendant
// either has been checked or will be checked.
// However, if this descendant is not in "descendants"...
if (!descendants.Contains(child))
{
// We need to add this child to our set of descendants,
// as well as our set of descendants that need to be checked.
descendants.Add(child);
descendantsToCheck.Add(child);
}
}
// We are done checking thisDescendant,
// so remove it from the set of descendants to check.
descendantsToCheck.Remove(thisDescendant);
}
// At this point, there are no descendants left to check.
// This means that we have found all of the descendants. Return them.
return descendants;
}
このアルゴリズムは、祖先ではなく、子孫のみを提供します。両方の子孫を見つけるにはおよび祖先を見つけるには、アルゴリズムを2回使用します。1回は子孫用、もう1回は祖先用です。
祖先を見つけるには、いくつかのオプションがあります。 1つのオプションは、FindAncestors
関数と同じFindDescendants
関数を作成することですが、子ではなく親を参照する点が異なります。別のオプションは、FindDescendants
オプションを変更して、子と親のどちらを見るかを示すパラメーターを取ることです。
開始点から2つの単純な グラフトラバーサル を実行する必要があります。1つはグラフのエッジを左から右(親から子の方向)にのみ追跡し、もう1つは反対方向に追跡します。
深さ優先または幅優先の方法でトラバーサルを実行する場合は、どちらも機能します。追加の制約や要件がない限り、どちらも「一般的に優れている」ものはありません。すべてのノードを選択します。