Algoritmos y Estructuras de Datos I Ordenamiento de un arreglo La

Anuncio
Ordenamiento de un arreglo
Algoritmos y Estructuras de Datos I
Tenemos un arreglo de elementos de un tipo T .
Queremos modificar el arreglo
Segundo cuatrimestre de 2011
Departamento de Computación - FCEyN - UBA
I
para que sus elementos queden en orden creciente
I
vamos a hacerlo permutando elementos
Si pre(a) == [2, 1, 2, 1], debo terminar con a == [1, 1, 2, 2].
Programación imperativa - clase 4
Algoritmos de ordenamiento
1
La especificación
2
El algoritmo Upsort
Usamos ≤ para denotar una relación de orden entre elementos de T .
problema sort<T> (a : [T], n : Int){
requiere n == |a| ∧ n ≥ 1;
modifica a;
asegura mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1 ;
}
I
ordenamos de derecha a izquierda
I
el segmento a ordenar va desde el principio hasta la posición
que vamos a llamar actual
I
comenzamos con actual = n − 1
mientras actual > 0
I
aux cuenta(x : T, a : [T]) : Int = |[y | y ∈ a, y == x]|;
I
I
aux mismos(a, b : [T]) : Bool = |a| == |b| ∧
(∀x ∈ a) cuenta(x, a) == cuenta(x, b);
I
3
encontrar el mayor elemento del segmento
intercambiarlo con el de la posición actual
decrementar actual
4
Ejemplo de Upsort
Especificación del ciclo
arreglo a[] de dimensión n, variable actual
9
2
2
2
2
1
5
5
5
1
1
2
2
2
2
2
2
2
7
7
1
5
5
5
1
1
7
7
7
7
2
9
9
9
9
9
PC : a == pre(a) ∧ actual == n − 1
QC : mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1
B : actual > 0
invariante I : 0 ≤ actual ≤ n − 1 ∧
mismos(a, pre(a)) ∧
(∀k ∈ (actual..n − 1)) ak ≤ ak+1 ∧
actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
variante v : actual
6
5
El programa
Correctitud de Upsort
void upsort (int a[], int n) {
void upsort (int a[], int n) {
int m, actual = n-1;
while (actual > 0) {
m = maxPos(a,0,actual);
swap(a[m],a[actual]);
actual--;
}
}
// vale P : 1 ≤ n == |a|
(es la precondición del problema)
int m, actual = n-1;
// vale PC : a == pre(a) ∧ actual == n − 1 (es la precondición del ciclo)
while (actual > 0) {
m = maxPos(a,0,actual);
swap(a[m],a[actual]);
actual--;
}
problema maxPos(a : [Int], desde, hasta : Int) = pos : Int{
requiere 0 ≤ desde ≤ hasta ≤ |a| − 1;
asegura desde ≤ pos ≤ hasta ∧ (∀x ∈ a[desde..hasta]) x ≤ apos ;
}
// vale QC : mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1
(es la poscondición del ciclo)
// vale Q : mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1
(es la poscondición del problema)
problema swap(x, y : Int){
modifica x, y ;
asegura x == pre(y ) ∧ y == pre(x);
}
}
Como QC ≡ Q, lo que queda es probar que el ciclo es correcto para su
especificación.
7
8
Especificación del ciclo
Correctitud del ciclo
void upsort (int a[], int n) {
int m, actual = n-1;
// vale PC
// implica I
while (actual > 0) {
// vale PC : a == pre(a) ∧ actual == n − 1
// estado E
// vale I ∧ actual > 0
while (actual > 0) {
1. El cuerpo del ciclo preserva el
invariante
2. La función variante decrece
m = maxPos(a,0,actual); 3. Si la función variante pasa la
cota, el ciclo termina:
swap(a[m],a[actual]);
actual--;
v ≤ 0 ⇒ ¬(actual > 0)
// invariante I : 0 ≤ actual ≤ n − 1 ∧ mismos(a, pre(a))
∧ (∀k ∈ (actual..n − 1)) ak ≤ ak+1
∧ actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
// variante v : actual
// vale I
// vale v < v @E
m = maxPos(a,0,actual);
swap(a[m],a[actual]);
actual--;
4. La precondición del ciclo
implica el invariante
}
5. La poscondición vale al final
// vale I ∧ ¬(actual > 0)
// implica QC
El Teorema del Invariante nos garantiza que si valen 1, 2, 3, 4, 5 el ciclo
termina y es correcto con respecto a su especificación.
}
// vale QC : mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1
}
9
1. El cuerpo del ciclo preserva el invariante
10
1. El cuerpo del ciclo preserva el invariante (cont.)
// estado E (invariante + guarda del ciclo)
// vale 0 < actual ≤ n − 1 ∧ mismos(a, pre(a))
∧(∀k ∈ (actual..n − 1)) ak ≤ ak+1
∧(actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1 )
//
//
//
//
//
m = maxPos(a,0,actual);
swap(a[m],a[actual]);
// estado E1


