如何使用 dafny 证明冒泡排序的时间复杂度?
How to prove time complexity of bubble sort using dafny?
dafny 中有没有办法创建特定于单个循环迭代的不变量?下面我试图创建一个愚蠢的程序来限制冒泡排序的交换次数。该值存储在变量 n 中。所以我想将 n 上限设为 (a.Length * (a.Length - 1))/2。原因是可能发生的最大交换次数是,如果数组的顺序相反,那么内部循环的第一次迭代必须有 n 次交换,然后内部循环的第二次迭代必须有 n-1 次交换循环等,直到数组排序完毕。这相当于 1+2+3+...+n-1+n = n(n-1)/2,这正是我要展示的。我想知道 dafny 中是否有一种方法可以根据内部循环的迭代来限制下面代码中 i-j 或 n 的值。有这样的方法吗?
如果没有,是否有另一种方法可以根据上面提供的解释来上限此 n 值?也许我没有想到其他不变量?
method BubbleSort(a: array<int>) returns (n: nat)
modifies a
requires a != null
ensures n <= (a.Length * (a.Length - 1))/2
{
var i := a.Length - 1;
n := 0;
while (i > 0)
invariant 0 < a.Length ==> 0 <= i < a.Length;
decreases i;
{
var j := 0;
while (j < i)
invariant i - j <= old(i) - old(j);
invariant i < 0 && j < 0 ==> 0 <= n <= a.Length;
decreases i - j;
{
if(a[j] > a[j+1])
{
a[j], a[j+1] := a[j+1], a[j];
n := n + 1;
}
j := j + 1;
}
i := i -1;
}
}
这是一种方法。首先,我们介绍两个辅助函数。 NChoose2
只是为了方便 n * (n - 1) / 2
的包装。 SumRange(lo, hi)
是从lo
(含)到hi
(不含)的整数之和。我们将需要下面的 SumRange
来表达一些循环不变量。
function NChoose2(n: int): int
{
n * (n - 1) / 2
}
// sum of all integers in the range [lo, hi)
// (inclusive of lo, exclusive of hi)
function SumRange(lo: int, hi: int): int
decreases hi - lo
{
if lo >= hi then 0
else SumRange(lo, hi - 1) + hi - 1
}
这是 BubbleSort
的新注释版本。
method BubbleSort(a: array<int>) returns (n: nat)
modifies a
ensures n <= NChoose2(a.Length)
{
// it simplifies the remaining invariants to handle the empty array here
if a.Length == 0 { return 0; }
var i := a.Length - 1;
n := 0;
while i > 0
invariant 0 <= i < a.Length
invariant n <= SumRange(i+1, a.Length)
{
var j := 0;
while j < i
invariant n <= SumRange(i+1, a.Length) + j
invariant j <= i
{
if a[j] > a[j+1]
{
a[j], a[j+1] := a[j+1], a[j];
n := n + 1;
}
j := j + 1;
}
assert n <= SumRange(i, a.Length) by {
SumRangeUnrollLeft(i, a.Length); // see lemma below
}
i := i - 1;
}
calc <= {
n; // less or equal to next line by the loop invariant
SumRange(1, a.Length);
{ SumRangeNChoose2(a.Length); } // see lemma below
NChoose2(a.Length);
}
}
更改摘要:
- (无关紧要)根据
NChoose2
. 重新表述后置条件
- (无关紧要但简化)立即处理空数组的特殊情况,并与循环分开。
- 外层循环的循环不变量是
SumRange
,而不是 NChoose2
。如果你考虑一下你对参数的非正式描述,外循环的第一次迭代最多贡献 a.Length - 1
次交换;第二次迭代 a.Length - 2
次交换,等等。所以要汇总到目前为止的交换次数,它是 (a.Length - 1) + ... + (i + 1)
。这正是 SumRange(i+1, a.Length)
.
- 内部循环的循环不变量只是说到目前为止,内部循环最多进行了
j
次额外交换。
- 要从内循环不变量重新建立外循环不变量,需要证明
SumRange(i+1, a.Length) + i == SumRange(i, a.Length)
。这是由引理 SumRangeUnrollLeft
处理的,它通过归纳法自动证明。
- 要从外循环不变量建立后置条件,我们需要证明
SumRange(1, a.Length) == NChoose2(a.Length)
。这是由引理 SumRangeNChoose2
处理的,它也可以通过归纳法自动证明。
这里是确切的引理陈述和证明。请注意,SumRangeUnrollLeft
的 decreases 子句是必需的,因为默认的推断子句始终是词典顺序。
// dafny proves this automatically by induction
lemma SumRangeNChoose2(n: nat)
ensures SumRange(0, n) == NChoose2(n)
{}
// dafny proves this automatically by induction
// (given the correct decreases clause)
lemma SumRangeUnrollLeft(lo: int, hi: int)
decreases hi - lo
ensures SumRange(lo, hi) ==
if lo >= hi then 0 else lo + SumRange(lo + 1, hi)
{}
dafny 中有没有办法创建特定于单个循环迭代的不变量?下面我试图创建一个愚蠢的程序来限制冒泡排序的交换次数。该值存储在变量 n 中。所以我想将 n 上限设为 (a.Length * (a.Length - 1))/2。原因是可能发生的最大交换次数是,如果数组的顺序相反,那么内部循环的第一次迭代必须有 n 次交换,然后内部循环的第二次迭代必须有 n-1 次交换循环等,直到数组排序完毕。这相当于 1+2+3+...+n-1+n = n(n-1)/2,这正是我要展示的。我想知道 dafny 中是否有一种方法可以根据内部循环的迭代来限制下面代码中 i-j 或 n 的值。有这样的方法吗?
如果没有,是否有另一种方法可以根据上面提供的解释来上限此 n 值?也许我没有想到其他不变量?
method BubbleSort(a: array<int>) returns (n: nat)
modifies a
requires a != null
ensures n <= (a.Length * (a.Length - 1))/2
{
var i := a.Length - 1;
n := 0;
while (i > 0)
invariant 0 < a.Length ==> 0 <= i < a.Length;
decreases i;
{
var j := 0;
while (j < i)
invariant i - j <= old(i) - old(j);
invariant i < 0 && j < 0 ==> 0 <= n <= a.Length;
decreases i - j;
{
if(a[j] > a[j+1])
{
a[j], a[j+1] := a[j+1], a[j];
n := n + 1;
}
j := j + 1;
}
i := i -1;
}
}
这是一种方法。首先,我们介绍两个辅助函数。 NChoose2
只是为了方便 n * (n - 1) / 2
的包装。 SumRange(lo, hi)
是从lo
(含)到hi
(不含)的整数之和。我们将需要下面的 SumRange
来表达一些循环不变量。
function NChoose2(n: int): int
{
n * (n - 1) / 2
}
// sum of all integers in the range [lo, hi)
// (inclusive of lo, exclusive of hi)
function SumRange(lo: int, hi: int): int
decreases hi - lo
{
if lo >= hi then 0
else SumRange(lo, hi - 1) + hi - 1
}
这是 BubbleSort
的新注释版本。
method BubbleSort(a: array<int>) returns (n: nat)
modifies a
ensures n <= NChoose2(a.Length)
{
// it simplifies the remaining invariants to handle the empty array here
if a.Length == 0 { return 0; }
var i := a.Length - 1;
n := 0;
while i > 0
invariant 0 <= i < a.Length
invariant n <= SumRange(i+1, a.Length)
{
var j := 0;
while j < i
invariant n <= SumRange(i+1, a.Length) + j
invariant j <= i
{
if a[j] > a[j+1]
{
a[j], a[j+1] := a[j+1], a[j];
n := n + 1;
}
j := j + 1;
}
assert n <= SumRange(i, a.Length) by {
SumRangeUnrollLeft(i, a.Length); // see lemma below
}
i := i - 1;
}
calc <= {
n; // less or equal to next line by the loop invariant
SumRange(1, a.Length);
{ SumRangeNChoose2(a.Length); } // see lemma below
NChoose2(a.Length);
}
}
更改摘要:
- (无关紧要)根据
NChoose2
. 重新表述后置条件
- (无关紧要但简化)立即处理空数组的特殊情况,并与循环分开。
- 外层循环的循环不变量是
SumRange
,而不是NChoose2
。如果你考虑一下你对参数的非正式描述,外循环的第一次迭代最多贡献a.Length - 1
次交换;第二次迭代a.Length - 2
次交换,等等。所以要汇总到目前为止的交换次数,它是(a.Length - 1) + ... + (i + 1)
。这正是SumRange(i+1, a.Length)
. - 内部循环的循环不变量只是说到目前为止,内部循环最多进行了
j
次额外交换。 - 要从内循环不变量重新建立外循环不变量,需要证明
SumRange(i+1, a.Length) + i == SumRange(i, a.Length)
。这是由引理SumRangeUnrollLeft
处理的,它通过归纳法自动证明。 - 要从外循环不变量建立后置条件,我们需要证明
SumRange(1, a.Length) == NChoose2(a.Length)
。这是由引理SumRangeNChoose2
处理的,它也可以通过归纳法自动证明。
这里是确切的引理陈述和证明。请注意,SumRangeUnrollLeft
的 decreases 子句是必需的,因为默认的推断子句始终是词典顺序。
// dafny proves this automatically by induction
lemma SumRangeNChoose2(n: nat)
ensures SumRange(0, n) == NChoose2(n)
{}
// dafny proves this automatically by induction
// (given the correct decreases clause)
lemma SumRangeUnrollLeft(lo: int, hi: int)
decreases hi - lo
ensures SumRange(lo, hi) ==
if lo >= hi then 0 else lo + SumRange(lo + 1, hi)
{}