Accumulare complessità del tempo e lingua C

Accumulare complessità del tempo e lingua C
“Lo scopo di questo articolo è produrre la complessità temporale per la costruzione di un mucchio. La complessità del tempo è il tempo di esecuzione relativo di qualche codice. Un heap è un albero in cui tutti i bambini per ciascun nodo genitore sono maggiori o uguali di valore al nodo genitore. Questo è un mucchio minimo (i.e., Min-heap). C'è anche un heap massimo (max-heap), in cui tutti i figli di ciascun nodo genitore sono inferiori o uguali al nodo genitore. Per il resto di questo articolo, viene considerato solo Min-Heap."

Il mucchio minimo di cui sopra descrive un heap in cui il numero di figli per nodo genitore può essere più di due. Un mucchio binario è quello in cui il numero più alto di figli per nodo genitore è due. Un mucchio binario completo è quello in cui ogni nodo ha due figli, con l'eccezione che, al livello più basso, potrebbe esserci un solo nodo foglia senza fratello; con il resto dei nodi foglia più bassi accoppiati e a partire dall'estremo sinistro dell'ultima riga, terminando con questo singolo nodo foglia, che deve essere un nodo sinistro.

Heapifying significa costruire (creare) un mucchio. Poiché un albero in cui ogni nodo può avere un numero qualsiasi di bambini può essere trasformato in un albero binario completo, il vero obiettivo di questo articolo è quello di produrre la complessità temporale per accumulare un albero binario completo.

Un diagramma di esempio di un albero binario completo è:

Qualsiasi nodo fogliare al livello più basso che non ha un fratello deve essere un nodo sinistro. Tutti i nodi dell'ultima riga, incluso un possibile nodo a sinistra singolo, sono "arrossati" all'estremità sinistra dell'ultima riga.

Con la definizione di un mucchio, un fratello sinistro può essere più piccolo, maggiore o uguale al fratello destro. L'ordinamento di entrambi i fratelli non è specificato.

Albero binario completo

L'albero sopra è un albero binario completo, ma non è un albero binario completo. È anche un mucchio minimo. Se fosse un albero binario completo, allora tutti i nodi dell'ultimo ma a livello avrebbero avuto due bambini ciascuno. L'albero sopra è ridisegnato sotto come un albero binario completo:

Un albero può essere rappresentato da un array. L'albero viene letto dall'alto verso il basso, da sinistra a destra, riga per riga; quindi inserito nell'array. La tabella seguente mostra l'array di contenuto e indici per questo albero:

4 6 12 8 7 16 15 23 10 20 18 25 nullo nullo nullo
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

I valori della cella sono nella prima riga della tabella. Gli indici a base zero sono nella seconda riga della tabella.

Relazione tra un indice genitore e i suoi indici di figli

Nell'albero o nell'array corrispondente, la radice è all'indice 0 per l'indicizzazione a base zero. Lascia che io sia la variabile per trattenere ogni indice. I bambini della radice sono agli indici 1 e 2, che sono 0 + 1 e 0 + 2. I bambini del nodo 1 sono agli indici 3 e 4, che sono 1 + 2 e 1 + 3. I bambini del nodo 2 sono all'indice 5 e 6, che sono 2 + 3 e 2 + 4. I bambini del nodo 3 sono all'indice 7 e 8, che sono 3 + 4 e 3 + 5. I bambini del nodo 4 sono all'indice 9 e 10, che sono 4 + 5 e 4 + 6; e così via.

Lascia che io sia l'indice dei genitori per ora. Quindi i figli di ogni genitore sono all'indice i + i + 1 e in i + i + 2, che sono:

2i +1 e 2i +2

