选择在Dafny中排序

时间:2014-07-06 00:24:55

标签: sorting assertions selection-sort dafny

我正在尝试在Dafny中实现选择排序。

我的sortedFindMin函数确实有效,但selectionsort本身包含Dafny无法证明的断言,即使它们是正确的。

这是我的计划:

predicate sorted(a:array<int>,i:int)
  requires a != null;
  requires 0 <= i <= a.Length;
  reads a;
{
  forall k :: 0 < k < i ==> a[k-1] < a[k]
}
method FindMin(a:array<int>,i:int) returns(m:int)
  requires a != null;
  requires 0 <= i < a.Length;
  ensures i <= m < a.Length;
  ensures forall k :: i <= k < a.Length ==> a[k] >= a[m];
{
  var j := i;
  m := i;
  while(j < a.Length)
    decreases a.Length - j;
    invariant i <= j <= a.Length;
    invariant i <= m < a.Length;
    invariant forall k :: i <= k < j ==> a[k] >= a[m];
  {
    if(a[j] < a[m]){m := j;}
    j := j + 1;
  }
}
method selectionsort(a:array<int>) returns(s:array<int>)
  requires a != null;
  modifies a;
  ensures s != null;
  ensures sorted(s,s.Length);
{
  var c,m := 0,0;
  var t;
  s := a;
  assert s != null;
  assert s.Length == a.Length;
  while(c<s.Length)
    decreases s.Length-c;
    invariant 0 <= c <= s.Length;
    invariant c-1 <= m <= s.Length;
    invariant sorted(s,c);
  {
    m := FindMin(s,c);
    assert forall k :: c <= k < s.Length ==> s[k] >= s[m];
    assert forall k :: 0 <= k < c ==> s[k] <= s[m];
    assert s[c] >= s[m];
    t := s[c];
    s[m] := t;
    s[c] := s[m];
    assert s[m] >= s[c];
    assert forall k :: c <= k < s.Length ==> s[k] >= s[c];
    c := c+1;
    assert  c+1 < s.Length ==> s[c-1] <= s[c];
  }
}

为什么这是错的? “postcondtion可能不成立”是什么意思? Dafny可以举一个反例吗?

1 个答案:

答案 0 :(得分:6)

您似乎理解循环不变量背后的基本思想,这是使用Dafny验证程序所必需的。

您的计划不正确。发现这种情况的一种方法是在Visual Studio中使用Dafny IDE中的验证调试器。单击报告的最后一个错误(c增量之前的行上的断言),您将看到数组的上半部分包含一个小于s[c]和{{1}的元素}}。然后选择3语句交换操作周围的程序点,你会发现你的交换实际上没有交换。

要修复交换,请交换3语句交换的第二和第三个语句。更好的是,利用Dafny的多重赋值语句,使代码更容易正确:

s[m]

还有另外两个问题。一个是循环内的第二个断言不验证:

s[c], s[m] := s[m], s[c];

虽然assert forall k :: 0 <= k < c ==> s[k] <= s[m]; 是数组上半部分中的最小元素,但循环不变量需要记录数组下半部分中的元素不大于上部中的元素 - 选择排序算法的基本属性。以下循环不变量可以解决这个问题:

s[m]

最后,关于属性invariant forall k, l :: 0 <= k < c <= l < a.Length ==> s[k] <= s[l]; 未被循环维护的投诉源于您将sorted(s,c)定义为严格增加的事实,除非交换将永远不会实现数组的元素最初都是不同的。因此,Dafny指出了您必须对排序例程做出的设计决定。您可以决定您的sorted方法仅适用于没有重复元素的数组,您可以通过添加

来执行此操作
selectionsort

作为(并且循环不变)forall k, l :: 0 <= k < l < a.Length ==> a[k] != a[l]; 的前提条件。或者,更常规地,您可以修改selectionsort的定义,将sorted替换为a[k] > a[m]

要稍微清理一下代码,现在可以删除所有断言语句和a[k] >= a[m]的声明。由于t仅在循环内部使用,因此您可以将m的声明移动到调用m的语句,这也表明循环不变FindMin是不需要。可以省略两个c-1 <= m <= s.Length子句;对于您的程序,Dafny将自动提供这些。最后,您的decreases方法会修改给定的数组,因此没有真正的理由在out-parameter selectionsort中返回引用a;相反,您可以省略out-parameter并将s替换为s