如何在图中找到先决条件的总数

How to find the total of prerequisites in a graph

我有以下图形节点:

这些节点代表视频游戏中角色的技能。第0节点是所有角色开始的基础技能。第1st节点是需要第0th节点作为前提的技能。同样,第2nd节点需要第1st节点作为技能,这就需要第0th节点作为技能。但是,当一个技能学会了,就不需要再学了。

我得到一个数组 T,它是 [0,0,1,1] 和一个数组 A,它是 [2]。只有学习了T[k]th技能才能学习第K技能。 0 是根,因此无需任何其他技能即可学习。上面的数组解释如下:T[0] = 0T[1]=0T[2]=1T[3]=1,因此 T=[0,0,1,1]。数组 A 告诉我我想学习的技能。我的输出需要是我需要学习技能 2 的技能总数。在上面的例子中,答案是 3

示例 2

在 2nd 图中,答案是 5,因为我被要求学习 2、5、6,所以我将学习 0、2、3、5 和6. 我的输入是:T=[0,0,0,0,2,3,3]A = [2,5,6]

我一直在想怎么解决这个问题。这是一个改进我的图论的编码练习。但是我不知道如何在算法上关联T列表和A列表。

示例 3

在上面的例子中T = [0,3,0,0,5,0,5]A = [4,2,6,1,0]。 我创建了 X = [0,1,2,3,4,5,6] 来将 T 映射到它的连接。

我知道什么?

所以我要return这个数,也就是7

我的代码

到目前为止,这是我所拥有的:

def sol(T,A):
    count = 1
    x = [i for i in range(len(T))]

    if 0 in A:
       A.remove(0)

    for i in range(0, len(A)):
        indx = x.index(A[i])
        if T[indx]==0:
           count += 1
           break
        else:
           # nothing yet

您确实可以使用递归作为深度优先遍历的实现(广度优先遍历也可以)。使用 set 来跟踪您已经访问过的节点。对于更大的输入,这将比调用 index.

更有效

这是一个递归算法。请注意,我使用 itertools 将多个结果合并为一个。还有许多其他方法可以做到这一点:

import itertools

def sol(tree, needed):
    visited = set()

    def dfs(node):
        if node in visited:
            return []
        visited.add(node)
        return dfs(tree[node]) + [node] if node else [0]

    return list(itertools.chain(*map(dfs, needed)))

这会输出需要学习的技能,所以你只需要慢慢来,就能得到想要的答案。但是,获取此列表可能会有用:它也会按照您可以学习这些技能的顺序返回,始终从 0 开始,并确保在您坚持该顺序时满足先决条件。

正如@Nuclearman 在评论中所说,您想找到通往您需要学习的所有技能的最短路径。

您还需要知道的是,这是图论中的标准问题,使用 Dijkstra 算法可以最有效地解决该问题。您可以找到该算法的大量描述和实现它的示例代码。

但为什么要重新发明轮子呢?任何图形库都将提供经过良好测试的高效实现。我使用升压图。

算法:

read input
Run Dijsktra
loop over required skills
   loop over path to required skill (from Dijsktra run )
       if not skill already learned
           record new skill
display skills learnt

我使用 PathFinder C++ wrapper 作为 boost 图形库

输入

t 0 0 0 0 2 3 3
a 2 5 6

输出

skill 2 needs 0 2
skill 5 needs 0 2 5
skill 6 needs 0 3 6
Total skills needed 5 ( 0 2 3 5 6  )

输入例如3

t 0 3 0 0 5 0 5
a 4 2 6 1 0

输出

skill 4 needs 0 5 4
skill 2 needs 0 2
skill 6 needs 0 5 6
skill 1 needs 0 3 1
Total skills needed 7 ( 1 3 2 0 4 5 6  )

密码

void doPreReqs()
{
    cPathFinder finder,
    cPathFinderReader reader( finder );
    std::set<int> setSkillsNeeded;

    // read input. va are the required skills
    auto va = reader.singleParentTree();

    // starting node
    finder.start("0");

    // paths to all end nodes
    finder.end(-1);

    // run Dijsktra
    finder.path();

    // loop over required skills
    for (auto &a : va)
    {
        // skill 0 does not need to be learned
        if( a == "0" )
            continue;

        //skills needed to get required skill
        auto path = finder.pathPick(finder.find(a));

        std::cout << "skill " << a << " needs ";
        for (int s : path)
            std::cout << finder.nodeName(s) << " ";
        std::cout << "\n";

        //loop over prerequsites
        for (auto s : path)
        {
            //record skill if not already learned
            setSkillsNeeded.insert(s);
        }
    }

    std::cout << "Total skills needed "
              << setSkillsNeeded.size() << " ( ";
    for (int s : setSkillsNeeded)
        std::cout << finder.nodeName(s) << " ";
    std::cout << " )\n";
}

我已将此作为图形用户界面中的一个选项添加到探路者。 Documentation.

您可以通过迭代方法使用 collections.deque

from collections import deque
def earn_skill(T, A):
   a, r = deque(A), [0]
   while a:
      if not T[(n:=a.popleft())] or any(j in r for j in T[:n]):
         r.extend([T[n], n])
      else:
         return
   return len(set(r))

vals = [([0,0,1,1], [2]), ([0,0,0,0,2,3,3], [2,5,6]), ([0,3,0,0,5,0,5], [4,2,6,1,0])]
r = [earn_skill(t, a) for t, a in vals]

输出:

[3, 5, 7]