复杂的C#Linq算法

时间:2015-10-06 14:49:56

标签: c# algorithm linq shortest-path

我有一个要解决的C#问题。我正在创建一个可以确定最短路径的应用程序。有一个端口网络,该方法需要找出最快的路线。

我用我的方法得到了这么多,但似乎我正在复制代码,可能的路线可能永远存在。有没有人知道一个更好的方法来测试所有可能的旅程,所以我能够计算最短的一个?

 public int ShortestJourneyTime(string startPort, string endPort)
    {
        int shortJourneyTime = 0;
        var possStartRoutes = RouteData.Where(p => p.Key[0] == startPort);

        if (possStartRoutes.Count() <= 0)
            throw new InvalidOperationException(string.Format("{0} : Start port is invalid", startPort));

        foreach (var p in possStartRoutes)
        {
            int currentJourneyTime = p.Value;

            var possRoutes2 = RouteData.Where(p2 => p2.Key[0] == p.Key[1]);

            foreach (var p2 in possRoutes2)
            {
                if (p2.Key[1] == endPort)
                {
                    currentJourneyTime += p2.Value;
                    if (shortJourneyTime > currentJourneyTime || shortJourneyTime == 0)
                        shortJourneyTime = currentJourneyTime;
                }
                else
                {
                    var possRoutes3 = RouteData.Where(p3 => p3.Key[0] == p2.Key[1]);
                }
            }
        }

        return shortJourneyTime;
    }

端口数据存储如下

  Dictionary<List<string>, int>  testData = new Dictionary<List<string>, int>();

        List<string> routeOne = new List<string>() { "Buenos Aires", "New York" };
        testData.Add(routeOne, 6);

1 个答案:

答案 0 :(得分:2)

正如人们建议的Dijkstra算法。这是适合您需求的算法。我保留了您的数据结构Dictionary<List<string>, int>,因此您仍然可以使用它。但我改变了算法内部的数据结构,选择了更合适的数据结构,使算法更容易。

我们的想法是从开始到结束获得所有可能的路径。然后取最短路径作为结果。请注意,传递的路径不应该重复意味着如果我们通过New York我们不应该再次传递它,否则我们会陷入无限循环。

此方法的返回类型为Tuple<List<string>, int>。其中List<string>是按顺序保存端口的最短路径,int是此路径的长度。您可以使用Item1Item2属性访问它们。

例如,从Buenos AiresLiverpool的最短路径是此"Buenos Aires","Casablanca","Liverpool"字符串列表,其长度为8天。

请注意,如果未找到任何内容,则此方法返回null。如果需要,可以抛出异常。只是取消评论我评论throw expetion

的地方

此方法中的路径是Tuple<string, string, int>的类型。 Item1是起始端口。 Item2是结束端口,Item3是这两个端口之间的长度。

public Tuple<List<string>, int> TakeShortestJourney(string startPort, string endPort)
{
    if (startPort == endPort) // just for special case when user puts same start and end port.
    {
        return new Tuple<List<string>, int>(new List<string>(){startPort}, 0);
    }

    // convert from Dictionary<List<string>, int> into List<Tuple<string, string, int>>
    var t = RouteData.Select(x => new Tuple<string, string, int>(x.Key[0], x.Key[1], x.Value));
    var allPaths = new List<Tuple<string, string, int>>(t);

    // This will hold all possible short paths.
    var passedPaths = new List<Tuple<List<string>, int>>();

    // create a recursion method to do the search and fill the passedPaths.
    Action<List<string>, string, int> getPath = null;
    getPath = (list, start, length) =>
    {
        list.Add(start);

        foreach (
            var currentRoad in
                allPaths.Where(x => !list.Contains(x.Item2) && x.Item1 == start).OrderBy(x => x.Item3))
        {
            int newLength = length + currentRoad.Item3; // calculate new length.

            if (currentRoad.Item2 == endPort)
            {
                list.Add(currentRoad.Item2);
                passedPaths.Add(new Tuple<List<string>, int>(list, newLength));
                break;
            }

            if (passedPaths.Any(x => x.Item2 < newLength)) break;

            getPath(new List<string>(list), currentRoad.Item2, newLength);
        }
    };

    // start search with initial empty list and 0 length. start from startPort
    getPath(new List<string>(), startPort, 0);

    // Take the shortest path from passed paths.
    var shortestPath = passedPaths.OrderBy(x=> x.Item2).FirstOrDefault();

    if (shortestPath == null) {
    //    throw new ApplicationException("nothing was found");
    }

    return shortestPath;
}

以下是如何使用此方法的示例。

var shortestPath = TakeShortestJourney("Buenos Aires", "Liverpool");

foreach (var p in shortestPath.Item1) // Item1 holds path
{
    Console.WriteLine(p);
}

Console.WriteLine(shortestPath.Item2); // Item2 holds length

关于递归操作:

list.Add(start);

我们为每次调用执行此操作,因此我们按顺序构建路径。

allPaths.Where(x => !list.Contains(x.Item2) && x.Item1 == start).OrderBy(x => x.Item3)

此查询将获取以start开头的路径,该路径是上一个路径(x.Item1 == start)的结尾,但它不会采用我们列表中已存在的路径来防止重复({{1 }})。最后它会按!list.Contains(x.Item2)(长度)排序,所以我们先选择最短的(Item3)。

OrderBy(x => x.Item3)

这将检查我们路径的结束。它发生在if (currentRoad.Item2 == endPort) { list.Add(currentRoad.Item2); passedPaths.Add(new Tuple<List<string>, int>(list, newLength)); break; } (当前路径的末尾)等于结束端口时。最后,我们将最后一项(Item2)添加到列表中,我们将列表和最终长度保存在Item2内。我们打破了循环,因为我们知道这条路径之后的长度会更长,因为我们已经到了末尾所以不必继续其他路径。 (记住我们是按passedPaths

订购的
Item3

这个仅用于进一步优化意味着如果存在任何完整路径并且当前路径的长度大于该路径,则停止构建此路径,因为存在较短路径。也因为查询是有序的,下一个项目的长度更长,所以我们只是从这条路径返回。基于RouteData,这可能会增加或降低性能。您可以安全地删除此部分而不会影响结果。

if (passedPaths.Any(x => x.Item2 < newLength)) break;

这是此算法的核心!递归调用。第一个参数是我们正在创建的getPath(new List<string>(list), currentRoad.Item2, newLength); ,但我们每次创建它的新引用,因此列表保持不受递归的影响,我们可以继续我们的foreach循环。第二个参数list是当前路径的结尾,它将是下一个路径的开始。最后一个参数currentRoad.Item2,我们在newLength保存最终路径时需要这个。