对数字对进行排序的算法

31
我遇到了一个问题,需要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

感谢


5
不是,这是我朋友的面试问题。但即使它是作业,我认为对大家来说也很有趣。 - Klark
你可以将LIS算法应用于它。我需要一些时间来写出完整的解释,所以让你先开始思考这个问题。 - Shamim Hafiz - MSFT
你在第一个例子中改变了对的元素顺序(7,1 变成了 1,7),这是有意为之吗? - Björn Pollex
@Space_C0wb0y 是的。你可能已经注意到,我的英语不是很好,所以我无法用其他方式解释“对换一对”。我更新了问题,希望现在清楚了。 - Klark
1
@Klark 为什么第二个例子的解决方案“不可能”?(2,9) (5,1) 怎么样? - NPE
显示剩余3条评论
8个回答

16

O(n log n)解决方案

输入图像描述


不错。你可能需要澄清“合法”的含义。仅仅“顶部和底部必须是列表,其中每个项目都是前一个项目的子区间”是不够的。即使是(1,2),(3,4)也满足这个要求。我认为“中间”的两个项目也必须重叠。(我不明白最后一个带X的情况的意义,但我也不能确定所有报告的失败都是真正的失败。) - xan
@xan:感谢您的评论。如果添加的区间是列表中最新区间的子区间,则“添加”操作是“合法”的。是的,“中间”的两个项目也必须重叠,我忘了提到这一点。关于失败:当我的算法返回失败时,它总是会识别出3个不兼容的区间。请注意,只有几种方式可以对3个区间的端点进行排序,并且两个非平凡和适用的排序如图所示。 - Tom Sirgedas
15
我看到你在回答中付出了很多努力,这很好。但是嵌入在图片中的文本无法被编辑或索引以供搜索。 - Wim Coenen
嗯,听起来没问题。我实现了它并且通过了所有的测试。非常感谢。 - Klark
@Wim:好观点,我会记住的,以备将来发布。@Klark:不错!没问题,好问题! - Tom Sirgedas

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)]

1
嗯,尝试使用对(0,100),(0,101),(0,102),...,(0,199)。大约有2^100个有效的排序方式。 - Tom Sirgedas
确切地说,我已经指出最大的排序将是 2^n。 - Rozuur

2

这是一个有趣的问题。我独立思考出了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

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

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


1
以下是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),并尝试将每个剩余的对按原始顺序和反向顺序附加到其中,而不违反排序条件。

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

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


谢谢你的回答。看起来是有效的。但我仍然认为可以用更好的复杂度来完成它。 - Klark
2
稍微好一些的复杂度(O(2^n n log n))- 生成所有可能的交换,按第一个数字排序,打破第二个数字的关系,并查看是否满足第二个数字的条件。 - Martin DeMello

1

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

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

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


您似乎没有考虑到对偶中的两个数字可以交换位置,这是问题陈述的一部分。 - NPE
2
哦,我看到了这条信息 - “不要在SO上回答:作者会在不通知的情况下更改问题的语义,你的答案将被投票降低。” - hoha
1
很抱歉改变了问题。如果声望对你来说意义重大,我可以给你点赞。但是我仍然认为,如果答案没有回答问题,删除答案是一个好的习惯。顺便说一句,谢谢你的时间。 - Klark
不,不要误会。这个-2的声望(-10,-20或其他)什么都不是。反正没有人能赶上Jon Skeet,所以为什么要费心呢? :) 顺便说一句,我在meta上找到了几个关于我提出的确切模型的答案(问题修订的答案)。他们两年前就讨论过这个问题了-http://meta.stackexchange.com/questions/25997/indicate-which-revision-of-a-question-or-answer-a-comment-relates-to。看起来这个功能还不够必要,所以没有人实现它。 - hoha
@hoha:他没有改变问题的语义。在你和其他几个人误读之后,他澄清了问题,但是在一对元素中翻转顺序的能力从一开始就存在。 - wnoise
显示剩余3条评论

0

在您的情况下,交换只是一种2元素数组的排序方式。 所以您可以这样做: tuple[] = (4,6),(1,5),(7,1),(8,6), ...

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

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

  1. 按第1个升序排序元组

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

  1. 按第2个降序排序元组

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


0

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

接下来我注意到的是,差异较小的元组排序在中间,差异较大的元组排序在两端。

有了这两个信息,您应该能够找出合理的解决方案。

阶段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)。)

第四阶段:重新排列元组。从后端开始(差异最小的元组),在每个具有相等差异的元组组合内,必须交换其第二个变化的元素,并将元组附加到列表的末尾。(例如,(1,6),(2,7),(5,5)=>(2,7),(5,5),(6,1)。)

我认为这应该就可以了。


0

这是一个非常有趣的问题。以下是我在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分钟来想出一个解决方案,又花了一个小时左右来编写和调试它。代码中充满了注释,以便任何人都可以跟踪它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接