繁体   English   中英

遍历具有任意数目的连接的节点的最快方法

[英]Fastest way to iterate through nodes with arbitrary number of connections to each other

我正在制作一个破裂脚本,其中有一些节点,每个节点都拥有与其他节点的任意数量的连接。

碎片和大块:

碎片和大块

每个节点代表预断裂的“块”的“碎片”。

当一个分片损坏时,它与其他分片的所有连接及其与之连接的连接都将被删除。

发生这种情况时,我需要遍历碎片,找出哪些碎片连接在一起,以及是否将碎片分开以创建新的碎片。

最快的方法是什么?

目前我有:

  • 一个具有

    • 对父块的引用
    • 与相邻分片的连接列表。
    • 一个Break()方法
      • 删除从相邻分片到此分片的所有连接。
      • 清除从此到相邻分片的连接。
      • 调用父块的UpdateChunk()方法。
  • 具有

    • 它包含的所有分片的列表。
    • 一个UpdateChunk()方法,
      • 检查是否只有一个分片,然后销毁该块。
      • 在此块中创建所有碎片的新列表,以跟踪未处理的碎片。
      • 调用ProcessShard()方法以通过其连接从所有未处理分片列表中的第一个分片开始递归处理所有分片。
      • 检查未处理列表中是否仍存在分片,如果存在,则创建一个新块并在其中转储剩余的分片,并调用其UpdateChunk()方法。
    • 一个ProcessShard()方法
      • 从未处理的列表中删除给定的碎片
      • 使给定碎片的父块成为当前块
      • 循环遍历该对象连接的所有分片,并检查它们是否不在处理后的列表中,如果是,则为它们调用ProcessShard()。
public class Shard : MonoBehaviour
{
    public GameObject parentChunk;

    public List<GameObject> connectedShards = new List<GameObject>();

    public void Break()
    {
        // remove all connections that point to this shard
        foreach (GameObject shard in connectedShards)
        {
            // remove connection to this shard
            shard.GetComponent<Shard>().connectedShards.Remove(gameObject);
        }

        // clear connected shards list
        connectedShards.Clear();

        // check that there is a parent chunk
        if (parentChunk)
        {
            // force parent chunk to update
            parentChunk.GetComponent<Chunk>().UpdateChunk();

            // set parent chunk to null
            parentChunk = null;
        }
    }
}




public class Chunk : MonoBehaviour
{
    public List<GameObject> shards = new List<GameObject>();

    private List<GameObject> unprocessedShards = new List<GameObject>();

    public void UpdateChunk()
    {
        // if there is only one shard in this chunk
        if (shards.Count == 1)
        {
            // break shard
            shards[0].GetComponent<Shard>().parentChunk = null;
            shards[0].GetComponent<Shard>().Break();

            // destroy this chunk
            Destroy(gameObject);
        }
        else // there are multiple shards in this chunk
        {
            // fill the unprocessed shards list
            unprocessedShards = new List<GameObject>(shards);

            // process all shards in this chunk recursively starting from the first
            ProcessShard(unprocessedShards[0], gameObject);

            // if there are still unprocessed shards (this chunk was split)
            if (unprocessedShards.Count > 0)
            {
                // remove the unprocessed shards from this chunk's shard list
                foreach (GameObject unprocessedShard in unprocessedShards)
                {
                    shards.Remove(unprocessedShard);
                }

                // create new chunk(s) from the leftover shards
                parentObject.GetComponent<Fragmenter>().NewChunk(unprocessedShards);
            }
        }
    }



    // process shard
    private void ProcessShard(GameObject shard, GameObject chunk)
    {
        // remove shard from unprocessed list
        unprocessedShards.Remove(shard);

        // add shard to the given chunk
        shard.GetComponent<Shard>().parentChunk = chunk;

        // loop through all shards connected to the given shard
        foreach (GameObject connectedShard in shard.GetComponent<Shard>().connectedShards)
        {
            // connected shard has not been processed yet
            if (unprocessedShards.Contains(connectedShard))
            {
                // process all shards connected to the connected shard
                ProcessShard(connectedShard, chunk);
            }
        }
    }
}

我主要考虑的是ProcessShard()方法,该方法通常会将每个碎片处理多次,因为一个碎片可以与许多碎片相邻。 如何最有效地忽略已经处理过的内容?

首先,我建议远离List.Remove因为最坏的情况是它需要检查每个项目以找到要删除的项目。 可以从保留的未处理碎片列表中删除正在处理的碎片,而可以保留另一个已处理碎片列表并添加到该列表中。 然后,检查连接的碎片是否未处理的条件将变为if(!processedShards.Contains(connectedShard))

您可以在microsoft docs上阅读有关List操作性能的更多信息,只需向下滚动至“备注”部分。


另一个改进是找到List.Contains的替代项。包含在检查分片是否已处理时。 List.Remove类似,它需要潜在地检查列表中的每个项目,然后才能判断该项目是否存在。

一种方法是使用HashSet而不是List。 它提供了访问特定项目以及检查项目中是否存在项目的更快方法。 它还提供了许多相同的方法,因此重构应该很容易。


只是要注意,就性能而言,我个人不会为优化而烦恼。 您可能会发现这些改进几乎使您无所适从,因为您的收藏还不够大。 如果您还没有,请检查Unity分析器,然后从那里了解您的重要性能来源。

似乎该脚本在ProcessShard()方法的递归过程中减慢了很多,因此我将其重命名并重写为ProcessShards(),该方法使用while循环和Queue处理所有分片。 在具有100个分片的块上,性能提高了大约十倍。

    // process shards
    private void ProcessShards()
    {
        // create a list for shards to process
        Queue<GameObject> shardsToProcess = new Queue<GameObject>();

        // select the first shard to process
        shardsToProcess.Enqueue(unprocessedShards[0]);

        // remove shard from unprocessed list
        unprocessedShards.Remove(unprocessedShards[0]);

        // loop while there are shards to process
        while (shardsToProcess.Count > 0)
        {
            // get the first shard to process
            GameObject shard = shardsToProcess.Dequeue();

            // add the shard to this chunk
            shard.GetComponent<Shard>().parentChunk = gameObject;

            // loop through all shards connected to the given shard
            foreach (GameObject connectedShard in shard.GetComponent<Shard>().connectedShards)
            {
                // connected shard has not been processed yet
                if (unprocessedShards.Contains(connectedShard))
                {
                    // add shard to be processed
                    shardsToProcess.Enqueue(connectedShard);

                    // remove shard from unprocessed list
                    unprocessedShards.Remove(connectedShard);
                }
            }
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM