但我想直接影响 spam_list,比如 list.sort(),而不是像 sorted() 那样复制它
只有 一个 解决方案,完全符合您的要求。 其他每一个解决方案都隐含地复制一个或两个列表(或将其转换为字典等)。您要求的是一种对两个列表进行就地排序的方法,使用O(1) 额外空间,使用一个列表作为另一个列表的键。我个人会接受额外的空间复杂度,但如果你真的愿意,你可以这样做:
(编辑:可能是原始发布者并不真正关心.sort,因为它很有效,而是因为它修改了状态;总的来说,这是一件危险的事情,非低级语言试图避免这种情况甚至禁止它,但使用切片分配的解决方案将实现“就地”语义)
- 创建一个自定义字典子类(实际上是
Zip 类),它由您正在排序的两个列表支持。
- 索引
myZip[i] -> 生成元组(list1[i],list2[i])
- 分配
myZip[i]=(x1,x2) -> 分派到list1[i]=x1, list2[i]=x2。
- 使用 that 来执行
myZip(spam_list,spam_order).sort(),现在spam_list 和spam_order 都已就地排序
例子:
#!/usr/bin/python3
class LiveZip(list):
def __init__(self, list1, list2):
self.list1 = list1
self.list2 = list2
def __len__(self):
return len(self.list1)
def __getitem__(self, i):
return (self.list1[i], self.list2[i])
def __setitem__(self, i, tuple):
x1,x2 = tuple
self.list1[i] = x1
self.list2[i] = x2
spam_list = ["We", "are", "the", "knights", "who", "say", "Ni"]
spam_order = [0,1,2,4,5,6,3]
#spam_list.magical_sort(spam_order)
proxy = LiveZip(spam_order, spam_list)
现在让我们看看它是否有效......
#proxy.sort()
#fail --> oops, the internal implementation is not meant to be subclassed! lame
# It turns out that the python [].sort method does NOT work without passing in
# a list to the constructor (i.e. the internal implementation does not use the
# public interface), so you HAVE to implement your own sort if you want to not
# use any extra space. This kind of dumb. But the approach above means you can
# just use any standard textbook in-place sorting algorithm:
def myInPlaceSort(x):
# [replace with in-place textbook sorting algorithm]
现在可以了:
myInPlaceSort(proxy)
print(spam_list)
不幸的是没有办法只对O(1)空间中的一个列表进行排序而不对另一个列表进行排序;如果您不想对两个列表都进行排序,则不妨采用构建虚拟列表的原始方法。
但是,您可以执行以下操作:
spam_list.sort(key=lambda x:x)
但是如果 key 或 cmp 函数对任何集合进行了任何引用(例如,如果您传入一个必须构造的 dict 的 dict.__getitem__),这并不比您原来的 O(N)-space 方法好,除非您已经碰巧有这样一本字典了。
原来这是 Python sort parallel arrays in place? 的重复问题,但除了 this one 之外,该问题也没有正确答案,这与我的相同,但没有示例代码。除非您对代码进行了难以置信的优化或专门化,否则我只会使用您的原始解决方案,它在空间复杂度上与其他解决方案相当。
编辑2:
正如 senderle 指出的那样,OP 根本不需要排序,而是希望,我认为,应用排列。为了实现这一点,您可以并且应该使用简单的索引其他答案建议[spam_list[i] for i in spam_order],但是仍然必须制作显式或隐式副本,因为您仍然需要中间数据。 (无关的,为了记录,应用逆排列是我认为与身份并行排序的逆,你可以使用一个来获得另一个,尽管排序的时间效率较低。_,spam_order_inverse = parallelSort(spam_order, range(N)), then 按spam_order_inverse排序。我把上面关于排序的讨论留作记录。)
编辑3:
然而,在O(#cycles) 空间中实现就地排列是可能的,但时间效率很差。每个排列都可以分解为并行应用于子集的不相交排列。这些子集称为循环或轨道。周期等于它们的大小。因此,您信心大增,并执行以下操作:
Create a temp variable.
For index i=0...N:
Put x_i into temp, assign NULL to x_i
Swap temp with x_p(i)
Swap temp with x_p(p(i))
...
Swap temp with x_p(..p(i)..), which is x_i
Put a "do not repeat" marker on the smallest element you visited larger than i
Whenever you encounter a "do not repeat" marker, perform the loop again but
without swapping, moving the marker to the smallest element larger than i
To avoid having to perform the loop again, use a bloom filter
这将在 O(N^2) 时间和 O(#cycles) 地方运行,没有布隆过滤器,或者如果你使用它们,则在 ~O(N) 时间和 O(#cycle + bloomfilter_space) 空间