给定一个随机顺序的整数数组,您必须找到最小交换次数才能将其转换为循环排序数组
Posted
技术标签:
【中文标题】给定一个随机顺序的整数数组,您必须找到最小交换次数才能将其转换为循环排序数组【英文标题】:given an array of integers in random order you have to find the minimum number of swaps to convert it to cyclic sorted array 【发布时间】:2013-02-28 03:58:24 【问题描述】:如果以随机顺序给出数组,则必须输出转换为循环排序数组所需的最小交换次数。
例如给定的数组是 3 5 4 2 1
所以第一次交换将是 54 结果:3 4 5 2 1 第二次交换将是 21 结果:3 4 5 1 2(最终)
输出:2
我无法理解这个问题背后的逻辑。
添加更多: 只能在相邻元素之间交换,数字在 1 到 N 之间
【问题讨论】:
数组中的数字是否总是连续的? 您只需要交换次数,而不需要实际交换次数本身吗? 查找“河内塔”。 如果我们在 nlog(n)(合并排序)中对数组进行排序,它应该可以工作。您在寻找更好的复杂性吗? 数字将在 1 到 N 的范围内。交换只能在相邻元素之间进行。 【参考方案1】:好吧,不知道它是否是最好的算法,但我能想到一个 O(n^2) 的解决方案:
首先,忽略循环数组的可能性。让我们解决一个更简单的问题:对数组进行排序的最小交换次数是多少。
这里要小心,因为这与排序算法无关。基于比较的排序算法的最坏情况至少为O(n log n)
。在这个问题中,你需要的最大交换次数是n
。
为什么?因为这是您可以达到的最大permutation cycle size。您需要的最小交换次数恰好是排列周期大小减一。我的意思是您可以将数组的任何排列表示为排列循环,例如:
3 2 1 4 5
-> (2)(4)(5)(1 3)
对于大小为 1 的排列循环,您不需要任何交换。对于大小为 2 的置换循环,您恰好需要 1 个交换。这比例为:
2 3 4 5 1
-> (1 2 3 4 5)
忽略这个数组已经是循环排序的,这个数组完全是一团糟。要正常排序,我需要 4 次交换,基本上将 1 移动到正常位置。
计算排列周期非常简单,只要将数字跟随到数组排序后的位置即可。使用前面的例子
3 2 1 4 5
A[0]
;
因为A[0]==3
,并且3 将是排序数组中的第三个元素,所以跟随到第三个位置;
因为 A[2]==1
和 1 将是...,所以排在第 1 位。因为我们已经在那里 这里我们有一个大小为 2 的循环;
在下一个未访问的位置重新开始 (1)
A[1]==2
在正确的位置,所以我们不需要做任何事情,这里我们有一个大小为 1 的循环。
等等……
这个算法是 O(n),但是由于我们需要对从每个可能位置开始的数组执行此操作(因为它是循环的),所以我们会执行 n 次,所以,整个算法是 O(n^ 2).
更新;一些python代码来展示我的算法:
def offset_swaps(A, S, offset):
visited = [False]*len(A)
swaps = 0
for i in xrange(len(A)):
if visited[i]: continue
cycle, current = 0, i
while not visited[current]:
cycle += 1
visited[current] = True
current = (S[A[current]] + offset) % len(A)
swaps += cycle - 1
return swaps
def number_of_swaps(A):
S = x:i for i,x in enumerate(sorted(A))
min_swaps = len(A)
for i in xrange(len(A)):
min_swaps = min(min_swaps, offset_swaps(A, S, i))
return min_swaps
print number_of_swaps((3, 5, 4, 2, 1))
【讨论】:
@Knoothe 抱歉,我忘了删除这条评论,这确实是错误的 谢谢,但我不确定您提供的解决方案能否给出结果。我尝试使用修改后的冒泡排序来解决它,但无法猜测条件是有效的交换。 O(N^2) 显然不如普通的 O(N log N) 排序,或(满足约束条件)O(N) 基数排序。 您对不同问题的灵巧回答给我留下了深刻的印象,尽管我理解其中一些问题的能力有些有限。我很好奇你会怎么看我的想法:***.com/questions/15364607/… @PriyankBolia 在原始语句中(允许任何 2 元素交换),考虑 (3, 5, 6, 4, 2, 1):交换 1 -> 4,然后 4 -> 3 , 变成 (4, 5, 6, 1, 2, 3)。【参考方案2】:我认为这里的方法应该是 - 将所有数字排序到一个辅助数组中。然后对于每个循环移位计算使原始数组到该循环移位所需的交换次数。选择其中的最小值。
要找到从数组 A 到数组 B 所需的最小交换次数,只需计算交换值的数量(即值 a 在 A 中值 b 的左侧,但在数组 B 中反之亦然)。这个问题等同于计算给定数组中的反转,并且可以在O(n*log(n))
中再次使用修改的合并排序来解决。
我的方法的复杂性是O(n^2*log(n))
(因为您对大小为 n 的数组的所有循环移位进行合并排序)。
我想不出更快的解决方案来解决您的问题。
【讨论】:
不完全是。暂时忽略循环要求。2 3 1 5 4
的所有值都放错了位置,但您只需要 3 次交换即可对其进行排序(1 与 3,然后 1 与 2,然后 4 与 5)。但是2 3 4 5 1
的所有值也都放错了位置,您需要进行 4 次交换才能对其进行排序(将 1 滑动到正确的位置)。
@JuanLopes 我说您需要尝试所有循环移位并选择需要最少交换次数的循环移位。我不确定你的not exactly
指的是什么
是的,但是您没有提供有关如何计算最小交换次数的解决方案。仅仅看到有多少元素被置换是不够的。您需要计算排列周期。
@JuanLopes 最小交换数等于我所说的数组中的反转数
您愿意解释一下如何计算这个反转次数吗?【参考方案3】:
假设:
-
数组元素是整数 1 到 N。
步骤:
-
分配第二个(直方图)数组 H,长度为 N。
在单次遍历初始数组 A 时,对于每个索引 i 递增 H[ (A[i] - i) % N]
在单次遍历 H 中,识别 H 中具有最大计数的元素 R
清除 H.
确定原始阵列中在 R 旋转下的轨道数。(通过向前步进 H 直到 H[i]=0,然后在旋转 R 下步进通过 A[i] 是成员的轨道) 为轨道的每个成员设置 H[i]=1,并重复直到元素 H[N-1] 被处理(一边计算轨道数)。这也是 O(N),因为 A 的每个元素只被访问一次。
所需的交换次数 = N - 轨道。
这是 O(N)。
更新:我已更新算法以考虑多个轨道。在处理每个轨道时,最终交换会放置两个元素,而不仅仅是 1。
我怀疑轨道的数量在任何旋转下都是不变的,这将大大简化算法但不会影响它的复杂性,它保持在 O(N)。
【讨论】:
当A = [1, 2, 3, 4, 5]
时,H 将是[0, 5, 0, 0, 0]
和N - 5 - 1 = -1
。对吗?
@Juan:我已经更正了算法以考虑起始数组中的轨道数。【参考方案4】:
def number_of_swaps(A):
l=len(A)
if l < 2:
return 0
S1=x:i for i,x in enumerate(A)
pos_of_0=S1[0]
pos_of_N=S1[l-1]
if pos_of_0 > 0:
if pos_of_N != (l-1):
if pos_of_N < pos_of_0:
n=(pos_of_0+(l-1)-pos_of_N-1)
else:
n=(pos_of_0+(l-1)-pos_of_N)
else:
n=(pos_of_0)
else :
n=(l-pos_of_N-1)
A.remove(0)
A.remove(l-1)
B=[x-1 for x in A ]
return n+number_of_swaps(B)
def min_number_of_swaps(A):
B=sorted(A)
swaps=[]
for i in range(len(A)):
if i == 0:
C=B
else:
C=B[-i:]+B[0:len(A)-i]
S = x:i for i,x in enumerate(C)
D=[S[i] for i in A]
swaps.append(number_of_swaps(D))
return min(swaps)
打印 min_number_of_swaps([8,5,7,1,2,4,3,6])
7
以上代码是解决复杂度O(N^3)问题的递归方法
代码已被编辑为仅打印最少交换次数。
【讨论】:
以上是关于给定一个随机顺序的整数数组,您必须找到最小交换次数才能将其转换为循环排序数组的主要内容,如果未能解决你的问题,请参考以下文章