Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Arrays 为什么数组大小必须为3^k+;1循环领导迭代算法是否有效?_Arrays_Algorithm_Math_Permutation_Number Theory - Fatal编程技术网

Arrays 为什么数组大小必须为3^k+;1循环领导迭代算法是否有效?

Arrays 为什么数组大小必须为3^k+;1循环领导迭代算法是否有效?,arrays,algorithm,math,permutation,number-theory,Arrays,Algorithm,Math,Permutation,Number Theory,是一种通过将所有偶数编号的项移到前面,将所有奇数编号的项移到后面,同时保持它们的相对顺序来洗牌数组的算法。例如,给定此输入: a 1 b 2 c 3 d 4 e 5 输出将是 a b c d e 1 2 3 4 5 该算法在O(n)时间内运行,仅使用O(1)空间 该算法的一个不寻常的细节是,它通过将阵列分成大小为3k+1的块来工作。显然,这对算法的正确运行至关重要,但我不知道为什么会这样 为什么在算法中需要选择3k+1 谢谢 这将是一个很长的答案。你的问题的答案并不简单,需要一些数论才能完全

是一种通过将所有偶数编号的项移到前面,将所有奇数编号的项移到后面,同时保持它们的相对顺序来洗牌数组的算法。例如,给定此输入:

a 1 b 2 c 3 d 4 e 5
输出将是

a b c d e 1 2 3 4 5
该算法在O(n)时间内运行,仅使用O(1)空间

该算法的一个不寻常的细节是,它通过将阵列分成大小为3k+1的块来工作。显然,这对算法的正确运行至关重要,但我不知道为什么会这样

为什么在算法中需要选择3k+1

谢谢

这将是一个很长的答案。你的问题的答案并不简单,需要一些数论才能完全回答。我花了大约半天的时间研究这个算法,现在我有了一个很好的答案,但我不确定我能不能简明扼要地描述它

简短版本:
  • 将输入分解为大小为3k+1的块实质上是将输入分解为大小为3k-1的块,其周围有两个元素,这些元素最终不会移动

  • 块中剩余的3k-1元素按照一个有趣的模式移动:每个元素移动到索引除以两个模3k得到的位置

  • 这种特殊的运动模式与数论和群论中的一个概念有关,称为

  • 因为数字2是一个原始的根模3k,从数字1、3、9、27等开始,运行模式可以保证在数组的所有元素中循环一次,并将它们放在正确的位置

  • 这种模式高度依赖于这样一个事实,即2是任何k的3k的原始根≥ 1.将数组的大小更改为另一个值几乎肯定会破坏这一点,因为保留了错误的属性

长版本 为了给出这个答案,我将分步骤进行。首先,我将介绍一种算法,该算法将根据一个重要的警告,以正确的顺序高效地洗牌元素。接下来,我将指出一个有趣的特性,即当应用这个置换时,元素是如何在数组中移动的。然后,我将把它与一个数论概念联系起来,这个数论概念被称为解释正确实现这个算法所涉及的挑战。最后,我将解释为什么选择3k+1作为块大小

循环分解 假设有一个数组A和该数组元素的排列。按照标准的数学表示法,我们将该数组的置换表示为σ(A)。我们可以将初始数组A排列在置换数组σ(A)的顶部,以了解每个元素的结束位置。例如,下面是一个数组及其排列:

   A    0 1 2 3 4
 σ(A)   2 3 0 4 1
描述排列的一种方法是列出排列中的新元素。然而,从算法的角度来看,将排列表示为一个,一种写出排列的方式通常更有用,它展示了如何从初始数组开始,然后循环排列其中的一些元素,形成排列

看看上面的排列。首先,看看0在哪里结束。在σ(A)中,元素0最终取代了元素2原来所在的位置。反过来,元素2最终取代了元素0原来所在的位置。我们通过写(02)来表示这一点,表示0应该去2过去所在的地方,2应该去0过去所在的地方

现在,看看元素1。元素1结束于原来的位置4。然后,数字4在原来的3处结束,元素3在原来的1处结束。我们通过书写(143)来表示这一点,1应该去4过去所在的地方,4应该去3过去所在的地方,3应该去1过去所在的地方

结合这些元素,我们可以将上述元素的整体置换表示为(02)(143)-我们应该交换0和2,然后循环置换1、4和3。如果我们从初始数组开始这样做,我们将得到我们想要的置换数组