Recordar especificación de MaxPos:
 PMP : 0 ≤ desde ≤ hasta ≤ |a| − 1, se cumple porque 0 < atual ≤ n − 1 
QMP : desde ≤ pos ≤ hasta ∧ (∀x ∈ a[desde..hasta]) x ≤ apos
//
//
//
//
//
estado E1
vale 0 ≤ m ≤ actual ∧ (∀x ∈ a[0..actual]) x ≤ am
implica 0 < actual ≤ n − 1 ∧ mismos(a, pre(a))
implica (∀k ∈ (actual..n − 1)) ak ≤ ak+1
implica actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
vale 0 ≤ m ≤ actual ∧ (∀x ∈ a[0..actual]) x ≤ am
vale a == a@E ∧ actual == actual@E
implica 0 < actual ≤ n − 1 ∧ mismos(a, pre(a))
implica (∀k ∈ (actual..n − 1)) ak ≤ ak+1
implica (actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1 )
Justificación de los implica: actual y a no cambiaron
Lo dice el segundo vale de este estado
11
//
//
//
//
//
//
//
estado E2
vale am == (a@E1 )actual ∧ aactual == (a@E1 )m (por poscon. de swap)
vale (∀i ∈ [0..n), i 6= m, i 6= actual) ai == (a@E1 )i
(idem)
vale actual == actual@E1 ∧ m == m@E1
implica 0 < actual ≤ n − 1
(actual no se modificó)
implica mismos(a, pre(a))
(el swap no agrega ni quita elementos)
implica (∀k ∈ (actual..n − 1)) ak ≤ ak+1
(a(actual..n − 1] no se modificó porque m ≤ actual)
//
implica
(∀k
∈
(actual
− 1..n − 1)) ak ≤ ak+1


del tercer vale de E2 : m == m@E1 y actual == actual@E1 ;
 del primer vale de y último implica de E1 : (a@E1 )m ≤ (a@E1 )actual+1 ; 
