【问题标题】:Algorithm to sort pairs of numbers对数对进行排序的算法
【发布时间】:2011-03-17 17:51:45
【问题描述】:

我遇到了一个问题,我需要一些聪明的 SO 的帮助。 我有 N 对无符号整数。我需要对它们进行排序。对的结束向量应按每对中的第一个数字非递减排序,并且按每对中的第二个数字非递增排序。每对可以使第一个和第二个元素相互交换。有的时候是没有办法解决的,那我需要抛出异常。

例子:

in pairs:
1 5
7 1
3 8
5 6

out pairs:
1 7     <-- swapped
1 5     
6 5     <-- swapped
8 3     <-- swapped

^^ 如果不交换对,就不可能构建解决方案。所以我们交换对 (7, 1), (3, 8) 和 (5, 6) 并构建结果。 或

in pairs:
1 5
6 9

out:
not possible

另一个例子说明了如何首先“排序对”不是解决方案。

in pairs:
1 4
2 5
out pairs:
1 4
5 2

谢谢

【问题讨论】:

  • 或许可以试试CS overflow
  • 不,我朋友的一个面试问题。但即使这是一个家庭作业,我认为这对你们所有人来说都会很有趣。
  • 你可以应用LIS算法。我需要一些时间来写出完整的解释,所以让你开始思考这个问题
  • 您正在更改第一个示例中对的元素的顺序(7,1 变为 1,7)。这是故意的吗?
  • @Klark 为什么第二个示例的解决方案“不可能”? (2,9) (5,1) 怎么样?

标签: algorithm sorting


【解决方案1】:

O(n log n) 解

【讨论】:

  • 不错。您可能会澄清“法律”的含义。 “顶部和底部必须是每个项目是前一个项目的子间隔的列表”是不够的。甚至 (1,2),(3,4) 也满足该要求。我认为“中间”两个项目也必须重叠。 (我不明白 X 的最后一个案例的意义,但我也不确定所有报告的失败都是实际失败。)
  • @xan:感谢 cmets。如果它是列表中最新间隔的子间隔,则添加间隔是“合法的”。是的,“中间”两个项目也必须重叠,忘了说。关于失败:当我的算法返回失败时,它总是识别出 3 个不兼容的间隔。请注意,对 3 个区间的端点进行排序的方法只有几种,图中显示了两种非平凡且适用的排序。
  • 我看到你在这个答案中付出了很多努力,这很好。但是嵌入在图像中的文本无法编辑或索引以进行搜索。
  • 嗯,听起来不错。我实现了它,它通过了我所有的测试。非常感谢。
  • @Wim:好点子,我会在以后的帖子中记住这一点。 @克拉克:很好! np,好问题!
【解决方案2】:

S(n) 等于所有有效的排序顺序,其中 n 对应于包含 [0,n] 的对。

S(n) = []
for each order in S(n-1)
   for each combination of n-th pair
      if pair can be inserted in order, add the order after insertion to S(n)
      else don't include the order in S(n)

一对可以以最多两种方式插入到一个订单中(正常对和反向对)。

Maximum orderings = O(2^n)

我不太确定这个摊销订单,但请听我说。

对于一个订单和一对,我们有四种方法在插入后获得排序订单 (两个顺序,一个(正常),一个(反转),零)

订购数量(摊销)= (1/4)*2 + (1/4)*1 + (1/4)*1 + (1/4)*0 = 1

 Amortized orderings = O(1)

同样的时间复杂度将是 O(n^2),同样不确定。 以下程序使用插入排序的变体查找排序。

debug = False

(LEFT, RIGHT, ERROR) = range(3)
def position(first, second):
    """ Returns the position of first pair when compared to second """
    x,y = first
    a,b = second
    if x <= a and b <= y:
        return LEFT
    if x >= a and b >= y:
        return RIGHT
    else:
        return ERROR