循环分解对于就地排列数组非常有用,因为它可以在O(C)时间和O(1)辅助空间中排列任何单个循环,其中C是循环中的元素数。例如,假设您有一个循环(1 6 8 4 2)。您可以使用如下代码排列循环中的元素:

int[] cycle = {1, 6, 8, 4, 2};

int temp = array[cycle[0]];
for (int i = 1; i < cycle.length; i++) {
    swap(temp, array[cycle[i]]);
}
array[cycle[0]] = temp;
 0  1  2  3  4  5  6  7  8  9 10 11 12 13
 0  2  4  6  8 10 12  1  3  5  7  9 11 13
 Element    2  4  6  8 10 12  1  3  5  7  9 11
 Index      1  2  3  4  5  6  7  8  9 10 11 12
 Initial:  1  2  3  4  5  6  7  8  9 10
 Final:    2  4  6  8 10  1  3  5  7  9
 Initial:  1  2  3  4  5  6  7  8  9 10 11 12
 Final:    2  4  6  8 10 12  1  3  5  7  9 11
 Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14
 Final:    2  4  6  8 10 12 14  1  3  5  7  9 11 13
 Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
 Final:    2  4  6  8 10 12 14 16  1  3  5  7  9 11 13 15
此算法的总体时间和空间复杂度取决于以下因素:

for (each cycle in the cycle decomposition algorithm) {
   apply the above algorithm to cycle those elements;
}
  • 我们能多快确定我们想要的循环分解
  • 我们在内存中存储循环分解的效率如何
  • 为了得到一个O(n)-时间,O(1)-空间算法,我们将展示一种确定O(1)时间和空间中循环分解的方法。因为所有东西都将被移动一次,所以整个运行时将是O(n),而整个空间复杂度将是O(1)。正如你将看到的,要到达那里并不容易,但话说回来,也不可怕

    排列结构 这个问题的首要目标是获取一个包含2n个元素的数组并将其洗牌,以便偶数位置的元素最终位于数组的前面,奇数位置的元素最终位于数组的末尾。现在假设我们有14个元素,如下所示:

    int[] cycle = {1, 6, 8, 4, 2};
    
    int temp = array[cycle[0]];
    for (int i = 1; i < cycle.length; i++) {
        swap(temp, array[cycle[i]]);
    }
    array[cycle[0]] = temp;
    
     0  1  2  3  4  5  6  7  8  9 10 11 12 13
    
     0  2  4  6  8 10 12  1  3  5  7  9 11 13
    
     Element    2  4  6  8 10 12  1  3  5  7  9 11
     Index      1  2  3  4  5  6  7  8  9 10 11 12
    
     Initial:  1  2  3  4  5  6  7  8  9 10
     Final:    2  4  6  8 10  1  3  5  7  9
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12
     Final:    2  4  6  8 10 12  1  3  5  7  9 11
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14
     Final:    2  4  6  8 10 12 14  1  3  5  7  9 11 13
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
     Final:    2  4  6  8 10 12 14 16  1  3  5  7  9 11 13 15
    
    我们希望对元素进行洗牌,使它们像这样出现:

    int[] cycle = {1, 6, 8, 4, 2};
    
    int temp = array[cycle[0]];
    for (int i = 1; i < cycle.length; i++) {
        swap(temp, array[cycle[i]]);
    }
    array[cycle[0]] = temp;
    
     0  1  2  3  4  5  6  7  8  9 10 11 12 13
    
     0  2  4  6  8 10 12  1  3  5  7  9 11 13
    
     Element    2  4  6  8 10 12  1  3  5  7  9 11
     Index      1  2  3  4  5  6  7  8  9 10 11 12
    
     Initial:  1  2  3  4  5  6  7  8  9 10
     Final:    2  4  6  8 10  1  3  5  7  9
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12
     Final:    2  4  6  8 10 12  1  3  5  7  9 11
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14
     Final:    2  4  6  8 10 12 14  1  3  5  7  9 11 13
    
     Initial:  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
     Final:    2  4  6  8 10 12 14 16  1  3  5  7  9 11 13 15
    
    关于这种排列产生的方式,我们有一些有用的观察结果。首先,请注意,第一个元素在此排列中不移动,因为偶数索引元素应该显示在数组的前面,并且它是第一个偶数索引元素。接下来,请注意,最后一个元素在此置换中不移动