给出成对距离的里程碑位置

时间:2013-06-17 17:50:21

标签: algorithm permutation

  

有一条'n'个里程碑的直道。你得到了   一个数组,其中包含所有里程碑对之间的距离   一些随机顺序。找到里程碑的位置。

示例:

  

考虑一条有4个里程碑的道路(a,b,c,d):

     

a --- 3Km --- b --- 5Km --- c --- 2Km --- d

     

a和b之间的距离是3

     

a和c之间的距离是8

     

a和d之间的距离是10

     

b和c之间的距离是5

     

b和d之间的距离是7

     

c和d之间的距离是2

所有上述值均以随机顺序给出,例如7,10,5,2,8,3。

输出必须为3,5,2或2,5,3。

假设给定数组的长度为n。我的想法是:

  1. 通过求解二次方程计算里程碑的数量,称其为x。
  2. 有P(n,x-1)种可能性。
  3. 验证所有可能的排列。
  4. 这个问题有更好的解决方案吗?

4 个答案:

答案 0 :(得分:1)

我找不到具有良好最坏情况行为的算法。但是,以下启发式方法可能对实际解决方案有用:

  • 假设第一个地标位于零位置。你可以找到最后的里程碑。然后所有其他地标位置需要出现在输入数组中。他们与最后一个地标的距离也必须出现。
  • 让我们为这些可能的地标位置建立一个图表。
  • 如果ab是两个可能的地标位置,则输入数组中会显示|a-b|ab中的至少一个这是一个里程碑式的位置。如果输入数组中出现a,请在b|a-b|之间画一条边。
  • 迭代过滤掉程度太小的地标位置。

你结束了几乎一个集团发现问题的事情。找一个适当的大集团;它对应于地标的定位。检查这种定位是否真正产生了正确的距离。

在最糟糕的情况下,你已经将可能的地标位置缩小到更易于管理的位置。

答案 1 :(得分:0)

确定。我会提出我的想法,这可以减少排列的数量。

查找n很简单,您甚至可以运行反向因子https://math.stackexchange.com/questions/171882/is-there-a-way-to-reverse-factorials

<强>假设: 目前我不知道如何找到这些数字。但我猜你已经以某种方式找到了这些数字。在找到n和元素之后,我们可以将其应用于部分减少计算。

考虑像

这样的问题
|<--3-->|<--6-->|<--1-->|<--7-->|
A       B       C       D       E

现在正如你所说,他们将给出的金额(按随机顺序)3,9,10,17,6,7,14,1,8,7。

但你可以采取任何组合(主要是错误的),

6-3-1-7. (say this is our taken combination)
Now,

6+3 -> 9 There, so Yes    //Checking in the list whether the 2 numbers could possibly be adjacent.
3+1 -> 4 NOT THERE, so cannot
1+7 -> 8 There, So Yes
6+7 -> 13 NOT THERE, So cannot be ajacent

心脏概念:

对于2个相邻的数字,它们的总和必须在列表中。如果总和不在列表中,则数字不相邻。

优化:

所以,3和1不会在附近。并且6和7不会到达附近。

因此,在进行排列时,我们可以消除

*31*,*13*,*76* and *67*组合。其中*为0或更多,前面或后面没有数字。

,而不是尝试排列4! = 24次,我们只能检查3617,1637,3716,1736。即只有4次。即保存了84%的计算。

最坏情况:

在你的情况下说它是5,2,3。 现在,我们必须执行此操作。

5+2 -> 7 There
2+3 -> 5 There
5+3 -> 8 There

哎呀,你的例子是最糟糕的情况,在这种情况下我们无法优化解决方案。

答案 2 :(得分:0)

逐个放置里程碑

编辑请参阅下面的新实施(包括时间)。

