优化的旅程计划规划

时间:2018-03-16 08:38:16

标签: algorithm

我有一个问题要解决。

问题陈述

我已经设置了大约5000家商店,其中包含geo-coordinates.Every商店必须访问一次,以便从商店收集订单收件人。

这将由销售员完成。

我想生成N个优化的旅程计划,这样每个推销员都能够覆盖最大数量。 8小时内的商店。

即在旅行计划中,访问所有商店所需的时间不应超过8小时。

并且没有两个推销员应该访问同一家商店。

即一旦访问了商店,就不应该再次访问另一个旅程计划。

在这种情况下,产生的旅程计划数量间接等于否。需要推销员。

需要最终结果

尽量减少号码。旅程计划涵盖所有商店

我有什么:

商店到商店的距离矩阵。每个商店之间的距离覆盖和时间

Challange : 我没有关于推销员的任何信息(我没有他们的地理位置),因此很难选择每个旅程计划的起点。

我最初的想法:

我正在考虑通过群集将商店划分到不同的区域。然后为每个群集形成旅程计划(针对优化路线)。

我将在python中开发它。

关于尝试和处理此类问题的最佳方法的任何想法。

1 个答案:

答案 0 :(得分:0)

这是一个启发式的想法:

  1. 从每个商店的一位推销员开始。每个销售人员都存储覆盖的路径(例如商店ID列表),这些路径仅从其初始商店开始,并且从0时间开始。
  2. 选择两个商店s1s2之间最短的未覆盖链接,时间成本为t
  3. 寻找这对推销员a1a2,使他们的路径在s1s2开始或结束。如果您找不到这样的对(因为商店已经在现有路径的中间,或者s1s2是一个推销员路径的开始和结束),请丢弃该链接并转到回到2。
  4. t_suma1花费的时间总和,a2t所花费的时间。如果t_sum小于每个推销员可以花费的最长时间T(8小时),请丢弃该链接并返回2.
  5. a1a2合并为一名推销员。这个过程会根据具体情况稍微改变一下,但它应该是相当微不足道的。例如,如果a1s1结束而a2s2开始,则您可以连接其路径,但如果a1s1开始并且a2s2开始,然后您必须(例如)反转a1的路径,然后将它们连接起来。新推销员的花费时间为t_sum
  6. 转到2.然后重复,直到没有未探索的链接为止。
  7. 这是基本的想法。有一些空间可以决定使用哪些数据结构,影响您查找下一个最短链接的方式,查找a1a2的方式以及如何跟踪已覆盖/丢弃的链接。您可能还包括许多改进或优化:

    • 合并a1a2后,您可以放弃s1s2的所有链接,除非它们是a1路径中的唯一元素}或a2。例如,如果我查看商店12之间的链接,我正在合并路径[1, 3, 4][2](导致[2, 1, 3, 4]或{ {1}}),我可以删除商店[4, 3, 1, 2]的所有剩余链接,因为它现在位于路径的中间,但不是来自1,因为它仍然位于合并的一端路径,因此可以连接。
    • 合并2a1时,如果a2,那么此推销员将无法再进一步合并(因为每个剩余链接的成本至少为T - t_sum < t }),所以你可以&#34;关闭&#34;路径,意味着你可以丢弃每端的链接,如果这可以节省你的时间(取决于具体的实现),你可以在下一步的执行中搜索ta1 3。
    • 事实上,如果图表非常密集,您可以节省时间检查,每次迭代或每几次,如果a2与任何当前&#34;的剩余时间之间的差异。活性&#34;代理小于上一个值T,&#34; close&#34;如果是这样的话。

    编辑:

    这是一个Python概念证明。我避免使用NumPy或任何其他外部软件包,虽然因为算法大多是迭代的,所以我不确定你能不能更快地使用它。

    t

    该函数返回一个元组列表,其中包含商店ID的路径和旅程的总时间。这是一个测试:

    def make_journeys(dists, max_dist):
        n = len(dists)
        ds = sorted((dists[i][j], i, j) for i in range(n) for j in range(i + 1, n))
        starts = list(range(n))
        ends = list(range(n))
        salesmen = [([i], 0) for i in range(n)]
        for d, i, j in ds:
            if starts[i] is not None and starts[j] is not None:
                si, sj = starts[i], starts[j]
                reverse_i, reverse_j = True, False
            elif starts[i] is not None and ends[j] is not None:
                si, sj = starts[i], ends[j]
                reverse_i, reverse_j = True, True
            elif ends[i] is not None and starts[j] is not None:
                si, sj = ends[i], starts[j]
                reverse_i, reverse_j = False, False
            elif ends[i] is not None and ends[j] is not None:
                si, sj = ends[i], ends[j]
                reverse_i, reverse_j = False, True
            else:
                continue
            if si == sj:
                continue
            (pi, di) = salesmen[si]
            (pj, dj) = salesmen[sj]
            dt = d + di + dj
            if dt > max_dist:
                continue
            starts[pi[0]] = None
            ends[pi[-1]] = None
            starts[pj[0]] = None
            ends[pj[-1]] = None
            if reverse_i:
                pi = list(reversed(pi))
            if reverse_j:
                pj = list(reversed(pj))
            pt = pi + pj
            starts[pt[0]] = si
            ends[pt[-1]] = si
            salesmen[si] = (pt, dt)
            salesmen[sj] = None
        return [s for s in salesmen if s]
    

    输出:

    def random_symmetric(N, min, max):
        # Build a random symmetric matrix
        dists = [[random.randint(1, 9) if i != j else 0 for j in range(N)] for i in range(N)]
        return [[(dists[i][j] + dists[j][i]) // 2 for j in range(N)] for i in range(N)]
    
    random.seed(100)
    
    # This takes long!
    distances = random_symmetric(5000, 1, 9)
    max_distance = 100
    
    journeys = make_journeys(distances, max_distance)
    print('{} journeys computed.'.format(len(journeys)))
    

    在上面的测试中,生成随机距离矩阵需要相当长的时间(使用NumPy会更快)。对50 journeys computed. 的调用在我的电脑中花费了大约16秒。显然是YMMV,但它没有运行数小时或数分钟。