def insert(pair, order):
    """ A pair can be inserted in normal order or reversed order
     For each order of insertion we will get one solution or none"""
    solutions = []
    paircombinations = [pair]
    if pair[0] != pair[1]: # reverse and normal order are distinct
        paircombinations.append(pair[::-1])

    for _pair in paircombinations:
        insertat = 0
        if debug: print "Inserting", _pair, 
        for i,p in enumerate(order):
            pos = position(_pair, p)
            if pos == LEFT:
                break
            elif pos == RIGHT:
                insertat += 1
            else:
                if debug: print "into", order,"is not possible"
                insertat = None
                break
        if insertat != None:
            if debug: print "at",insertat,"in", order
            solutions.append(order[0:insertat] + [_pair] + order[insertat:])
    return solutions


def swapsort(pairs):
    """
    Finds all the solutions of pairs such that ending vector
    of pairs are be sorted non decreasingly by the first number in
    each pair and non increasingly by the second in each pair.
    """
    solutions = [ pairs[0:1] ] # Solution first pair
    for pair in pairs[1:]:
        # Pair that needs to be inserted into solutions
        newsolutions = []
        for solution in solutions:
            sols = insert(pair, solution) # solutions after inserting pair
            if sols:
                newsolutions.extend(sols)
        if newsolutions:
            solutions = newsolutions
        else:
            return None
    return solutions

if __name__ == "__main__":
    groups = [ [(1,5), (7,1), (3,8), (5,6)],
               [(1,5), (2,3), (3,3), (3,4), (2,4)],
               [(3,5), (6,6), (7,4)],
               [(1,4), (2,5)] ]
    for pairs in groups:
        print "Solutions for",pairs,":"
        solutions = swapsort(pairs)
        if solutions:
            for sol in solutions:
                print sol
        else:
            print "not possible"

输出:

Solutions for [(1, 5), (7, 1), (3, 8), (5, 6)] :
[(1, 7), (1, 5), (6, 5), (8, 3)]
Solutions for [(1, 5), (2, 3), (3, 3), (3, 4), (2, 4)] :
[(1, 5), (2, 4), (2, 3), (3, 3), (4, 3)]
[(1, 5), (2, 3), (3, 3), (4, 3), (4, 2)]
[(1, 5), (2, 4), (3, 4), (3, 3), (3, 2)]
[(1, 5), (3, 4), (3, 3), (3, 2), (4, 2)]
Solutions for [(3, 5), (6, 6), (7, 4)] :
not possible
Solutions for [(1, 4), (2, 5)] :
[(1, 4), (5, 2)]

【讨论】:

  • 嗯,试试对 (0,100), (0,101), (0,102), ... , (0,199)。大约有 2^100 个有效订单。
  • 正是我已经指出最大订购量将是 2^n
【解决方案3】:

这是一个有趣的问题。我独立提出了 Tom 的解决方案,这是我的 Python 代码:

class UnableToAddPair:
    pass

def rcmp(i,j):
    c = cmp(i[0],j[0])
    if c == 0:
        return -cmp(i[1],j[1])
    return c

def order(pairs):
    pairs = [list(x) for x in pairs]
    for x in pairs:
        x.sort()
    pairs.sort(rcmp)
    top, bottom = [], []
    for p in pairs:
        if len(top) == 0 or p[1] <= top[-1][1]:
            top += [p]
        elif len(bottom) == 0 or p[1] <= bottom[-1][1]:
            bottom += [p]
        else:
            raise UnableToAddPair
    bottom = [[x[1],x[0]] for x in bottom]
    bottom.reverse()
    print top + bottom

汤姆的解决方案中没有提到的一个重要点是,在排序阶段,如果任何两对的较小值相同,则必须按较大元素的递减值进行排序。

我花了很长时间才弄清楚为什么失败必须表明没有解决方案;我的原始代码有回溯。

