Algorithmes efficaces pour problème de visibilité verticale

18

En réfléchissant à un problème, j'ai réalisé que je devais créer un algorithme efficace résolvant la tâche suivante:

Le problème: on nous donne une boîte carrée bidimensionnelle de côté dont les côtés sont parallèles aux axes. Nous pouvons l'examiner par le haut. Cependant, il y a aussi segments horizontaux. Chaque segment a un nombre entier de coordonnées ( ) et coordonnées ( ) et relie les points et (x_2, y) (regardez le image ci-dessous).nmy0ynx0x1<x2n(x1,y)(x2,y)

Nous aimerions savoir, pour chaque segment d'unité en haut de la boîte, à quelle profondeur pouvons-nous regarder verticalement à l'intérieur de la boîte si nous regardons à travers ce segment.

Formellement, pour x{0,,n1} , nous aimerions trouver maxi: [x,x+1][x1,i,x2,i]yi .

Exemple: étant donné n=9 et m=7 segments situés comme dans l'image ci-dessous, le résultat est (5,5,5,3,8,3,7,8,7) . Regardez comment la lumière profonde peut pénétrer dans la boîte.

Sept segments;  la partie ombrée indique la région accessible par la lumière

Heureusement pour nous, n et m sont assez petits et nous pouvons faire les calculs hors ligne.

L'algorithme le plus simple pour résoudre ce problème est la force brute: pour chaque segment, parcourez l'ensemble du tableau et mettez-le à jour si nécessaire. Cependant, cela nous donne un O (mn) pas très impressionnant O(mn).

Une grande amélioration consiste à utiliser une arborescence de segments capable de maximiser les valeurs sur le segment pendant la requête et de lire les valeurs finales. Je ne le décrirai pas plus loin, mais nous voyons que la complexité temporelle est .O((m+n)logn)

Cependant, j'ai trouvé un algorithme plus rapide:

Contour:

  1. Triez les segments par ordre décroissant de coordonnées (temps linéaire en utilisant une variation de tri de comptage). Notez maintenant que si un segment d'unité a été couvert par un segment auparavant, aucun segment suivant ne peut plus lier le faisceau lumineux traversant ce segment d'unité . Ensuite, nous ferons un balayage de ligne du haut vers le bas de la boîte.yxx

  2. Introduisons maintenant quelques définitions: le segment unit est un segment horizontal imaginaire sur le balayage dont les coordonnées sont des entiers et dont la longueur est 1. Chaque segment pendant le processus de balayage peut être non marqué (c'est-à-dire, un faisceau lumineux partant du haut de la boîte peut atteindre ce segment) ou marqué (cas opposé). Considérons un segment unité avec , toujours non marqué. Présentons également les ensembles . Chaque ensemble contiendra une séquence entière de segments consécutifs marqués d' unité (s'il y en a) avec un suivant non marquéx x x 1 = n x 2 = n + 1 S 0 = { 0 } , S 1 = { 1 } , , S n = { n } xxxXX1=nX2=n+1S0={0},S1={1},,Sn={n} X segment.

  3. Nous avons besoin d'une structure de données capable de fonctionner efficacement sur ces segments et ensembles. Nous utiliserons une structure de recherche d'union étendue par un champ contenant l' indice de segment maximal d' unité (indice du segment non marqué ).X

  4. Nous pouvons maintenant gérer les segments efficacement. Disons que nous considérons maintenant le ème segment dans l'ordre (appelez-le "requête"), qui commence en et se termine en . Nous devons trouver tous les segments non marqués de qui sont contenus à l'intérieur du ème segment (ce sont exactement les segments sur lesquels le faisceau lumineux finira son chemin). Nous allons faire ce qui suit: premièrement, nous trouvons le premier segment non marqué à l'intérieur de la requête ( Trouvez le représentant de l'ensemble dans lequel est contenu et obtenez l'index max de cet ensemble, qui est le segment non marqué par définition ). Alors cet indicex 1 x 2 x i x 1 x yjeX1X2 XjeX1Xest à l'intérieur de la requête, ajoutez-le au résultat (le résultat pour ce segment est ) et marquez cet index ( ensembles d' Union contenant et ). Répétez ensuite cette procédure jusqu'à ce que nous trouvions tous les segments non marqués , c'est-à-dire que la prochaine requête Find nous donne l'index .yx + 1XX+1XX2

Notez que chaque opération find-union se fera dans seulement deux cas: soit nous commençons à considérer un segment (ce qui peut arriver fois), soit nous venons de marquer un segment -unit (cela peut arriver fois). La complexité globale est donc ( est une fonction Ackermann inverse ). Si quelque chose n'est pas clair, je peux en dire plus. Je pourrai peut-être ajouter quelques photos si j'ai un peu de temps.x n O ( ( n + m ) α ( n ) ) αmXnO((n+m)α(n))α

Maintenant, j'ai atteint "le mur". Je ne peux pas proposer d'algorithme linéaire, bien qu'il semble qu'il devrait y en avoir un. J'ai donc deux questions:

  • Existe-t-il un algorithme de temps linéaire (c'est-à-dire ) qui résout le problème de visibilité du segment horizontal?O(n+m)
  • Sinon, quelle est la preuve que le problème de visibilité est ?ω(n+m)
mnbvmar
la source
À quelle vitesse triez-vous vos m segments?
babou
@babou, la question spécifie le tri de comptage qui, comme le dit la question, s'exécute en temps linéaire ("temps linéaire utilisant une variation du tri de comptage").
DW
Avez-vous essayé de balayer de gauche à droite? Il vous suffit de trier sur et en étapes et pour marcher vers la droite. Donc au total . x 2 O ( m ) O ( m ) O ( m )x1x2O(m)O(m)O(m)
invalid_id
@invalid_id Oui, j'ai essayé. Cependant, dans ce cas, la ligne de balayage doit réagir de manière appropriée lorsqu'elle rencontre le début du segment (en d'autres termes, ajouter le nombre égal à la coordonnée du segment au multiset), rencontre la fin du segment (supprimer une occurrence de -coordonnée) et produit le segment actif le plus élevé (valeur maximale de sortie dans le multiset). Je n'ai entendu parler d'aucune structure de données nous permettant de le faire en temps constant (amorti). yyy
mnbvmar
@mnbvmar peut-être une suggestion stupide, mais que diriez-vous d'un tableau de taille , vous balayez et arrêtez chaque cellule . Pour chaque cellule, vous connaissez max et vous pouvez le saisir dans la matrice, de plus vous pouvez garder une trace du maximum global avec une variable. O ( n ) ynO(n)y
invalid_id

Réponses:

1
  1. Tri tant et coordonnées des lignes dans deux réseaux séparés et . x 2 A B O ( m )X1X2UNEBO(m)
  2. Nous conservons également une taille de tableau de bits auxiliaires pour garder une trace des segments actifs.n
  3. Commencez à balayer de gauche à droite:
  4. pour(je=0,je<n,je++)
  5. {
  6. ..si avec la valeury c O ( 1 )X1=jeyc O(1)
  7. .. {
  8. .... trouver ( )max
  9. .... stocker ( )O ( 1 )maxO(1)
  10. ..}
  11. ..si avec la valeury c O ( 1 )x2=iyc O(1)
  12. .. {
  13. .... trouver ( )max
  14. .... stocker ( )O ( 1 )maxO(1)
  15. ..}
  16. }

find ( ) peut être implémenté en utilisant un tableau de bits avec bits. Maintenant, chaque fois que nous supprimons ou ajoutons un élément à nous pouvons mettre à jour cet entier en définissant un bit sur true ou false respectivement. Maintenant, vous avez deux options en fonction du langage de programmation utilisé et l'hypothèse est relativement petite, c'est-à-dire plus petite que qui est d'au moins 64 bits ou une quantité fixe de ces entiers:n L n l o n g l o n g i n tmaxnLnlonglongint

  • Obtenir le bit le moins significatif en temps constant est pris en charge par certains matériels et gcc.
  • En convertissant en un entier vous obtiendrez le maximum (pas directement mais vous pouvez le dériver).O ( 1 )LO(1)

Je sais que c'est tout à fait un hack car il suppose une valeur maximale pour et donc peut être considéré comme une constante alors ...nnn

ID invalide
la source
Comme je le vois, en supposant que vous avez un processeur x86 64 bits, vous ne pouvez gérer que . Et si est de l'ordre de millions? nn64n
mnbvmar
Ensuite, vous aurez besoin de plus d'entiers. Avec deux entiers, vous pouvez gérer jusqu'à 128, etc. Ainsi, l' étape de recherche maximale est cachée dans le nombre d'entiers requis, que vous pouvez encore optimiser si est petit. Vous avez mentionné dans votre question que est relativement petit, donc je suppose que ce n'est pas de l'ordre de millions. Par ailleurs, long long int est toujours au moins 64 bits par définition, même sur un processeur 32 bits. O ( m ) m nnO(m)mn
invalid_id
Bien sûr, c'est vrai, la norme C ++ définit long long intcomme un type entier d'au moins 64 bits. Cependant, ne sera-t-il pas que si est énorme et que nous désignons la taille du mot comme (généralement ), alors chacun prendra ? Nous finirions alors avec un total de . w w = 64 Onww=64findOO(nw)O(mnw)
mnbvmar
Oui, malheureusement pour les grandes valeurs de c'est le cas. Alors maintenant, je me demande quelle sera la taille de dans votre cas et s'il est borné. S'il est en effet de l'ordre de millions, ce hack-around ne fonctionnera plus, mais si pour les faibles valeurs il sera rapide et pratiquement . Ainsi, le meilleur choix d'algorithme dépend, comme d'habitude, de l'entrée. Par exemple, pour tri par insertion est normalement plus rapide que le tri par fusion, même avec un temps d'exécution de par rapport à . n c w n c O ( n + m ) n 100 O ( n 2 ) O ( n log n )nncwncO(n+m)n100O(n2)O(nlogn)
invalid_id
3
Je suis confus par votre choix de formatage. Vous savez que vous pouvez saisir du code ici, non?
Raphael
0

Je n'ai pas d'algorithme linéaire, mais celui-ci semble être O (m log m).

Triez les segments en fonction de la première coordonnée et de la hauteur. Cela signifie que (x1, l1) précède toujours (x2, l2) chaque fois que x1 <x2. De plus, (x1, l1) à la hauteur y1 précède (x1, l2) à la hauteur y2 chaque fois que y1> y2.

Pour chaque sous-ensemble avec la même première coordonnée, nous procédons comme suit. Soit le premier segment (x1, L). Pour tous les autres segments du sous-ensemble: Si le segment est plus long que le premier, changez-le de (x1, xt) à (L, xt) et ajoutez-le au sous-ensemble L dans l'ordre approprié. Sinon, laissez tomber. Enfin, si le sous-ensemble suivant a une première coordonnée inférieure à L, divisez ensuite (x1, L) en (x1, x2) et (x2, L). Ajoutez le (x2, L) au sous-ensemble suivant dans l'ordre correct. Nous pouvons le faire parce que le premier segment du sous-ensemble est plus élevé et couvre la plage de (x1, L). Ce nouveau segment peut être celui qui couvre (L, x2), mais nous ne le saurons pas avant de regarder le sous-ensemble qui a la première coordonnée L.

Après avoir parcouru tous les sous-ensembles, nous aurons un ensemble de segments qui ne se chevauchent pas. Pour déterminer quelle est la valeur Y pour un X donné, nous n'avons qu'à parcourir les segments restants.

Alors, quelle est la complexité ici: Le tri est O (m log m). La boucle à travers les sous-ensembles est O (m). Une recherche est également O (m).

Il semble donc que cet algorithme soit indépendant de n.

Personne en particulier
la source