Il contrario dovrebbe essere noto. Cioè, dato un indice, io per un figlio sinistro o un figlio a destra, qual è l'indice principale? Per il figlio sinistro all'indice 1 e il figlio destro all'indice 2, il genitore è all'indice 0. Per il figlio sinistro all'indice 3 e il figlio destro all'indice 4, il genitore è all'indice 1. Per il figlio sinistro all'indice 5 e il figlio destro all'indice 6, il genitore è all'indice 2. Per il figlio sinistro all'indice 7 e il figlio destro all'indice 8, il genitore è all'indice 3. Per il figlio sinistro all'indice 9 e il figlio destro all'indice 10, il genitore è all'indice 4.

Io questa volta, è un indice figlio (non un indice genitore). Quindi il genitore di ogni figlio sinistro è all'indice I/2 della divisione intera, e il genitore del figlio destro, che è lo stesso del genitore del figlio sinistro (fratello), è a risultato intero di (I-1) /2, i.e.:

I/2 e (i-1)/2

dove la divisione è divisione intera.

È anche bene sapere se un nodo è un bambino sinistro o un bambino a destra: se la divisione normale di 2 ha un resto, allora questo è un nodo sinistro mediante indicizzazione a base zero. Se la divisione normale di 2 non ha il resto, allora questo è un nodo giusto mediante indicizzazione a base zero.

Il codice in C, per sapere se il nodo figlio è un nodo sinistro o un nodo destro, è:

if (i%2 == 0)
// Il nodo è un nodo giusto
altro
// Il nodo è il nodo sinistro

Dopo aver saputo che un nodo è un nodo sinistro, l'indice principale può essere ottenuto come risultato intero di I/2. Dopo aver saputo che un nodo è un nodo giusto, l'indice principale può essere ottenuto come risultato intero di (I-1)/2.

Altezza di un mucchio binario completo e alcuni indici

Numero totale di nodi
Si noti dall'ultimo diagramma completo sopra che quando il livello di un mucchio aumenta di 1, il suo numero totale di nodi raddoppia approssimativamente. Proprio, il livello successivo viene fornito con il numero di nodi che rende il nuovo totale, la somma di tutti i nodi precedenti, più 1, tempi 2, quindi meno 1. Quando l'altezza è 1, c'è 1 nodo = (0 + 1) x 2 - 1 = 2 - 1 = 21 - 1. Quando l'altezza è 2, ci sono 3 nodi = (1 + 1) x 2 - 1 = 4 - 1 = 22 - 1. Quando l'altezza è 3, ci sono 7 nodi = (3 + 1) x 2 - 1 = 8 - 1 = 23 - 1. Quando l'altezza è 4, ci sono 15 nodi = (7 + 1) x 2 - 1 = 16 - 1 = 24 - 1. Quando l'altezza è 5, ci sono 31 nodi = (15 + 1) x 2 - 1 = 32 - 1 = 25 - 1. Quando l'altezza è 6, ci sono 63 nodi = (31 + 1) x 2 - 1 = 64 - 1 = 26 - 1. Quando l'altezza è 7, ci sono 127 nodi = (63 + 1) x 2 - 1 = 128 - 1 = 27 - 1; e così via.

In generale, quando l'altezza è h,

NO. di nodi = 2H - 1

Ultimo indice nodo
Per un albero binario e per indicizzazione basata su zero, l'ultimo indice è:

Ultimo indice = n - 1

dove n è il numero totale di nodi, o semplicemente il numero di nodi.

Numero di nodi senza ultima riga
Per un albero binario completo, due volte il numero di nodi per l'ultima riga fornisce il numero totale di nodi meno 1. Visto in un altro modo, il numero di nodi per l'ultima riga è uguale al numero di tutti i nodi precedenti, tempi due, più 1. Quindi il numero di nodi senza l'ultima riga è:

NO. di nodi senza ultimo = risultato intero di n/2

Questo perché, per l'indicizzazione a base zero, il numero totale di nodi per un albero binario completo è sempre un numero dispari. Ad esempio: se il numero di nodi (totale) è 15, allora 15/2 = 7½. Il risultato intero, 7, viene preso e la metà viene gettata via. E 7 è il numero di nodi senza l'ultima riga - vedi sopra.

