RedBlackTree.java 9,55 ko
Newer Older
Mathys MACIA's avatar
Mathys MACIA a validé
package collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Stack;

/**
 * Arbre Rouge-Noir générique. Respecte les propriétés des arbres RB : - chaque
 * nœud est rouge ou noir - la racine est toujours noire - les feuilles null
 * sont noires - un nœud rouge ne peut pas avoir un parent rouge (pas de
 * rouge-rouge) - tous les chemins depuis un nœud jusqu’aux feuilles null
 * contiennent le même nombre de nœuds noirs
 */
public class RedBlackTree<E extends Comparable<E>> implements Collection<E> {
Mathys MACIA's avatar
Mathys MACIA a validé
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    /** Classe interne représentant un nœud de l'arbre */
    private class Node {
	E key;
	Node left, right, parent;
	boolean color = RED; // par défaut, un nouveau nœud est rouge

	Node(E key, Node parent) {
	    this.key = key;
	    this.parent = parent;
	}
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    private Node root;
    private int size = 0;

    /** Rotation gauche standard */
    private void rotateLeft(Node x) {
	Node y = x.right;
	x.right = y.left;

	if (y.left != null)
	    y.left.parent = x;

	y.parent = x.parent;

	if (x.parent == null)
	    root = y;
	else if (x == x.parent.left)
	    x.parent.left = y;
	else
	    x.parent.right = y;

	y.left = x;
	x.parent = y;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    /** Rotation droite standard */
    private void rotateRight(Node x) {
	Node y = x.left;
	x.left = y.right;

	if (y.right != null)
	    y.right.parent = x;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	y.parent = x.parent;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	if (x.parent == null)
	    root = y;
	else if (x == x.parent.right)
	    x.parent.right = y;
	else
	    x.parent.left = y;

	y.right = x;
	x.parent = y;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    /**
     * Correction après insertion. Assure que les propriétés RB sont conservées.
     */
    private void fixAfterInsert(Node z) {
	while (z.parent != null && z.parent.color == RED) {
	    if (z.parent == z.parent.parent.left) {
		Node y = z.parent.parent.right; // oncle

		if (y != null && y.color == RED) {
		    // Cas 1 : parent rouge + oncle rouge -> recolorations
		    z.parent.color = BLACK;
		    y.color = BLACK;
		    z.parent.parent.color = RED;
		    z = z.parent.parent;
		} else {
		    if (z == z.parent.right) {
			// Cas 2 : triangle -> rotation gauche
			z = z.parent;
			rotateLeft(z);
		    }

		    // Cas 3 : ligne -> rotation droite
		    z.parent.color = BLACK;
		    z.parent.parent.color = RED;
		    rotateRight(z.parent.parent);
		}
	    } else {
		// Symétrique : parent est enfant droit
		Node y = z.parent.parent.left;

		if (y != null && y.color == RED) {
		    z.parent.color = BLACK;
		    y.color = BLACK;
		    z.parent.parent.color = RED;
		    z = z.parent.parent;
		} else {
		    if (z == z.parent.left) {
			z = z.parent;
			rotateRight(z);
		    }
		    z.parent.color = BLACK;
		    z.parent.parent.color = RED;
		    rotateLeft(z.parent.parent);
		}
	    }
	}

	root.color = BLACK;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    /** Remplace un nœud par un autre dans l’arbre */
    private void transplant(Node u, Node v) {
	if (u.parent == null)
	    root = v;
	else if (u == u.parent.left)
	    u.parent.left = v;
	else
	    u.parent.right = v;

	if (v != null)
	    v.parent = u.parent;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    /** Renvoie le minimum du sous-arbre */
    private Node minimum(Node x) {
	while (x.left != null)
	    x = x.left;
	return x;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    /**
     * Correction après suppression. Restaure les propriétés Rouge-Noir.
     */
    private void fixAfterDelete(Node x, Node parent) {
	while ((x != root) && (x == null || x.color == BLACK)) {
	    if (parent != null && x == parent.left) {
		Node w = parent.right;

		if (w != null && w.color == RED) {
		    w.color = BLACK;
		    parent.color = RED;
		    rotateLeft(parent);
		    w = parent.right;
		}

		if ((w == null)
			|| ((w.left == null || w.left.color == BLACK) && (w.right == null || w.right.color == BLACK))) {
		    if (w != null)
			w.color = RED;

		    x = parent;
		    parent = x.parent;
		} else {
		    if (w.right == null || w.right.color == BLACK) {
			if (w.left != null)
			    w.left.color = BLACK;

			w.color = RED;
			rotateRight(w);
			w = parent.right;
		    }

		    if (w != null)
			w.color = parent.color;

		    parent.color = BLACK;

		    if (w != null && w.right != null)
			w.right.color = BLACK;

		    rotateLeft(parent);
		    x = root;
		    break;
		}
	    } else {
		if (parent == null)
		    break;

		Node w = parent.left;

		if (w != null && w.color == RED) {
		    w.color = BLACK;
		    parent.color = RED;
		    rotateRight(parent);
		    w = parent.left;
		}

		if ((w == null)
			|| ((w.right == null || w.right.color == BLACK) && (w.left == null || w.left.color == BLACK))) {
		    if (w != null)
			w.color = RED;

		    x = parent;
		    parent = x.parent;
		} else {
		    if (w.left == null || w.left.color == BLACK) {
			if (w.right != null)
			    w.right.color = BLACK;

			w.color = RED;
			rotateLeft(w);
			w = parent.left;
		    }

		    if (w != null)
			w.color = parent.color;

		    parent.color = BLACK;

		    if (w != null && w.left != null)
			w.left.color = BLACK;

		    rotateRight(parent);
		    x = root;
		    break;
		}
	    }
	}

	if (x != null)
	    x.color = BLACK;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean add(E e) {
	Objects.requireNonNull(e);

	// Arbre vide : nouvelle racine noire
	if (root == null) {
	    root = new Node(e, null);
	    root.color = BLACK;
	    size = 1;
	    return true;
	}

	Node cur = root, parent = null;
	int cmp = 0;

	// Recherche de la position d'insertion
	while (cur != null) {
	    parent = cur;
	    cmp = e.compareTo(cur.key);

	    if (cmp < 0)
		cur = cur.left;
	    else if (cmp > 0)
		cur = cur.right;
	    else
		return false; // pas de doublons
	}

	Node node = new Node(e, parent);

	if (cmp < 0)
	    parent.left = node;
	else
	    parent.right = node;

	// Correction RB après insertion
	fixAfterInsert(node);
	size++;

	return true;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    /** Recherche un nœud contenant la clé */
    private Node findNode(E e) {
	Node cur = root;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	while (cur != null) {
	    int cmp = e.compareTo(cur.key);
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	    if (cmp == 0)
		return cur;
	    if (cmp < 0)
		cur = cur.left;
	    else
		cur = cur.right;
	}
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	return null;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean contains(Object o) {
	if (o == null)
	    return false;
	try {
	    @SuppressWarnings("unchecked")
	    E e = (E) o;
	    return findNode(e) != null;
	} catch (ClassCastException ex) {
	    return false;
	}
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean remove(Object o) {
	if (o == null)
	    return false;

	E e;

	try {
	    e = (E) o;
	} catch (ClassCastException ex) {
	    return false;
	}

	Node z = findNode(e);

	if (z == null)
	    return false;

	Node y = z;
	boolean yOriginalColor = y.color;
	Node x;
	Node xParent;

	// Cas 1 : un seul enfant
	if (z.left == null) {
	    x = z.right;
	    xParent = z.parent;
	    transplant(z, z.right);
	} else if (z.right == null) {
	    x = z.left;
	    xParent = z.parent;
	    transplant(z, z.left);
	}

	// Cas 2 : deux enfants → successeur
	else {
	    y = minimum(z.right);
	    yOriginalColor = y.color;
	    x = y.right;

	    if (y.parent == z) {
		if (x != null)
		    x.parent = y;
		xParent = y;
	    } else {
		transplant(y, y.right);
		y.right = z.right;

		if (y.right != null)
		    y.right.parent = y;

		xParent = y.parent;
	    }

	    transplant(z, y);
	    y.left = z.left;

	    if (y.left != null)
		y.left.parent = y;

	    y.color = z.color;
	}

	if (yOriginalColor == BLACK)
	    fixAfterDelete(x, xParent);

	size--;
	return true;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public Iterator<E> iterator() {
	return new Iterator<E>() {
	    private final Stack<Node> stack = initStack(root);

	    private Stack<Node> initStack(Node r) {
		Stack<Node> s = new Stack<>();
		Node cur = r;

		while (cur != null) {
		    s.push(cur);
		    cur = cur.left;
		}

		return s;
	    }

	    @Override
	    public boolean hasNext() {
		return !stack.isEmpty();
	    }

	    @Override
	    public E next() {
		if (!hasNext())
		    throw new NoSuchElementException();

		Node n = stack.pop();
		E res = n.key;
		Node cur = n.right;

		// Descente maximale à gauche
		while (cur != null) {
		    stack.push(cur);
		    cur = cur.left;
		}

		return res;
	    }

	    @Override
	    public void remove() {
		throw new UnsupportedOperationException();
	    }
	};
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public int size() {
	return size;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean isEmpty() {
	return size == 0;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public void clear() {
	root = null;
	size = 0;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public Object[] toArray() {
	Object[] arr = new Object[size];
	int i = 0;

	for (E e : this)
	    arr[i++] = e;

	return arr;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public <T> T[] toArray(T[] a) {
	if (a.length < size)
	    a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	int i = 0;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	for (E e : this)
	    a[i++] = (T) e;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	if (a.length > size)
	    a[size] = null;
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
	return a;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
	for (Object o : c)
	    if (!contains(o))
		return false;
	return true;
    }
Mathys MACIA's avatar
Mathys MACIA a validé

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean addAll(Collection<? extends E> c) {
	boolean changed = false;
	for (E e : c)
	    changed |= add(e);
	return changed;
Mathys MACIA's avatar
Mathys MACIA a validé
    }

Mathys MACIA's avatar
Mathys MACIA a validé
    @Override
    public boolean removeAll(Collection<?> c) {
	boolean changed = false;
	for (Object o : c)
	    changed |= remove(o);
	return changed;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
	List<E> toRemove = new ArrayList<>();

	for (E e : this)
	    if (!c.contains(e))
		toRemove.add(e);

	for (E e : toRemove)
	    remove(e);

	return !toRemove.isEmpty();
    }
Mathys MACIA's avatar
Mathys MACIA a validé
}