16

有人可以给我一个用于旅行商问题的 2-opt 算法的代码示例。现在我使用最近邻来查找路径,但这种方法远非完美,经过一些研究,我发现 2-opt 算法可以将该路径校正到可接受的水平。我找到了一些示例应用程序,但没有源代码。

4

3 回答 3

30

于是无聊就写了。看起来它有效,但我还没有非常彻底地测试它。它假设三角形不等式,所有边都存在,诸如此类。它的工作原理与我概述的答案非常相似。它打印每次迭代;最后一个是 2 优化的。

我相信它可以通过无数种方式进行改进。

using System;
using System.Collections.Generic;
using System.Linq;


namespace TSP
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            //create an initial tour out of nearest neighbors
            var stops = Enumerable.Range(1, 10)
                                  .Select(i => new Stop(new City(i)))
                                  .NearestNeighbors()
                                  .ToList();

            //create next pointers between them
            stops.Connect(true);

            //wrap in a tour object
            Tour startingTour = new Tour(stops);

            //the actual algorithm
            while (true)
            {
                Console.WriteLine(startingTour);
                var newTour = startingTour.GenerateMutations()
                                          .MinBy(tour => tour.Cost());
                if (newTour.Cost() < startingTour.Cost()) startingTour = newTour;
                else break;
            }

            Console.ReadLine();
        }


        private class City
        {
            private static Random rand = new Random();


            public City(int cityName)
            {
                X = rand.NextDouble() * 100;
                Y = rand.NextDouble() * 100;
                CityName = cityName;
            }


            public double X { get; private set; }

            public double Y { get; private set; }

            public int CityName { get; private set; }
        }


        private class Stop
        {
            public Stop(City city)
            {
                City = city;
            }


            public Stop Next { get; set; }

            public City City { get; set; }


            public Stop Clone()
            {
                return new Stop(City);
            }


            public static double Distance(Stop first, Stop other)
            {
                return Math.Sqrt(
                    Math.Pow(first.City.X - other.City.X, 2) +
                    Math.Pow(first.City.Y - other.City.Y, 2));
            }


            //list of nodes, including this one, that we can get to
            public IEnumerable<Stop> CanGetTo()
            {
                var current = this;
                while (true)
                {
                    yield return current;
                    current = current.Next;
                    if (current == this) break;
                }
            }


            public override bool Equals(object obj)
            {
                return City == ((Stop)obj).City;
            }


            public override int GetHashCode()
            {
                return City.GetHashCode();
            }


            public override string ToString()
            {
                return City.CityName.ToString();
            }
        }


        private class Tour
        {
            public Tour(IEnumerable<Stop> stops)
            {
                Anchor = stops.First();
            }


            //the set of tours we can make with 2-opt out of this one
            public IEnumerable<Tour> GenerateMutations()
            {
                for (Stop stop = Anchor; stop.Next != Anchor; stop = stop.Next)
                {
                    //skip the next one, since you can't swap with that
                    Stop current = stop.Next.Next;
                    while (current != Anchor)
                    {
                        yield return CloneWithSwap(stop.City, current.City);
                        current = current.Next;
                    }
                }
            }


            public Stop Anchor { get; set; }


            public Tour CloneWithSwap(City firstCity, City secondCity)
            {
                Stop firstFrom = null, secondFrom = null;
                var stops = UnconnectedClones();
                stops.Connect(true);

                foreach (Stop stop in stops)
                {
                    if (stop.City == firstCity) firstFrom = stop;

                    if (stop.City == secondCity) secondFrom = stop;
                }

                //the swap part
                var firstTo = firstFrom.Next;
                var secondTo = secondFrom.Next;

                //reverse all of the links between the swaps
                firstTo.CanGetTo()
                       .TakeWhile(stop => stop != secondTo)
                       .Reverse()
                       .Connect(false);

                firstTo.Next = secondTo;
                firstFrom.Next = secondFrom;

                var tour = new Tour(stops);
                return tour;
            }


            public IList<Stop> UnconnectedClones()
            {
                return Cycle().Select(stop => stop.Clone()).ToList();
            }


            public double Cost()
            {
                return Cycle().Aggregate(
                    0.0,
                    (sum, stop) =>
                    sum + Stop.Distance(stop, stop.Next));
            }


            private IEnumerable<Stop> Cycle()
            {
                return Anchor.CanGetTo();
            }


            public override string ToString()
            {
                string path = String.Join(
                    "->",
                    Cycle().Select(stop => stop.ToString()).ToArray());
                return String.Format("Cost: {0}, Path:{1}", Cost(), path);
            }

        }


        //take an ordered list of nodes and set their next properties
        private static void Connect(this IEnumerable<Stop> stops, bool loop)
        {
            Stop prev = null, first = null;
            foreach (var stop in stops)
            {
                if (first == null) first = stop;
                if (prev != null) prev.Next = stop;
                prev = stop;
            }

            if (loop)
            {
                prev.Next = first;
            }
        }


        //T with the smallest func(T)
        private static T MinBy<T, TComparable>(
            this IEnumerable<T> xs,
            Func<T, TComparable> func)
            where TComparable : IComparable<TComparable>
        {
            return xs.DefaultIfEmpty().Aggregate(
                (maxSoFar, elem) =>
                func(elem).CompareTo(func(maxSoFar)) > 0 ? maxSoFar : elem);
        }


        //return an ordered nearest neighbor set
        private static IEnumerable<Stop> NearestNeighbors(this IEnumerable<Stop> stops)
        {
            var stopsLeft = stops.ToList();
            for (var stop = stopsLeft.First();
                 stop != null;
                 stop = stopsLeft.MinBy(s => Stop.Distance(stop, s)))
            {
                stopsLeft.Remove(stop);
                yield return stop;
            }
        }
    }
}
于 2010-05-29T01:49:50.407 回答
4

好吧,您对 TSP 的解决方案总是远非完美。没有代码,但这里是关于 2-Opt 的方法。还不错:

  1. 您需要一个名为 Stop 的类,它具有 Next、Prev 和 City 属性,并且可能还有一个 Stops 属性,它只返回包含 Next 和 Prev 的数组。
  2. 当您将它们链接在一起时,我们将其称为巡回赛。Tour 有一个 Stop 属性(任何站点都可以)和一个 AllStops 属性,它的 getter 只是走过站点并返回它们
  3. 您需要一种方法来进行游览并返回其成本。我们称之为 Tour.Cost()。
  4. 你需要 Tour.Clone(),它只是遍历站点并单独克隆它们
  5. 您需要一种方法来生成具有两条边切换的游览集。调用这个 Tour.PossibleMutations()
  6. 从您的 NN 解决方案开始
  7. 调用 PossibleMutations() 就可以了
  8. 对所有这些调用 Cost() 并取结果最低的那个
  9. 重复直到成本不下降
于 2010-05-28T08:42:29.097 回答
2

如果问题是欧几里得距离,并且您希望算法产生的解决方案的成本在最优值的 3/2 以内,那么您需要 Christofides 算法。ACO 和 GA 没有保证费用。

于 2011-03-12T07:06:07.563 回答