【问题标题】:Find all unsorted pairs in partially sorted array在部分排序的数组中查找所有未排序的对
【发布时间】:2021-09-28 23:56:58
【问题描述】:

我必须在部分排序的数组中找到(或至少计数)所有对(不一定相邻)未排序的元素。

如果我们假设排序为升序,则数组[1 4 3 2 5] 具有以下未排序的对:(4, 3)(3, 2)(4, 2)

我正在考虑一种沿插入排序方式工作的算法,因为插入排序倾向于将每个新元素与相对于新元素放错位置的所有元素进行比较。

编辑:在发布问题时,我没有意识到找到这些对比计算它们的时间复杂度更高。是否有更好的算法来计算存在多少这样的对?

【问题讨论】:

  • 这可以在 O(nlogn) 中完成,想想二叉搜索树。
  • @SomeDude 如果输入(几乎)按降序排序,则需要报告 O(n^2) 对。
  • @Thomas 我正在考虑将它们插入二叉搜索树中,每当您在左子节点上插入时,在遍历时更新每个节点的“左”插入数。你会得到结果。在数组的降序输入中,所有插入都将在左侧。
  • @SomeDude 但是由于您必须创建一个包含 O(n^2) 项的返回值,因此您的算法无法在 O(nlogn) 中运行。
  • 我想的是“计数”而不是列表,那么它是有道理的。

标签: arrays algorithm sorting


【解决方案1】:

这在一定程度上取决于您所说的“部分排序”的确切含义——有人可能会争辩说,每个数组在某种程度上都是部分排序的。

由于该算法具有最坏情况的复杂性O(n^2) 无论如何(考虑按降序 顺序排序的输入),您不妨直接走:

ret = []

for i in range(len(array)):
    for j in range(i, len(array)):
        if array[i] > array[j]:
            ret.append((array[i], array[j]))

return ret

这对随机数组非常有效。

但是,我想你的想法更多的是在数组内部有更大的延伸,其中数字被排序,但整个数组并非如此。

在这种情况下,您可以通过首先识别这些延伸来节省上述幼稚方法的时间 - 这可以通过线性传递来完成。一旦你有了它们,你只需要相互比较这些延伸,你可以使用二分搜索(因为延伸是按排序顺序的)。

这是我想到的 Python 实现:

# find all sorted stretches
stretches = []
begin = 0
for i in range(1, len(array)):
    if array[i-1] > array[i]:
        stretches.append(array[begin:i])
        begin = i
if i+1 > begin:
    stretches.append(array[begin:])

# compare stretches
ret = []
for i in range(len(stretches)):
    stretchi = stretches[i]
    stretchi_rev = None
    
    for j in range(i+1, len(stretches)):
        stretchj = stretches[j]

        if stretchi[-1] > stretchj[0]:
            if stretchi_rev is None:
                stretchi_rev = list(reversed(stretchi))

            hi = len(stretchj)
            for x in stretchi_rev:
                i = bisect.bisect_left(stretchj, x, 0, hi)
                if i == 0:
                    break
                else:
                    for y in stretchj[:i]:
                        ret.append((x, y))
                    hi = i
        
return ret

对于随机数组,这将比第一种方法慢。但是如果数组很大,并且部分排序的部分的数量足够多,那么这个算法将在某个时候开始击败蛮力搜索。

【讨论】:

  • 但是,这两种算法的渐近复杂度都是 O(n^2),对吧?
  • 在最坏的情况下,是的,你不能比这更快。
【解决方案2】:

正如@SomeDude 在 cmets 中所建议的那样,如果您只需要计算对,则有一个基于构建二叉搜索树的 O(nlogn) 解决方案。其中涉及到一些微妙之处 - 我们需要跟踪每个节点的重复数 (ic),出于性能原因,我们还需要跟踪正确子节点的数量 (rc)。

在以节点n 为根的树中插入值v 的基本方案是:

def insert(n, v)
  if v < n.data
    count = 1 + n.ic + n.rc
    if n.left is null
      n.left = node(v)
      return count
    return count + insert(n.left, v)
  else if v > n.data
    if n.right is null
      n.right = node(v)
      n.rc = 1
      return 0
    n.rc += 1
    return insert(n.right, v)
  else // v == n.data
    n.ic += 1
    return n.rc

这里有一些有效的 Java 代码 (Ideone):

static int pairsCount(Integer[] arr) {
    int count = 0;
    Node root = new Node(arr[0]);
    for(int i=1; i<arr.length; i++)
        count += insert(root, arr[i]);
    return count;
}

static int insert(Node n, int v) {
    if(v < n.value) {
        int count = 1 + n.rc + n.ic;
        if(n.left == null) {
            n.left = new Node(v);
            return count;
        }
        return count + insert(n.left, v);
    } 
    else if(v > n.value) {
        if(n.right == null) {
            n.right = new Node(v);
            n.rc = 1;
            return 0;
        }
        n.rc += 1;
        return insert(n.right, v);
    }
    else {
        n.ic += 1;
        return n.rc;
    }
}

static class Node {
    int value;
    Node left, right;
    int rc; // right children count
    int ic; // duplicate count
    
    Node(int value) {
        this.value = value;
    }
}

测试:

Integer[] arr = {1, 4, 3, 2, 5};
System.out.println(pairsCount(arr));

输出:

3

【讨论】:

  • 除了这根本没有实现二叉搜索树。由于您没有平衡树,因此在最坏的情况下,这将导致线性插入时间(因此总体复杂度再次为 O(n^2))。
  • @Thomas 我不同意——它是一棵二叉搜索树,只是不平衡的。因此,您是正确的,最坏的情况下运行时间将为O(n^2),但平均值为O(nlogn)。该问题涉及部分排序的数组,因此我认为后者更适用。
  • 你是对的,随机输入的预期运行时间是O(nlogn)。但是我们的输入不是随机的,我们知道它们是部分排序的。输入的排序越多,您的实现就越像线性列表。
猜你喜欢
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 2011-04-28
  • 2018-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
相关资源
最近更新 更多