del primer y segundo vale de E2 : aactual ≤ aactual+1 )
// implica (∀x ∈ a[0..actual]) x ≤ aactual
(del primer vale de E1 y E2 )
12
1. El cuerpo del ciclo preserva el invariante (cont.)
//
//
//
//
//
2 y 3 son triviales
estado E2
implica 0 < actual ≤ n − 1
implica mismos(a, pre(a))
implica (∀k ∈ (actual − 1..n − 1)) ak ≤ ak+1
implica (∀x ∈ a[0..actual]) x ≤ aactual
2. La función variante decrece:
// estado E (invariante + guarda del ciclo)
// vale I ∧ B
actual--;
m = maxPos(a,0,actual);
swap(a[m],a[actual]);
actual--;
//
//
//
//
//
//
//
estado E3
vale actual == actual@E2 − 1 ∧ a == a@E2
implica 0 ≤ actual@E2 − 1 < n − 1
(de primer vale de E3 )
implica 0 ≤ actual ≤ n − 1 (reemplazando actual@E2 − 1 por actual)
implica mismos(a, pre(a)) (de segundo implica de E2 y a == a@E2 )
implica (∀k ∈ (actual@E2 − 1..n − 1)) ak ≤ ak+1
(por E2 )
implica (∀k ∈ (actual..n − 1)) ak ≤ ak+1
(reemplazo actual@E2 − 1 por actual)
// implica (∀x ∈ a[0..actual + 1]) x ≤ aactual+1
(por E2 + reemplazo)
// implica (∀x ∈ a[0..actual]) x ≤ aactual+1
(por ser un selector más acotado)
// implica actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
(pues q implica p → q)
// estado F
// vale actual == actual@E − 1
// implica v @F == v @E − 1 < v @E
3. Si la función variante pasa la cota, el ciclo termina:
actual ≤ 0
es
¬B
14
13
4. La precondición del ciclo implica el invariante
5. La poscondición vale al final
Quiero probar que: (¬B ∧ I ) ⇒ QC
Recordemos
Recordar que
I
I
I
P : 1 ≤ n == |a|
PC : a == pre(a) ∧ actual == n − 1
I : 0 ≤ actual ≤ n − 1 ∧ mismos(a, pre(a))
∧(∀k ∈ (actual..n − 1)) ak ≤ ak+1
∧actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
¬B ∧ I
mismos(a, pre(a)) ∧ (∀k ∈ (actual..n − 1)) ak ≤ ak+1 ∧
actual < n − 1 → (∀x ∈ a[0..actual + 1]) x ≤ aactual+1 ∧
actual ≤ 0
Demostración de que PC ⇒ I :
I
1 ≤ n ∧ actual == n − 1 ⇒
I
a == pre(a)
I
I
⇒
: 0 ≤ actual ≤ n − 1 ∧
QC : mismos(a, pre(a)) ∧ (∀j ∈ [0..n − 1)) aj ≤ aj+1
0 ≤ actual ≤ n − 1
Veamos que vale cada parte de QC :
I
mismos(a, pre(a))
I
actual == n − 1 ⇒ (∀k ∈ (actual..n − 1)) ak ≤ ak+1
porque el selector actúa sobre una lista vacı́a
mismos(a, pre(a)): trivial porque está en I
(∀j ∈ [0..n − 1)) aj ≤ aj+1 :
I
I
I
actual == n − 1 ⇒
actual < n − 1 → (∀x ∈ a[0..actual]) x ≤ aactual+1
porque el antecedente es falso
primero observar que actual == 0
si n == 1, no hay nada que probar porque [0..n − 1) == []
si n > 1
I
I
I
15
sabemos (∀k ∈ (0..n − 1)) ak ≤ ak+1
sabemos que (∀x ∈ a[0..1]) x ≤ a1 , entonces a0 ≤ a1
concluimos (∀j ∈ [0..n − 1)) aj ≤ aj+1
16
Implementación de maxPos
Correctitud de maxPos
problema maxPos (a : [Int], desde, hasta : Int) = pos : Int{
requiere PMP : 0 ≤ desde ≤ hasta ≤ |a| − 1;
asegura QMP : desde ≤ pos ≤ hasta ∧
(∀x ∈ a[desde..hasta]) x ≤ apos ;
}
problema maxPos (a : [Int], desde, hasta : Int) = pos : Int{
requiere PMP : 0 ≤ desde ≤ hasta ≤ |a| − 1;
asegura QMP : desde ≤ pos ≤ hasta ∧
(∀x ∈ a[desde..hasta]) x ≤ apos ;
}
int maxPos(const int a[], int desde, int hasta) {
//vale PMP : 0 ≤ desde ≤ hasta ≤ |a| − 1 (precondición del problema)
int mp = desde, i = desde;
//vale PC : mp == i == desde
(precondición del ciclo)
while (i < hasta) {
i++;
if (a[i] > a[mp]) mp = i;
}
//vale QC : desde ≤ mp ≤ hasta ∧ (∀x ∈ a[desde..hasta]) x ≤ amp
(poscondición del ciclo)
return mp;
int maxPos(const int a[], int desde, int hasta) {
int mp = desde, i = desde;
while (i < hasta) {
i++;
if (a[i] > a[mp]) mp = i;
}
return mp;
}
17
Especificación del ciclo
//vale QMP : desde ≤ pos ≤ hasta ∧ (∀x ∈ a[desde..hasta]) x ≤ apos
(poscondición del problema)
}
18
Correctitud del ciclo
// vale PC
// implica I
// vale PC : mp == i == desde
while (i < hasta) {
// estado E
// vale I ∧ i < hasta
while (i < hasta) {
// invariante I : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i]) x ≤ amp
// variante v : hasta − i
i++;
if (a[i] > a[mp]) mp = i;
// vale I
// vale v < v @E
i++;
if (a[i] > a[mp]) mp = i;
}
}
// vale I ∧ i ≥ hasta
// implica QC
// vale QC : desde ≤ mp ≤ hasta ∧ (∀x ∈ a[desde..hasta]) x ≤ amp
1. El cuerpo del ciclo
preserva el invariante
2. La función variante
decrece
3. Si la función variante pasa
la cota, el ciclo termina:
v ≤ 0 ⇒ i ≥ hasta
4. La precondición del ciclo
implica el invariante
5. La poscondición vale al
final
El Teorema del Invariante nos garantiza que si valen 1, 2, 3, 4 y 5,
el ciclo termina y es correcto con respecto a su especificación.
19
20
1. El cuerpo del ciclo preserva el invariante
Especificación y correctitud del If
Recordar que desde, hasta y a no cambian porque son variables de
entrada que no aparecen en local ni modifica
Pif : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i)) x ≤ amp
Qif : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i]) x ≤ amp
// estado E (invariante + guarda del ciclo)
// vale desde ≤ mp ≤ i < hasta ∧ (∀x ∈ a[desde..i]) x ≤ amp
i++;
// estado E1
// vale i == i@E + 1 ∧ mp = mp@E
// implica Pif : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i)) x ≤ amp
de E , reemplazando i@E por i − 1
y cambiando el lı́mite del selector adecuadamente
// estado Eif
// vale desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i)) x ≤ amp
if (a[i] > a[mp]) mp = i;
// vale (ai > amp@Eif ∧ mp == i@Eif ∧ i == i@Eif )
∨(ai ≤ amp@Eif ∧ mp == mp@Eif ∧ i == i@Eif )
// implica desde ≤ mp ≤ i ≤ hasta


