Quicksort (arrays)
Sorting an array using quicksort, with partitioning a la Bentley.
Auteurs: Jean-Christophe Filliâtre
Catégories: Array Data Structure / Sorting Algorithms / Tricky termination
Outils: Why3
see also the index (by topic, by tool, by reference, by year)
Sorting an array of integers using quicksort
with a partitioning a la Bentley
module Quicksort use int.Int use ref.Ref use array.Array use array.IntArraySorted use array.ArraySwap use array.ArrayPermut use array.ArrayEq predicate qs_partition (a1 a2: array int) (l m r: int) (v: int) = permut_sub a1 a2 l r /\ (forall j: int. l <= j < m -> a2[j] < v) /\ (forall j: int. m < j < r -> v <= a2[j]) /\ a2[m] = v (* partitioning à la Bentley, that is l i m r +-+----------+----------+----------+ |v| < v | ? | >= v | +-+----------+----------+----------+ *) let rec quick_rec (a: array int) (l: int) (r: int) : unit requires { 0 <= l <= r <= length a } ensures { sorted_sub a l r } ensures { permut_sub (old a) a l r } variant { r - l } = if l + 1 < r then begin let v = a[l] in let m = ref l in label L in for i = l + 1 to r - 1 do invariant { a[l] = v /\ l <= !m < i } invariant { forall j:int. l < j <= !m -> a[j] < v } invariant { forall j:int. !m < j < i -> a[j] >= v } invariant { permut_sub (a at L) a l r } label K in if a[i] < v then begin m := !m + 1; swap a i !m; assert { permut_sub (a at K) a l r } end done; label M in swap a l !m; assert { qs_partition (a at M) a l !m r v }; label N in quick_rec a l !m; assert { qs_partition (a at N) a l !m r v }; label O in quick_rec a (!m + 1) r; assert { qs_partition (a at O) a l !m r v }; assert { qs_partition (a at N) a l !m r v }; end let quicksort (a: array int) = ensures { sorted a } ensures { permut_all (old a) a } quick_rec a 0 (length a) end
Knuth's shuffle
A realistic quicksort first shuffles the array.
module Shuffle use int.Int use array.Array use array.ArraySwap use array.ArrayPermut use random.Random let shuffle (a: array int) : unit writes { a, Random.s } ensures { permut_all (old a) a } = for i = 1 to length a - 1 do invariant { permut_all (old a) a } swap a i (Random.random_int (i+1)) done end module QuicksortWithShuffle use array.Array use array.IntArraySorted use array.ArrayPermut use Shuffle use Quicksort let qs (a: array int) : unit = ensures { sorted a } ensures { permut_all (old a) a } shuffle a; quicksort a end
with 3-way partitioning
A realistic quicksort also has to use 3-way partitioning, to cope with arrays were many values are identical.
module Quicksort3way use int.Int use ref.Refint use array.Array use array.IntArraySorted use array.ArraySwap use array.ArrayPermut use array.ArrayEq predicate qs_partition (a1 a2: array int) (l ml mr r: int) (v: int) = permut_sub a1 a2 l r /\ (forall j: int. l <= j < ml -> a2[j] < v) /\ (forall j: int. ml <= j < mr -> a2[j] = v) /\ (forall j: int. mr <= j < r -> a2[j] > v) (* 3-way partitioning l ml i mr r +----------+----------+----------+-----------+ | < v | = v | ? | > v | +----------+----------+----------+-----------+ *) let rec quick_rec (a: array int) (l r: int) : unit requires { 0 <= l <= r <= length a } ensures { sorted_sub a l r } ensures { permut_sub (old a) a l r } variant { r - l } = if l + 1 < r then begin let v = a[l] in let ml = ref l in let mr = ref r in let i = ref (l + 1) in label L in while !i < !mr do invariant { l <= !ml < !i <= !mr <= r } invariant { forall j: int. l <= j < !ml -> a[j] < v } invariant { forall j: int. !ml <= j < !i -> a[j] = v } invariant { forall j: int. !mr <= j < r -> a[j] > v } invariant { permut_sub (a at L) a l r } variant { !mr - !i } label K in if a[!i] < v then begin swap a !ml !i; incr ml; incr i; assert { permut_sub (a at K) a l r } end else if a[!i] > v then begin decr mr; swap a !i !mr; assert { permut_sub (a at K) a l r } end else incr i done; assert { qs_partition (a at L) a l !ml !mr r v }; label N in quick_rec a l !ml; assert { qs_partition (a at N) a l !ml !mr r v }; label O in quick_rec a !mr r; assert { qs_partition (a at O) a l !ml !mr r v }; assert { qs_partition (a at N) a l !ml !mr r v } end let quicksort (a: array int) = ensures { sorted a } ensures { permut_all (old a) a } quick_rec a 0 (length a) use Shuffle let qs (a: array int) : unit = ensures { sorted a } ensures { permut_all (old a) a } shuffle a; quicksort a end module Test use int.Int use array.Array use Quicksort let test1 () = let a = make 3 0 in a[0] <- 7; a[1] <- 3; a[2] <- 1; quicksort a; a let test2 () ensures { result.length = 8 } = let a = make 8 0 in a[0] <- 53; a[1] <- 91; a[2] <- 17; a[3] <- -5; a[4] <- 413; a[5] <- 42; a[6] <- 69; a[7] <- 6; quicksort a; a exception BenchFailure let bench () raises { BenchFailure -> true } = let a = test2 () in if a[0] <> -5 then raise BenchFailure; if a[1] <> 6 then raise BenchFailure; if a[2] <> 17 then raise BenchFailure; if a[3] <> 42 then raise BenchFailure; if a[4] <> 53 then raise BenchFailure; if a[5] <> 69 then raise BenchFailure; if a[6] <> 91 then raise BenchFailure; if a[7] <> 413 then raise BenchFailure; a end module Traditional use int.Int use ref.Refint use array.Array use array.IntArraySorted use array.ArraySwap use array.ArrayPermut use array.ArrayEq let rec quick_rec (a: array int) (l: int) (r: int) : unit requires { 0 <= l <= r <= length a } ensures { sorted_sub a l r } ensures { permut_sub (old a) a l r } variant { r - l } = if l + 1 >= r then return; let v = a[l] in let ref i = l in let ref j = r in while true do invariant { l <= i < r-1 } invariant { forall k. l <= k <= i -> a[k] <= v } invariant { l < j <= r } invariant { forall k. j <= k < r -> a[k] >= v } invariant { permut_sub (old a) a l r } invariant { a[l] = v } variant { j - i } label L in (* while (a[++i] < v) { if (i == r-1) break; } *) incr i; while a[i] < v do invariant { i at L < i < r } invariant { forall k. l <= k < i -> a[k] <= v } variant { r - i } if i = r - 1 then break; incr i done; (* while (v < a[--j]) { if (j == l) break; } *) decr j; while a[j] > v do invariant { l <= j < j at L } invariant { forall k. j < k < r -> a[k] >= v } variant { j } if j = l then break; decr j; done; if i >= j then break; swap a i j done; swap a l j; quick_rec a l j; quick_rec a (j+1) r let quicksort (a: array int) : unit ensures { sorted a } ensures { permut_all (old a) a } = (* insert a call to shuffle here if you wish *) quick_rec a 0 (length a) end
download ZIP archive