如何证明递归算法的正确性

时间:2019-10-21 04:08:29

标签: algorithm

// sorts the items from L[i] through L[j]
void threewaysort(int[] L, int i, int j) {
    if (L[i] > L[j]) swap (i, j);
    if (j - i + 1 > 2) {
        t = (j - i + 1) / 3;
        threewaysort(L, i, j - t);
        threewaysort(L, i + t, j);
        threewaysort(L, i, j - t);
    }
}

上面的代码将列表 L 从最小到最大排序。为了证明这种算法是正确的,我认为我们可以使用归纳法吗?提示是,呼叫threewaysort(L, 0, L.length-1)实际上具有使 L 排序的副作用。

但是我目前仍处于归纳证明的步骤中。

1 个答案:

答案 0 :(得分:3)

您确实可以使用归纳法。让我们使用符号 L i,j 表示从 L [i] L [j] < / em>。

基本情况

此归纳证明有两种基本情况:

  1. j-i + 1 = 1

    这意味着 L i,j 中只有一个元素,因此已经对其进行了排序。 if的条件都不成立,因此什么也没发生:调用threewaysort(L, i, j)后对 L i,j 进行排序。

  2. j-i + 1 = 2

    L i,j 中有两个元素。如果尚未排序,则第一个if条件为true,并且对swap的调用将有效地对 L i,j 进行排序。第二个if条件为false。因此,在调用threewaysort(L, i, j)

  3. 之后对 L i,j 进行排序

诱导步骤

我们得出了 j-i + 1> 2

的情况

L i,j 中现在至少包含3个元素。通过归纳证明,我们假设threewaysort对于较小的子数组正常工作。

我们暂时忽略可能执行swap的操作,而只关注第二个if的正文,将执行

t 确保大于零。

进行了三个递归调用:在子数组 L i,jt ,L i + t,j 上,再在 L i,jt

让我们定义:

A = L i,i + t-1
B = L i + t,j-t
C = L j-t + 1,j

这些是 L i,j 的不重叠相邻范围。 A C 的大小均为 t B 的大小至少为 t (可以是 t t +1或 t +2)。 我们还定义加号表示两个子数组的并集。因此, L i,j = A + B + C ,然后递归调用实际上对 A + B 进行排序, B + C ,然后再次 A + B

由于 t 严格为正,所以 A + B B + C 是比 A + B + C < / em>,因此我们可以假设这些递归调用成功地对相应的子数组进行了排序(归纳前提)。

让我们看看在 A + B + C t 最大值发生了什么。那些不在 C 中的变量将在第一次递归调用后以 B 结尾(回想一下 B 的大小至少为 t )。因此,我们可以确定 t 的最大值都在 B + C 中。因此,在第二次递归调用之后,我们可以确保所有 t 最大值只能在 C 中找到。

类似的事情发生在 A + B + C 中的 t 最小值。在第一次递归调用之后,它们都不能再位于 B 中,但这并没有帮助。在第二次递归调用之后,它们都不再位于 C 中。在第三次递归调用之后,它们都不可以在 B 中,因此它们都在 A 中。

总结一下,我们明白了:

  • A 已排序(因为最后一次递归调用 A + B 已排序)
  • B 已排序(原因相同)
  • C 已排序(因为第二次递归调用 B + C 已排序,而第三次调用未触摸 C
  • A 的值小于 A + B + C
  • C 的值大于 A + B + C
  • B 具有 A + B + C
  • 中的剩余值

这意味着 A + B + C 已排序。

这通过归纳完成了证明。

更少的交换次数

证明还表明,当数组的大小不同于2时,交换是可选的。因此,代码甚至是正确的:

void threewaysort(int[] L, int i, int j) {
    if (j - i + 1 > 2) {
        t = (j - i + 1) / 3;
        threewaysort(L, i, j - t);
        threewaysort(L, i + t, j);
        threewaysort(L, i, j - t);
    } else if (L[i] > L[j]) {
        swap(L, i, j);
    }
}

但是,按照原始代码中的描述进行交换平均会导致交换次数减少(但比较会更多)。

时间复杂度

首先,我们注意到除递归调用外,所有其他语句均在恒定时间内执行。

第二,递归调用是在数组上进行的,数组的大小大约小三分之一。

因此,对于 n = j-i + 1 ,重复关系为:

f(n)= 3·f((2/3)n)
f(2)= f(1)= 1

如果扩大重复率,则会得到:

f(n)= 3 2 ·f((2/3) 2 n)= ... = 3 k ·f((2/3) k n)

当选择 k 使得(2/3) k n = 2 (或1)时,我们知道 f((2/3) k n)= 1 ,该因子可以从表达式中省略:

f(n)= 3 k

现在我们必须根据 n 解析 k

(2/3) k n = 2
k = log 3/2 (n / 2)
k = log 3 (n / 2)/ log 3 (3/2)
k = 2.7 log 3 (n / 2)

所以,现在我们有了:

f(n)= 3 k
f(n)=(3 log 3 (n / 2) 2.7
f(n)=(n / 2) 2.7

将时间复杂度设置为大约:

f(n)= O(n 2.7

...效率很低的排序算法;比气泡排序效率低。