堆的算法检查奇数和偶数

Posted

技术标签:

【中文标题】堆的算法检查奇数和偶数【英文标题】:Heap's algorithm checking for odd and even 【发布时间】:2021-12-24 10:10:04 【问题描述】:

有人可以解释为什么堆算法使用条件语句检查K是偶数还是奇数,并且每个交换都不同? 我在网上找不到任何直观的解释

此代码来自Wikipedia

procedure generate(k : integer, A : array of any):
    if k = 1 then
        output(A)
    else
        // Generate permutations with kth unaltered
        // Initially k == length(A)
        generate(k - 1, A)

        // Generate permutations for kth swapped with each k-1 initial
        for i := 0; i < k-1; i += 1 do
            // Swap choice dependent on parity of k (even or odd)
            if k is even then
                swap(A[i], A[k-1]) // zero-indexed, the kth is at k-1
            else
                swap(A[0], A[k-1])
            end if
            generate(k - 1, A)

        end for
    end if

【问题讨论】:

请提供链接到您从哪里获得 sn-p 以获得更好的上下文 【参考方案1】:

您无法真正将基于k 的奇偶性更改交换行为的原因与算法作为一个整体工作的解释/理由分开,这是一个复杂的演示,但基本上,它与generate 对底层数组的不同副作用有关,具体取决于k 是偶数还是奇数,并以交替方式利用这些副作用来探索整个排列空间。这些副作用及其相互作用在数学上有点复杂,因此,为了简化讨论,首先我将把算法重新编写成一个完全等效的形式,这样更容易推理。我们没有将第一次调用 generate 放在 for 循环之外并从 0 迭代到 k-1,而是将它放在里面,从 0 迭代到 k,并提前退出最后一次迭代(以避免不必要的交换已经生成了当前k 值的所有输出)。

procedure generate(k : integer, A : array of any):
  if k = 1:
    output(A)
  else:
    for i := 0; i < k; i += 1 do:
      generate(k - 1, A)
      if i + 1 = k:
        break
      if k is even:
        swap(A[i], A[k-1])
      else:
        swap(A[0], A[k-1])

但是,通过删除 break 语句让不必要的交换发生在最终循环迭代的尾端会产生相同的排列(尽管顺序不同),并使程序每次迭代的副作用显着更容易推理:

procedure generate(k : integer, A : array of any):
  if k = 1:
    output(A)
  else:
    for i := 0; i < k; i += 1 do:
      generate(k - 1, A)
      if k is even:
        swap(A[i], A[k-1])
      else:
        swap(A[0], A[k-1])

除此之外,这里是堆算法的基本思想(任何形式):

使用给定的k 值调用 generate 函数意味着置换数组的前 k 个元素(从索引 k 开始的所有元素都必须是固定的,因为没有交换涉及超过 k-1 的索引)。为了置换这些前 k 个元素,我们对 generate(k-1, A) 进行 k 次递归调用,并且在每次进行这些递归调用之前,我们希望从要置换的段中交换一个 唯一元素它的结尾(即进入第 k 个位置:索引 k-1),因为将不同的元素放在所有大小为 k-1 的排列的后面有效地生成所有大小为 k 的排列。我们从哪里交换来选择这个唯一元素的条件选择完全取决于调用 generate(k-1, A) 对底层数组的不同副作用,这取决于 k-1 是偶数还是奇数,但要点是相同的:选择一个我们还没有在for循环中使用的元素,并在我们生成下一批大小为k-1的排列之前将它交换回数组的第k个位置。

这些交换在这个算法的修正版本中起作用的原因实际上非常简单。调用generate(k-1, A)(其中k-1 是偶数)的副作用是将数组的第一个k-1 元素向右旋转一个位置,并且调用generate(k-1, A) 和奇数k-1 实际上没有副作用元素顺序:数组的第一个 k-1 元素在调用之前完全位于它们所在的位置。

以偶数k为例,如果我们在数组[1,2,3,4,5,...]上连续调用generate(4, A),前4个元素会像这样循环(并且它们后面的所有内容都是固定的):

[1,2,3,4,...]
[4,1,2,3,...]
[3,4,1,2,...]
[2,3,4,1,...]
[1,2,3,4,...]
...

如果我们在 [1,2,3,4,5,6,...] 上调用 generate(5, A),则数组顺序方面的副作用是无操作(前 5 个元素的所有排列仍由递归调用生成,只是额外的当我们删除 break 语句时进行的交换消除了排序的副作用)。

[1,2,3,4,5,6,...]
[1,2,3,4,5,6,...]
...

条件互换策略直接源于以下事实:

k 是偶数时,我们用奇数调用generate(k-1, A),它没有排序副作用,所以如果我们想在每次迭代中选择不同的元素交换到后面,我们可以使用@987654351 @,因为i 每次迭代都会递增,而其他元素不会移动。

另一方面,当k 为奇数时,我们用偶数调用generate(k-1, A),这具有将元素向右旋转一步的副作用。这就是为什么我们只是重复地从A[0] 中拉取:循环中对generate 的偶数调用的副作用为我们完成了循环元素的工作:我们每次都可以从第一个位置抓取元素,因为side-效果在每次迭代时将不同的元素放入该位置。

基本上,如果我们想在 for 循环中获取第一个 k 元素中的每一个并且它们是静态定位的,我们必须使用 i 的每个值,并且如果它们已经在循环,我们每次都必须从同一个位置抓取。它们是静态定位还是循环已经取决于generate 基于k 是偶数还是奇数的不同排序副作用。当您添加 break 时,偶数/奇数 k 的副作用的数学运算、这些副作用产生的循环以及这些特定规则起作用的原因会发生一些变化,但相同的规则仍然有效,并且你必须做的分析的一般形式来证明它也保持不变。

【讨论】:

以上是关于堆的算法检查奇数和偶数的主要内容,如果未能解决你的问题,请参考以下文章

[算法]奇数下标都是奇数或偶数下标都是偶数

在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边。

在算法复杂度比线性更好的列表中查找第一个偶数

算法入门03调整数组顺序使奇数位于偶数前面

2021-11-07:奇偶链表。给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。请尝试使用原地算法完成。你的算法

算法题:奇数在前 偶数在后,相对顺序保持不变