如何在线性时间内对2个可能值的元素进行“排序”?

时间:2013-09-09 12:59:34

标签: algorithm sorting language-agnostic

假设我有一个函数f和元素数组。

该函数为任何元素返回AB;您可以通过这种方式ABBAABABAA可视化元素。

我需要根据函数对元素进行排序,结果是:AAAAAABBBB

A值的数量不必等于B值的数量。元素的总数可以是任意的(不固定)。请注意,您不对字符进行排序,您可以对具有单个字符表示形式的对象进行排序。

更多的事情:

  • 排序应采用线性时间 - O(n)
  • 应该就地执行,
  • 应该是稳定的。

有什么想法吗?


注意:如果无法满足上述要求,您是否对算法有任何牺牲上述要求的想法?

7 个答案:

答案 0 :(得分:4)

如果具有线性和就地,则可以执行半稳定版本。半稳定我的意思是AB可能是稳定的,但不是两者都可以。类似于Dukeling的答案,但你从同一侧移动两个迭代器:

a = first A
b = first B
loop while next A exists
    if b < a
        swap a,b elements
        b = next B
        a = next A
    else
        a = next A

使用示例字符串ABBAABABAA,您将获得:

ABBAABABAA
AABBABABAA
AAABBBABAA
AAAABBBBAA
AAAAABBBBA
AAAAAABBBB
每次转弯

,如果你进行交换,你可以移动两个,如果不是,你只需移动a。这将使A保持稳定,但B将失去其排序。为了保持B稳定,从最后开始,继续前进。

有可能完全稳定地完成它,但我不知道如何。

答案 1 :(得分:2)

对于其他给定的约束,可能无法进行稳定排序,因此这是一种不稳定的排序,类似于quick-sort的分区步骤。

  1. 有2个迭代器,一个从左侧开始,一个从右侧开始。
  2. 虽然右边的迭代器有一个B,但递减迭代器。
  3. 虽然左侧迭代器中有A,但递增迭代器。
  4. 如果迭代器没有相互交叉,则交换它们的元素并从2开始重复。

答案 2 :(得分:0)

让我们说,   Object_Array[1...N]

Type_A objs are A1,A2,...Ai

Type_B objs are B1,B2,...Bj

i+j = N

FOR i=1 :N
    if Object_Array[i] is of Type_A
       obj_A_count=obj_A_count+1
    else
       obj_B_count=obj_B_count+1
LOOP

使用obj_Aobj_B为结果数组填充各自的计数,具体取决于obj_A > obj_B

答案 3 :(得分:0)

以下应该在线性时间内用于双向链表。因为涉及多达N个插入/删除,可能会导致数组的二次时间。

  1. 找到“排序”后第一个B应该位于的位置。这可以通过计算As。

  2. 以线性时间完成
  3. 从3个迭代器开始:iterA从容器的开头开始,iterB从As和Bs应该满足的上面的位置开始,iterMiddle在iterB之前启动一个元素。

  4. 使用iterA跳过As,找到第一个B,然后将对象从iterA移动到iterB->之前的位置。现在iterA指向移动元素之后的下一个元素,移动元素现在就在iterB之前。

  5. 继续执行第3步,直到找到它。之后,first()和iterB-1之间的所有元素都是As。

  6. 现在将iterA设置为iterB-1。

  7. 使用iterB跳过Bs。当找到A时,将其移至iterA之后并增加iterA。

  8. 继续步骤6,直到iterB到达end()。

  9. 这可以作为任何容器的稳定排序。该算法包括O(N)插入/删除,这是具有O(1)插入/删除的容器的线性时间,但是,唉,数组的O(N ^ 2)。适用于您的情况取决于容器是数组而不是列表。

答案 4 :(得分:0)

如果您的数据结构是链表而不是数组,那么您应该能够满足所有三个约束。你只需浏览列表并累积并移动“B”将是微不足道的指针变化。下面的伪代码:

sort(list) {
    node = list.head, blast = null, bhead = null
    while(node != null) {
        nextnode = node.next
        if(node.val == "a") { 
            if(blast != null){              
                //move the 'a' to the front of the 'B' list
                bhead.prev.next = node, node.prev = bhead.prev
                blast.next = node.next, node.next.prev = blast
                node.next = bhead, bhead.prev = node
            }
        }
        else if(node.val == "b") { 
            if(blast == null)
                bhead = blast = node
            else //accumulate the "b"s.. 
                blast = node
        }

3

        node = nextnode
    }
}

因此,您可以在数组中执行此操作,但是模拟列表交换的memcopies将使大型数组的安静性变慢。

答案 5 :(得分:0)

首先,假设A和B的数组是生成的或读入的,我想知道为什么不完全通过简单地应用f来避免这个问题,因为列表被累积到内存中,随后会被放入两个列表中合并。

否则,我们可以在O(n)时间和O(1)空间中提出替代解决方案,这可能足以取决于Bohumil爵士的最终需求:

遍历列表并使用段的排列周期就地对1,000,000个元素的每个段进行排序(一旦完成此步骤,列表可以通过递归交换内部块在技术上进行就地排序,例如,ABB AAB - &gt; AAABBB,但这可能太耗时而没有额外的空间)。再次遍历列表并使用相同的常量空间在两个间隔树中存储指向A和B的每个块的指针。例如,4段,

ABBAABABAA => AABB AABB AA + pointers to blocks of A's and B's

对A或B的顺序访问将立即可用,并且随机访问将来自使用间隔树来定位特定的A或B.一个选项可以是使间隔编号为A和B;例如,要查找第4个A,请查找包含4的间隔。

对于排序,1,000,000个四字节元素(3.8MB)的数组足以存储索引,在每个元素中使用一个位来记录交换期间访问的索引;两个临时变量,最大A或B的大小。对于十亿个元素的列表,最大组合间隔树将有4000个间隔。每个间隔使用128位,我们可以轻松地存储A和B的编号间隔,并且我们可以使用未使用的位作为块索引(10位)的指针,并且在B(20位)的情况下使用偏移。 4000 * 16字节= 62.5KB。我们可以存储一个额外的数组,只有B块的偏移量为4KB。对于10亿个元素的列表,总空间小于5MB。 (空间实际上取决于n,但因为它与n相比非常小,出于所有实际目的,我们可能认为它是O(1)。)

对百万元素段进行排序的时间是 - 一次传递到count和index(这里我们也可以累积间隔和B偏移)和一次传递来排序。构造区间树是O(nlogn),但是这里的n只有4000(十亿个列表计数中的0.00005)。总时间O(2n)= O(n)

答案 6 :(得分:-1)

这应该可以通过一些动态编程来实现。

它有点像计数排序,但有一个关键的区别。为a和b count_a [n]和count_b [n]制作大小为n的数组。在索引i之前填充这些数组中的As或B数量。

在一个循环之后,我们可以使用这些数组来查找O(1)中任何元素的正确索引。像这样:

int final_index(char id, int pos){
    if(id == 'A')
      return count_a[pos];
    else
      return count_a[n-1] + count_b[pos];
}

最后,为了满足总O(n)要求,交换需要以智能顺序完成。一个简单的选择是具有递归交换过程,该过程实际上不执行任何交换,直到两个元素都被放置在正确的最终位置。编辑:这实际上不是真的。即使天真的交换也会有O(n)交换。但是,执行此递归策略将为您提供绝对最小的所需交换。

请注意,在一般情况下,这将是非常糟糕的排序算法,因为它的内存要求为O(n *元素值范围)。