operaciones lógicas; observar que desde, hasta y a
 no pueden modificarse porque son variables de entrada que 


 no aparecen ni en modifica ni en local;

observar que i == i@Eif
if (a[i] > a[mp]) mp = i;
// vale Qif : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i]) x ≤ amp


observar que en este punto, tratamos al if como una sola
 gran instrucción que convierte Pif en Qif . La justificación

de este paso es la transformación de estados de la hoja siguiente
// implica I
observar que I es igual que Qif , pero en general
alcanzarı́a con que Qif implique I
// implica amp@Eif ≤ amp ∧ ai ≤ amp
// implica (∀x ∈ a[desde..i]) x ≤ amp
// implica Qif
22
21
2. La función variante decrece
3, 4 y 5 son triviales
// estado E (invariante + guarda del ciclo)
// vale desde ≤ i < hasta
3. Si la función variante pasa la cota, el ciclo termina:
i++;
hasta − i ≤ 0
// estado E1
// vale i == i@E + 1
// implica desde ≤ i ≤ hasta
⇒
hasta ≤ i
4. La precondición del ciclo implica el invariante:
Recordar que la precondición de maxPos dice
if (a[i] > a[mp]) mp = i;
PMP : 0 ≤ desde ≤ hasta ≤ |a| − 1
// estado F
// vale i == i@E1
// implica i == i@E + 1
y que desde y hasta no cambian de valor. Entonces
PC : i == desde ∧ mp == desde
⇓
I : desde ≤ mp ≤ i ≤ hasta ∧ (∀x ∈ a[desde..i]) x ≤ amp
¿Cuánto vale v = hasta − i en el estado F ?
v @F
(Justificar...)
== (hasta − i)@F
== hasta − i@F
== hasta − (i@E + 1)
5. La poscondición vale al final: es fácil ver que I ∧ i ≥ hasta
implica QC
== hasta − i@E − 1
<
hasta − i@E
== v @E
Entonces el ciclo es correcto con respecto a su especificación.
23
24
Complejidad de maxPos
Complejidad de Upsort
I
el ciclo de Upsort itera n veces
¿Cuántas comparaciones hacemos como máximo?
I
El ciclo itera hasta − desde veces
Cada iteración hace:
I
I
una comparación (en la guarda)
I
una asignación (el incremento)
I
otra comparación (guarda del condicional)
I
otra asignación (si se cumple esa guarda)
I
I
una iteración hace
I
I
I
I
I
Antes del ciclo se hacen dos asignaciones.
I
I
MaxPos es invocada la primera vez con desde == 0 y hasta == |a| − 1,
luego con segmentos sucesitamente más cortos hasta llegar a tener
longitud 1,
I
I
I
I
una búsqueda de maxPos
un swap
un decremento
de estos pasos, el único que no es O(1), constante, es el
primero
¿cuántos pasos hace maxPos en cada iteración?
Luego, la cantidad de operaciones de MaxPos es O(longitud del
segmento) = O(hasta − desde + 1) .
I
empieza con actual == n − 1
termina con actual == 0
en cada iteración decrementa actual en uno
siempre O(hasta − desde + 1) = O(actual + 1)
en la primera hace n (busca el máximo del segmento a ordenar)
en la segunda hace n − 1 (busca el segundo mayor)
en la tercera hace n − 2
y ası́ (podrı́amos verlo por inducción) hasta 2
el total de pasos es
O(n + (n − 1) + . . . + 2) = O(n ∗ (n + 1)/2 − 1) = O(n2 )
los mejores algoritmos de ordenamiento son O(n log n)
26
25
Cota inferior de complejidad tiempo para sorting
Demostración
Supongamos que ordenamos esta secuencia: 3, 2, 4, 5, 1.
El método comparará los números, y según sean los resultados de las
comparaciones permutará los elementos. El efecto neto será:
Consideremos algoritmos de ordemiento basados, exclusivamente,
en la operaciones de comparar elementos y permutarlos.
Teorema: Sea cualquier método de ordenamiento
(inventado o por inventarse).
Tomará al menos log2 n! pasos para ordenar n elementos.
El método no puede ejecutar exactamente de la misma manera si la
secuencia es otra, por ejemplo: 2, 3, 4, 5, 1
¿por qué? Si lo hiciera, el resultado neto serı́a
Concluı́mos que la ejecución debe ser una sucesión de pasos distinta para
cada permutación posible de la secuencia de entrada.
27
28
Visualicemos todas las ejecuciones como un árbol de ejecuciones.
Cada nodo es, o bien una asignación, o bien una comparación.
El árbol se ramifica en cada comparación.
Cada camino desde la raı́z hasta una hoja es una ejecución del programa.
La altura del árbol el da el peor caso de cantidad de instrucciones
ejecutadas por el programa.
Debe tener al menos n! hojas.
Sabemos que un árbol binario con L hojas tiene altura al menos log2 L.
Por lo tanto nuestro árbol tiene altura al menos log2 n!.
Si éste es el árbol de ejecuciones de un programa de ordenamiento,
que ordena n elementos, para cada una de las n! permutaciones de la
secuencia de entrada, la ejecución bebe alcanzar una hoja diferente.
El árbol debe tener al menos n! hojas.
Concluı́mos que el ordenamiento, en el peor caso, requiere al menos
log2 n! pasos.
29
Para poder hacer un ordenamiento más rápido necesitamos...
saber más acerca de la secuencia de entrada, de forma tal de poder
ahorrarnos comparaciones entre de elementos.
31
30
Descargar