Indice per il primo nodo dell'ultima riga
L'indice per il primo nodo dell'ultima riga dovrebbe essere noto. Per l'indicizzazione basata su zero, in cui il primo nodo (elemento) è all'indice 0, l'indice per il primo nodo dell'ultima riga è il numero di nodi per tutti i nodi senza l'ultima riga. È:

Risultato intero di N/2

Si noti che nell'array corrispondente, i nodi dell'albero sono chiamati elementi.

Setacciare e setacciare

Prendi in considerazione il seguente sub-albero di tre nodi:

Dalla proprietà minima heap, il nodo genitore dovrebbe essere inferiore o uguale al più piccolo dei nodi per bambini. Quindi il nodo C deve essere scambiato con il minimo dei nodi per bambini; Non importa se il minimo è il fratello sinistro o destro. Ciò significa che C deve essere scambiato con A per avere:

Mentre "a" si alza per prendere il posto di C, questo è setacciare. Mentre C si muove verso il basso per prendere il posto di A, questo sta setacciando.

Illustrazione di accantonamento

L'heap, come mostrato sopra, è un ordine parziale, dal valore più piccolo al valore maggiore. Non è completo ordinazione (non ordinamento). Come espresso sopra, il mucchio può essere in forma di albero o in forma di array. Come espresso sopra, il mucchio ha già avuto luogo. In pratica, il programmatore non troverà necessariamente un albero già accumulato. Troverebbe un elenco che è completamente in disordine (completamente non mobile). Questo elenco disordinato può esistere sotto forma di un albero o sotto forma di un array. Quello che segue è un albero disordinato (non mobile) e il suo array corrispondente:

L'array corrispondente è:

10 20 25 6 4 12 15 23 8 7 18 16 nullo nullo nullo
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

L'albero viene letto riga per fila, da sinistra a destra e dall'alto verso il basso, mentre si trova nelle celle dell'array. Lascia che il nome dell'array sia arr e lascia che la variabile che rappresenta l'indice basato su zero. In questo articolo, la radice è all'indice 0.

Questo albero subirà un ordine parziale per diventare un mucchio minimo. Quando l'ordinamento parziale è completo, sarà il mucchio minimo indicato nella sezione Introduzione di questo articolo. In questa sezione, l'ordinamento parziale viene eseguito manualmente.

Per semplificare il mucchio (processo di ordinazione parziale), l'albero binario non ordinato completo deve essere realizzato un albero binario completo prima di essere elaborato. Per renderlo un albero binario completo, aggiungi elementi i cui valori sono maggiori del valore più alto che si trova già nel mucchio non ordinato. In questo articolo, questo verrà fatto con l'array e non con la forma grafica dell'albero.

L'elemento più alto (nodo) è 25. Lascia che i tre numeri aggiunti per fare un albero completo siano: 26, 27 e 28. L'array corrispondente per l'albero completo diventa:

10 20 25 6 4 12 15 23 8 7 18 16 26 27 28
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

Quando l'elenco non ordinato viene parzialmente ordinato dalla definizione di heap, gli ultimi tre elementi rimarranno nelle loro ultime posizioni; e può essere facilmente abbandonato.

Quando questo elenco non ordinato è parzialmente ordinato dalla definizione di heap, sarebbe l'elenco parzialmente ordinato riportato (Introduzione). Un elenco parzialmente ordinato ha qualche ordinamento, ma non è completo ordinamento (non è completo ordinamento).

Riorganizzazione manuale (edificio heap)

L'array non ordinato può essere posizionato così:

10 20 25 06 04 12 15 23 08 07 18 16 26 27 28

Ci sono 15 elementi. Il risultato intero di 15/2 è 7. L'elenco dovrebbe essere diviso in due metà, con la prima metà 7 e la seconda metà, 8, come segue:

10 20 25 06 04 12 15 | 23 08 07 18 16 27 27 28

