J'y ai réfléchi et je crois que j'ai enfin compris ce que @algorithmist
signifié par ligne de balayage . Cependant, la présence même de sorting
les opérations signif signif signif d que c pour moi :
-
O(n log n)
en moyennement
-
O(n**2)
dans le pire cas
Ligne de balayage
Tout d'abord, nous avons besoin d'un certain tri, parce que notre sweep line
consistera à parcourir un ensemble ordonné.
Ce jeu ordonné comprendra les top
y bottom
de chacune des red
pour autant qu'ils se situent entre les top
y bottom
de blue
. Cela divise notre espace en (au maximum) n*2
des bandes horizontales.
Maintenant, nous devons nous assurer que dans chaque strip
, l'ensemble de blue
est couverte, et cette opération ne peut pas avoir plus de O(log n)
La complexité, cela pourrait être fait si nous avions (pour chaque bande) une liste des intervalles couverts. Voici l'événement @algorithmist
parle de
Pour gérer cet événement, nous utiliserons un arbre binaire décrit ci-dessous qui gère l'ajout ou la suppression d'un rectangle en exactement O(log n)
et donne l'intervalle le plus à droite couvert par l'arbre, que nous utilisons pour savoir si la bande de blue
est couvert ou non.
Arbre binaire
Tout d'abord, je vais indexer les red
rectangles. Nous les trions à l'aide de cette fonction :
def __lt__(lhs, rhs):
return (lhs.left < rhs.left)
or (lhs.left == rhs.left and lhs.right < rhs.right)
Je vais donc créer un arbre binaire dédié.
- Il aura
N
feuilles, chacune représentant un red
rectangle et indiquant sa présence ou son absence. Ils sont classés selon l'indice.
- Chaque nœud intermédiaire aura pour valeur l'intervalle le plus à droite couvert par ses enfants.
Gestion du bug "le bloc de code ne peut pas suivre la liste" :
class Node:
def __init__(self):
self.interval = []
self.left = None
self.right = None
Maintenant, nous avons deux possibilités, disons que les enfants couvrent [a,b]
y [c,d]
:
- si
c <= b
alors le nœud tient [a,d]
- sinon, il tient
[c,d]
Pourquoi cela fonctionne-t-il ? Prenons un exemple avec 4 feuilles :
_ [1,9] _
/ \
[1,7] [6,9] <-- Special node merge
/ \ / \
/ \ / \
[1,3] [2,7] [3,5] [6,9]
Le nœud spécial ignore [3,5]
parce que ce n'est pas l'intervalle le plus à droite. Le raisonnement est que les rectangles sont ordonnés :
- Aucun rectangle à droite de
[6,9]
couvrira le manque [5,6]
intervalle puisqu'ils commencent après 6
- Les rectangles à gauche de
[3,5]
commencer avant 3
donc s'ils couvrent les manquants [5,6]
ils couvriront [3,5]
de toute façon
La Racine peut ne pas indiquer l'ensemble exact des intervalles couverts : seulement l'intervalle le plus à droite couvert. Cependant, il est parfaitement suffisant pour nous permettre de savoir si blue
est complètement couvert ou non !
Il y a 2 opérations disponibles sur cet arbre :
- Marquer un rectangle comme présent
- Marquer un rectangle comme absent
Chacune est similaire :
- si le rectangle était déjà dans cet état, ne rien faire
- sinon, basculer son état, puis mettre à jour son intervalle parent (de manière récursive, jusqu'à la Racine)
La partie récursive prend O(log n)
. C'est une propriété classique des arbres binaires équilibrés. Et une fois que c'est fait, nous avons l'intervalle le plus à droite couvert par la Racine, ce qui est suffisant pour dire si oui ou non la blue
Le segment est entièrement couvert ou non.
Complexité
La complexité de l'algorithme est simple :
- Nous avons
O(n)
événements
- Chaque événement est traité dans
O(log n)
Ce qui donne O(n log n)
pour la partie centrale.
Cependant, nous ne devons pas oublier que nous avons également 2 sort
opérations :
- un pour classer les événements (pour la ligne de balayage)
- l'autre pour classer les rectangles (pour l'arbre binaire)
Chacun doit prendre O(n log n)
en Moyenne mais peut dégénérer en O(n**2)
dans le pire des cas, en fonction de l'algorithme de tri utilisé.