排序编号的最小特殊移动次数

时间:2015-02-11 12:32:01

标签: algorithm sorting reorderlist

给出数字列表

1 15 2 5 10

我需要获得

1 2 5 10 15

我能做的唯一操作是“在位置Y移动数字X”。

在上面的示例中,我只需要“在第5位移动数字15”。

我想尽量减少操作次数,但鉴于可用的操作,我无法找到/记住经典算法。

一些背景知识:

  • 我正在与类似看板的服务的API进行交互。
  • 我有大约600张卡片,我们的错误跟踪器上的一些操作可能意味着在看板上重新排序这600张卡片(如果项目的优先级发生变化,多张卡片可以同时移动)
  • 我可以在600次调用API时执行此操作,但我正在尝试尽可能减少该数量。

2 个答案:

答案 0 :(得分:2)

引理:您可以执行的对列表 L (按递增顺序)排序的最小(删除元素,插入元素)对的数量为:

  

S min (L) = | L | - | LIC(L) |

LIC(L)最长增长子序列

因此,你必须:

  • 建立列表的 LIC
  • 删除不在其中的元素,并将它们插回适当的位置(使用二进制搜索)。

<强>证明: 通过归纳。

对于大小为1的列表,增长最长的子序列的长度为1!该列表已经排序,因此所需的(del,ins)对的数量为

  

| | - | LIC(L) | = 1 - 1 = 0

现在让 L n 成为长度为 n 的列表,1≤ n 。设 L n + 1 是通过在<的左边添加元素 e n + 1 获得的列表EM>→<子>名词 的。
此元素可能会或可能不会影响最长增加子序列。让我们试着看看......

i n,1 i n,2 成为的两个第一个元素LIC(L n (*)

  • 如果 e n + 1 &gt; i n,2 ,然后 LIC(L n + 1 = LIC(L 名词
  • 如果 e n + 1 i n,1 ,则 LIC(L < sub> n + 1 ) = e n + 1 || LIC(L <子>名词
  • 否则, LIC(L n + 1 = LIC(L n - i n,1 + e n + 1 。我们将 LIC 最高第一个元素保持在一起。这是通过从 LIC 中删除 i n,1 并将其替换为 e n + 1

在第一种情况下,我们删除 e n + 1 ,因此我们得到排序 L n 。通过归纳假设,这需要 n (删除,插入)对。然后,我们必须在适当的位置插入 e n + 1 。因此:

  

S(L n + 1 min = 1 + S(L n ) <子>分钟 的     S(L n + 1 min = 1 + n - | LIC(L 名词 |     S(L n + 1 min = | L n + 1 | - | LIC(L n + 1 |


在第二种情况下,我们忽略 e n + 1 。我们首先删除不在 LIC(L n 中的元素。必须再次插入这些元素!有

  

S(L n min = | L n | - | LIC(L n |

这样的元素。

现在,我们必须小心并按正确的顺序插入它们(相对于 e n + 1 )。最后,它需要:

  

S(L n + 1 min = | L n | - | LIC(L n |    S(L n + 1 min = | L n | + 1 - (| LIC(L n | + 1)

因为我们有| LIC(L n + 1 | = | LIC(L n | + 1和| L n + 1 | = | L n | + 1,我们最终:

  

S(L n + 1 min = | L n + 1 | - | LIC(L n + 1 |


通过考虑通过删除 i n,1 <获得的列表 L&#39; n ,可以证明最后一种情况/ em>来自 L n + 1 。在那种情况下, LIC(L&#39; n = LIC(L n + 1 因此:

  

| LIC(L&#39; <子>名词 | = | LIC(L n |的(1)

从那里,我们可以对 L&#39; n 进行排序(这需要| L&#39; n | - | LIC(L&#39; n |由归纳假设。先前的等式(1)导致结果。< / p>

(*):如果 LIC(L n &lt; 2,然后 i n,2 不存在。只需忽略与它的比较。在这种情况下,只有案例2和案例3适用......结果仍然有效

答案 1 :(得分:0)

一种可能的解决方案是找到longest increasing subsequence并仅移动不在其中的元素。

我无法证明它是最优的,但很容易证明它是正确的并且比N交换更好。

这是Python 2中的概念验证。我将其实现为O(n 2 )算法,但我很确定它可以简化为O(n log n) )。

from operator import itemgetter

def LIS(V):
    T = [1]*(len(V))
    P = [-1]*(len(V))

    for i, v in enumerate(V):
        for j in xrange(i-1, -1, -1):
            if T[j]+1 > T[i] and V[j] <= V[i]:
                T[i] = T[j] + 1
                P[i] = j

    i, _ = max(enumerate(T), key=itemgetter(1))
    while i != -1:
        yield i
        i = P[i]

def complement(L, n):
    for a, b in zip(L, L[1:]+[n]):
        for i in range(a+1, b):
            yield i

def find_moves(V):
    n = len(V)
    L = list(LIS(V))[::-1]
    SV = sorted(range(n), key=lambda i:V[i])
    moves = [(x, SV.index(x)) for x in complement(L, n)]

    while len(moves):
        a, b = moves.pop()
        yield a, b
        moves = [(x-(x>a)+(x>b), y) for x, y in moves]


def make_and_print_moves(V):
    print 'Initial array:', V

    for a, b in find_moves(V):
        x = V.pop(a)
        V.insert(b, x)        
        print 'Move {} to {}. Result: {}'.format(a, b, V)
    print '***'

make_and_print_moves([1, 15, 2, 5, 10])

make_and_print_moves([4, 3, 2, 1])

make_and_print_moves([1, 2, 4, 3])

输出如下内容:

Initial array: [1, 15, 2, 5, 10]
Move 1 to 4. Result: [1, 2, 5, 10, 15]
***
Initial array: [4, 3, 2, 1]
Move 3 to 0. Result: [1, 4, 3, 2]
Move 3 to 1. Result: [1, 2, 4, 3]
Move 3 to 2. Result: [1, 2, 3, 4]
***
Initial array: [1, 2, 4, 3]
Move 3 to 2. Result: [1, 2, 3, 4]
***