Questa divisione può essere considerata come un'operazione principale. L'edificio heap continua dall'estremità destra con le coppie di elementi, 27 e 28, identificati come bambini di 15 anni. 15 è inferiore a 27 o 28. Quindi questo sub-albero di tre nodi soddisfa la proprietà minima heap e per ora i nodi non vengono toccati. Questo controllo può essere considerato un'altra operazione principale. Quindi finora ci sono due operazioni principali (una divisione array e un assegno).

16 e 26 sono bambini di 12 anni. 12 è inferiore a 16 o 26. Quindi questo sub-albero di tre nodi soddisfa la proprietà minima heap e i nodi non vengono toccati (per ora). Questo controllo può essere considerato un'altra operazione principale. Quindi finora ci sono tre operazioni principali (una divisione e due controlli).

07 e 18 sono i figli di 04. 04 è inferiore a 07 o 18. Quindi questo sub-albero di tre nodi soddisfa la proprietà minima heap e i nodi non vengono toccati (per ora). Questo controllo può essere considerato un'altra operazione principale. Quindi ci sono quattro operazioni principali finora (una divisione e tre assegni).

23 e 08 sono i figli di 06. 06 è inferiore a 23 o 08. Quindi questo sub-albero di tre nodi soddisfa la proprietà minima heap e i nodi non vengono toccati (per ora). Questo controllo può essere considerato un'altra operazione principale. Quindi ci sono cinque operazioni principali finora (una divisione e quattro controlli).

Tutti gli 8 elementi dell'ultima fila dell'albero e i loro genitori sono stati controllati. Per andare al livello precedente, la parte sinistra dell'albero deve essere divisa per 2 e il risultato intero viene preso. Il risultato intero di 7/2 è 3. Il nuovo posizionamento dell'elenco è:

10 20 25 | 06 04 12 15 | 23 08 07 18 16 27 27 28

Questa divisione può essere considerata come un'operazione principale. Quindi ci sono sei operazioni principali finora (due divisioni e quattro assegni).

12 e 15 sono i bambini di 25 anni. 25 è maggiore di 12 o 15. Questo sub-albero di tre nodi non soddisfa la proprietà del heap minimo, quindi i nodi devono essere toccati. Tuttavia, questo controllo può ancora essere considerato un'altra operazione principale. Quindi ci sono sette operazioni principali finora (due divisioni e cinque assegni).

Setacciare verso il basso deve avvenire e possibilmente fino all'ultima riga. Ogni setaccio (scambiarsi) è l'operazione principale.

25 viene scambiato con il minimo dei suoi figli, 12 per dare la configurazione:

10 20 12 | 06 04 25 15 | 23 08 07 18 16 27 27 28

25 è ora al terzo livello e non più al secondo livello. 25 è ora il genitore di 16 e 26. A questo punto, 25 sembra essere maggiore di 16 ma meno di 26. Quindi 25 e 16 sono scambiati. Questo scambio è un'altra operazione principale, e quindi finora ci sono nove operazioni principali (due divisioni, cinque controlli e due swap). La nuova configurazione è:

10 20 12 | 06 04 16 15 | 23 08 07 18 25 26 27 28

Al secondo livello dell'elenco indicato, c'erano 20 e 25. 25 è stato setacciato fino all'ultima riga. 20 è ancora da controllare.

Attualmente, 06 e 04 sono bambini di 20 anni. 20 è maggiore di 06 o 04. Questo sub-albero di tre nodi non soddisfa la proprietà del heap minimo, quindi i nodi devono essere toccati. Tuttavia, questo controllo può ancora essere considerato un'altra operazione principale. Quindi ci sono dieci operazioni principali finora (due divisioni, sei assegni e due swap). Setacciare verso il basso deve avvenire e possibilmente fino all'ultima riga. Ogni setaccio (scambiarsi) è l'operazione principale.

20 viene scambiato con il minimo dei suoi figli, 04 per dare la configurazione:

10 04 12 | 06 20 16 15 | 23 08 07 18 25 26 27 28

20 è ora al terzo livello e non più al secondo livello. 20 è ora il genitore di 07 e 18. A questo punto, 20 sembra essere maggiore di 07 o 18. Quindi 20 e 07 vengono scambiati. Questo scambio è un'altra operazione principale, e quindi finora ci sono dodici operazioni principali (due divisioni, sei controlli e quattro swap). La nuova configurazione è:

10 04 12 | 06 07 16 15 | 23 08 20 18 25 26 27 28

I setamenti verso il basso del percorso precedente sono finiti. La parte di sinistra che non è stata completamente controllata deve essere divisa in due (andando al livello precedente) per avere:

10 | 04 12 | 06 07 16 15 | 23 08 20 18 25 26 27 28

Il risultato intero di 3/2 è 1.

Attualmente, 04 e 12 sono figli di 10 anni. 10 è maggiore di 04 ma meno di 12. Questo sub-albero di tre nodi non soddisfa la proprietà del heap minimo, quindi i nodi devono essere toccati. Tuttavia, questo controllo dovrebbe essere ancora considerato come un'altra operazione principale. Quindi finora ci sono quattordici operazioni principali (tre divisioni, sette assegni e quattro swap). Setacciare verso il basso deve avvenire e possibilmente fino all'ultima riga. Ogni setaccio (scambiarsi) è l'operazione principale.

10 viene scambiato con il minimo dei suoi figli, 04 per dare la configurazione:

04 | 10 12 | 06 07 16 15 | 23 08 20 18 25 26 27 28

10 è ora al secondo livello e non più al primo livello. 10 è ora il genitore 06 e 07. A questo punto, 10 sembra essere maggiore di 06 o 07. Quindi 10 e 06 sono scambiati. Questo scambio è un'altra operazione principale, e quindi finora ci sono sedici operazioni principali (tre divisioni, sette controlli e sei swap). La nuova configurazione è:

04 | 06 12 | 10 07 16 15 | 23 08 20 18 25 26 27 28

10 è ora al terzo livello e non più al secondo livello. 10 è ora il genitore 23 e 08. A questo punto, 10 è inferiore a 23 ma maggiore di 08. Quindi 10 e 08 sono scambiati. Questo scambio è un'altra operazione principale, e quindi finora ci sono diciassette operazioni principali (tre divisioni, sette controlli e sette swap). La nuova configurazione è:

04 | 06 12 | 08 07 16 15 | 23 10 20 18 25 25 26 27 28

Bene, il controllo, la divisione e lo scambio sono iniziati all'ultimo indice e ha raggiunto il primo indice, con tutte le conseguenze dei setamenti verso il basso. Ciò significa che l'heap l'edificio è completo e, in questo caso, con diciassette operazioni principali (tre divisioni, sette assegni e sette swap). C'erano 15 elementi, sebbene gli ultimi tre fossero elementi fittizi necessari per semplificare l'edificio dell'heap.

Algoritmo

Esistono diversi algoritmi per la costruzione di heap. L'illustrazione sopra indicata sarà la più efficiente se un valore genitore viene scambiato con uno dei bambini che sono meno e non sempre il minimo dei bambini. I passaggi per l'algoritmo sono:

  • Dividi l'intero numero di elementi per due.
  • Continua dalla metà destra, controllando una coppia di fratelli con il genitore e se necessario.
  • Quando tutti i nodi dell'ultimo livello sono stati controllati, con possibile scambio, procedere al livello precedente e ripetere i due passaggi precedenti. Lo scambio è setacciato e questo potrebbe dover raggiungere il livello più basso.
  • Quando la radice è stata controllata e possibilmente scambiata, fermati.

Complessità temporale

La complessità del tempo è il runtime relativo di qualche codice. In questo caso, è il tempo di esecuzione relativo del processo di costruzione di heap. La complessità del tempo è in realtà il numero di operazioni principali nel codice (programma).