关键理念如下:

  1. 逐个构建里程碑列表,从0的一个里程碑和max(distances)的里程碑开始。让我们称之为端点
  2. 未考虑的最大距离必须来自其中一个端点,最多为相应的里程碑留下两个位置。
  3. 以下Python程序只检查里程碑是否可以从左端点放置,如果不是,则尝试从右端点放置里程碑(始终使用已经放置的里程碑未考虑的最大距离)。这必须通过反向跟踪来完成,因为之后的展示位置可能会出错。

    请注意,还有另一个(镜像)解决方案未输出。 (我认为不能有超过2个解决方案(对称),但我还没有证明它。)

    我将里程碑的位置视为solution,并使用辅助函数steps作为OP所需的输出。

    from collections import Counter
    
    def milestones_from_dists(dists, milestones=None):
        if not dists: # all dist are acounted for: we have a solution!
            return milestones
        if milestones is None:
            milestones = [0]
        max_dist = max(dists)
        solution_from_left = try_milestone(dists, milestones, min(milestones) + max_dist)
        if solution_from_left is not None:
            return solution_from_left
        return try_milestone(dists, milestones, max(milestones) - max_dist)
    
    def try_milestone(dists, milestones, new_milestone):    
        unused_dists = Counter(dists)
        for milestone in milestones:
            dist = abs(milestone - new_milestone)
            if unused_dists[dist]:
                unused_dists[dist] -= 1
                if unused_dists[dist] == 0:
                    del unused_dists[dist]
            else:
                return None # no solution
        return milestones_from_dists(unused_dists, milestones + [new_milestone])
    
    def steps(milestones):
        milestones = sorted(milestones)
        return [milestones[i] - milestones[i - 1] for i in range(1, len(milestones))]
    

    使用示例:

    >>> print(steps(milestones_from_dists([7, 10, 5, 2, 8, 3])))
    [3, 5, 2]
    >>> import random
    >>> milestones = random.sample(range(1000), 100)
    >>> dists = [abs(x - y) for x in milestones for y in milestones if x < y]
    >>> solution = sorted(milestones_from_dists(dists))
    >>> solution == sorted(milestones)
    True
    >>> print(solution)
    [0, 10, 16, 23, 33, 63, 72, 89, 97, 108, 131, 146, 152, 153, 156, 159, 171, 188, 210, 211, 212, 215, 219, 234, 248, 249, 273, 320, 325, 329, 339, 357, 363, 387, 394, 396, 402, 408, 412, 418, 426, 463, 469, 472, 473, 485, 506, 515, 517, 533, 536, 549, 586, 613, 614, 615, 622, 625, 630, 634, 640, 649, 651, 653, 671, 674, 697, 698, 711, 715, 720, 730, 731, 733, 747, 758, 770, 772, 773, 776, 777, 778, 783, 784, 789, 809, 828, 832, 833, 855, 861, 873, 891, 894, 918, 952, 953, 968, 977, 979]
    >>> print(steps(solution))
    [10, 6, 7, 10, 30, 9, 17, 8, 11, 23, 15, 6, 1, 3, 3, 12, 17, 22, 1, 1, 3, 4, 15, 14, 1, 24, 47, 5, 4, 10, 18, 6, 24, 7, 2, 6, 6, 4, 6, 8, 37, 6, 3, 1, 12, 21, 9, 2, 16, 3, 13, 37, 27, 1, 1, 7, 3, 5, 4, 6, 9, 2, 2, 18, 3, 23, 1, 13, 4, 5, 10, 1, 2, 14, 11, 12, 2, 1, 3, 1, 1, 5, 1, 5, 20, 19, 4, 1, 22, 6, 12, 18, 3, 24, 34, 1, 15, 9, 2]
    

    来自评论的新实施纳入建议

    from collections import Counter
    
    def milestones_from_dists(dists):
        dists = Counter(dists)
        right_end = max(dists)
        milestones = [0, right_end]
        del dists[right_end]
        sorted_dists = sorted(dists)
        add_milestones_from_dists(dists, milestones, sorted_dists, right_end)
        return milestones
    

    def add_milestone

    s_from_dists(dists, milestones, sorted_dists, right_end):
        if not dists:
            return True # success!
        # find max dist that's not fully used yet
        deleted_dists = [] 
        while not dists[sorted_dists[-1]]:
            deleted_dists.append(sorted_dists[-1])
            del sorted_dists[-1]
        max_dist = sorted_dists[-1]
    
        # for both possible positions, check if this fits the already placed milestones 
        for new_milestone in [max_dist, right_end - max_dist]:
            used_dists = Counter() # for backing up
            for milestone in milestones:
                dist = abs(milestone - new_milestone)
                if dists[dist]: # this distance is still available
                    dists[dist] -= 1
                    if dists[dist] == 0: 
                        del dists[dist]
                    used_dists[dist] += 1
                else: # no solution
                    dists.update(used_dists) # back up
                    sorted_dists.extend(reversed(deleted_dists))
                    break
            else: # unbroken
                milestones.append(new_milestone) 
                success = add_milestones_from_dists(dists, milestones, sorted_dists, right_end)
                if success:
                    return True
                dists.update(used_dists) # back up
                sorted_dists.extend(reversed(deleted_dists))
                del milestones[-1]  
        return False
    
    def steps(milestones):
        milestones = sorted(milestones)
        return [milestones[i] - milestones[i - 1] for i in range(1, len(milestones))]
    

    0到100000范围内随机里程碑的计时:

    • n = 10:0.00s

    • n = 100:0.05s

    • n = 1000:3.20s

    • n = 10000:仍然需要很长时间。

答案 3 :(得分:0)

给定距离集中的最大距离是第一个和最后一个里程碑之间的距离,即在示例10中。您可以在O(n)步骤中找到它。

对于每个其他里程碑(除第一个或最后一个之外的每个里程碑),您可以通过查找总和达到最大距离的一对距离来找到距离第一个和最后一个里程碑的距离,例如在您的示例中+3 = 10,8 + 2 = 10.你可以在O(n ^ 2)中找到这些对。

现在,如果您认为道路是从东到西,剩下的就是对于所有内部里程碑(除了第一个或最后一个),您需要知道两个距离中的哪一个(例如7和3,或8和2)朝东(另一个朝向西)。

您可以在时间O(2 ^(n-2))中简单地枚举所有可能性,并且对于每个可能的方向检查,您获得与问题中相同的距离集。这比枚举集合中最小距离的所有排列要快。

例如,如果假设7和8朝西,则两个内部里程碑之间的距离为1英里,这不在问题集中。所以它必须是7向西,8向东,导致解决方案(或它的镜像)

WEST | -- 2 -- | -- 5 -- | -- 3 -- | EAST

对于更大的里程碑集,您只需开始猜测到端点的两个距离的方向,并且每当您生成两个里程碑之间的距离不在问题集中时,您就会回溯。