【讨论】:

    【解决方案4】:

    下面是 Python 中一个简单的递归深度优先搜索算法:

    import sys
    
    def try_sort(seq, minx, maxy, partial):
      if len(seq) == 0: return partial
      for i, (x, y) in enumerate(seq):
        if x >= minx and y <= maxy:
          ret = try_sort(seq[:i] + seq[i+1:], x, y, partial + [(x, y)])
          if ret is not None: return ret
        if y >= minx and x <= maxy:
          ret = try_sort(seq[:i] + seq[i+1:], y, x, partial + [(y, x)])
          if ret is not None: return ret
      return None
    
    def do_sort(seq):
      ret = try_sort(seq, -sys.maxint-1, sys.maxint, [])
      print ret if ret is not None else "not possible"
    
    do_sort([(1,5), (7,1), (3,8), (5,6)])
    do_sort([(1,5), (2,9)])
    do_sort([(3,5), (6,6), (7,4)])
    

    它维护一个已排序的子序列 (partial) 并尝试以原始顺序和相反的顺序将所有剩余的对附加到它上面,而不会违反排序条件。

    如果需要,可以轻松更改算法以查找所有有效的排序顺序。

    编辑:我怀疑通过维护两个部分排序的序列(前缀和后缀)可以大大改进算法。我认为这将允许可以确定地选择下一个元素,而不是尝试所有可能的元素。不幸的是,我现在没有时间考虑这个问题。

    【讨论】:

    • 感谢您的回答。这似乎是有效的。但我仍然认为它可以通过更好的复杂性来完成。
    • 稍微好一点的复杂度 (O(2^n n log n) - 生成所有可能的交换,按第一个数字对每个交换排序,打破第二个数字的关系,看看是否满足第二个数字标准
    【解决方案5】:

    更新:由于问题已更改,此答案不再有效

    按第一个数字将向量对拆分为桶。对每个桶进行降序排序。按第一个数字的升序合并存储桶并跟踪最后一对的第二个数字。如果它大于当前的,则没有解决方案。否则,您将在合并完成后得到解决方案。

    如果您有稳定的排序算法,您可以按第二个数字进行降序排序,然后按第一个数字进行升序排序。之后检查第二个数字是否仍按降序排列。

    【讨论】:

    • 您似乎不允许这对中的两个数字互换,这是问题陈述的一部分。
    • 问题在于交换。排序是微不足道的。感谢您的回答。
    • 哦,我看到了消息 - “不要在 SO 上回答:作者会在不宣布的情况下更改问题的语义,您的答案将被否决”。
    • @hoha:你现在有 +1/-1,所以干脆删掉就忘了。
    • 很抱歉更改问题。如果声誉对您来说意义重大,我可以给您投票。但是我仍然认为如果答案没有回答问题,删除答案是一个很好的习惯。顺便说一句,谢谢你的时间。
    【解决方案6】:

    在您的情况下,交换只是一种 2 元素数组。 这样你就可以 元组[] = (4,6),(1,5),(7,1),(8,6), ...

    1. 对于每个元组 -> 对内部列表进行排序

    => (4,6),(1,5),(1,7),(6,8)

    1. 按第一个升序对元组进行排序

    => (1,5),(1,7),(4,6),(6,8)

    1. 按第 1 个降序对元组进行排序

    => (1,7),(1,5),(4,6),(6,8)

    【讨论】:

      【解决方案7】:

      我注意到的第一件事是,如果一个元组中的两个值都大于任何其他元组中的两个值,则没有解决方案。

      接下来我注意到,差异较小的元组向中间排序,而差异较大的元组向末端排序。

      有了这两条信息,你应该能够想出一个合理的解决方案。

      第 1 阶段:对每个元组进行排序,首先移动较小的值。

      第 2 阶段:对元组列表进行排序;首先按每个元组的两个值的差值降序排列,然后按每个元组的第一个成员的升序对每个差值相等的分组进行排序。 (例如(1,6),(2,7),(3,8),(4,4),(5,5)。)

      第 3 阶段:检查异常。 1:寻找一对元组,其中一个元组的两个元素都大于另一个元组的两个元素。 (例如(4,4),(5,5)。) 2:如果有四个或更多元组,则在每组元组中查找三个或更多变体的相同差异(例如(1,6) ,(2,7),(3,8).)

      第 4 阶段:重新排列元组。从后端(差异最小的元组)开始,每个差异相等的元组分组中的第二个变体必须交换它们的元素并将元组附加到列表的后面。 (例如,(1,6),(2,7),(5,5) => (2,7),(5,5),(6,1)。)

      我认为这应该涵盖它。

      【讨论】:

        【解决方案8】:

        这是一个非常有趣的问题。这是我在 VB.NET 中的解决方案。

        Module Module1
        
            Sub Main()
                Dim input = {Tuple.Create(1, 5),
                             Tuple.Create(2, 3),
                             Tuple.Create(3, 3),
                             Tuple.Create(3, 4),
                             Tuple.Create(2, 4)}.ToList
        
                Console.WriteLine(Solve(input))
                Console.ReadLine()
            End Sub
        
            Private Function Solve(ByVal input As List(Of Tuple(Of Integer, Integer))) As String
                Dim splitItems As New List(Of Tuple(Of Integer, Integer))
                Dim removedSplits As New List(Of Tuple(Of Integer, Integer))
                Dim output As New List(Of Tuple(Of Integer, Integer))
                Dim otherPair = Function(indexToFind As Integer, startPos As Integer) splitItems.FindIndex(startPos, Function(x) x.Item2 = indexToFind)
                Dim otherPairBackwards = Function(indexToFind As Integer, endPos As Integer) splitItems.FindLastIndex(endPos, Function(x) x.Item2 = indexToFind)
        
                'split the input while preserving their indices in the Item2 property
                For i = 0 To input.Count - 1
                    splitItems.Add(Tuple.Create(input(i).Item1, i))
                    splitItems.Add(Tuple.Create(input(i).Item2, i))
                Next
        
                'then sort the split input ascending order
                splitItems.Sort(Function(x, y) x.Item1.CompareTo(y.Item1))
        
                'find the distinct values in the input (which is pre-sorted)
                Dim distincts = splitItems.Select(Function(x) x.Item1).Distinct
        
                Dim dIndex = 0
                Dim lastX = -1, lastY = -1
        
                'go through the distinct values one by one
                Do While dIndex < distincts.Count
                    Dim d = distincts(dIndex)
        
                    'temporary list to store the output for the current distinct number
                    Dim temOutput As New List(Of Tuple(Of Integer, Integer))
        
                    'go through each of the split items and look for the current distinct number
                    Dim curIndex = 0, endIndex = splitItems.Count - 1
                    Do While curIndex <= endIndex
                        If splitItems(curIndex).Item1 = d Then
                            'find the pair of the item
                            Dim pairIndex = otherPair(splitItems(curIndex).Item2, curIndex + 1)
                            If pairIndex = -1 Then pairIndex = otherPairBackwards(splitItems(curIndex).Item2, curIndex - 1)
        
                            'create a pair and add it to the temporary output list
                            temOutput.Add(Tuple.Create(splitItems(curIndex).Item1, splitItems(pairIndex).Item1))
        
                            'push the items onto the temporary storage and remove it from the split list
                            removedSplits.Add(splitItems(curIndex))
                            removedSplits.Add(splitItems(pairIndex))
                            If curIndex > pairIndex Then
                                splitItems.RemoveAt(curIndex)
                                splitItems.RemoveAt(pairIndex)
                            Else
                                splitItems.RemoveAt(pairIndex)
                                splitItems.RemoveAt(curIndex)
                            End If
                            endIndex -= 2
                        Else
                            'increment the index or exit the iteration as appropriate
                            If splitItems(curIndex).Item1 <= d Then curIndex += 1 Else Exit Do
                        End If
                    Loop
        
                    'sort temporary output by the second item and add to the main output
                    output.AddRange(From r In temOutput Order By r.Item2 Descending)
        
                    'ensure that the entire list is properly ordered
                    'start at the first item that was added from the temporary output
                    For i = output.Count - temOutput.Count To output.Count - 1
                        Dim r = output(i)
                        If lastX = -1 Then
                            lastX = r.Item1
                        ElseIf lastX > r.Item1 Then
                            '!+ It appears this section of the if statement is unnecessary
                            'sorting on the first column is out of order so remove the temporary list
                            'and send the items in the temporary list back to the split items list
                            output.RemoveRange(output.Count - temOutput.Count, temOutput.Count)
                            splitItems.AddRange(removedSplits)
                            splitItems.Sort(Function(x, y) x.Item1.CompareTo(y.Item1))
                            dIndex += 1
                            Exit For
                        End If
                        If lastY = -1 Then
                            lastY = r.Item2
                        ElseIf lastY < r.Item2 Then
                            'sorting on the second column is out of order so remove the temporary list
                            'and send the items in the temporary list back to the split items list
                            output.RemoveRange(output.Count - temOutput.Count, temOutput.Count)
                            splitItems.AddRange(removedSplits)
                            splitItems.Sort(Function(x, y) x.Item1.CompareTo(y.Item1))
                            dIndex += 1
                            Exit For
                        End If
                    Next
                    removedSplits.Clear()
                Loop
        
                If splitItems.Count = 0 Then
                    Dim result As New Text.StringBuilder()
                    For Each r In output
                        result.AppendLine(r.Item1 & " " & r.Item2)
                    Next
        
                    Return result.ToString
        
                Else
                    Return "Not Possible"
                End If
            End Function
        
            <DebuggerStepThrough()> _
            Public Class Tuple(Of T1, T2)
                Implements IEqualityComparer(Of Tuple(Of T1, T2))
        
                Public Property Item1() As T1
                    Get
                        Return _first
                    End Get
                    Private Set(ByVal value As T1)
                        _first = value
                    End Set
                End Property
                Private _first As T1
        
                Public Property Item2() As T2
                    Get
                        Return _second
                    End Get
                    Private Set(ByVal value As T2)
                        _second = value
                    End Set
                End Property
                Private _second As T2
        
                Public Sub New(ByVal item1 As T1, ByVal item2 As T2)
                    _first = item1
                    _second = item2
                End Sub
        
                Public Overloads Function Equals(ByVal x As Tuple(Of T1, T2), ByVal y As Tuple(Of T1, T2)) As Boolean Implements IEqualityComparer(Of Tuple(Of T1, T2)).Equals
                    Return EqualityComparer(Of T1).[Default].Equals(x.Item1, y.Item1) AndAlso EqualityComparer(Of T2).[Default].Equals(x.Item2, y.Item2)
                End Function
        
                Public Overrides Function Equals(ByVal obj As Object) As Boolean
                    Return TypeOf obj Is Tuple(Of T1, T2) AndAlso Equals(Me, DirectCast(obj, Tuple(Of T1, T2)))
                End Function
        
                Public Overloads Function GetHashCode(ByVal obj As Tuple(Of T1, T2)) As Integer Implements IEqualityComparer(Of Tuple(Of T1, T2)).GetHashCode
                    Return EqualityComparer(Of T1).[Default].GetHashCode(Item1) Xor EqualityComparer(Of T2).[Default].GetHashCode(Item2)
                End Function
            End Class
        
            Public MustInherit Class Tuple
                <DebuggerStepThrough()> _
                Public Shared Function Create(Of T1, T2)(ByVal first As T1, ByVal second As T2) As Tuple(Of T1, T2)
                    Return New Tuple(Of T1, T2)(first, second)
                End Function
            End Class
        
        End Module
        

        输入

        1 5 2 3 3 3 3 4 2 4

        产生输出

        1 5 2 4 2 3 3 4 3 3

        3 5 6 6 7 4

        输出

        不重要

        评论

        我发现这个问题非常具有挑战性。我花了大约 15 分钟来想出一个解决方案,然后花了一个小时左右来编写和调试它。代码中到处都是 cmets,因此任何人都可以遵循它。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-12
          • 1970-01-01
          • 2010-09-22
          • 1970-01-01
          • 1970-01-01
          • 2017-10-21
          • 2021-10-11
          相关资源
          最近更新 更多