假设我有一个函数f
和元素数组。
该函数为任何元素返回A
或B
;您可以通过这种方式ABBAABABAA
可视化元素。
我需要根据函数对元素进行排序,结果是:AAAAAABBBB
A
值的数量不必等于B
值的数量。元素的总数可以是任意的(不固定)。请注意,您不对字符进行排序,您可以对具有单个字符表示形式的对象进行排序。
更多的事情:
O(n)
,有什么想法吗?
注意:如果无法满足上述要求,您是否对算法有任何牺牲上述要求的想法?
答案 0 :(得分:4)
如果具有线性和就地,则可以执行半稳定版本。半稳定我的意思是A
或B
可能是稳定的,但不是两者都可以。类似于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的分区步骤。
B
,但递减迭代器。 A
,但递增迭代器。 答案 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_A
和obj_B
为结果数组填充各自的计数,具体取决于obj_A > obj_B
答案 3 :(得分:0)
以下应该在线性时间内用于双向链表。因为涉及多达N个插入/删除,可能会导致数组的二次时间。
找到“排序”后第一个B应该位于的位置。这可以通过计算As。
从3个迭代器开始:iterA从容器的开头开始,iterB从As和Bs应该满足的上面的位置开始,iterMiddle在iterB之前启动一个元素。
使用iterA跳过As,找到第一个B,然后将对象从iterA移动到iterB->之前的位置。现在iterA指向移动元素之后的下一个元素,移动元素现在就在iterB之前。
继续执行第3步,直到找到它。之后,first()和iterB-1之间的所有元素都是As。
现在将iterA设置为iterB-1。
使用iterB跳过Bs。当找到A时,将其移至iterA之后并增加iterA。
继续步骤6,直到iterB到达end()。
这可以作为任何容器的稳定排序。该算法包括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 *元素值范围)。