Ufficialmente, si dice che la complessità temporale dell'algoritmo per questo articolo sia N operazioni, in cui n è il numero di elementi nell'array non ordinato più gli elementi fittizi. In questo caso, n è 15. Quindi la complessità temporale per questo algoritmo è 15.

Perché dovrebbero essere 15 invece di 17? Cioè, perché dovrebbe essere n? - Bene, poiché la divisione non è partizione, la durata per ogni azione di divisione è piccola e può essere trascurata. Con questo e per l'illustrazione di cui sopra, il numero di operazioni principali diventerà 14 (sette controlli e sette swap), con le 3 divisioni ignorate.

Inoltre, se un valore genitore viene scambiato con uno qualsiasi dei bambini che sono meno e non sempre il minimo dei bambini, il tempo di controllo complessivo sarà ridotto. Questo renderà il tempo di controllo piccolo e così ignorato. Con questo e per l'illustrazione di cui sopra, il numero di operazioni principali diventerà 7 (sette swap), con le 3 divisioni ignorate e anche i sette controlli ignorati.

NOTA: per un buon programma di costruzione di heap, solo le operazioni di scambio (disft downs in questo caso) sono considerate nella complessità del tempo. In questo caso, ci sono 7 operazioni e non 15. Nel trattare con la complessità del tempo, il numero massimo possibile di operazioni è ciò che deve essere dato.

È possibile che tutti i 15 nodi sopra vengano scambiati. Quindi la complessità del tempo per questo esempio deve essere data come 15 e non 7.

La complessità temporale per questo algoritmo viene data, in termini generali, come:

SU)

dove n è n, questa è la notazione grande. Questa notazione utilizza O maiuscole O e le sue parentesi. All'interno delle parentesi c'è l'espressione per il possibile numero massimo di operazioni per il codice (programma).

Coding in c

La funzione principale C per accumulare l'array sopra inosservato è:

int main (int argc, char ** argv)

int n1 = 12;
int a1 [] = 10, 20, 25, 6, 4, 12, 15, 23, 8, 7, 18, 16;
int a2 [] = 10, 20, 25, 6, 4, 12, 15, 23, 8, 7, 18, 16, 26, 27, 28;
buildHeap (A2, N2);
for (int i = 0; i = arr [leftIndx] && arr [leftIndx]> = arr [a deskindx])
int temp = arr [parentIndx]; arr [parentIndx] = arr [dirittoIdx]; arr [readindx] = temp;
LastDown = RightIndX;

else if (arr [parentIndx]> = arr [dirittoIndx] && arr [dirittoindx]> = arr [leftindx])
int temp = arr [parentIndx]; arr [parentIndx] = arr [leftIndx]; arr [leftInIndx] = temp;
LastDown = LeftIndX;

else if (arr [parentIndx]> = arr [dirittoIndx] && arr [dirittoindx] = arr [leftindx] && arr [leftIndx] <= arr[rightIndx])
int temp = arr [parentIndx]; arr [parentIndx] = arr [leftIndx]; arr [leftInIndx] = temp;
LastDown = LeftIndX;

restituire per la danno;

C'è una funzione di setacciatura. Utilizzerebbe la funzione di swap () e setaccia il livello più basso in un percorso. È:

int nextIndx;
void siftdown (int arr [], int n2, int i)
Int LeftIndx, reggetto;
int parentIndx = i;
LeftInIndx = 2*i+1;
RightIndx = 2*i+2;
if (parentIndx = 0)
NextIndx = swap (arr, parentIndx, leftInIndx, a destraIndx);
if (nextIndx = Halfindx/2; i--)
seta
N = n/2;
if (n> = 1)
buildheap (A2, n);

Tutti i segmenti di codice sopra possono essere assemblati per formare un programma che accumulerà l'array non ordinato.

Conclusione

L'algoritmo più efficiente per la complessità del tempo di accantonamento è:

SU)