Progetto di I.U.M. Modellazione Geometrica

Alternativa 1: Algoritmo di Graham

Funzione principale

Parametri di ingresso

Parametri di uscita

Variabili locali

Pseudocodice

Algorithm Graham(input: points, output: hull)
 begin
    center = FindInternalPoint(points);
    RadialSort(center,points,hull);
    GrahamScan(hull);
 end
Dove sono usate le funzioni ausiliarie:

Calcolo dell'ordinamento radiale

Suggerimento

Si puo' utilizzare la funzione qsort delle librerie standard C, che implementa l'algoritmo di QUICKSORT (nota: questo e' solo suggerimento, non deve essere inteso come vincolante).

A tale scopo e' necessario che i punti da ordinare siano contenuti in un array, quindi e' conveniente implementare l'insieme points come un array.

Inoltre bisogna fornire una funzione di confronto che, dati due punti, ritorna:

La dichiarazione C della funzione di confronto deve essere del tipo:
int ComparePoints(const void* arg1, const void* arg2)
dove arg1 e arg2 sono dichiarati come puntatori generici (tipo void*), ma saranno in pratica puntatori al tipo che implementa un punto.
Internamente alla funzione ComparePoints, verra' fatto un cast da puntatori generici a puntatori a punti.
Per esempio, supponendo che nel nostro programma un punto sia implementato come un array di due float, le prime linee della funzione ComparePoints conterranno due istruzioni del tipo:
  float * p1 = (float *)(arg1);
  float * p2 = (float *)(arg2);
Invece supponendo che sia implementato come indice intero in un array, convertiremo:
  int i1 = *(int *)(arg1);
  int i2 = *(int *)(arg2);

dopo di che all'interno della funzione si useranno le variabili p1 e p2.

Funzione di confronto fra due punti

Quale che sia l'algoritmo di ordinamento utilizzato (qsort delle librerie C, o una funzione scritta da voi), e' necessaria una primitiva che, dati due punti p1 e p2, stabilisca quale dei due precede l'altro: L'implementazione della funzione di confronto non calcola le coordinate radiali theta1 e theta2. Invece si basa sul controllo delle svolte.

Precisazione rispetto a quanto scritto nelle dispense

E' stato detto che il controllo se l'angolo theta1 e' minore (maggiore) di theta2 equivale al controllo se la terna di punti center,p1,p2 definisce una svolta a sinistra (a destra).
In realta' questo vale solo se la differenza in valore assoluto fra theta1 e theta2 e' minore di 180 gradi.

Nel caso generale e' opportuno prima distinguere il quadrante in cui giacciono i punti p1 e p2 rispetto a center. Dato un sistema di assi cartesiani con origine in center, numeriamo i quattro quadranti nel modo usuale:

     |
  2  | 1
-----+-----
  3  | 4
     |
Se il quadrante di p1 e' minore del quadrante di p2, allora p1 precede p2 (e viceversa).
Se p1 e p2 giacciono nello stesso quadrante, allora possiamo operare il controllo sull'orientamento della terna perche' certamente la differenza degli angoli theta1 e theta2 e' minore di 180 gradi.

Se i punti center, p1 e p2 sono allineati, non e' necessario calcolare le distanze center-p1 e center-p2, ma si possono operare controlli sulle coordinate x ed y separatamente, in base al quadrante nel quale ci troviamo.

Pseudocodice della funzione di confronto

Function ComparePoints(input: p1, p2)
 begin
   q1 = quadrante di p1 rispetto a center;
   q2 = quadrante di p2 rispetto a center;
   if (q1<q2) return "p1 precede p2";
   if (q2<q1) return "p2 precede p1";
   /* altrimenti sono nello stesso quadrante, controllo la svolta */
   if (la terna di punti center,p1,p2 definisce una svolta a sinistra) 
      return "p1 precede p2";
   if (la terna di punti center,p1,p2 definisce una svolta a destra)
      return "p2 precede p1";
   /* altrimenti sono allineati, controllo le distanze */
   diff_x = p2.x - p1.x;
   diff_y = p2.y - p1.y;
   switch (q1) /* a seconda del quadrante, nota che q1==q2 */
      begin
        case 1:
         if (diff_x>0) or (diff_y>0) return "p1 precede p2";
         else return "p2 precede p1";
        case 2:
         if (diff_x<0) or (diff_y>0) return "p1 precede p2";
         else return "p2 precede p1";
        case 3:
         if (diff_x<0) or (diff_y<0) return "p1 precede p2";
         else return "p2 precede p1";
        case 4:
         if (diff_x>0) or (diff_y<0) return "p1 precede p2";
         else return "p2 precede p1";
      end
 end

Scansione di Graham

Idea

Esaminare successivamente terne di vertici consecutivi sul contorno del poligono corrente. Se l'angolo da essi formato e' concavo, eliminare il punto centrale della terna.

Il poligono corrente e' quello contenuto nella lista hull. Tale lista deve essere implementata come una lista circolare linkata bidirezionalmente. Vedere anche piu' avanti.

Nelle dispense si e' detto che:

Precisazione rispetto a quanto scritto nelle dispense

Il criterio di uscita dal ciclo della scanzione di Graham non e' esattamente quello detto sopra.

L'intenzione e' quella di terminare quando si e' giunti al punto di partenza dopo aver esaminato tutta la lista muovendosi all'avanti.

Invece, con la condizione di terminazione scritta sopra, puo' succedere di tornare al punto di partenza arretrando in seguito alla cancellazione di vertici. In questo caso non bisogna terminare.

Condizione di terminazione rivista

Sia N il numero di vertici di hull al momento di iniziare la scansione.

Osservazione: Ho compiuto un ciclo completo sul poligono se ho eseguito il caso (1) esattamente N volte.

E' facile verificarlo considerando i due casi:

E' conveniente introdurre un contatore, inizializzato a 0 prima di cominciare la scansione, che viene incrementato di 1 ogni volta che si esegue il caso (1). La scansione termina quando il valore del contatore raggiunge N+1.

Pseudocodice della scansione di Graham

Procedure GrahamScan(input/output: hull)
 begin
  pos1 = prima posizione nella lista hull;
  count = 0;
  num = numero di elementi in hull;
  repeat
    pos2 = posizione succesisva a pos1 in hull;
    pos3 = posizione succesisva a pos2 in hull;
    p1 = il punto contenuto nella posizione pos1;
    p2 = il punto contenuto nella posizione pos2;
    p3 = il punto contenuto nellaposizione pos3;
    if ( la terna p1,p2,p3 definisce una svolta a sinistra )
       pos1 = posizione succesisva a pos1 in hull;
       count++;
    else
       cancella la posizione pos2 da hull;
       pos1 = posizione precedente a pos1 in hull;
  until ( count == num+1 );
 end

Strutture dati ed operazioni ausiliarie

La principale struttura dati ausiliaria e' la lista circolare linkata bidirezionalmente di punti che implementa hull.

Su tale lista sono necessarie almeno le operazioni:

La struttura dati per l'insieme points puo' essere un array (scelta conveniente anche in considerazione del fatto che su tale insieme va applicato un algoritmo di ordinamento, vedere anche sopra).

Se si implementa l'insieme points ad array, nella lista hull possono essere memorizzati gli indici dei corrispondenti punti anziche' i punti stessi.

Primitiva fondamentale e' il calcolo dell'orientamento di una terna di punti che avete implementato nell'esercitazione guidata.