Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AVL mal equilibres dans Set #8176

Closed
vicuna opened this issue Jun 18, 2003 · 1 comment
Closed

AVL mal equilibres dans Set #8176

vicuna opened this issue Jun 18, 2003 · 1 comment
Labels

Comments

@vicuna
Copy link

vicuna commented Jun 18, 2003

Original bug ID: 1717
Reporter: administrator
Status: closed
Resolution: fixed
Priority: normal
Severity: minor
Category: ~DO NOT USE (was: OCaml general)

Bug description

Full_Name: Jean-Christophe Filliatre
Version: 3.06
OS: Linux (Debian Woody)
Submission from: pc8-123.lri.fr (129.175.8.123)

Bonjour,

En cherchant à certifier le module Set d'ocaml en Coq, j'ai trouvé un bug : les
AVL construits ne respectent pas (toujours) l'invariant sur les hauteurs.

Je joints ci-dessous un code qui illustre le problème : c'est une copie du code
de
Set sur le type int (seulement empty, add, union) à laquelle j'ajoute une
fonction
check vérifiant l'invariant. Je fais alors l'union d'ensembles construits
aléatoirement jusqu'à trouver une mise en défaut de l'invariant (c'est très
rapide;
faire "ocaml bug.ml" pour obtenir un Assert_failure).

Explication : Le problème vient de la fonction "join l x r", qui peut constuire
un
AVL mal formé. En effet, si "l" est très haut, avec deux sous-arbres de même
hauteur,
t que "r" est très petit, alors "bal l x r" va construire un arbre "Node (l',
x', r', _)" qui vérifie bien l'invariant |height l' - height r'| <= 2, et donc
join s'arrête tout de suite, mais "r'" sera mal formé parce que composé d'un
sous-arbre gauche très grand (l'ancien sous-arbre droit de "l") et d'un
sous-arbre droit très petit, "r".

On illustre donc le problème en faisant l'union d'un gros ensemble et d'un petit
ensemble : avec un peu de (mal)chance les appels récursifs "union l1 l2" et
"union r1 r2" vont donner un gros ensemble et un petit, et "join" va alors
fabriquer un AVL mal formé.

Je ne vois pas de solution simple : il ne semble pas facile d'écrire "join" en
utilisant "bal" sans indication du rééquilibrage effectué. Il faudrait sans
doute inliner le code "bal" dans "join" pour pouvoir faire des appels récursifs
à "join" sur les arbres construits par le rééquilibrage. Ou trouver une autre
manière de faire l'union :-)

Pour l'instant je vais me concenter de la certification des fonctions
n'utilisant pas join...

Amicalement,

Jean-Christophe

===================================================================================
module Ord = struct
type t = int
let compare = compare
end

type elt = Ord.t
type t = Empty | Node of t * elt * t * int

(* Sets are represented by balanced binary trees (the heights of the
   children differ by at most 2 *)

let height = function
    Empty -> 0
  | Node(_, _, _, h) -> h

(* Creates a new node with left son l, value x and right son r.
   l and r must be balanced and | height l - height r | <= 2.
   Inline expansion of height for better speed. *)

let create l x r =
  let hl = match l with Empty -> 0 | Node(_,_,_,h) -> h in
  let hr = match r with Empty -> 0 | Node(_,_,_,h) -> h in
  Node(l, x, r, (if hl >= hr then hl + 1 else hr + 1))

(* Same as create, but performs one step of rebalancing if necessary.
   Assumes l and r balanced.
   Inline expansion of create for better speed in the most frequent case
   where no rebalancing is required. *)

let bal l x r =
  let hl = match l with Empty -> 0 | Node(_,_,_,h) -> h in
  let hr = match r with Empty -> 0 | Node(_,_,_,h) -> h in
  if hl > hr + 2 then begin
    match l with
      Empty -> invalid_arg "Set.bal"
    | Node(ll, lv, lr, _) ->
        if height ll >= height lr then
          create ll lv (create lr x r)
        else begin
          match lr with
            Empty -> invalid_arg "Set.bal"
          | Node(lrl, lrv, lrr, _)->
              create (create ll lv lrl) lrv (create lrr x r)
        end
  end else if hr > hl + 2 then begin
    match r with
      Empty -> invalid_arg "Set.bal"
    | Node(rl, rv, rr, _) ->
        if height rr >= height rl then
          create (create l x rl) rv rr
        else begin
          match rl with
            Empty -> invalid_arg "Set.bal"
          | Node(rll, rlv, rlr, _) ->
              create (create l x rll) rlv (create rlr rv rr)
        end
  end else
    Node(l, x, r, (if hl >= hr then hl + 1 else hr + 1))

(* Same as bal, but repeat rebalancing until the final result
   is balanced. *)

let rec join l x r =
  match bal l x r with
    Empty -> invalid_arg "Set.join"
  | Node(l', x', r', _) as t' ->
      let d = height l' - height r' in
      if d < -2 || d > 2 then join l' x' r' else t'

(* Splitting *)

let rec split x = function
    Empty ->
      (Empty, None, Empty)
  | Node(l, v, r, _) ->
      let c = Ord.compare x v in
      if c = 0 then (l, Some v, r)
      else if c < 0 then
        let (ll, vl, rl) = split x l in (ll, vl, join rl v r)
      else
        let (lr, vr, rr) = split x r in (join l v lr, vr, rr)

(* Implementation of the set operations *)

let empty = Empty

let rec add x = function
    Empty -> Node(Empty, x, Empty, 1)
  | Node(l, v, r, _) as t ->
      let c = Ord.compare x v in
      if c = 0 then t else
      if c < 0 then bal (add x l) v r else bal l v (add x r)

let rec union s1 s2 =
  match (s1, s2) with
    (Empty, t2) -> t2
  | (t1, Empty) -> t1
  | (Node(l1, v1, r1, h1), Node(l2, v2, r2, h2)) ->
      if h1 >= h2 then
        if h2 = 1 then add v2 s1 else begin
          let (l2, _, r2) = split v1 s2 in
          join (union l1 l2) v1 (union r1 r2)
        end
      else
        if h1 = 1 then add v1 s2 else begin
          let (l1, _, r1) = split v2 s1 in
          join (union l1 l2) v2 (union r1 r2)
        end

(* ---------------------------------------------------------------------- )
(
check for AVL invariant *)
let check s =
let rec check_rec = function
| Empty ->
0
| Node (l, _, r, h) ->
let hl = check_rec l in
let hr = check_rec r in
assert (h = max hl hr + 1);
assert (abs (hl - hr) <= 2);
h
in
ignore (check_rec s)

(* a set of n elements between min and max *)
let build min max n =
let rec build_rec acc = function
| 0 -> acc
| n -> build_rec (add (min + Random.int (max - min)) acc) (n - 1)
in
build_rec empty n

(* wrapper for union *)
let union_c s1 s2 =
let s = union s1 s2 in
check s;
s

let _ = 
  while true do 
ignore (union_c (build 0 10000 (Random.int 10000))
	  (build 0 1000 (Random.int 1000))) done;;
@vicuna
Copy link
Author

vicuna commented Jun 23, 2003

Comment author: administrator

Fixed 2003-06-23 by XL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant