组合两个列表,使元素之间的距离最小

时间:2013-06-25 16:33:13

标签: algorithm sorting

我必须列出这样的名单:

a = ["1a","2a","3a","4a","5a","6a","7a","8a","9a","10a","11a","12a","13a","14a"] 
b = ["1b","2b","3b","4b","5b","6b","7b","8b","9b","10b","11b","12b","13b","14b"]

我想要的是将它们组合在一起,以便 a 中的 n 元素与 n 元素之间至少存在差异。 b 中的相应元素。

例如,如果我的 n 是10,那么" 3a"在3号位和" 3b"位于第5位,这不是一个解决方案,因为这些对应元素之间的距离只有2。

我已经通过强力方法解决了这个问题:将两个数组的并集洗牌并查看是否满足约束条件;如果没有,再次洗牌等等......毋庸置疑,对于14个元素数组,有时会有5到10秒的计算产生一个最小距离为10的解决方案。尽管这样做还不错对于我正在寻找的东西,我很好奇如何以更优化的方式解决这个问题。

我目前正在使用Python,但是对任何语言(或伪代码)的代码都非常欢迎。

编辑:此问题的背景类似于问号,预计约有100名参与者加入。因此,我不一定对所有感兴趣解决方案,但更像是前100个。

感谢。

3 个答案:

答案 0 :(得分:1)

对于您的特定情况,您可以使用随机方法 - 尽管不像您已尝试过的那样随意。像这样:

  1. 以两个列表中的项目的随机排列开头
  2. 通过创建另一个的副本随机交换两个项目来创建新的排列
  3. 测量排列的质量,例如,每对相关项目的距离之和,或此类距离的最小值
  4. 如果新排列的质量优于原始排列的质量,保留新排列,否则丢弃新排列并继续原始排列< / LI>
  5. 从2开始重复。直到每个距离至少 10 或者直到质量在多次迭代中没有改善
  6. 在每次迭代中改变整个列表的差异(如在你的方法中)是在每次迭代中,排列只能变得更好,直到满意的解决方案是找到。

    每次运行此算法时,结果都会略有不同,因此您可以针对100种不同的解决方案运行100次。当然,这个算法并不能保证找到一个解决方案(更不用说所有这样的解决方案)了,但它应该足够快,以便你可以在它失败的情况下重新启动它。

    在Python中,这看起来有点像(稍微简化,但仍然有效):

    def shuffle(A, B):
        # original positions, i.e. types of questions
        kind = dict([(item, i) for i, item in list(enumerate(A)) + list(enumerate(B))])
    
        # get positions of elements of kinds, and return sum of their distances
        def quality(perm):
            pos = dict([(kind[item], i) for i, item in enumerate(perm)])
            return sum(abs(pos[kind[item]] - i) for i, item in enumerate(perm))
    
        # initial permutation and quality
        current = A + B
        random.shuffle(current)
        best = quality(current)
    
        # improve upon initial permutation by randomly swapping items
        for g in range(1000):
            i = random.randint(0, len(current)-1)
            j = random.randint(0, len(current)-1)
            copy = current[:]
            copy[i], copy[j] = copy[j], copy[i]
            q = quality(copy)
            if q > best:
                current, best = copy, q
    
        return current
    

    print shuffle(a, b)的示例输出:

      

    ['14b','2a','13b','3a','9b','4a','6a','1a','8a','5b','12b','11a ','10b','7b','4b','11b','5a','7a','8b','12a','13a','14a','1b','2b', '3b','6b','10a','9a']

答案 1 :(得分:0)

正如我从你的问题中理解的那样,可以通过完全依赖于数组的索引(即纯整数)来执行所有排序,因此可以减少问题以创建(有效)范围而不是分析每个元素。

对于每个a&lt; = total_items-n,有效b = if(a + n == total_items){total_items} else {[a + n,total_items]}

例如:

n = 10; total_items = 15;

表示a = 1 - &gt;有效b = [11,15]

表示a = 2 - &gt;有效b = [12,15]

等。

这将执行4次:向前和向后表示对b的尊重,对于b对b表示相同。

通过这种方式,您可以将迭代次数减少到最小值,并作为输出获得每个位置的一组“解决方案”,而不是一对一的绑定(这就是你所拥有的)现在,不是吗?)。

答案 2 :(得分:0)

如果Python中有等效的.NET列表和LINQ,那么您可以直接转换以下代码。它可以非常快速地生成多达100个列表:我按下“debug”来运行它,然后在不到一秒的时间内弹出一个窗口,结果很短。

' VS2012
Option Infer On

Module Module1

    Dim minDistance As Integer = 10
    Dim rand As New Random ' a random number generator

    Function OkToAppend(current As List(Of Integer), x As Integer) As Boolean
        ' see if the previous minDistance values contain the number x
        Return Not (current.Skip(current.Count - minDistance).Take(minDistance).Contains(x))
    End Function

    Function GenerateList() As List(Of String)
        ' We don't need to start with strings: integers will make it faster.
        ' The "a" and "b" suffixes can be sprinkled on at random once the
        ' list is created.
        Dim numbersToUse() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}

        Dim pool As New List(Of Integer)
        ' we need all the numbers twice
        pool.AddRange(numbersToUse)
        pool.AddRange(numbersToUse)

        Dim newList As New List(Of Integer)

        Dim pos As Integer

        For i = 0 To pool.Count - 1
            ' limit the effort it puts in
            Dim sanity As Integer = pool.Count * 10

            Do
                pos = rand.Next(0, pool.Count)
                sanity -= 1
            Loop Until OkToAppend(newList, pool(pos)) OrElse sanity = 0

            If sanity > 0 Then ' it worked
                ' append the value to the list
                newList.Add(pool(pos))
                ' remove the value which has been used
                pool.RemoveAt(pos)
            Else ' give up on this arrangement
                Return Nothing
            End If

        Next

        ' Create the final list with "a" and "b" stuck on each value.
        Dim stringList As New List(Of String)
        Dim usedA(numbersToUse.Length) As Boolean
        Dim usedB(numbersToUse.Length) As Boolean

        For i = 0 To newList.Count - 1
            Dim z = newList(i)
            Dim suffix As String = ""

            If usedA(z) Then
                suffix = "b"
            ElseIf usedB(z) Then
                suffix = "a"
            End If

            ' rand.Next(2) generates an integer in the range [0,2)
            If suffix.Length = 0 Then suffix = If(rand.Next(2) = 1, "a", "b")

            If suffix = "a" Then
                usedA(z) = True
            Else
                usedB(z) = True
            End If

            stringList.Add(z.ToString & suffix)
        Next

        Return stringList

    End Function

    Sub Main()
        Dim arrangements As New List(Of List(Of String))
        For i = 1 To 100
            Dim thisArrangement = GenerateList()
            If thisArrangement IsNot Nothing Then
                arrangements.Add(thisArrangement)
            End If
        Next

        'TODO: remove duplicate entries and generate more to make it up to
        ' the required quantity.
        For Each a In arrangements
            ' outputs the elements of a with ", " as a separator
            Console.WriteLine(String.Join(", ", a))
        Next

        ' wait for user to press enter
        Console.ReadLine()

    End Sub

End Module